ysharma's picture
ysharma HF Staff
Update app.py
9172b56 verified
import gradio as gr
import json
from datetime import datetime, timezone
from datasets import Dataset, load_dataset
from huggingface_hub import login, HfApi
import pandas as pd
import os
import time
import tempfile
import logging
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Authenticate with Hugging Face
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")
# Replace with your actual dataset name for Winter 2025
DATASET_NAME = "ysharma/gradio-hackathon-registrations-winter-2025"
def get_countdown_html():
"""Generate countdown HTML with current time values calculated in Python"""
# Target date: November 13, 2025, 11:59 PM UTC
target_date = datetime(2025, 11, 13, 23, 59, 59, tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
difference = target_date - now
if difference.total_seconds() > 0:
total_seconds = int(difference.total_seconds())
days = total_seconds // (24 * 3600)
hours = (total_seconds % (24 * 3600)) // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
else:
days = hours = minutes = seconds = 0
# Calculate progress for circles (stroke-dashoffset calculation)
# The circumference is 283, so we calculate the offset
days_offset = 283 - min((days / 365) * 283, 283)
hours_offset = 283 - ((hours / 24) * 283)
minutes_offset = 283 - ((minutes / 60) * 283)
seconds_offset = 283 - ((seconds / 60) * 283)
return f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hackathon Countdown</title>
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
margin: 0;
padding: 20px;
}}
.countdown-container {{
background: #C8DDD7;
border-radius: 20px;
padding: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
text-align: center;
max-width: 600px;
width: 100%;
margin: 0 auto;
display: block;
}}
.title {{
font-size: 28px;
font-weight: 600;
color: #000000;
margin-bottom: 40px;
}}
.countdown {{
display: flex;
justify-content: space-around;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}}
.time-unit {{
display: flex;
flex-direction: column;
align-items: center;
min-width: 100px;
}}
.circle {{
position: relative;
width: 100px;
height: 100px;
margin-bottom: 15px;
}}
.circle svg {{
width: 100%;
height: 100%;
transform: rotate(-90deg);
}}
.circle-bg {{
fill: none;
stroke: rgba(0, 0, 0, 0.15);
stroke-width: 6;
}}
.circle-progress {{
fill: none;
stroke: #000000;
stroke-width: 6;
stroke-linecap: round;
stroke-dasharray: 283;
transition: stroke-dashoffset 0.3s ease;
}}
.number {{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 32px;
font-weight: 700;
color: #000000;
font-family: 'Courier New', monospace;
}}
.label {{
font-size: 16px;
font-weight: 500;
color: #000000;
text-transform: uppercase;
letter-spacing: 1px;
}}
@keyframes pulse {{
0% {{ opacity: 1; }}
50% {{ opacity: 0.8; }}
100% {{ opacity: 1; }}
}}
.number {{
animation: pulse 1s ease-in-out infinite;
}}
@media (max-width: 480px) {{
.countdown {{
gap: 15px;
}}
.circle {{
width: 80px;
height: 80px;
}}
.number {{
font-size: 24px;
}}
.title {{
font-size: 22px;
margin-bottom: 30px;
}}
}}
</style>
</head>
<body>
<div class="countdown-container">
<h1 class="title">Countdown to the Global Event Has Begun. Registrations are now open!</h1>
<div class="countdown">
<div class="time-unit">
<div class="circle">
<svg>
<circle class="circle-bg" cx="50" cy="50" r="45"></circle>
<circle class="circle-progress" cx="50" cy="50" r="45" style="stroke-dashoffset: {days_offset:.1f};"></circle>
</svg>
<div class="number">{days:02d}</div>
</div>
<div class="label">Days</div>
</div>
<div class="time-unit">
<div class="circle">
<svg>
<circle class="circle-bg" cx="50" cy="50" r="45"></circle>
<circle class="circle-progress" cx="50" cy="50" r="45" style="stroke-dashoffset: {hours_offset:.1f};"></circle>
</svg>
<div class="number">{hours:02d}</div>
</div>
<div class="label">Hours</div>
</div>
<div class="time-unit">
<div class="circle">
<svg>
<circle class="circle-bg" cx="50" cy="50" r="45"></circle>
<circle class="circle-progress" cx="50" cy="50" r="45" style="stroke-dashoffset: {minutes_offset:.1f};"></circle>
</svg>
<div class="number">{minutes:02d}</div>
</div>
<div class="label">Minutes</div>
</div>
<div class="time-unit">
<div class="circle">
<svg>
<circle class="circle-bg" cx="50" cy="50" r="45"></circle>
<circle class="circle-progress" cx="50" cy="50" r="45" style="stroke-dashoffset: {seconds_offset:.1f};"></circle>
</svg>
<div class="number">{seconds:02d}</div>
</div>
<div class="label">Seconds</div>
</div>
</div>
</div>
<script>
// Auto-refresh every second using meta refresh or JavaScript
setTimeout(function(){{
location.reload();
}}, 1000);
</script>
</body>
</html>
"""
def safe_add_to_dataset(registration_data, max_retries=5, retry_delay=3):
"""
Safely add new registration data with bulletproof error handling
NEVER creates new datasets - only adds to existing ones
"""
try:
logger.info("Starting new registration process")
# Create new row with updated fields
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": str(registration_data["participation"]["track_interest"]),
"previous_participation": registration_data["participation"]["previous_participation"],
"experience_level": registration_data["participation"]["experience_level"],
"how_heard": registration_data["participation"]["how_heard"],
"project_description": registration_data["additional"]["project_description"] or "",
}
logger.info("Created new row data")
# Multi-attempt loading with different strategies
existing_df = None
load_successful = False
for attempt in range(max_retries):
logger.info(f"Loading attempt {attempt + 1}/{max_retries}")
try:
# Strategy 1: Direct parquet file access (most reliable)
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]}")
# Download to temporary location
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
# CRITICAL SAFETY CHECK: Never proceed without existing data
if not load_successful or existing_df is None:
error_msg = "🚨 CRITICAL SAFETY ERROR: Could not load existing dataset after multiple attempts."
logger.error(error_msg)
logger.error("🚨 REFUSING to proceed to prevent data loss!")
logger.error("🚨 Please check dataset manually or contact administrators.")
return False, (
"❌ Registration temporarily unavailable due to technical issues. "
"Please try again in a few minutes. If the problem persists, contact support."
)
# Check for duplicates
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."
# Add new row safely
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)})")
# Create timestamped backup before upload
backup_timestamp = int(time.time())
try:
# Convert to Dataset and upload
logger.info("Converting to HuggingFace Dataset format...")
updated_dataset = Dataset.from_pandas(combined_df)
# Create backup first
backup_name = f"{DATASET_NAME}-auto-backup-{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("βœ… Successfully saved new registration")
logger.info(f"Total rows in dataset: {len(combined_df)}")
# Quick verification
time.sleep(2)
try:
verify_files = api.list_repo_files(DATASET_NAME, repo_type="dataset")
logger.info("βœ… Upload verification: Files updated successfully")
except:
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."
else:
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 if a user is registered by checking both email and HF username
Returns registration details if both match
"""
try:
# Validate inputs
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"
logger.info(f"Verification attempt for email: {email}")
# Load dataset
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."
# Search for exact match (both email AND username must match)
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:
# Check if email exists with different username
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."
elif len(username_exists) > 0 and len(email_exists) == 0:
return "❌ Hugging Face username found but email doesn't match. Please check your email."
else:
return "❌ No registration found with this email and Hugging Face username combination."
# Registration found - format the details
registration = match.iloc[0]
# Parse timestamp
try:
timestamp = pd.to_datetime(registration['timestamp'])
reg_date = timestamp.strftime("%B %d, %Y at %I:%M %p UTC")
except:
reg_date = registration['timestamp']
# Format track interests
track_interest = registration['track_interest']
if isinstance(track_interest, str):
# Clean up the string representation of list
track_interest = track_interest.strip("[]'\"").replace("'", "")
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:** {track_interest}
- **Gradio Usage:** {registration['gradio_usage']}
- **Previous Participation:** {registration['previous_participation']}
- **Experience Level:** {registration['experience_level']}
- **How You Heard:** {registration['how_heard']}
**Project Idea:**
{registration['project_description'] if registration['project_description'] else '_No project description provided_'}
---
**Next Steps:**
- πŸ”‘ API and Compute credits will be distributed before or during the hackathon
- πŸ’¬ Join our Discord community channel `mcp-1st-birthday-officialπŸ†`: https://discord.gg/92sEPT2Zhv
- πŸ“§ Watch your email for important updates
- πŸš€ Start planning your project!
"""
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,
acknowledgment, project_description):
"""Process the registration form submission with enhanced validation"""
# Enhanced validation
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 at least one track of interest"
if not previous_participation:
return "❌ Error: Please select your 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"
# Email format validation
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"
# Process the registration data
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,
},
"additional": {
"project_description": project_description.strip() if project_description else None,
}
}
# Save to Hugging Face dataset with bulletproof error handling
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 registration has been received and saved.<br>
πŸ“§ You will receive information about API credits as we finalize sponsor partnerships.<br>
πŸ”‘ API and Compute credits will be distributed before or during the Hackathon starting date.<br>
πŸ’¬ Be sure to **join the Huggingface organization** for regular updates on the hackathon and **to submit your entries**. Join our Discord community channel `mcp-1st-birthday-officialπŸ†` for updates and support during the event: https://discord.gg/92sEPT2Zhv
**See you at the hackathon! πŸš€**"""
# Health check function
def check_dataset_health():
"""Check if the dataset is accessible and healthy"""
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
else:
logger.warning("⚠️ Dataset health check: No parquet files found")
return False
except Exception as e:
logger.error(f"❌ Dataset health check failed: {e}")
return False
# Initialize with health check
logger.info("πŸš€ Starting Gradio Hackathon Registration System - Winter 2025")
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 for MCP theme
custom_css = """
.gradio-container {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}
.header-gradient {
color: #000000;
}
/* MCP-themed Submit Button Styling */
#gradient-submit-btn {
background: #C8DDD7 !important;
border: 2px solid #000000 !important;
color: #000000 !important;
font-weight: 600 !important;
font-size: 18px !important;
padding: 16px 32px !important;
border-radius: 12px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
transition: all 0.3s ease !important;
text-transform: none !important;
}
#gradient-submit-btn:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25) !important;
background: #B8CEC7 !important;
}
#gradient-submit-btn:active {
transform: translateY(0px) !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2) !important;
}
/* Disabled state for the button */
#gradient-submit-btn:disabled {
opacity: 0.6 !important;
cursor: not-allowed !important;
transform: none !important;
}
/* Verify button styling - Inverted MCP theme */
#verify-btn {
background: #000000 !important;
border: 2px solid #C8DDD7 !important;
color: #C8DDD7 !important;
font-weight: 600 !important;
font-size: 16px !important;
padding: 12px 24px !important;
border-radius: 8px !important;
box-shadow: 0 3px 10px rgba(200, 221, 215, 0.3) !important;
transition: all 0.3s ease !important;
}
#verify-btn:hover {
transform: translateY(-2px) !important;
box-shadow: 0 5px 15px rgba(200, 221, 215, 0.5) !important;
background: #1a1a1a !important;
border-color: #D8EDE7 !important;
}
"""
# Function to update countdown periodically
def update_countdown():
"""Update the countdown HTML"""
return gr.HTML(get_countdown_html())
# Create the Gradio interface
with gr.Blocks(title="Gradio Agents & MCP Hackathon - Winter 2025", css=custom_css, theme="ocean") as demo:
# Header
gr.Markdown("""
# MCP's 1st Birthday - Hosted by Anthropic and Gradio
**Join our [Discord Community](https://discord.gg/92sEPT2Zhv) channel `mcp-1st-birthday-officialπŸ†` for active support during the hackathon.**
**NOTE: If the registration app says you are registered, then that information is correct and you don't need to send us anything else. NO CONFIRMATION EMAILS ARE SENT FROM OUR SIDE.**
**πŸ“… Event Dates:** November 14-30, 2025 (17 days, 3 weekends) | **πŸ† Prizes: $17,000+ USD in cash prizes** | **πŸ’» Location:** Online & Global
**🎁 FREE API & Compute Credits** (Details announced soon):
- API credits from major AI providers
- Access to latest and strongest LLMs
- Compute resources for building your projects
**The definitive Agents & MCP event is back!** OpenAI, Microsoft, Google DeepMind, and numerous startups have already adopted MCP. Join the community that launched the MCP developer movement. Participate and stand a chance to learn the latest AI technologies and also Win BIG!
""")
# Create countdown HTML component that will be updated
countdown_display = gr.HTML(get_countdown_html())
gr.Markdown("---")
# Create tabs
with gr.Tabs():
# Registration Tab
with gr.Tab("πŸ“ Register"):
with gr.Row():
with gr.Column():
# Personal Information Section
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 important updates here)",
max_lines=1
)
hf_username = gr.Textbox(
label="Hugging Face Username *",
placeholder="Required for organization 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 our community better"
)
with gr.Column():
# Hackathon Participation Section
gr.Markdown("## 2. Hackathon Participation")
track_interest = gr.CheckboxGroup(
label="Which track interests you most? *",
choices=[
"Track 1: Building MCP",
"Track 2: MCP in Action (Agents)",
]
)
previous_participation = gr.Radio(
label="Hackathon experience *",
choices=[
"I participated in June 2025 Agents & MCP Hackathon",
"I've done other AI hackathons before",
"This is my first AI hackathon"
]
)
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",
"Twitter/X",
"LinkedIn",
"Discord",
"From a colleague/friend",
"YouTube",
"Reddit",
"Sponsor announcement",
"I participated in June 2025",
"Other"
]
)
with gr.Row():
with gr.Column():
# Additional Information Section
gr.Markdown("## 3. Additional Information")
project_description = gr.Textbox(
label="What type of project are you most excited to build?",
placeholder="Brief description of your project idea or what interests you most",
lines=3
)
with gr.Column():
# Acknowledgment Section
gr.Markdown("## 4. Acknowledgment")
acknowledgment = gr.Checkbox(
label="Acknowledgment *",
info="""I commit to actively participate and submit a project by November 30, 2025. I understand that API/compute credits are provided to support hackathon participation and should be used for building my hackathon project. I commit to using these credits responsibly during the event period.""",
)
# Submit button
submit_btn = gr.Button("πŸš€ Register for Hackathon", variant="primary", size="lg", elem_id="gradient-submit-btn")
# Output
output = gr.Markdown()
# Enhanced submit function
def handle_registration_with_state(*args):
try:
result = submit_registration(*args)
return result, gr.Button("πŸš€ Register for Hackathon", interactive=True, variant="primary")
except Exception as e:
logger.error(f"Registration handling error: {e}")
return f"❌ An unexpected error occurred: {str(e)}", gr.Button("πŸš€ Register for Hackathon", interactive=True, variant="primary")
# Click event with updated inputs
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,
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,
acknowledgment, project_description,
],
outputs=[output, submit_btn],
queue=True
)
# Verification Tab
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.
**Note:** Both email and username must match exactly 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("""
### Need Help?
- Make sure you enter the **exact** email and username you used during registration
- Both fields are **case-insensitive** but must match your registration
- If you can't find your registration, try registering again or contact support
**Support:**
- Discord: https://discord.gg/92sEPT2Zhv
- Email: [email protected]
""")
verify_output = gr.Markdown()
# Verification click event
verify_btn.click(
fn=verify_registration,
inputs=[verify_email, verify_hf_username],
outputs=verify_output
)
# Footer
gr.Markdown("""
**Questions?** Join Huggingface [Discord](https://discord.gg/92sEPT2Zhv) or email: [email protected]
""")
# Set up a timer to update the countdown every second
# This will refresh the countdown display periodically
demo.load(update_countdown, None, countdown_display, every=1)
if __name__ == "__main__":
demo.launch(allowed_paths=["."])