import json
from datetime import datetime, timezone
from pathlib import Path
import gradio as gr
from huggingface_hub import snapshot_download
from loguru import logger
from pydantic import BaseModel, Field
from envs import API, REPO_ID, USERS_PATH, USERS_REPO
from formatting import styled_error, styled_message, styled_warning
css = """
:root {
color-scheme: light !important;
--block-border-width: 0 !important;
--section-header-text-weight: 600 !important;
--section-header-text-size: 14px !important;
--input-radius: var(--radius-xl) !important;
--font-mono: "Space Mono", monospace !important;
--text-sm: 12px !important;
--text-md: 14px !important;
--text-lg: 16px !important;
--input-text-size: var(--text-sm) !important;
--body-text-size: 14px !important;
--input-background-fill-focus: var(--secondary-300) !important;
// Button Colors
--button-primary-background-fill: var(--primary-800) !important;
--button-secondary-background-fill: var(--secondary-600) !important;
--checkbox-label-text-color: var(--body-text-color) !important;
--card-bg-color: #fcecd4;
--card-btn-color: #D4E4FC;
--card-btn-color-hover: #7DAEF6;
--answer-bg-color: #f0f8ff;
--hover-border-color: #121212;
}
:root .dark {
color-scheme: dark !important;
--block-border-width: 0 !important;
--section-header-text-weight: 600 !important;
--section-header-text-size: 14px !important;
--input-radius: var(--radius-xl) !important;
--font-mono: "Space Mono", monospace !important;
--text-sm: 12px !important;
--text-md: 14px !important;
--text-lg: 16px !important;
--input-text-size: var(--text-sm) !important;
--body-text-size: 14px !important;
--button-primary-background-fill: var(--neutral-100) !important;
--button-secondary-background-fill: var(--secondary-300) !important;
--button-primary-text-color: black !important;
--button-secondary-text-color: black !important;
--checkbox-label-text-color: var(--body-text-color) !important;
--card-bg-color: #383127;
--answer-bg-color: #1a2b3c;
--hover-border-color: #ffffff;
}
.gradio-app {
}
.warning-header {
color: red;
}
.neutral-header {
color: blue;
}
.success-header {
color: green;
}
.gradio-container {
max-width: 1000px;
margin: 0 auto;
padding: 0 8px;
}
"""
def restart_space():
API.restart_space(repo_id=REPO_ID)
def download_dataset_snapshot(repo_id, local_dir):
try:
logger.info(f"Downloading dataset snapshot from {repo_id} to {local_dir}")
snapshot_download(
repo_id=repo_id,
local_dir=local_dir,
repo_type="dataset",
tqdm_class=None,
)
except Exception as e:
logger.error(f"Error downloading dataset snapshot from {repo_id} to {local_dir}: {e}. Restarting space.")
API.restart_space(repo_id=repo_id)
download_dataset_snapshot(USERS_REPO, USERS_PATH)
class User(BaseModel):
"""
Represents a user in the competition system, formatted for HuggingFace datasets.
"""
username: str = Field(description="HuggingFace username of the user")
name: str = Field(description="Full name of the user")
email: str = Field(description="Contact email of the user")
affiliation: str = Field(description="Affiliation of the user")
def create_user(
username: str,
name: str,
email: str,
affiliation: str,
profile: gr.OAuthProfile | None,
) -> User:
"""
Create a user for a tossup model.
Args:
name: Display name of the submission
description: Detailed description of what the submission does
user_email: Email of the user who created the submission
workflow: The workflow configuration for the tossup model
Returns:
Submission object if successful, None if validation fails
"""
# Create the submission
dt = datetime.now(timezone.utc)
submission = User(
username=username,
name=name,
email=email,
affiliation=affiliation,
created_at=dt.isoformat(),
)
return submission
def get_user(username: str) -> User | None:
"""
Get a user from the registered users dataset.
"""
out_path = Path(f"{USERS_PATH}/{username}.json")
if not out_path.exists():
return None
with out_path.open("r") as f:
user_dict = json.load(f)
return User.model_validate(user_dict)
def is_user_logged_in(profile: gr.OAuthProfile | None) -> bool:
"""
Check if a user is logged in.
"""
return profile is not None
def user_signup(
name: str,
email: str,
affiliation: str,
profile: gr.OAuthProfile | None = None,
) -> User:
"""
Sign up for the competition.
"""
if profile is None:
return styled_error("Please sign in using your HuggingFace account to register for the competition.")
try:
username = profile.username
user = get_user(username)
new_user = create_user(
username=username,
name=name,
email=email,
affiliation=affiliation,
profile=profile,
)
# Convert to dictionary format
user_dict = new_user.model_dump()
# Upload to HuggingFace dataset
updated = bool(user)
API.upload_file(
path_or_fileobj=json.dumps(user_dict, indent=2).encode(),
path_in_repo=f"{username}.json",
repo_id=USERS_REPO,
repo_type="dataset",
commit_message=f"{'Update' if updated else 'Add'} user {username}",
)
return styled_message(
f"Successfully {'updated' if updated else 'registered'} user {username} for the competition!
"
)
except Exception as e:
logger.exception(e)
return styled_error(
"Error registering for the competition. Please try again later, or contact the organizers at
qanta-challenge@googlegroups.com."
)
def load_user_info(profile: gr.OAuthProfile | None):
if profile is None:
return (
gr.update(interactive=False),
gr.update(interactive=False),
gr.update(interactive=False),
gr.update(interactive=False),
gr.update(
value="