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="

Please sign in using your HuggingFace account to register for the competition.

", ), ) user = get_user(profile.username) if user is None: return ( gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True, value="Register"), gr.update( value="

Fill in your details below to register for the competition.

", ), ) return ( gr.update(interactive=True, value=user.name), gr.update(interactive=True, value=user.email), gr.update(interactive=True, value=user.affiliation), gr.update(interactive=True, value="Update Information"), gr.update( value="

You are already registered! You can update your details below.

", ), ) with gr.Blocks(title="Register - QANTA 2025 Quizbowl System Submission", css=css) as demo: gr.Markdown("# Register for QANTA 2025 Quizbowl System Submission") with gr.Row(): with gr.Column(): # HuggingFace Login Button hf_login_btn = gr.LoginButton(scale=1) header = gr.HTML("

Fill in your details below

") # Registration form name = gr.Textbox(label="Full Name", placeholder="Enter your full name") email = gr.Textbox(label="Contact Email", placeholder="Enter your email") affiliation = gr.Textbox( label="Affiliation", placeholder="Enter your organization or institution", ) submit_btn = gr.Button("Register") status = gr.Markdown() submit_btn.click( fn=user_signup, inputs=[name, email, affiliation], outputs=[status], ) demo.load( load_user_info, inputs=[], outputs=[name, email, affiliation, submit_btn, header], ) demo.queue(default_concurrency_limit=40).launch()