ysharma HF Staff commited on
Commit
9c9c182
Β·
verified Β·
1 Parent(s): 85646f1

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +927 -0
  2. requirements.txt +4 -0
app.py ADDED
@@ -0,0 +1,927 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from datetime import datetime
3
+ from datasets import Dataset, load_dataset
4
+ from huggingface_hub import login, HfApi
5
+ import pandas as pd
6
+ import os
7
+ import time
8
+ import tempfile
9
+ import logging
10
+
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ HF_TOKEN = os.environ.get("HF_TOKEN")
15
+ if HF_TOKEN:
16
+ login(token=HF_TOKEN)
17
+ logger.info("Authenticated with Hugging Face")
18
+ else:
19
+ logger.warning("HF_TOKEN not found - running without authentication")
20
+
21
+ DATASET_NAME = "ysharma/build-small-hackathon-registrations"
22
+
23
+ HEADER_HTML = """
24
+ <div style="
25
+ background: linear-gradient(135deg, #f5ecd9 0%, #ebe0c4 50%, #dccfa8 100%);
26
+ border-radius: 20px;
27
+ padding: 0;
28
+ margin-bottom: 24px;
29
+ box-shadow: 0 10px 40px rgba(74, 49, 16, 0.15);
30
+ overflow: hidden;
31
+ border: 2px solid #8b6f47;
32
+ ">
33
+ <!-- Banner Placeholder (artwork coming soon) -->
34
+ <div style="
35
+ background: linear-gradient(135deg, #2d5016 0%, #4a7c2e 50%, #6b9039 100%);
36
+ padding: 56px 32px;
37
+ text-align: center;
38
+ position: relative;
39
+ color: #f5ecd9;
40
+ border-bottom: 2px solid #8b6f47;
41
+ ">
42
+ <div style="
43
+ font-size: 13px;
44
+ letter-spacing: 4px;
45
+ text-transform: uppercase;
46
+ opacity: 0.85;
47
+ margin-bottom: 12px;
48
+ ">❋ Gradio &times; Hugging Face presents ❋</div>
49
+ <h1 style="
50
+ margin: 0 0 12px 0;
51
+ font-size: 44px;
52
+ font-weight: 800;
53
+ letter-spacing: -1px;
54
+ color: #f5ecd9;
55
+ ">πŸͺ΅ Build Small Hackathon</h1>
56
+ <div style="
57
+ font-size: 20px;
58
+ font-style: italic;
59
+ margin-bottom: 8px;
60
+ color: #e8dcc0;
61
+ ">Making AI Fun Again</div>
62
+ <div style="font-size: 16px; opacity: 0.85;">May 29 – June 8, 2026 Β· Online &amp; Global</div>
63
+ <div style="
64
+ margin-top: 24px;
65
+ display: inline-block;
66
+ background: rgba(245, 236, 217, 0.15);
67
+ border: 1px dashed rgba(245, 236, 217, 0.5);
68
+ border-radius: 8px;
69
+ padding: 8px 16px;
70
+ font-size: 13px;
71
+ opacity: 0.85;
72
+ ">πŸ—ΊοΈ event banner being illustrated Β· placeholder for now</div>
73
+ </div>
74
+
75
+ <!-- Content Section -->
76
+ <div style="padding: 28px 32px;">
77
+ <!-- Two Tracks -->
78
+ <a href="https://huggingface.co/build-small-hackathon" target="_blank" style="text-decoration: none;">
79
+ <div style="
80
+ background: rgba(74, 124, 46, 0.08);
81
+ border: 2px solid rgba(74, 124, 46, 0.35);
82
+ border-radius: 14px;
83
+ padding: 18px 22px;
84
+ margin-bottom: 14px;
85
+ transition: all 0.25s ease;
86
+ "
87
+ onmouseover="this.style.background='rgba(74, 124, 46, 0.15)'; this.style.transform='translateY(-2px)'"
88
+ onmouseout="this.style.background='rgba(74, 124, 46, 0.08)'; this.style.transform='translateY(0)'"
89
+ >
90
+ <div style="text-align: center; color: #2d5016; font-weight: 700; letter-spacing: 2px; font-size: 12px; margin-bottom: 14px;">
91
+ ❋ TWO TRACKS ❋
92
+ </div>
93
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
94
+ <div style="text-align: center;">
95
+ <div style="font-size: 32px; margin-bottom: 6px;">🏑</div>
96
+ <div style="color: #2d5016; font-weight: 700; font-size: 15px;">Backyard AI</div>
97
+ <div style="color: #5d4a2e; font-size: 13px; line-height: 1.4; margin-top: 4px;">Solve a real problem for someone you know</div>
98
+ </div>
99
+ <div style="text-align: center;">
100
+ <div style="font-size: 32px; margin-bottom: 6px;">πŸ„</div>
101
+ <div style="color: #2d5016; font-weight: 700; font-size: 15px;">Thousand Token Wood</div>
102
+ <div style="color: #5d4a2e; font-size: 13px; line-height: 1.4; margin-top: 4px;">Build something delightful and whimsical</div>
103
+ </div>
104
+ </div>
105
+ <div style="text-align: center; margin-top: 14px; color: #6b4423; font-size: 12px;">
106
+ Read the full chapter book β†’ huggingface.co/build-small-hackathon
107
+ </div>
108
+ </div>
109
+ </a>
110
+
111
+ <!-- Three Rules + Coming Soon row -->
112
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 14px;">
113
+ <div style="
114
+ background: rgba(139, 111, 71, 0.10);
115
+ border: 1px solid rgba(139, 111, 71, 0.35);
116
+ border-radius: 10px;
117
+ padding: 14px 16px;
118
+ ">
119
+ <div style="color: #6b4423; font-weight: 700; font-size: 12px; letter-spacing: 1.5px; margin-bottom: 8px;">✦ THE 3 RULES</div>
120
+ <div style="color: #3a2a14; font-size: 13px; line-height: 1.7;">
121
+ 1. Models ≀ 32B parameters<br>
122
+ 2. Built on Gradio + Spaces<br>
123
+ 3. Demo video + social post
124
+ </div>
125
+ </div>
126
+ <div style="
127
+ background: rgba(201, 176, 114, 0.20);
128
+ border: 1px solid rgba(201, 176, 114, 0.55);
129
+ border-radius: 10px;
130
+ padding: 14px 16px;
131
+ ">
132
+ <div style="color: #6b4423; font-weight: 700; font-size: 12px; letter-spacing: 1.5px; margin-bottom: 8px;">🎁 PRIZES &amp; CREDITS</div>
133
+ <div style="color: #3a2a14; font-size: 13px; line-height: 1.7; font-style: italic;">
134
+ Sponsor lineup, prize pool, and free API/compute credits announcing soon β€” sit tight, friend.
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <!-- Discord Notice -->
140
+ <div style="
141
+ background: rgba(74, 124, 46, 0.10);
142
+ border: 1px solid rgba(74, 124, 46, 0.35);
143
+ border-radius: 10px;
144
+ padding: 14px 18px;
145
+ text-align: center;
146
+ ">
147
+ <p style="margin: 0; color: #2d5016; font-size: 14px; line-height: 1.6;">
148
+ πŸ’¬ Join the <a href="https://discord.gg/92sEPT2Zhv" target="_blank" style="color: #4a7c2e; font-weight: 700; text-decoration: underline;">Gradio Discord</a>
149
+ channel <code style="background: rgba(45, 80, 22, 0.12); padding: 2px 7px; border-radius: 4px; font-size: 12px; color: #2d5016;">build-small-hackathon-official</code>
150
+ for office hours, AMAs, and support.
151
+ </p>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ """
156
+
157
+ DEADLINES_HTML = """
158
+ <div style="
159
+ background: #f5ecd9;
160
+ border: 2px solid #8b6f47;
161
+ border-radius: 16px;
162
+ padding: 22px 24px;
163
+ margin-bottom: 16px;
164
+ box-shadow: 0 4px 12px rgba(74, 49, 16, 0.10);
165
+ height: 100%;
166
+ box-sizing: border-box;
167
+ ">
168
+ <div style="text-align: center; color: #6b4423; font-weight: 800; letter-spacing: 2px; font-size: 13px; margin-bottom: 16px;">
169
+ ✦ TRAIL MAP β€” KEY DATES ✦
170
+ </div>
171
+ <div style="display: grid; gap: 10px; color: #3a2a14;">
172
+ <div style="display: flex; align-items: center; gap: 10px;">
173
+ <span style="font-size: 20px;">πŸ“…</span>
174
+ <div><strong>Registration closes:</strong> Wed, May 27, 2026 Β· 23:59 UTC</div>
175
+ </div>
176
+ <div style="display: flex; align-items: center; gap: 10px;">
177
+ <span style="font-size: 20px;">🎟️</span>
178
+ <div><strong>Credits assigned:</strong> Thu, May 28, 2026</div>
179
+ </div>
180
+ <div style="display: flex; align-items: center; gap: 10px;">
181
+ <span style="font-size: 20px;">πŸš€</span>
182
+ <div><strong>Hack window opens:</strong> Fri, May 29, 2026</div>
183
+ </div>
184
+ <div style="display: flex; align-items: center; gap: 10px;">
185
+ <span style="font-size: 20px;">🏁</span>
186
+ <div><strong>Submissions due:</strong> Mon, June 8, 2026</div>
187
+ </div>
188
+ <div style="
189
+ margin-top: 6px;
190
+ background: rgba(178, 60, 50, 0.10);
191
+ border: 1px solid rgba(178, 60, 50, 0.40);
192
+ border-radius: 8px;
193
+ padding: 10px 12px;
194
+ color: #8b2e25;
195
+ font-weight: 600;
196
+ text-align: center;
197
+ font-size: 13px;
198
+ ">β›” No registrations accepted once the event starts. Lock it in before May 27!</div>
199
+ </div>
200
+ </div>
201
+ """
202
+
203
+ COUNTER = """
204
+ <!DOCTYPE html>
205
+ <html lang="en">
206
+ <head>
207
+ <meta charset="UTF-8">
208
+ <style>
209
+ * { margin: 0; padding: 0; box-sizing: border-box; }
210
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: transparent; }
211
+ .countdown-container {
212
+ background: linear-gradient(135deg, #2d5016 0%, #1f3a0f 100%);
213
+ border: 2px solid #8b6f47;
214
+ border-radius: 16px;
215
+ padding: 24px 22px;
216
+ box-shadow: 0 4px 12px rgba(74, 49, 16, 0.20);
217
+ text-align: center;
218
+ color: #f5ecd9;
219
+ height: 100%;
220
+ box-sizing: border-box;
221
+ }
222
+ .title {
223
+ font-size: 14px;
224
+ font-weight: 800;
225
+ letter-spacing: 2px;
226
+ text-transform: uppercase;
227
+ color: #f5ecd9;
228
+ margin-bottom: 4px;
229
+ }
230
+ .subtitle {
231
+ font-size: 13px;
232
+ color: #c9b072;
233
+ margin-bottom: 20px;
234
+ font-style: italic;
235
+ }
236
+ .countdown {
237
+ display: flex;
238
+ justify-content: space-around;
239
+ align-items: center;
240
+ gap: 10px;
241
+ flex-wrap: wrap;
242
+ }
243
+ .time-unit {
244
+ display: flex;
245
+ flex-direction: column;
246
+ align-items: center;
247
+ min-width: 70px;
248
+ }
249
+ .circle { position: relative; width: 78px; height: 78px; margin-bottom: 6px; }
250
+ .circle svg { width: 100%; height: 100%; transform: rotate(-90deg); }
251
+ .circle-bg { fill: none; stroke: rgba(245, 236, 217, 0.15); stroke-width: 5; }
252
+ .circle-progress {
253
+ fill: none;
254
+ stroke: #c9b072;
255
+ stroke-width: 5;
256
+ stroke-linecap: round;
257
+ stroke-dasharray: 220;
258
+ stroke-dashoffset: 220;
259
+ transition: stroke-dashoffset 0.3s ease;
260
+ }
261
+ .number {
262
+ position: absolute; top: 50%; left: 50%;
263
+ transform: translate(-50%, -50%);
264
+ font-size: 22px; font-weight: 700;
265
+ color: #f5ecd9;
266
+ font-family: 'Courier New', monospace;
267
+ }
268
+ .label {
269
+ font-size: 10px;
270
+ font-weight: 700;
271
+ color: #c9b072;
272
+ text-transform: uppercase;
273
+ letter-spacing: 1.5px;
274
+ }
275
+ @media (max-width: 480px) {
276
+ .countdown { gap: 6px; }
277
+ .circle { width: 62px; height: 62px; }
278
+ .number { font-size: 18px; }
279
+ }
280
+ </style>
281
+ </head>
282
+ <body>
283
+ <div class="countdown-container">
284
+ <div class="title">πŸ•οΈ Hack window opens in</div>
285
+ <div class="subtitle">May 29, 2026 Β· 00:00 UTC</div>
286
+ <div class="countdown">
287
+ <div class="time-unit">
288
+ <div class="circle">
289
+ <svg><circle class="circle-bg" cx="39" cy="39" r="35"></circle><circle class="circle-progress" cx="39" cy="39" r="35" id="days-progress"></circle></svg>
290
+ <div class="number" id="days">00</div>
291
+ </div>
292
+ <div class="label">Days</div>
293
+ </div>
294
+ <div class="time-unit">
295
+ <div class="circle">
296
+ <svg><circle class="circle-bg" cx="39" cy="39" r="35"></circle><circle class="circle-progress" cx="39" cy="39" r="35" id="hours-progress"></circle></svg>
297
+ <div class="number" id="hours">00</div>
298
+ </div>
299
+ <div class="label">Hours</div>
300
+ </div>
301
+ <div class="time-unit">
302
+ <div class="circle">
303
+ <svg><circle class="circle-bg" cx="39" cy="39" r="35"></circle><circle class="circle-progress" cx="39" cy="39" r="35" id="minutes-progress"></circle></svg>
304
+ <div class="number" id="minutes">00</div>
305
+ </div>
306
+ <div class="label">Minutes</div>
307
+ </div>
308
+ <div class="time-unit">
309
+ <div class="circle">
310
+ <svg><circle class="circle-bg" cx="39" cy="39" r="35"></circle><circle class="circle-progress" cx="39" cy="39" r="35" id="seconds-progress"></circle></svg>
311
+ <div class="number" id="seconds">00</div>
312
+ </div>
313
+ <div class="label">Seconds</div>
314
+ </div>
315
+ </div>
316
+ </div>
317
+ <script>
318
+ // Hack window opens May 29, 2026 at 00:00 UTC (month is 0-indexed: 4 = May)
319
+ const targetDate = new Date(Date.UTC(2026, 4, 29, 0, 0, 0));
320
+ function updateCountdown() {
321
+ const now = new Date();
322
+ const difference = targetDate.getTime() - now.getTime();
323
+ if (difference > 0) {
324
+ const days = Math.floor(difference / (1000 * 60 * 60 * 24));
325
+ const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
326
+ const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
327
+ const seconds = Math.floor((difference % (1000 * 60)) / 1000);
328
+ document.getElementById('days').textContent = days.toString().padStart(2, '0');
329
+ document.getElementById('hours').textContent = hours.toString().padStart(2, '0');
330
+ document.getElementById('minutes').textContent = minutes.toString().padStart(2, '0');
331
+ document.getElementById('seconds').textContent = seconds.toString().padStart(2, '0');
332
+ updateProgress('days-progress', days, 365);
333
+ updateProgress('hours-progress', hours, 24);
334
+ updateProgress('minutes-progress', minutes, 60);
335
+ updateProgress('seconds-progress', seconds, 60);
336
+ } else {
337
+ document.getElementById('days').textContent = '00';
338
+ document.getElementById('hours').textContent = '00';
339
+ document.getElementById('minutes').textContent = '00';
340
+ document.getElementById('seconds').textContent = '00';
341
+ updateProgress('days-progress', 0, 365);
342
+ updateProgress('hours-progress', 0, 24);
343
+ updateProgress('minutes-progress', 0, 60);
344
+ updateProgress('seconds-progress', 0, 60);
345
+ }
346
+ }
347
+ function updateProgress(elementId, current, max) {
348
+ const circle = document.getElementById(elementId);
349
+ const circumference = 220;
350
+ const progress = (current / max) * circumference;
351
+ const offset = circumference - progress;
352
+ circle.style.strokeDashoffset = offset;
353
+ }
354
+ updateCountdown();
355
+ setInterval(updateCountdown, 1000);
356
+ </script>
357
+ </body>
358
+ </html>
359
+ """
360
+
361
+
362
+ def safe_add_to_dataset(registration_data, max_retries=5, retry_delay=3):
363
+ """Append a new registration; refuse to write if existing rows can't be loaded (data-loss guard)."""
364
+ try:
365
+ logger.info("Starting new registration process")
366
+ new_row = {
367
+ "timestamp": registration_data["timestamp"],
368
+ "full_name": registration_data["personal_info"]["full_name"],
369
+ "email": registration_data["personal_info"]["email"],
370
+ "hf_username": registration_data["personal_info"]["hf_username"],
371
+ "gradio_usage": registration_data["personal_info"]["gradio_usage"],
372
+ "track_interest": registration_data["participation"]["track_interest"],
373
+ "previous_participation": str(registration_data["participation"]["previous_participation"]),
374
+ "experience_level": registration_data["participation"]["experience_level"],
375
+ "how_heard": registration_data["participation"]["how_heard"],
376
+ "planned_small_model": registration_data["build_small"]["planned_small_model"] or "",
377
+ "bonus_quests": str(registration_data["build_small"]["bonus_quests"]),
378
+ "project_description": registration_data["additional"]["project_description"] or "",
379
+ }
380
+
381
+ existing_df = None
382
+ load_successful = False
383
+
384
+ for attempt in range(max_retries):
385
+ logger.info(f"Loading attempt {attempt + 1}/{max_retries}")
386
+ try:
387
+ api = HfApi()
388
+ files = api.list_repo_files(DATASET_NAME, repo_type="dataset")
389
+ parquet_files = [f for f in files if f.endswith('.parquet') and 'train' in f]
390
+ if parquet_files:
391
+ logger.info(f"Found parquet file: {parquet_files[0]}")
392
+ with tempfile.TemporaryDirectory() as temp_dir:
393
+ parquet_file = api.hf_hub_download(
394
+ repo_id=DATASET_NAME,
395
+ filename=parquet_files[0],
396
+ repo_type="dataset",
397
+ cache_dir=temp_dir,
398
+ force_download=True,
399
+ )
400
+ existing_df = pd.read_parquet(parquet_file)
401
+ logger.info(f"Successfully loaded {len(existing_df)} existing rows")
402
+ load_successful = True
403
+ break
404
+ else:
405
+ logger.warning("No parquet files found")
406
+ except Exception as load_error:
407
+ logger.warning(f"Attempt {attempt + 1} failed: {str(load_error)[:100]}")
408
+ if attempt < max_retries - 1:
409
+ logger.info(f"Waiting {retry_delay} seconds before retry...")
410
+ time.sleep(retry_delay)
411
+ continue
412
+
413
+ if not load_successful or existing_df is None:
414
+ logger.error("CRITICAL SAFETY ERROR: Could not load existing dataset after multiple attempts.")
415
+ logger.error("REFUSING to proceed to prevent data loss!")
416
+ return False, (
417
+ "❌ Registration temporarily unavailable due to technical issues. "
418
+ "Please try again in a few minutes. If the problem persists, contact support."
419
+ )
420
+
421
+ if len(existing_df) > 0:
422
+ duplicate_check = existing_df[
423
+ (existing_df['email'].str.lower() == new_row['email'].lower())
424
+ | (existing_df['hf_username'].str.lower() == new_row['hf_username'].lower())
425
+ ]
426
+ if len(duplicate_check) > 0:
427
+ logger.warning("Duplicate registration attempt detected")
428
+ return False, "❌ Error: This email or Hugging Face username is already registered."
429
+
430
+ combined_df = pd.concat([existing_df, pd.DataFrame([new_row])], ignore_index=True)
431
+ logger.info(f"Combined data now has {len(combined_df)} rows (was {len(existing_df)})")
432
+
433
+ backup_timestamp = int(time.time())
434
+ try:
435
+ updated_dataset = Dataset.from_pandas(combined_df)
436
+ backup_name = f"{DATASET_NAME}-auto-backup-{backup_timestamp}"
437
+ logger.info(f"Creating backup: {backup_name}")
438
+ updated_dataset.push_to_hub(backup_name, private=True)
439
+ logger.info("Pushing to main dataset...")
440
+ updated_dataset.push_to_hub(DATASET_NAME, private=True)
441
+ logger.info(f"Successfully saved registration. Total rows: {len(combined_df)}")
442
+ time.sleep(2)
443
+ try:
444
+ api.list_repo_files(DATASET_NAME, repo_type="dataset")
445
+ logger.info("Upload verification: Files updated successfully")
446
+ except Exception:
447
+ logger.warning("Could not verify upload (this may be normal)")
448
+ return True, "Registration successful!"
449
+ except Exception as upload_error:
450
+ error_msg = str(upload_error).lower()
451
+ if any(indicator in error_msg for indicator in ['rate limit', '429', 'too many requests']):
452
+ logger.warning("Rate limit hit - registration system temporarily busy")
453
+ return False, "⏳ Registration temporarily unavailable due to high server load. Please try again in 10–15 minutes."
454
+ logger.error(f"Upload failed: {upload_error}")
455
+ return False, f"❌ Registration failed during upload: {str(upload_error)}"
456
+ except Exception as e:
457
+ logger.error(f"Unexpected error in registration: {e}")
458
+ import traceback
459
+ traceback.print_exc()
460
+ return False, f"❌ Registration failed: {str(e)}"
461
+
462
+
463
+ def verify_registration(email, hf_username):
464
+ """Verify a registration by exact email + HF username match (case-insensitive)."""
465
+ try:
466
+ if not email or not email.strip():
467
+ return "❌ Please enter your email address"
468
+ if not hf_username or not hf_username.strip():
469
+ return "❌ Please enter your Hugging Face username"
470
+
471
+ try:
472
+ dataset = load_dataset(DATASET_NAME, split="train")
473
+ df = dataset.to_pandas()
474
+ logger.info(f"Loaded dataset with {len(df)} registrations")
475
+ except Exception as load_error:
476
+ logger.error(f"Failed to load dataset: {load_error}")
477
+ return "❌ Unable to verify registration at this time. Please try again later."
478
+
479
+ if len(df) == 0:
480
+ return "❌ No registration found with this email and Hugging Face username combination."
481
+
482
+ email_lower = email.strip().lower()
483
+ username_lower = hf_username.strip().lower()
484
+
485
+ match = df[
486
+ (df['email'].str.lower() == email_lower)
487
+ & (df['hf_username'].str.lower() == username_lower)
488
+ ]
489
+
490
+ if len(match) == 0:
491
+ email_exists = df[df['email'].str.lower() == email_lower]
492
+ username_exists = df[df['hf_username'].str.lower() == username_lower]
493
+ if len(email_exists) > 0 and len(username_exists) == 0:
494
+ return "❌ Email found but Hugging Face username doesn't match. Please check your username."
495
+ if len(username_exists) > 0 and len(email_exists) == 0:
496
+ return "❌ Hugging Face username found but email doesn't match. Please check your email."
497
+ return "❌ No registration found with this email and Hugging Face username combination."
498
+
499
+ registration = match.iloc[0]
500
+ try:
501
+ timestamp = pd.to_datetime(registration['timestamp'])
502
+ reg_date = timestamp.strftime("%B %d, %Y at %I:%M %p UTC")
503
+ except Exception:
504
+ reg_date = registration['timestamp']
505
+
506
+ prev = registration['previous_participation']
507
+ if isinstance(prev, str):
508
+ prev = prev.strip("[]'\"").replace("'", "")
509
+
510
+ quests = registration.get('bonus_quests', '') if hasattr(registration, 'get') else registration['bonus_quests']
511
+ if isinstance(quests, str):
512
+ quests = quests.strip("[]'\"").replace("'", "")
513
+ quests_str = quests if quests else "_None selected_"
514
+
515
+ planned_model = registration['planned_small_model'] if 'planned_small_model' in registration.index else ''
516
+ planned_model_str = planned_model if planned_model else "_Not specified_"
517
+
518
+ result = f"""
519
+ ## βœ… Registration Confirmed!
520
+
521
+ **Participant Details:**
522
+ - **Full Name:** {registration['full_name']}
523
+ - **Email:** {registration['email']}
524
+ - **Hugging Face Username:** {registration['hf_username']}
525
+ - **Registered On:** {reg_date}
526
+
527
+ **Hackathon Participation:**
528
+ - **Track Interest:** {registration['track_interest']}
529
+ - **Hackathon Experience:** {prev}
530
+ - **Experience Level:** {registration['experience_level']}
531
+ - **Gradio Usage:** {registration['gradio_usage']}
532
+ - **How You Heard:** {registration['how_heard']}
533
+
534
+ **Build Small Plans:**
535
+ - **Small Model You Plan to Use:** {planned_model_str}
536
+ - **Bonus Quests of Interest:** {quests_str}
537
+
538
+ **Project Idea:**
539
+ {registration['project_description'] if registration['project_description'] else '_No project description provided_'}
540
+
541
+ ---
542
+ **Next Steps:**
543
+ - 🎟️ API & compute credits will be assigned on **Thu, May 28, 2026**
544
+ - πŸš€ Hack window opens **Fri, May 29, 2026**
545
+ - 🏁 Submissions due **Mon, June 8, 2026**
546
+ - πŸ’¬ Join the Gradio Discord channel `build-small-hackathon-official`: https://discord.gg/92sEPT2Zhv
547
+ - πŸ“§ Watch your email for important updates
548
+ """
549
+ logger.info(f"Verification successful for {email}")
550
+ return result
551
+ except Exception as e:
552
+ logger.error(f"Error during verification: {e}")
553
+ import traceback
554
+ traceback.print_exc()
555
+ return f"❌ An error occurred during verification: {str(e)}"
556
+
557
+
558
+ def submit_registration(full_name, email, hf_username, gradio_usage,
559
+ track_interest, previous_participation, experience_level, how_heard,
560
+ planned_small_model, bonus_quests,
561
+ acknowledgment, project_description):
562
+ """Validate and submit a registration."""
563
+ if not full_name or not full_name.strip():
564
+ return "❌ Error: Please enter your full name"
565
+ if not email or not email.strip():
566
+ return "❌ Error: Please enter your email address"
567
+ if not hf_username or not hf_username.strip():
568
+ return "❌ Error: Please enter your Hugging Face username"
569
+ if not gradio_usage:
570
+ return "❌ Error: Please select how you're currently using Gradio"
571
+ if not track_interest:
572
+ return "❌ Error: Please select your preferred track"
573
+ if not previous_participation:
574
+ return "❌ Error: Please select at least one option for hackathon experience"
575
+ if not experience_level:
576
+ return "❌ Error: Please select your experience level"
577
+ if not how_heard:
578
+ return "❌ Error: Please select how you heard about this hackathon"
579
+ if not acknowledgment:
580
+ return "❌ Error: Please confirm your acknowledgment to participate"
581
+
582
+ import re
583
+ email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
584
+ if not re.match(email_pattern, email.strip()):
585
+ return "❌ Error: Please enter a valid email address"
586
+
587
+ registration_data = {
588
+ "timestamp": datetime.now().isoformat(),
589
+ "personal_info": {
590
+ "full_name": full_name.strip(),
591
+ "email": email.strip().lower(),
592
+ "hf_username": hf_username.strip(),
593
+ "gradio_usage": gradio_usage,
594
+ },
595
+ "participation": {
596
+ "track_interest": track_interest,
597
+ "previous_participation": previous_participation,
598
+ "experience_level": experience_level,
599
+ "how_heard": how_heard,
600
+ },
601
+ "build_small": {
602
+ "planned_small_model": planned_small_model.strip() if planned_small_model else None,
603
+ "bonus_quests": bonus_quests or [],
604
+ },
605
+ "additional": {
606
+ "project_description": project_description.strip() if project_description else None,
607
+ },
608
+ }
609
+
610
+ success, message = safe_add_to_dataset(registration_data)
611
+ if not success:
612
+ return f"❌ Registration failed: {message}"
613
+
614
+ return f"""βœ… Registration Successful!
615
+
616
+ Thank you, **{full_name}**! Your spot is locked in.
617
+
618
+ 🎟️ API & compute credits will be assigned on **Thu, May 28, 2026**.<br>
619
+ πŸš€ Hack window opens **Fri, May 29, 2026** Β· submissions due **Mon, June 8, 2026**.<br>
620
+ πŸ’¬ Join the Gradio Discord channel `build-small-hackathon-official` for office hours, AMAs, and support: https://discord.gg/92sEPT2Zhv<br>
621
+ πŸ“§ Watch your email for credits and updates.
622
+
623
+ **See you in the woods! πŸ„πŸͺ΅**"""
624
+
625
+
626
+ def check_dataset_health():
627
+ try:
628
+ api = HfApi()
629
+ files = api.list_repo_files(DATASET_NAME, repo_type="dataset")
630
+ parquet_files = [f for f in files if f.endswith('.parquet')]
631
+ if parquet_files:
632
+ logger.info(f"Dataset health check passed - found {len(parquet_files)} parquet files")
633
+ return True
634
+ logger.warning("Dataset health check: No parquet files found (did you run seed_dataset.py?)")
635
+ return False
636
+ except Exception as e:
637
+ logger.error(f"Dataset health check failed: {e}")
638
+ return False
639
+
640
+
641
+ logger.info("Starting Build Small Hackathon Registration System")
642
+ logger.info(f"Dataset: {DATASET_NAME}")
643
+ if check_dataset_health():
644
+ logger.info("System ready - dataset is healthy")
645
+ else:
646
+ logger.warning("System starting with dataset health warnings")
647
+
648
+
649
+ custom_css = """
650
+ .gradio-container {
651
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
652
+ }
653
+ #submit-btn {
654
+ background: linear-gradient(135deg, #4a7c2e 0%, #2d5016 100%) !important;
655
+ border: 2px solid #6b4423 !important;
656
+ color: #f5ecd9 !important;
657
+ font-weight: 700 !important;
658
+ font-size: 18px !important;
659
+ padding: 16px 32px !important;
660
+ border-radius: 12px !important;
661
+ box-shadow: 0 4px 12px rgba(74, 49, 16, 0.20) !important;
662
+ transition: all 0.25s ease !important;
663
+ }
664
+ #submit-btn:hover {
665
+ transform: translateY(-2px) !important;
666
+ box-shadow: 0 6px 20px rgba(74, 49, 16, 0.30) !important;
667
+ background: linear-gradient(135deg, #5a8c3e 0%, #3d6020 100%) !important;
668
+ }
669
+ #submit-btn:active {
670
+ transform: translateY(0) !important;
671
+ }
672
+ #verify-btn {
673
+ background: #6b4423 !important;
674
+ border: 2px solid #c9b072 !important;
675
+ color: #f5ecd9 !important;
676
+ font-weight: 700 !important;
677
+ font-size: 16px !important;
678
+ padding: 12px 24px !important;
679
+ border-radius: 8px !important;
680
+ box-shadow: 0 3px 10px rgba(74, 49, 16, 0.25) !important;
681
+ transition: all 0.25s ease !important;
682
+ }
683
+ #verify-btn:hover {
684
+ transform: translateY(-2px) !important;
685
+ box-shadow: 0 5px 15px rgba(74, 49, 16, 0.35) !important;
686
+ background: #7d5430 !important;
687
+ }
688
+ """
689
+
690
+
691
+ with gr.Blocks(
692
+ title="Build Small Hackathon β€” Registration",
693
+ css=custom_css,
694
+ theme=gr.themes.Soft(primary_hue="green", secondary_hue="amber", neutral_hue="stone"),
695
+ ) as demo:
696
+
697
+ gr.HTML(HEADER_HTML)
698
+
699
+ with gr.Row(equal_height=True):
700
+ with gr.Column(scale=1):
701
+ gr.HTML(DEADLINES_HTML)
702
+ with gr.Column(scale=1):
703
+ gr.HTML(COUNTER)
704
+
705
+ gr.Markdown("---")
706
+
707
+ with gr.Tabs():
708
+ with gr.Tab("πŸ“ Register"):
709
+ with gr.Row():
710
+ with gr.Column():
711
+ gr.Markdown("## ❋ 1. Personal Information")
712
+ full_name = gr.Textbox(
713
+ label="Full Name *",
714
+ placeholder="Your full name as you'd like it on certificates",
715
+ max_lines=1,
716
+ )
717
+ email = gr.Textbox(
718
+ label="Email Address *",
719
+ placeholder="Primary contact email (we'll send updates and credits info here)",
720
+ max_lines=1,
721
+ )
722
+ hf_username = gr.Textbox(
723
+ label="Hugging Face Username *",
724
+ placeholder="Required for org access and submissions",
725
+ max_lines=1,
726
+ )
727
+ gradio_usage = gr.Radio(
728
+ label="How are you currently using Gradio? *",
729
+ choices=[
730
+ "Professional work β€” my company uses Gradio",
731
+ "Personal projects β€” building side projects",
732
+ "Academic/Research β€” university or research work",
733
+ "Learning β€” new to Gradio, want to learn",
734
+ "Not using yet β€” interested to start",
735
+ ],
736
+ info="Helps us understand the community better",
737
+ )
738
+
739
+ with gr.Column():
740
+ gr.Markdown("## ❋ 2. Hackathon Participation")
741
+ track_interest = gr.Radio(
742
+ label="Which track interests you most? *",
743
+ choices=[
744
+ "🏑 Backyard AI",
745
+ "πŸ„ An Adventure in Thousand Token Wood",
746
+ "Both",
747
+ "Undecided / Not sure yet",
748
+ ],
749
+ )
750
+ previous_participation = gr.CheckboxGroup(
751
+ label="Hackathon experience (select all that apply) *",
752
+ choices=[
753
+ "MCP 1st Birthday",
754
+ "Agents & MCP Hackathon",
755
+ "Other AI hackathons",
756
+ "First timer",
757
+ ],
758
+ )
759
+ experience_level = gr.Radio(
760
+ label="Your experience with AI/Agents development *",
761
+ choices=[
762
+ "Beginner β€” new to AI development",
763
+ "Intermediate β€” some AI projects",
764
+ "Advanced β€” regular AI developer",
765
+ "Expert β€” professional AI engineer",
766
+ ],
767
+ )
768
+ how_heard = gr.Dropdown(
769
+ label="How did you hear about this hackathon? *",
770
+ choices=[
771
+ "Hugging Face email/newsletter",
772
+ "Build Small org page",
773
+ "Twitter/X",
774
+ "LinkedIn",
775
+ "Gradio Discord",
776
+ "From a colleague/friend",
777
+ "YouTube",
778
+ "Reddit",
779
+ "Sponsor announcement",
780
+ "Previous Gradio hackathon",
781
+ "Other",
782
+ ],
783
+ )
784
+
785
+ with gr.Row():
786
+ with gr.Column():
787
+ gr.Markdown("## ❋ 3. Build Small Specifics")
788
+ planned_small_model = gr.Textbox(
789
+ label="Which small model are you planning to use? (optional)",
790
+ placeholder="e.g. Llama-3.2-3B, Qwen2.5-7B, gemma-2-2b-it, SmolLM-1.7B...",
791
+ info="Reminder: ≀ 32B parameters. Leave blank if you haven't decided.",
792
+ max_lines=1,
793
+ )
794
+ bonus_quests = gr.CheckboxGroup(
795
+ label="Which bonus quests / merit badges interest you? (optional)",
796
+ choices=[
797
+ "πŸ”Œ Off the Grid β€” no cloud APIs",
798
+ "🎯 Well-Tuned β€” use a fine-tuned published model",
799
+ "🎨 Off-Brand β€” custom Gradio frontend",
800
+ "πŸ¦™ Llama Champion β€” use llama.cpp runtime",
801
+ "πŸ“‘ Sharing is Caring β€” share an agent trace",
802
+ "πŸ““ Field Notes β€” write a blog post / report",
803
+ ],
804
+ info="Pick as many as you'd like. You can change your mind during the event β€” this is just signal for us.",
805
+ )
806
+
807
+ with gr.Column():
808
+ gr.Markdown("## ❋ 4. Project Idea (optional)")
809
+ project_description = gr.Textbox(
810
+ label="What are you most excited to build?",
811
+ placeholder="A neighbor's recipe-translator? A whimsical mushroom-identifier? Tell us in a sentence or two.",
812
+ lines=4,
813
+ )
814
+
815
+ gr.Markdown("## ❋ 5. Acknowledgment")
816
+ acknowledgment = gr.Checkbox(
817
+ label="I'm in. *",
818
+ info=(
819
+ "I commit to actively participate, build with a model ≀ 32B parameters, "
820
+ "host on a Gradio Hugging Face Space, and submit a project (with demo video and social post) "
821
+ "by Mon, June 8, 2026. I understand that any API/compute credits provided are intended for use on my "
822
+ "hackathon project during the event period."
823
+ ),
824
+ )
825
+
826
+ submit_btn = gr.Button(
827
+ "πŸͺ΅ Register for the Build Small Hackathon",
828
+ variant="primary",
829
+ size="lg",
830
+ elem_id="submit-btn",
831
+ )
832
+ output = gr.Markdown()
833
+
834
+ def handle_registration_with_state(*args):
835
+ try:
836
+ result = submit_registration(*args)
837
+ return result, gr.Button(
838
+ "πŸͺ΅ Register for the Build Small Hackathon",
839
+ interactive=True,
840
+ variant="primary",
841
+ elem_id="submit-btn",
842
+ )
843
+ except Exception as e:
844
+ logger.error(f"Registration handling error: {e}")
845
+ return f"❌ An unexpected error occurred: {str(e)}", gr.Button(
846
+ "πŸͺ΅ Register for the Build Small Hackathon",
847
+ interactive=True,
848
+ variant="primary",
849
+ elem_id="submit-btn",
850
+ )
851
+
852
+ submit_btn.click(
853
+ fn=lambda *args: (gr.Button("⏳ Processing registration...", interactive=False, variant="secondary"), ""),
854
+ inputs=[
855
+ full_name, email, hf_username, gradio_usage,
856
+ track_interest, previous_participation, experience_level, how_heard,
857
+ planned_small_model, bonus_quests,
858
+ acknowledgment, project_description,
859
+ ],
860
+ outputs=[submit_btn, output],
861
+ queue=False,
862
+ ).then(
863
+ fn=handle_registration_with_state,
864
+ inputs=[
865
+ full_name, email, hf_username, gradio_usage,
866
+ track_interest, previous_participation, experience_level, how_heard,
867
+ planned_small_model, bonus_quests,
868
+ acknowledgment, project_description,
869
+ ],
870
+ outputs=[output, submit_btn],
871
+ queue=True,
872
+ )
873
+
874
+ with gr.Tab("πŸ” Verify Registration"):
875
+ gr.Markdown("""
876
+ ## Check Your Registration Status
877
+
878
+ Enter your email address and Hugging Face username to verify your registration and view your details.
879
+
880
+ **Note:** Both email and username must match exactly for security purposes.
881
+ """)
882
+ with gr.Row():
883
+ with gr.Column():
884
+ verify_email = gr.Textbox(
885
+ label="Email Address",
886
+ placeholder="Enter your registered email",
887
+ max_lines=1,
888
+ )
889
+ verify_hf_username = gr.Textbox(
890
+ label="Hugging Face Username",
891
+ placeholder="Enter your registered HF username",
892
+ max_lines=1,
893
+ )
894
+ verify_btn = gr.Button(
895
+ "πŸ” Check Registration Status",
896
+ variant="primary",
897
+ size="lg",
898
+ elem_id="verify-btn",
899
+ )
900
+ with gr.Column():
901
+ gr.Markdown("""
902
+ ### Need Help?
903
+
904
+ - Make sure you enter the **exact** email and username you used during registration
905
+ - Both fields are **case-insensitive** but must match your registration
906
+ - If you can't find your registration and registration is still open (before May 27, 23:59 UTC), try registering again
907
+ - Once registration closes, no new entries can be added
908
+
909
+ **Support:**
910
+ - Discord: https://discord.gg/92sEPT2Zhv (channel `build-small-hackathon-official`)
911
+ - Email: gradio-team@huggingface.co
912
+ """)
913
+ verify_output = gr.Markdown()
914
+ verify_btn.click(
915
+ fn=verify_registration,
916
+ inputs=[verify_email, verify_hf_username],
917
+ outputs=verify_output,
918
+ )
919
+
920
+ gr.Markdown("""
921
+ ---
922
+ ✦ **Questions?** Join the Gradio [Discord](https://discord.gg/92sEPT2Zhv) channel `build-small-hackathon-official` or email gradio-team@huggingface.co ✦
923
+ """)
924
+
925
+
926
+ if __name__ == "__main__":
927
+ demo.launch(allowed_paths=["."])
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio==5.49.1
2
+ datasets>=3.0.0
3
+ huggingface_hub>=0.28.0
4
+ pandas>=2.2.0