import gradio as gr from datetime import datetime from datasets import Dataset, load_dataset from huggingface_hub import login, HfApi import pandas as pd import os import time import tempfile import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) HF_TOKEN = os.environ.get("HF_TOKEN") if HF_TOKEN: login(token=HF_TOKEN) logger.info("Authenticated with Hugging Face") else: logger.warning("HF_TOKEN not found - running without authentication") DATASET_NAME = "build-small-hackathon/build-small-hackathon-registrations" # Auto-backups land under the user namespace (not the org) so the org's dataset # list isn't polluted with thousands of timestamped backup repos. The HF_TOKEN # secret on the Space must have write access to BOTH the org dataset (above) # and this user namespace. BACKUP_DATASET_PREFIX = "ysharma/build-small-hackathon-registrations-auto-backup" DISCORD_INVITE = "https://discord.gg/YHECTft87Z" DISCORD_CHANNEL = "build-small-hackathon-official" HEADER_HTML = """

πŸͺ΅ Build Small Hackathon

Making AI Fun Again
May 29 – June 8, 2026 Β· Online & Global
πŸ—ΊοΈ event artwork being illustrated Β· placeholder for now
""" INFO_ROW_HTML = """
❋ Two Tracks
🏑 Backyard AI
Solve a real problem for someone you know
πŸ„ Thousand Token Wood
Build something delightful and whimsical
✦ Three Rules
  1. Models ≀ 32B parameters
  2. Built on Gradio + Spaces
  3. Demo video + social post
🎁 Prizes & Credits
Sponsor lineup, prize pool, and free API/compute credits announcing soon β€” sit tight, friend.
""" DATES_ROW_HTML = """
✦ Trail Map · Key Dates
πŸ“… Reg closes: Wed, May 27 Β· 23:59 UTC
🎟️ Credits: Thu, May 28
πŸš€ Hack opens: Fri, May 29
🏁 Submit by: Mon, June 8
β›” No registrations accepted once the event starts. Lock it in before May 27!
πŸ•οΈ Hack opens in
May 29, 2026 Β· 00:00 UTC
--Days
--Hrs
--Min
--Sec
""" DISCORD_RIBBON_HTML = f"""
πŸ’¬ Join the Gradio Discord Β· channel {DISCORD_CHANNEL} Β· office hours, AMAs, and support.
""" COUNTDOWN_JS = """ () => { const targetDate = new Date(Date.UTC(2026, 4, 29, 0, 0, 0)); function update() { const now = new Date(); const diff = targetDate - now; let d, h, m, s; if (diff <= 0) { d = h = m = s = 0; } else { d = Math.floor(diff / 86400000); h = Math.floor((diff % 86400000) / 3600000); m = Math.floor((diff % 3600000) / 60000); s = Math.floor((diff % 60000) / 1000); } const set = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = String(v).padStart(2, '0'); }; set('bsh-days', d); set('bsh-hours', h); set('bsh-mins', m); set('bsh-secs', s); } update(); setInterval(update, 1000); } """ def safe_add_to_dataset(registration_data, max_retries=5, retry_delay=3): """Append a new registration; refuse to write if existing rows can't be loaded (data-loss guard).""" try: logger.info("Starting new registration process") new_row = { "timestamp": registration_data["timestamp"], "full_name": registration_data["personal_info"]["full_name"], "email": registration_data["personal_info"]["email"], "hf_username": registration_data["personal_info"]["hf_username"], "gradio_usage": registration_data["personal_info"]["gradio_usage"], "track_interest": registration_data["participation"]["track_interest"], "previous_participation": str(registration_data["participation"]["previous_participation"]), "experience_level": registration_data["participation"]["experience_level"], "how_heard": registration_data["participation"]["how_heard"], "planned_small_model": registration_data["build_small"]["planned_small_model"] or "", "bonus_quests": str(registration_data["build_small"]["bonus_quests"]), "project_description": registration_data["additional"]["project_description"] or "", } existing_df = None load_successful = False for attempt in range(max_retries): logger.info(f"Loading attempt {attempt + 1}/{max_retries}") try: api = HfApi() files = api.list_repo_files(DATASET_NAME, repo_type="dataset") parquet_files = [f for f in files if f.endswith('.parquet') and 'train' in f] if parquet_files: logger.info(f"Found parquet file: {parquet_files[0]}") with tempfile.TemporaryDirectory() as temp_dir: parquet_file = api.hf_hub_download( repo_id=DATASET_NAME, filename=parquet_files[0], repo_type="dataset", cache_dir=temp_dir, force_download=True, ) existing_df = pd.read_parquet(parquet_file) logger.info(f"Successfully loaded {len(existing_df)} existing rows") load_successful = True break else: logger.warning("No parquet files found") except Exception as load_error: logger.warning(f"Attempt {attempt + 1} failed: {str(load_error)[:100]}") if attempt < max_retries - 1: logger.info(f"Waiting {retry_delay} seconds before retry...") time.sleep(retry_delay) continue if not load_successful or existing_df is None: logger.error("CRITICAL SAFETY ERROR: Could not load existing dataset after multiple attempts.") logger.error("REFUSING to proceed to prevent data loss!") return False, ( "❌ Registration temporarily unavailable due to technical issues. " "Please try again in a few minutes. If the problem persists, contact support." ) if len(existing_df) > 0: duplicate_check = existing_df[ (existing_df['email'].str.lower() == new_row['email'].lower()) | (existing_df['hf_username'].str.lower() == new_row['hf_username'].lower()) ] if len(duplicate_check) > 0: logger.warning("Duplicate registration attempt detected") return False, "❌ Error: This email or Hugging Face username is already registered." combined_df = pd.concat([existing_df, pd.DataFrame([new_row])], ignore_index=True) logger.info(f"Combined data now has {len(combined_df)} rows (was {len(existing_df)})") backup_timestamp = int(time.time()) try: updated_dataset = Dataset.from_pandas(combined_df) backup_name = f"{BACKUP_DATASET_PREFIX}-{backup_timestamp}" logger.info(f"Creating backup: {backup_name}") updated_dataset.push_to_hub(backup_name, private=True) logger.info("Pushing to main dataset...") updated_dataset.push_to_hub(DATASET_NAME, private=True) logger.info(f"Successfully saved registration. Total rows: {len(combined_df)}") time.sleep(2) try: api.list_repo_files(DATASET_NAME, repo_type="dataset") logger.info("Upload verification: Files updated successfully") except Exception: logger.warning("Could not verify upload (this may be normal)") return True, "Registration successful!" except Exception as upload_error: error_msg = str(upload_error).lower() if any(indicator in error_msg for indicator in ['rate limit', '429', 'too many requests']): logger.warning("Rate limit hit - registration system temporarily busy") return False, "⏳ Registration temporarily unavailable due to high server load. Please try again in 10–15 minutes." logger.error(f"Upload failed: {upload_error}") return False, f"❌ Registration failed during upload: {str(upload_error)}" except Exception as e: logger.error(f"Unexpected error in registration: {e}") import traceback traceback.print_exc() return False, f"❌ Registration failed: {str(e)}" def verify_registration(email, hf_username): """Verify a registration by exact email + HF username match (case-insensitive).""" try: if not email or not email.strip(): return "❌ Please enter your email address" if not hf_username or not hf_username.strip(): return "❌ Please enter your Hugging Face username" try: dataset = load_dataset(DATASET_NAME, split="train") df = dataset.to_pandas() logger.info(f"Loaded dataset with {len(df)} registrations") except Exception as load_error: logger.error(f"Failed to load dataset: {load_error}") return "❌ Unable to verify registration at this time. Please try again later." if len(df) == 0: return "❌ No registration found with this email and Hugging Face username combination." email_lower = email.strip().lower() username_lower = hf_username.strip().lower() match = df[ (df['email'].str.lower() == email_lower) & (df['hf_username'].str.lower() == username_lower) ] if len(match) == 0: email_exists = df[df['email'].str.lower() == email_lower] username_exists = df[df['hf_username'].str.lower() == username_lower] if len(email_exists) > 0 and len(username_exists) == 0: return "❌ Email found but Hugging Face username doesn't match. Please check your username." if len(username_exists) > 0 and len(email_exists) == 0: return "❌ Hugging Face username found but email doesn't match. Please check your email." return "❌ No registration found with this email and Hugging Face username combination." registration = match.iloc[0] try: timestamp = pd.to_datetime(registration['timestamp']) reg_date = timestamp.strftime("%B %d, %Y at %I:%M %p UTC") except Exception: reg_date = registration['timestamp'] prev = registration['previous_participation'] if isinstance(prev, str): prev = prev.strip("[]'\"").replace("'", "") quests = registration['bonus_quests'] if 'bonus_quests' in registration.index else '' if isinstance(quests, str): quests = quests.strip("[]'\"").replace("'", "") quests_str = quests if quests else "_None selected_" planned_model = registration['planned_small_model'] if 'planned_small_model' in registration.index else '' planned_model_str = planned_model if planned_model else "_Not specified_" result = f""" ## βœ… Registration Confirmed! **Participant Details:** - **Full Name:** {registration['full_name']} - **Email:** {registration['email']} - **Hugging Face Username:** {registration['hf_username']} - **Registered On:** {reg_date} **Hackathon Participation:** - **Track Interest:** {registration['track_interest']} - **Hackathon Experience:** {prev} - **Experience Level:** {registration['experience_level']} - **Gradio Usage:** {registration['gradio_usage']} - **How You Heard:** {registration['how_heard']} **Build Small Plans:** - **Small Model You Plan to Use:** {planned_model_str} - **Bonus Quests of Interest:** {quests_str} **Project Idea:** {registration['project_description'] if registration['project_description'] else '_No project description provided_'} --- **Next Steps:** - 🎟️ API & compute credits will be assigned on **Thu, May 28, 2026** - πŸš€ Hack window opens **Fri, May 29, 2026** - 🏁 Submissions due **Mon, June 8, 2026** - πŸ’¬ Join the Gradio Discord channel `{DISCORD_CHANNEL}`: {DISCORD_INVITE} - πŸ“§ Watch your email for important updates """ logger.info(f"Verification successful for {email}") return result except Exception as e: logger.error(f"Error during verification: {e}") import traceback traceback.print_exc() return f"❌ An error occurred during verification: {str(e)}" def submit_registration(full_name, email, hf_username, gradio_usage, track_interest, previous_participation, experience_level, how_heard, planned_small_model, bonus_quests, acknowledgment, project_description): """Validate and submit a registration.""" if not full_name or not full_name.strip(): return "❌ Error: Please enter your full name" if not email or not email.strip(): return "❌ Error: Please enter your email address" if not hf_username or not hf_username.strip(): return "❌ Error: Please enter your Hugging Face username" if not gradio_usage: return "❌ Error: Please select how you're currently using Gradio" if not track_interest: return "❌ Error: Please select your preferred track" if not previous_participation: return "❌ Error: Please select at least one option for hackathon experience" if not experience_level: return "❌ Error: Please select your experience level" if not how_heard: return "❌ Error: Please select how you heard about this hackathon" if not acknowledgment: return "❌ Error: Please confirm your acknowledgment to participate" import re email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' if not re.match(email_pattern, email.strip()): return "❌ Error: Please enter a valid email address" registration_data = { "timestamp": datetime.now().isoformat(), "personal_info": { "full_name": full_name.strip(), "email": email.strip().lower(), "hf_username": hf_username.strip(), "gradio_usage": gradio_usage, }, "participation": { "track_interest": track_interest, "previous_participation": previous_participation, "experience_level": experience_level, "how_heard": how_heard, }, "build_small": { "planned_small_model": planned_small_model.strip() if planned_small_model else None, "bonus_quests": bonus_quests or [], }, "additional": { "project_description": project_description.strip() if project_description else None, }, } success, message = safe_add_to_dataset(registration_data) if not success: return f"❌ Registration failed: {message}" return f"""βœ… Registration Successful! Thank you, **{full_name}**! Your spot is locked in. 🎟️ Credits assigned **Thu, May 28** Β· πŸš€ Hack opens **Fri, May 29** Β· 🏁 Submissions due **Mon, Jun 8**.
πŸ’¬ Join the Gradio Discord channel `{DISCORD_CHANNEL}`: {DISCORD_INVITE}
πŸ“§ Watch your email for credits and updates. **See you in the woods! πŸ„πŸͺ΅**""" def check_dataset_health(): try: api = HfApi() files = api.list_repo_files(DATASET_NAME, repo_type="dataset") parquet_files = [f for f in files if f.endswith('.parquet')] if parquet_files: logger.info(f"Dataset health check passed - found {len(parquet_files)} parquet files") return True logger.warning("Dataset health check: No parquet files found (did you run seed_dataset.py?)") return False except Exception as e: logger.error(f"Dataset health check failed: {e}") return False logger.info("Starting Build Small Hackathon Registration System") logger.info(f"Dataset: {DATASET_NAME}") if check_dataset_health(): logger.info("System ready - dataset is healthy") else: logger.warning("System starting with dataset health warnings") custom_css = """ .gradio-container { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; /* Brand color tokens β€” light mode defaults */ --bsh-card-bg: #fbf6e8; --bsh-card-border: rgba(139, 111, 71, 0.30); --bsh-text: #2a1d0a; --bsh-text-soft: #6b4423; --bsh-accent: #4a7c2e; --bsh-accent-deep: #2d5016; --bsh-banner-text: #f5ecd9; --bsh-warn: #8b2e25; --bsh-warn-bg: rgba(178, 60, 50, 0.10); --bsh-warn-border: rgba(178, 60, 50, 0.40); --bsh-counter-bg: linear-gradient(135deg, #2d5016 0%, #1f3a0f 100%); --bsh-counter-num: #f5ecd9; --bsh-counter-label: #c9b072; --bsh-banner-bg: linear-gradient(135deg, #2d5016 0%, #4a7c2e 50%, #6b9039 100%); --bsh-discord-bg: rgba(74, 124, 46, 0.08); --bsh-link: #4a7c2e; } .dark .gradio-container, body.dark .gradio-container, .gradio-container.dark { /* Brand color tokens β€” dark mode overrides */ --bsh-card-bg: #1f1a12; --bsh-card-border: rgba(201, 176, 114, 0.35); --bsh-text: #ede1c3; --bsh-text-soft: #c9b072; --bsh-accent: #9bc466; --bsh-accent-deep: #6b9039; --bsh-banner-text: #f5ecd9; --bsh-warn: #ff9b8e; --bsh-warn-bg: rgba(255, 122, 110, 0.12); --bsh-warn-border: rgba(255, 122, 110, 0.40); --bsh-counter-bg: linear-gradient(135deg, #1a2e0d 0%, #0c1606 100%); --bsh-counter-num: #f5ecd9; --bsh-counter-label: #c9b072; --bsh-banner-bg: linear-gradient(135deg, #1a2e0d 0%, #2d5016 60%, #4a7c2e 100%); --bsh-discord-bg: rgba(155, 196, 102, 0.08); --bsh-link: #9bc466; } /* Banner */ .bsh-banner { background: var(--bsh-banner-bg); color: var(--bsh-banner-text); border-radius: 14px; padding: 20px 22px; margin: 0 0 12px 0; text-align: center; border: 1px solid var(--bsh-card-border); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12); } .bsh-banner h1 { margin: 0 0 4px 0 !important; font-size: 28px !important; font-weight: 800 !important; letter-spacing: -0.5px !important; color: var(--bsh-banner-text) !important; line-height: 1.15 !important; } .bsh-banner .tagline { font-size: 16px; font-style: italic; opacity: 0.92; margin-bottom: 4px; } .bsh-banner .meta { font-size: 14px; opacity: 0.85; } .bsh-banner .placeholder { margin-top: 12px; display: inline-block; background: rgba(245, 236, 217, 0.12); border: 1px dashed rgba(245, 236, 217, 0.40); border-radius: 6px; padding: 4px 12px; font-size: 11px; opacity: 0.80; letter-spacing: 0.3px; } /* Three-card info row */ .bsh-info-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; margin-bottom: 12px; } .bsh-card { background: var(--bsh-card-bg); border: 1px solid var(--bsh-card-border); border-radius: 10px; padding: 12px 14px; color: var(--bsh-text); } .bsh-title { color: var(--bsh-text-soft); font-size: 11px; font-weight: 800; letter-spacing: 1.5px; text-transform: uppercase; margin-bottom: 8px; } .bsh-content { font-size: 13.5px; line-height: 1.55; color: var(--bsh-text); } .bsh-track { display: flex; align-items: baseline; gap: 8px; margin-top: 4px; } .bsh-emoji { font-size: 16px; line-height: 1; } .bsh-track-name { font-weight: 700; color: var(--bsh-accent); font-size: 13.5px; } .bsh-track-desc { font-size: 12px; opacity: 0.82; color: var(--bsh-text); padding-left: 24px; margin-bottom: 4px; line-height: 1.4; } .bsh-rules { margin: 0 !important; padding-left: 18px !important; color: var(--bsh-text); } .bsh-rules li { font-size: 13.5px; line-height: 1.7; color: var(--bsh-text); } .bsh-soon { font-size: 13px; line-height: 1.55; color: var(--bsh-text); opacity: 0.92; } .bsh-link { margin-top: 6px; font-size: 12px; } .bsh-link a { color: var(--bsh-link); font-weight: 600; text-decoration: underline; } /* Dates + countdown row */ .bsh-dates-row { display: grid; grid-template-columns: 1.6fr 1fr; gap: 10px; margin-bottom: 14px; } .bsh-dates { background: var(--bsh-card-bg); border: 1px solid var(--bsh-card-border); border-radius: 10px; padding: 12px 14px; color: var(--bsh-text); } .bsh-dates-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 14px; font-size: 13px; color: var(--bsh-text); } .bsh-dates-grid b { color: var(--bsh-accent); font-weight: 700; } .bsh-warn { margin-top: 10px; background: var(--bsh-warn-bg); border: 1px solid var(--bsh-warn-border); border-radius: 6px; padding: 6px 10px; color: var(--bsh-warn); font-size: 12px; font-weight: 600; text-align: center; } .bsh-counter { background: var(--bsh-counter-bg); border: 1px solid var(--bsh-card-border); border-radius: 10px; padding: 12px 14px; color: var(--bsh-counter-num); text-align: center; display: flex; flex-direction: column; justify-content: center; } .bsh-counter-title { color: var(--bsh-counter-num); font-size: 11px; letter-spacing: 1.5px; font-weight: 800; text-transform: uppercase; margin-bottom: 2px; } .bsh-counter-target { color: var(--bsh-counter-label); font-size: 11px; font-style: italic; margin-bottom: 8px; opacity: 0.85; } .bsh-nums { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 4px; } .bsh-unit { display: flex; flex-direction: column; align-items: center; } .bsh-num { font-family: 'Courier New', monospace; font-size: 22px; font-weight: 700; color: var(--bsh-counter-num); line-height: 1; } .bsh-lbl { font-size: 9px; letter-spacing: 1px; color: var(--bsh-counter-label); margin-top: 4px; text-transform: uppercase; } /* Discord ribbon */ .bsh-discord { margin-top: 14px; background: var(--bsh-discord-bg); border: 1px solid var(--bsh-card-border); border-radius: 8px; padding: 10px 14px; text-align: center; color: var(--bsh-text); font-size: 13px; } .bsh-discord a { color: var(--bsh-link); font-weight: 700; text-decoration: underline; } .bsh-discord code { background: var(--bsh-discord-bg); border: 1px solid var(--bsh-card-border); padding: 1px 6px; border-radius: 4px; font-size: 11.5px; color: var(--bsh-text); } /* Buttons */ #submit-btn { background: linear-gradient(135deg, #4a7c2e 0%, #2d5016 100%) !important; border: 1px solid #6b4423 !important; color: #f5ecd9 !important; font-weight: 700 !important; font-size: 16px !important; padding: 12px 24px !important; border-radius: 10px !important; box-shadow: 0 3px 10px rgba(74, 49, 16, 0.18) !important; transition: all 0.25s ease !important; } #submit-btn:hover { transform: translateY(-1px) !important; box-shadow: 0 5px 14px rgba(74, 49, 16, 0.28) !important; background: linear-gradient(135deg, #5a8c3e 0%, #3d6020 100%) !important; } #verify-btn { background: #6b4423 !important; border: 1px solid #c9b072 !important; color: #f5ecd9 !important; font-weight: 700 !important; font-size: 14px !important; padding: 10px 20px !important; border-radius: 8px !important; transition: all 0.25s ease !important; } #verify-btn:hover { transform: translateY(-1px) !important; background: #7d5430 !important; } /* Responsive */ @media (max-width: 760px) { .bsh-info-row { grid-template-columns: 1fr; } .bsh-dates-row { grid-template-columns: 1fr; } .bsh-banner h1 { font-size: 24px !important; } } """ with gr.Blocks( title="Build Small Hackathon β€” Registration", css=custom_css, theme=gr.themes.Soft(primary_hue="green", secondary_hue="amber", neutral_hue="stone"), ) as demo: gr.HTML(HEADER_HTML) gr.HTML(INFO_ROW_HTML) gr.HTML(DATES_ROW_HTML) with gr.Tabs(): with gr.Tab("πŸ“ Register"): with gr.Row(): with gr.Column(): gr.Markdown("### ❋ 1. Personal Information") full_name = gr.Textbox( label="Full Name *", placeholder="Your full name as you'd like it on certificates", max_lines=1, ) email = gr.Textbox( label="Email Address *", placeholder="Primary contact email β€” we'll send updates and credits info here", max_lines=1, ) hf_username = gr.Textbox( label="Hugging Face Username *", placeholder="Required for org access and submissions", max_lines=1, ) gradio_usage = gr.Radio( label="How are you currently using Gradio? *", choices=[ "Professional work β€” my company uses Gradio", "Personal projects β€” building side projects", "Academic/Research β€” university or research work", "Learning β€” new to Gradio, want to learn", "Not using yet β€” interested to start", ], info="Helps us understand the community better", ) with gr.Column(): gr.Markdown("### ❋ 2. Hackathon Participation") track_interest = gr.Radio( label="Which track interests you most? *", choices=[ "🏑 Backyard AI", "πŸ„ An Adventure in Thousand Token Wood", "Both", "Undecided / Not sure yet", ], ) previous_participation = gr.CheckboxGroup( label="Hackathon experience (select all that apply) *", choices=[ "MCP 1st Birthday", "Agents & MCP Hackathon", "Other AI hackathons", "First timer", ], ) experience_level = gr.Radio( label="Your experience with AI/Agents development *", choices=[ "Beginner β€” new to AI development", "Intermediate β€” some AI projects", "Advanced β€” regular AI developer", "Expert β€” professional AI engineer", ], ) how_heard = gr.Dropdown( label="How did you hear about this hackathon? *", choices=[ "Hugging Face email/newsletter", "Build Small org page", "Twitter/X", "LinkedIn", "Gradio Discord", "From a colleague/friend", "YouTube", "Reddit", "Sponsor announcement", "Previous Gradio hackathon", "Other", ], ) with gr.Row(): with gr.Column(): gr.Markdown("### ❋ 3. Build Small Specifics") planned_small_model = gr.Textbox( label="Which small model are you planning to use? (optional)", placeholder="e.g. Llama-3.2-3B, Qwen2.5-7B, gemma-2-2b-it, SmolLM-1.7B...", info="Reminder: ≀ 32B parameters. Leave blank if you haven't decided.", max_lines=1, ) bonus_quests = gr.CheckboxGroup( label="Which bonus quests / merit badges interest you? (optional)", choices=[ "πŸ”Œ Off the Grid β€” no cloud APIs", "🎯 Well-Tuned β€” use a fine-tuned published model", "🎨 Off-Brand β€” custom Gradio frontend", "πŸ¦™ Llama Champion β€” use llama.cpp runtime", "πŸ“‘ Sharing is Caring β€” share an agent trace", "πŸ““ Field Notes β€” write a blog post / report", ], info="Pick as many as you'd like. Just signal for us β€” you can change your mind during the event.", ) with gr.Column(): gr.Markdown("### ❋ 4. Project Idea (optional)") project_description = gr.Textbox( label="What are you most excited to build?", placeholder="A neighbor's recipe-translator? A whimsical mushroom-identifier? Tell us in a sentence or two.", lines=4, ) gr.Markdown("### ❋ 5. Acknowledgment") acknowledgment = gr.Checkbox( label="I'm in. *", info=( "I commit to actively participate, build with a model ≀ 32B parameters, " "host on a Gradio Hugging Face Space, and submit a project (with demo video and social post) " "by Mon, June 8, 2026. I understand that any API/compute credits provided are intended for use on my " "hackathon project during the event period." ), ) submit_btn = gr.Button( "πŸͺ΅ Register for the Build Small Hackathon", variant="primary", size="lg", elem_id="submit-btn", ) output = gr.Markdown() def handle_registration_with_state(*args): try: result = submit_registration(*args) return result, gr.Button( "πŸͺ΅ Register for the Build Small Hackathon", interactive=True, variant="primary", elem_id="submit-btn", ) except Exception as e: logger.error(f"Registration handling error: {e}") return f"❌ An unexpected error occurred: {str(e)}", gr.Button( "πŸͺ΅ Register for the Build Small Hackathon", interactive=True, variant="primary", elem_id="submit-btn", ) submit_btn.click( fn=lambda *args: (gr.Button("⏳ Processing registration...", interactive=False, variant="secondary"), ""), inputs=[ full_name, email, hf_username, gradio_usage, track_interest, previous_participation, experience_level, how_heard, planned_small_model, bonus_quests, acknowledgment, project_description, ], outputs=[submit_btn, output], queue=False, ).then( fn=handle_registration_with_state, inputs=[ full_name, email, hf_username, gradio_usage, track_interest, previous_participation, experience_level, how_heard, planned_small_model, bonus_quests, acknowledgment, project_description, ], outputs=[output, submit_btn], queue=True, ) with gr.Tab("πŸ” Verify Registration"): gr.Markdown(""" ### Check Your Registration Status Enter your email address and Hugging Face username to verify your registration and view your details. Both must match exactly (case-insensitive) for security purposes. """) with gr.Row(): with gr.Column(): verify_email = gr.Textbox( label="Email Address", placeholder="Enter your registered email", max_lines=1, ) verify_hf_username = gr.Textbox( label="Hugging Face Username", placeholder="Enter your registered HF username", max_lines=1, ) verify_btn = gr.Button( "πŸ” Check Registration Status", variant="primary", size="lg", elem_id="verify-btn", ) with gr.Column(): gr.Markdown(f""" **Need Help?** - Use the **exact** email and username you registered with - Both fields are case-insensitive but must match - Registration is open until **Wed, May 27, 2026 Β· 23:59 UTC** β€” once it closes, no new entries can be added **Support:** - Discord: [{DISCORD_INVITE}]({DISCORD_INVITE}) (channel `{DISCORD_CHANNEL}`) - Email: gradio-team@huggingface.co """) verify_output = gr.Markdown() verify_btn.click( fn=verify_registration, inputs=[verify_email, verify_hf_username], outputs=verify_output, ) gr.HTML(DISCORD_RIBBON_HTML) gr.Markdown(f"""
✦ Questions? Email gradio-team@huggingface.co ✦
""") # Hook up the countdown JS β€” runs once on page load, then ticks every second demo.load(fn=lambda: None, inputs=None, outputs=None, js=COUNTDOWN_JS) if __name__ == "__main__": demo.launch(allowed_paths=["."])