knjdkjafk / app.py
sepp81's picture
Update app.py
d80d81e verified
import streamlit as st
import pandas as pd
import numpy as np
import random
import os
from datetime import datetime
import ast
# Custom CSS for enhanced styling
def local_css():
st.markdown("""
<style>
/* Global Styling */
/* Game Container */
.game-container {
border-radius: 15px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
/* Sentences Styling */
.sentence-container {
display: flex;
align-items: center;
border-left: 4px solid #3498db;
padding: 10px;
margin-bottom: 10px;
transition: all 0.3s ease;
}
.sentence-container:hover {
transform: translateX(5px);
}
/* Buttons */
.stButton>button {
color: black;
border-radius: 20px;
border: none;
padding: 10px 20px;
transition: all 0.3s ease;
}
.stButton>button:hover {
transform: scale(1.05);
}
/* Radio Buttons */
.stRadio>div {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.stRadio>div>label {
background-color: #000;
padding: 10px;
border-radius: 10px;
transition: all 0.3s ease;
}
.stRadio>div>label:hover {
background-color: #3498db;
color: white;
}
/* Sidebar */
.css-1aumxhk {
background-color: #2c3e50;
color: white;
}
/* Reason Validation */
.reason-valid {
color: #2ecc71;
font-weight: bold;
}
.reason-invalid {
color: #e74c3c;
font-weight: bold;
}
</style>
""", unsafe_allow_html=True)
class RoFTGame:
def __init__(self, dataset_path):
"""
Initialize the RoFT Game with the dataset
:param dataset_path: Path to the roft.csv file
"""
self.df = pd.read_csv(dataset_path)
self.current_sample = None
self.current_sentences = None
self.true_boundary_index = None
self.current_guess_index = None
# Predefined reasons from the dataset description
self.predefined_reasons = [
"grammar",
"repetition",
"irrelevant",
"contradicts_sentence",
"contradicts_knowledge",
"common_sense",
"coreference",
"generic"
]
def load_random_sample(self):
"""
Load a random sample from the dataset
"""
# Filter for samples with valid generations and reasons
valid_samples = self.df[
(self.df['gen_body'].notna()) &
(self.df['reason'].notna()) &
(self.df['reason'] != '[]')
]
# Select a random sample
self.current_sample = valid_samples.sample(n=1).iloc[0]
# Prepare sentences
prompt_sentences = self.current_sample['prompt_body'].split('_SEP_')
gen_sentences = self.current_sample['gen_body'].split('_SEP_')
# Combine and truncate to 10 sentences
combined_sentences = prompt_sentences + gen_sentences
self.current_sentences = combined_sentences[:10]
# Store true boundary
self.true_boundary_index = self.current_sample['true_boundary_index']
# Parse reasons from the dataset
try:
self.current_reasons = ast.literal_eval(self.current_sample['reason'])
except:
self.current_reasons = []
# Reset current guess
self.current_guess_index = None
def check_guess(self, guess_index):
"""
Check if the guess is correct
:param guess_index: Index of the guessed boundary
:return: Points earned
"""
self.current_guess_index = guess_index
# Calculate points based on closeness to true boundary
if guess_index == self.true_boundary_index:
return 5
elif guess_index > self.true_boundary_index:
return max(5 - (guess_index - self.true_boundary_index), 0)
else:
return 0
def validate_reason(self, user_reason):
"""
Validate user's reason against dataset reasons
:param user_reason: Reason provided by user
:return: Tuple of (is_valid, matching_reasons)
"""
# Convert user reason to lowercase for matching
user_reason_lower = user_reason.lower()
# Check against predefined reasons and current sample's reasons
matching_reasons = []
# Check predefined reasons
for reason in self.predefined_reasons:
if reason.lower() in user_reason_lower:
matching_reasons.append(reason)
# Check original sample's reasons
for orig_reason in self.current_reasons:
if orig_reason.lower() in user_reason_lower:
matching_reasons.append(orig_reason)
return len(matching_reasons) > 0, matching_reasons
def save_annotation(self, guess_index, reason, reason_validity):
"""
Save annotation to a text file
:param guess_index: Index of the guessed boundary
:param reason: Reason for the guess
:param reason_validity: Validity of the reason
"""
# Ensure logs directory exists
os.makedirs('logs', exist_ok=True)
# Generate unique filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f'logs/annotation_{timestamp}.txt'
# Prepare annotation details
annotation_details = [
f"Timestamp: {timestamp}",
f"Model: {self.current_sample['model']}",
f"Dataset: {self.current_sample['dataset']}",
f"Guess Index: {guess_index + 1}",
f"True Boundary Index: {self.true_boundary_index + 1}",
f"Original Dataset Reasons: {self.current_reasons}",
f"User Reason: {reason}",
f"Reason Validity: {reason_validity[0]}",
f"Matching Reasons: {reason_validity[1]}",
"\nFull Text:\n" + "\n".join(f"{i+1}. {sent}" for i, sent in enumerate(self.current_sentences))
]
# Write to file
with open(filename, 'w') as f:
f.write("\n".join(annotation_details))
def main():
local_css()
# Fancy title with animation
st.markdown("""
<h1 style='text-align: center; color: #2c3e50;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
animation: fadeIn 2s;'>
🕵️ Real or Fake Text Detective 🕵️‍♀️
</h1>
""", unsafe_allow_html=True)
# Game introduction
st.markdown("""
<div class='game-container'>
<p style='text-align: center; font-style: italic;'>
Sharpen your AI detection skills! Read carefully and identify where human writing transforms into machine-generated text.
</p>
</div>
""", unsafe_allow_html=True)
# Initialize game session state
if 'game' not in st.session_state:
st.session_state.game = RoFTGame('roft.csv')
st.session_state.game.load_random_sample()
st.session_state.total_points = 0
st.session_state.rounds_played = 0
# Game container
st.markdown("<div class='game-container'>", unsafe_allow_html=True)
# Game information in sidebar with icons
st.sidebar.markdown("## 🎮 Game Stats")
st.sidebar.markdown(f"### 🏆 Total Points: {st.session_state.total_points}")
st.sidebar.markdown(f"### 🎲 Rounds Played: {st.session_state.rounds_played}")
# Animated difficulty indicator
difficulty_map = {
'gpt2': '🟢 Easy',
'gpt2-xl': '🟠 Medium',
'ctrl': '🔴 Hard'
}
current_model = st.session_state.game.current_sample['model']
difficulty = difficulty_map.get(current_model, '⚪ Unknown')
st.sidebar.markdown(f"### 🎯 Difficulty: {difficulty}")
# Display sentences with enhanced styling and radio buttons
st.subheader("🔍 Examine the Text Carefully")
selected_guess = st.radio(
"Where do you think the AI-generated text begins?",
options=[f"{i+1}. {sentence}" for i, sentence in enumerate(st.session_state.game.current_sentences)],
label_visibility="collapsed"
)
# Reason input with predefined options and visual enhancements
st.markdown("### 🧐 Explain Your Reasoning")
reason_options = st.session_state.game.predefined_reasons
selected_predefined_reasons = st.multiselect(
"Select indicators of AI generation",
options=reason_options
)
# Custom reason input
custom_reason = st.text_area("Additional detective notes (optional)")
# Combine predefined and custom reasons
full_reason = " ".join(selected_predefined_reasons)
if custom_reason:
full_reason += f" {custom_reason}"
# Guess submission
if st.button("Submit Guess"):
if not full_reason.strip():
st.warning("Please provide a reason for your guess.")
else:
# Convert guess to index (subtract 1 for 0-based indexing)
guess_index = int(selected_guess.split('.')[0]) - 1
# Check guess and update points
points_earned = st.session_state.game.check_guess(guess_index)
st.session_state.total_points += points_earned
st.session_state.rounds_played += 1
# If guess is correct, validate reason
if guess_index == st.session_state.game.true_boundary_index:
reason_validity = st.session_state.game.validate_reason(full_reason)
st.subheader("Results")
st.write(f"Your Guess: Sentence {selected_guess}")
st.write(f"Actual Boundary: Sentence {st.session_state.game.true_boundary_index + 1}")
st.write(f"Points Earned: {points_earned}")
# Display reason validation
st.write("Reason Validation:")
st.write(f"Valid Reason: {reason_validity[0]}")
if reason_validity[1]:
st.write("Matching Reasons:", ", ".join(reason_validity[1]))
# Save annotation
st.session_state.game.save_annotation(guess_index, full_reason, reason_validity)
else:
st.subheader("Results")
st.write(f"Your Guess: Sentence {selected_guess}")
st.write(f"Actual Boundary: Sentence {st.session_state.game.true_boundary_index + 1}")
st.write(f"Points Earned: {points_earned}")
# Option to continue
if st.button("Next Round"):
st.session_state.game.load_random_sample()
st.experimental_rerun()
st.markdown("</div>", unsafe_allow_html=True)
# Optional: Show metadata for current sample
if st.checkbox("Show Sample Metadata"):
st.write("Current Sample Details:")
sample = st.session_state.game.current_sample
st.write(f"Model: {sample['model']}")
st.write(f"Dataset: {sample['dataset']}")
st.write(f"Sampling Strategy (p): {sample['dec_strat_value']}")
st.write(f"Original Reasons: {st.session_state.game.current_reasons}")
if __name__ == "__main__":
main()