ArtificalFaceDetector / src /ui_components.py
VanKee's picture
hw2
6f6e572
"""
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"""
<div class="score-display">
You: {player_score} | AI: {ai_score}
</div>
"""
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"""
<div class="progress-display">
Round: {current_round}/{total_rounds}
</div>
"""
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"""
<div class="feedback-box">
<span class="{css_class}">{message}</span>
</div>
"""
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"""
<div class="detector-result">
Prediction: {prediction}
</div>
<div class="confidence-display">
Confidence: {confidence * 100:.1f}%
</div>
"""
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"""
<div class="game-over-banner">
{result_text}
</div>
<div class="score-display">
Your score: {player_score}/{total_rounds} ({player_pct:.1f}%)<br>
AI score: {ai_score}/{total_rounds} ({ai_pct:.1f}%)
</div>
"""
# ==================== 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