""" UI Components Module Defines Gradio interface CSS styles and UI component building functions. """ # ==================== Custom CSS Styles ==================== CUSTOM_CSS = """ /* Global styles - Minimalist white UI, black text, no emojis */ .gradio-container { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif; background-color: white; color: black; } /* Main container background - Dynamic color controlled by Python */ .main-container { transition: background-color 0.5s ease; padding: 20px; border-radius: 8px; } /* Button styles */ .primary-button { background-color: black !important; color: white !important; border: none !important; padding: 10px 20px !important; font-size: 16px !important; font-weight: 500 !important; cursor: pointer !important; transition: opacity 0.2s ease !important; border-radius: 4px !important; } .primary-button:hover { opacity: 0.8 !important; } .secondary-button { background-color: white !important; color: black !important; border: 2px solid black !important; padding: 10px 20px !important; font-size: 16px !important; font-weight: 500 !important; cursor: pointer !important; transition: background-color 0.2s ease !important; border-radius: 4px !important; } .secondary-button:hover { background-color: #f0f0f0 !important; } /* Image display area */ .image-display { border: 2px solid black; border-radius: 8px; padding: 10px; background-color: white; display: flex; justify-content: center; align-items: center; } .image-display img { max-width: 100%; max-height: 600px; object-fit: contain; } /* Score display */ .score-display { font-size: 24px; font-weight: 600; text-align: center; padding: 15px; background-color: white; border: 2px solid black; border-radius: 8px; margin: 10px 0; } /* Progress display */ .progress-display { font-size: 18px; font-weight: 500; text-align: center; padding: 10px; background-color: white; border: 1px solid black; border-radius: 4px; margin: 10px 0; } /* Feedback message */ .feedback-box { font-size: 16px; padding: 15px; background-color: white; border: 2px solid black; border-radius: 8px; margin: 10px 0; min-height: 60px; display: flex; align-items: center; justify-content: center; text-align: center; } /* Mode selector area */ .mode-selector { display: flex; justify-content: center; gap: 20px; margin: 20px 0; } /* Sidebar styles */ .sidebar { background-color: white; border-right: 2px solid black; padding: 20px; min-height: 100vh; } /* Main content area */ .main-content { padding: 20px; flex: 1; } /* Markdown styles */ .markdown-text { color: black; font-size: 16px; line-height: 1.6; } /* Heading styles */ h1, h2, h3, h4, h5, h6 { color: black; font-weight: 600; margin: 10px 0; } /* Hide unnecessary Gradio elements */ .footer { display: none !important; } /* Detector mode specific styles */ .detector-result { font-size: 20px; font-weight: 600; padding: 20px; background-color: white; border: 2px solid black; border-radius: 8px; text-align: center; } .confidence-display { font-size: 18px; padding: 15px; background-color: white; border: 1px solid black; border-radius: 8px; margin-top: 10px; } /* Game over styles */ .game-over-banner { font-size: 28px; font-weight: 700; text-align: center; padding: 30px; background-color: white; border: 3px solid black; border-radius: 12px; margin: 20px 0; } /* Responsive design */ @media (max-width: 768px) { .sidebar { border-right: none; border-bottom: 2px solid black; min-height: auto; } .score-display { font-size: 20px; } .progress-display { font-size: 16px; } .feedback-box { font-size: 14px; } } /* Disabled state styles */ .disabled { opacity: 0.5; cursor: not-allowed !important; pointer-events: none; } /* Loading state */ .loading { opacity: 0.6; pointer-events: none; } /* Success/error indicators */ .success-indicator { color: #2e7d32; font-weight: 600; } .error-indicator { color: #c62828; font-weight: 600; } /* Divider */ .divider { border-top: 1px solid #000; margin: 20px 0; } """ # ==================== UI Component Helper Functions ==================== def get_custom_css() -> str: """ Return custom CSS style string Returns: CSS style string """ return CUSTOM_CSS def create_score_html(player_score: int, ai_score: int) -> str: """ Create HTML for score display Args: player_score: Player score ai_score: AI score Returns: HTML string """ return f"""
You: {player_score} | AI: {ai_score}
""" def create_progress_html(current_round: int, total_rounds: int) -> str: """ Create HTML for progress display Args: current_round: Current round total_rounds: Total rounds Returns: HTML string """ return f"""
Round: {current_round}/{total_rounds}
""" def create_feedback_html(message: str, is_error: bool = False) -> str: """ Create HTML for feedback message Args: message: Feedback message is_error: Whether this is an error message Returns: HTML string """ css_class = "error-indicator" if is_error else "" return f"""
{message}
""" def create_detector_result_html(prediction: str, confidence: float) -> str: """ Create HTML for detector result Args: prediction: Prediction result ("AI" or "Human") confidence: Confidence score Returns: HTML string """ return f"""
Prediction: {prediction}
Confidence: {confidence * 100:.1f}%
""" def create_game_over_html(player_score: int, ai_score: int, total_rounds: int) -> str: """ Create HTML for game over screen Args: player_score: Player score ai_score: AI score total_rounds: Total rounds Returns: HTML string """ # Determine winner if player_score > ai_score: result_text = "You won!" elif player_score < ai_score: result_text = "AI won!" else: result_text = "It's a tie!" player_pct = (player_score / total_rounds) * 100 ai_pct = (ai_score / total_rounds) * 100 return f"""
{result_text}
Your score: {player_score}/{total_rounds} ({player_pct:.1f}%)
AI score: {ai_score}/{total_rounds} ({ai_pct:.1f}%)
""" # ==================== UI Interface Building Functions ==================== def format_score(player_score: int, ai_score: int, current_round: int, total_rounds: int) -> str: """ Format score display as Markdown text Args: player_score: Player score ai_score: AI score current_round: Current round total_rounds: Total rounds Returns: Markdown formatted score text """ return f"""### Score **You**: {player_score} **AI**: {ai_score} **Progress**: {current_round}/{total_rounds} """ def create_game_interface(): """ Create game mode UI interface (using Gradio components) Returns: Dictionary containing all UI components """ import gradio as gr components = {} with gr.Row(): # Left sidebar (1:3 ratio) with gr.Column(scale=1): components['score_display'] = gr.Markdown( value=format_score(0, 0, 0, 20), elem_classes=["markdown-text"] ) components['ai_button'] = gr.Button( "AI Generated", variant="primary", size="lg", elem_classes=["primary-button"] ) components['human_button'] = gr.Button( "Real Human", variant="secondary", size="lg", elem_classes=["secondary-button"] ) components['next_button'] = gr.Button( "Next", size="lg", elem_classes=["secondary-button"], visible=False ) components['reset_button'] = gr.Button( "Reset Game", size="sm", elem_classes=["secondary-button"] ) components['feedback_display'] = gr.Markdown( value="", elem_classes=["markdown-text", "feedback-box"] ) # Right main content area (3:1 ratio) with gr.Column(scale=3): components['image_display'] = gr.Image( label="Face Image", type="filepath", interactive=False, elem_classes=["image-display"] ) return components def handle_guess(player_guess: str, state, image_pool, model): """ Handle player's guess Args: player_guess: Player's guess ("AI" or "Human") state: GameState instance image_pool: ImagePool instance model: FaceDetectorModel instance Returns: Updated UI component values (score, feedback, button states, etc.) """ # If already guessed or game over, don't process if state.guess_made or state.game_over: return {} # Get AI prediction ai_prediction, ai_confidence = model.predict(state.current_image_path) # Record guess result state.record_guess(player_guess, ai_prediction, ai_confidence) # Prepare feedback feedback = state.last_result # Check if game is over if state.game_over: feedback = state.get_final_result() # Return updated UI state updates = { 'score_display': format_score( state.player_score, state.ai_score, state.current_round, state.total_rounds ), 'feedback_display': feedback, 'ai_button_interactive': False, 'human_button_interactive': False, 'next_button_visible': not state.game_over, 'reset_button_visible': state.game_over } return updates def handle_next_picture(state): """ Handle "Next" button click Args: state: GameState instance Returns: Updated UI component values """ # Move to next round state.next_round() # Return updated UI state updates = { 'image_display': state.current_image_path, 'feedback_display': "", 'ai_button_interactive': True, 'human_button_interactive': True, 'next_button_visible': False, 'score_display': format_score( state.player_score, state.ai_score, state.current_round, state.total_rounds ) } return updates def handle_reset_game(state, image_pool): """ Handle game reset Args: state: GameState instance image_pool: ImagePool instance Returns: Updated UI component values """ # Reset game state state.reset() # Create new game image set state.images = image_pool.create_game_set() state.current_round = 0 state.next_round() # Return initial UI state updates = { 'image_display': state.current_image_path, 'score_display': format_score(0, 0, 1, 20), 'feedback_display': "New game started! Is this AI-generated or real human?", 'ai_button_interactive': True, 'human_button_interactive': True, 'next_button_visible': False, 'reset_button_visible': True } return updates