|
import gradio as gr |
|
import json |
|
from datetime import datetime |
|
from datasets import 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__) |
|
|
|
|
|
DATASET_NAME = "ysharma/gradio-hackathon-registrations-2025" |
|
COUNTER = """ |
|
<!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-image: url('/gradio_api/file=background.jpg'); |
|
background-size: cover; |
|
background-position: center; |
|
background-repeat: no-repeat; |
|
border-radius: 20px; |
|
padding: 40px; |
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); |
|
text-align: center; |
|
max-width: 600px; |
|
width: 100%; |
|
margin: 0 auto; |
|
display: block; |
|
position: relative; |
|
} |
|
.countdown-container::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
bottom: 0; |
|
background: rgba(255, 255, 255, 0.85); |
|
border-radius: 20px; |
|
z-index: 1; |
|
} |
|
/* Dark theme adjustments */ |
|
@media (prefers-color-scheme: dark) { |
|
.countdown-container::before { |
|
background: rgba(0, 0, 0, 0.6); |
|
} |
|
|
|
.title { |
|
color: #ffffff; |
|
} |
|
|
|
.number { |
|
color: #ffffff; |
|
} |
|
|
|
.label { |
|
color: #cccccc; |
|
} |
|
} |
|
.countdown-container > * { |
|
position: relative; |
|
z-index: 2; |
|
} |
|
.title { |
|
font-size: 28px; |
|
font-weight: 600; |
|
color: #333; |
|
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: #e9ecef; |
|
stroke-width: 6; |
|
} |
|
.circle-progress { |
|
fill: none; |
|
stroke: #ff6b35; |
|
stroke-width: 6; |
|
stroke-linecap: round; |
|
stroke-dasharray: 283; |
|
stroke-dashoffset: 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: #333; |
|
font-family: 'Courier New', monospace; |
|
} |
|
.label { |
|
font-size: 16px; |
|
font-weight: 500; |
|
color: #666; |
|
text-transform: uppercase; |
|
letter-spacing: 1px; |
|
} |
|
@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">Registrations and Submissions are available until June 10 UTC EOD</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" id="days-progress"></circle> |
|
</svg> |
|
<div class="number" id="days">00</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" id="hours-progress"></circle> |
|
</svg> |
|
<div class="number" id="hours">00</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" id="minutes-progress"></circle> |
|
</svg> |
|
<div class="number" id="minutes">00</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" id="seconds-progress"></circle> |
|
</svg> |
|
<div class="number" id="seconds">00</div> |
|
</div> |
|
<div class="label">Seconds</div> |
|
</div> |
|
</div> |
|
</div> |
|
<script> |
|
// Set your target date here in UTC (Year, Month-1, Day, Hour, Minute, Second) |
|
// Month is 0-indexed (0 = January, 11 = December) |
|
const targetDate = new Date(Date.UTC(2025, 5, 10, 23, 59, 59)); // June 15, 2025, 10:00 AM UTC |
|
function updateCountdown() { |
|
const now = new Date(); // Current time in UTC milliseconds |
|
const difference = targetDate.getTime() - now.getTime(); |
|
if (difference > 0) { |
|
const days = Math.floor(difference / (1000 * 60 * 60 * 24)); |
|
const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); |
|
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)); |
|
const seconds = Math.floor((difference % (1000 * 60)) / 1000); |
|
// Update numbers |
|
document.getElementById('days').textContent = days.toString().padStart(2, '0'); |
|
document.getElementById('hours').textContent = hours.toString().padStart(2, '0'); |
|
document.getElementById('minutes').textContent = minutes.toString().padStart(2, '0'); |
|
document.getElementById('seconds').textContent = seconds.toString().padStart(2, '0'); |
|
// Update progress circles |
|
updateProgress('days-progress', days, 365); // Max 365 days |
|
updateProgress('hours-progress', hours, 24); |
|
updateProgress('minutes-progress', minutes, 60); |
|
updateProgress('seconds-progress', seconds, 60); |
|
} else { |
|
// Countdown has ended |
|
document.getElementById('days').textContent = '00'; |
|
document.getElementById('hours').textContent = '00'; |
|
document.getElementById('minutes').textContent = '00'; |
|
document.getElementById('seconds').textContent = '00'; |
|
|
|
// Reset all progress circles |
|
updateProgress('days-progress', 0, 365); |
|
updateProgress('hours-progress', 0, 24); |
|
updateProgress('minutes-progress', 0, 60); |
|
updateProgress('seconds-progress', 0, 60); |
|
} |
|
} |
|
function updateProgress(elementId, current, max) { |
|
const circle = document.getElementById(elementId); |
|
const circumference = 283; // 2 * Ο * 45 (radius) |
|
const progress = (current / max) * circumference; |
|
const offset = circumference - progress; |
|
circle.style.strokeDashoffset = offset; |
|
} |
|
// Update countdown immediately and then every second |
|
updateCountdown(); |
|
setInterval(updateCountdown, 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") |
|
|
|
|
|
new_row = { |
|
"timestamp": registration_data["timestamp"], |
|
"full_name": registration_data["personal_info"]["full_name"], |
|
"email": registration_data["personal_info"]["email"], |
|
"github_username": registration_data["personal_info"]["github_username"] or "", |
|
"hf_username": registration_data["personal_info"]["hf_username"], |
|
"track_interest": str(registration_data["participation"]["track_interest"]), |
|
"participation_type": registration_data["participation"]["participation_type"], |
|
"teammates": registration_data["teammates"] or "", |
|
"project_description": registration_data["additional"]["project_description"] or "" |
|
} |
|
|
|
logger.info("Created new row data") |
|
|
|
|
|
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: |
|
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." |
|
) |
|
|
|
|
|
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: |
|
|
|
logger.info("Converting to HuggingFace Dataset format...") |
|
updated_dataset = Dataset.from_pandas(combined_df) |
|
|
|
|
|
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)}") |
|
|
|
|
|
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 submit_registration(full_name, email, github_username, hf_username, |
|
track_interest, participation_type, teammates, |
|
commitment, api_credits_ack, project_description): |
|
"""Process the registration form submission with 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 track_interest: |
|
return "β Error: Please select at least one track of interest" |
|
|
|
if not participation_type: |
|
return "β Error: Please select your participation type" |
|
|
|
if not commitment: |
|
return "β Error: Please confirm your commitment to participate" |
|
|
|
if not api_credits_ack: |
|
return "β Error: Please acknowledge the API credits terms" |
|
|
|
|
|
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(), |
|
"github_username": github_username.strip() if github_username else "", |
|
"hf_username": hf_username.strip() |
|
}, |
|
"participation": { |
|
"track_interest": track_interest, |
|
"participation_type": participation_type, |
|
}, |
|
"teammates": teammates.strip() if teammates else None, |
|
"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 registration has been received and saved. |
|
|
|
π§ You will receive an email prior to the Hackathon start dates containing information about the API credits, provided you meet the required criteria. |
|
|
|
π API and Compute credits will be distributed well before the Hackathon starting date and time. |
|
|
|
π¬ Be sure to join the Huggingface organization for regular updates on the hackathon and to submit your entries. Join our Discord community channel `agents-mcp-hackathon` for updates and support during the event: https://discord.gg/Qe3jMKVczR |
|
|
|
See you at the hackathon! π""" |
|
|
|
|
|
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 |
|
|
|
|
|
logger.info("π Starting Gradio 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") |
|
|
|
|
|
with gr.Blocks(title="Gradio Agents & MCP Hackathon 2025",) as demo: |
|
|
|
|
|
gr.Markdown(""" |
|
# π€ Gradio Agents & MCP Hackathon 2025 Registration π |
|
**Updates: TOTAL SIGNUPS: 4K+. Signups are available all week till June 8. Emails containing credits are being sent regularly. You won't receive any email on successful registrations.**<br> |
|
**Join our [Discord Community](https://discord.gg/Qe3jMKVczR) channel `agents-mcp-hackathon` for active support during the hackathon.** |
|
|
|
**π
Event Dates:** June 2-10, 2025 | **π Prizes: $16,500 in cash prizes** **π» Location:** Online & Global | <br> |
|
**π Bonus: FREE Credits** for participants* from our sponsors worth **$1 MILLION++** |
|
- $250 of GPU/CPU Compute credits **TO EVERY PARTICIPANT** in the Hackathon from [Modal Labs](https://modal.com/) |
|
- $25 of API credits **TO EVERY PARTICIPANT** in the Hackathon from [Huggingface](https://huggingface.co/) |
|
- $25 of API credits to each of the first 3300 participants from [Nebius](https://nebius.com/) |
|
- $25 of API credits to each of the first 1000 participants from [Anthropic](https://www.anthropic.com/) |
|
- $25 of API credits to each of the first 1000 participants from [OpenAI](https://openai.com/) |
|
- $15 of API credits to each of the first 1000 participants from [Hyperbolic Labs](https://hyperbolic.xyz/) |
|
- $25 of API credits to each of the first 500 participants from [MistralAI](https://mistral.ai/) |
|
- $25 of API credits to each of the first 250 participants from [Sambanova.AI](https://sambanova.ai/) |
|
*API and GPU/CPU Compute credits are provided to support hackathon participation and will be distributed based on registration verification. |
|
""") |
|
gr.HTML(COUNTER) |
|
gr.Markdown("---") |
|
|
|
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 important updates here)", |
|
max_lines=1 |
|
) |
|
|
|
github_username = gr.Textbox( |
|
label="GitHub Username", |
|
placeholder="Your GitHub username (helps us verify your developer status)", |
|
max_lines=1 |
|
) |
|
|
|
hf_username = gr.Textbox( |
|
label="Hugging Face Username *", |
|
placeholder="Required for organization access and submissions", |
|
max_lines=1 |
|
) |
|
|
|
|
|
gr.Markdown("## 2. Hackathon Participation") |
|
|
|
track_interest = gr.CheckboxGroup( |
|
label="Which track interests you most? *", |
|
choices=[ |
|
"Track 1: MCP Server Implementation", |
|
"Track 2: Custom Agent Components", |
|
"Track 3: Agentic Demo Showcase", |
|
] |
|
) |
|
|
|
participation_type = gr.Radio( |
|
label="Participation Type *", |
|
choices=[ |
|
"Individual participant", |
|
"Joining as a team", |
|
] |
|
) |
|
|
|
teammates = gr.Textbox( |
|
label="If you have a team, list your teammates' emails separated by commas", |
|
placeholder="List names and emails of your team members (optional)", |
|
lines=3 |
|
) |
|
|
|
|
|
gr.Markdown("## 3. Commitment & Verification") |
|
|
|
commitment = gr.Checkbox( |
|
label="Hackathon Commitment *", |
|
info="I commit to actively participate and submit a project by June 8, 2025", |
|
) |
|
|
|
api_credits_ack = gr.Checkbox( |
|
label="API Credits Acknowledgment *", |
|
info="I understand that API 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." |
|
) |
|
|
|
|
|
gr.Markdown("## 4. 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 |
|
) |
|
|
|
|
|
submit_btn = gr.Button("π Register for Hackathon", variant="primary", size="lg") |
|
|
|
|
|
output = gr.Markdown() |
|
|
|
|
|
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") |
|
|
|
|
|
submit_btn.click( |
|
fn=lambda *args: (gr.Button("β³ Processing Registration...", interactive=False, variant="secondary"), ""), |
|
inputs=[ |
|
full_name, email, github_username, hf_username, |
|
track_interest, participation_type, teammates, |
|
commitment, api_credits_ack, project_description, |
|
], |
|
outputs=[submit_btn, output], |
|
queue=False |
|
).then( |
|
fn=handle_registration_with_state, |
|
inputs=[ |
|
full_name, email, github_username, hf_username, |
|
track_interest, participation_type, teammates, |
|
commitment, api_credits_ack, project_description, |
|
], |
|
outputs=[output, submit_btn], |
|
queue=True |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch(allowed_paths=["."]) |