ysharma HF Staff commited on
Commit
218c9af
Β·
verified Β·
1 Parent(s): 073785a

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +734 -0
app.py ADDED
@@ -0,0 +1,734 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import tempfile
4
+ import os
5
+ import uuid
6
+ import urllib.parse
7
+ from datasets import load_dataset
8
+ from datetime import datetime
9
+ from gradio_client import Client, handle_file
10
+ from certificate_upload_module import upload_user_certificate
11
+ from PIL import Image
12
+
13
+
14
+ hf_token = os.getenv("HF_TOKEN")
15
+
16
+ # HTML template for the certificate (unchanged)
17
+ CERTIFICATE_HTML_TEMPLATE = """<!DOCTYPE html>
18
+ <html lang="en">
19
+ <head>
20
+ <meta charset="UTF-8">
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
+ <title>Gradio Agents & MCP Hackathon 2025 - Certificate of Participation</title>
23
+ <style>
24
+ @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Inter:wght@300;400;500;600&display=swap');
25
+
26
+ * {
27
+ margin: 0;
28
+ padding: 0;
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ body {
33
+ width: 2000px;
34
+ height: 1414px;
35
+ margin: 0;
36
+ padding: 0;
37
+ overflow: hidden;
38
+ }
39
+
40
+ .certificate {
41
+ width: 2000px;
42
+ height: 1414px;
43
+ background: linear-gradient(135deg, #FF6B35 0%, #F7931E 50%, #FF8C00 100%);
44
+ position: relative;
45
+ margin: 0;
46
+ border-radius: 0;
47
+ overflow: hidden;
48
+ box-shadow: 0 33px 100px rgba(0,0,0,0.3);
49
+ print-color-adjust: exact;
50
+ -webkit-print-color-adjust: exact;
51
+ }
52
+
53
+ .certificate::before {
54
+ content: '';
55
+ position: absolute;
56
+ top: 0;
57
+ left: 0;
58
+ right: 0;
59
+ bottom: 0;
60
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="20" cy="20" r="1" fill="white" opacity="0.1"/><circle cx="80" cy="40" r="1" fill="white" opacity="0.1"/><circle cx="40" cy="80" r="1" fill="white" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>') repeat;
61
+ opacity: 0.3;
62
+ }
63
+
64
+ .inner-border {
65
+ position: absolute;
66
+ top: 50px;
67
+ left: 50px;
68
+ right: 50px;
69
+ bottom: 50px;
70
+ border: 5px solid rgba(255,255,255,0.8);
71
+ border-radius: 0;
72
+ background: rgba(255,255,255,0.97);
73
+ }
74
+
75
+ .content {
76
+ position: relative;
77
+ padding: 100px 133px;
78
+ height: 100%;
79
+ display: flex;
80
+ flex-direction: column;
81
+ justify-content: space-between;
82
+ text-align: center;
83
+ z-index: 2;
84
+ }
85
+
86
+ .header {
87
+ margin-bottom: 33px;
88
+ }
89
+
90
+ .logo-section {
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ gap: 33px;
95
+ margin-bottom: 33px;
96
+ }
97
+
98
+ .gradio-logo {
99
+ height: 66px;
100
+ }
101
+
102
+ .hf-logo {
103
+ height: 58px;
104
+ }
105
+
106
+ .logo-text {
107
+ font-size: 46px;
108
+ font-weight: bold;
109
+ color: #E85D04;
110
+ font-family: 'Inter', sans-serif;
111
+ }
112
+
113
+ .event-title {
114
+ font-family: 'Playfair Display', serif;
115
+ font-size: 60px;
116
+ font-weight: 700;
117
+ color: #2d3748;
118
+ margin-bottom: 17px;
119
+ line-height: 1.2;
120
+ }
121
+
122
+ .certificate-type {
123
+ font-family: 'Inter', sans-serif;
124
+ font-size: 30px;
125
+ color: #E85D04;
126
+ font-weight: 500;
127
+ letter-spacing: 3.3px;
128
+ text-transform: uppercase;
129
+ }
130
+
131
+ .main-content {
132
+ flex-grow: 1;
133
+ display: flex;
134
+ flex-direction: column;
135
+ justify-content: center;
136
+ margin: 66px 0;
137
+ }
138
+
139
+ .certifies-text {
140
+ font-family: 'Inter', sans-serif;
141
+ font-size: 33px;
142
+ color: #4a5568;
143
+ margin-bottom: 33px;
144
+ }
145
+
146
+ .participant-name {
147
+ font-family: 'Playfair Display', serif;
148
+ font-size: 80px;
149
+ font-weight: 700;
150
+ color: #2d3748;
151
+ margin: 33px 0;
152
+ padding: 17px 0;
153
+ border-bottom: 5px solid #E85D04;
154
+ border-top: 2px solid #e2e8f0;
155
+ background: linear-gradient(90deg, transparent 0%, rgba(232, 93, 4, 0.1) 50%, transparent 100%);
156
+ }
157
+
158
+ .description {
159
+ font-family: 'Inter', sans-serif;
160
+ font-size: 30px;
161
+ color: #4a5568;
162
+ line-height: 1.6;
163
+ max-width: 1333px;
164
+ margin: 50px auto;
165
+ }
166
+
167
+ .project-info {
168
+ background: rgba(232, 93, 4, 0.1);
169
+ padding: 33px;
170
+ border-radius: 17px;
171
+ margin: 33px 0;
172
+ border-left: 7px solid #E85D04;
173
+ }
174
+
175
+ .project-title {
176
+ font-family: 'Inter', sans-serif;
177
+ font-size: 26px;
178
+ color: #2d3748;
179
+ font-weight: 600;
180
+ }
181
+
182
+ .track-info {
183
+ font-family: 'Inter', sans-serif;
184
+ font-size: 23px;
185
+ color: #E85D04;
186
+ margin-top: 8px;
187
+ }
188
+
189
+ .footer {
190
+ display: grid;
191
+ grid-template-columns: 1fr 1fr 1fr;
192
+ gap: 66px;
193
+ align-items: end;
194
+ margin-top: 50px;
195
+ }
196
+
197
+ .date-section {
198
+ text-align: left;
199
+ }
200
+
201
+ .signatures-section {
202
+ text-align: center;
203
+ }
204
+
205
+ .verification-section {
206
+ text-align: right;
207
+ }
208
+
209
+ .date, .certificate-id {
210
+ font-family: 'Inter', sans-serif;
211
+ font-size: 23px;
212
+ color: #4a5568;
213
+ margin-bottom: 8px;
214
+ }
215
+
216
+ .date-value, .id-value {
217
+ font-family: 'Inter', sans-serif;
218
+ font-size: 26px;
219
+ font-weight: 600;
220
+ color: #2d3748;
221
+ }
222
+
223
+ .signature-line {
224
+ border-top: 3px solid #2d3748;
225
+ width: 333px;
226
+ margin: 33px auto 17px;
227
+ }
228
+
229
+ .signature-title {
230
+ font-family: 'Inter', sans-serif;
231
+ font-size: 20px;
232
+ color: #4a5568;
233
+ text-transform: uppercase;
234
+ letter-spacing: 1.7px;
235
+ }
236
+
237
+ .sponsors {
238
+ margin-top: 66px;
239
+ padding-top: 50px;
240
+ border-top: 2px solid #e2e8f0;
241
+ }
242
+
243
+ .sponsors-title {
244
+ font-family: 'Inter', sans-serif;
245
+ font-size: 20px;
246
+ color: #4a5568;
247
+ text-transform: uppercase;
248
+ letter-spacing: 1.7px;
249
+ margin-bottom: 25px;
250
+ }
251
+
252
+ .sponsor-logos {
253
+ display: flex;
254
+ justify-content: center;
255
+ align-items: center;
256
+ flex-wrap: wrap;
257
+ gap: 25px;
258
+ }
259
+
260
+ .sponsor-logo {
261
+ background: #E85D04;
262
+ color: white;
263
+ padding: 13px 26px;
264
+ border-radius: 33px;
265
+ font-family: 'Inter', sans-serif;
266
+ font-size: 20px;
267
+ font-weight: 600;
268
+ }
269
+
270
+ .stats {
271
+ display: flex;
272
+ justify-content: center;
273
+ gap: 50px;
274
+ margin: 33px 0;
275
+ font-family: 'Inter', sans-serif;
276
+ font-size: 23px;
277
+ color: #E85D04;
278
+ }
279
+
280
+ .stat {
281
+ text-align: center;
282
+ }
283
+
284
+ .stat-number {
285
+ font-weight: 700;
286
+ font-size: 30px;
287
+ color: #2d3748;
288
+ }
289
+
290
+ @media print {
291
+ body { margin: 0; }
292
+ .certificate {
293
+ margin: 0;
294
+ box-shadow: none;
295
+ page-break-inside: avoid;
296
+ }
297
+ }
298
+ </style>
299
+ </head>
300
+ <body>
301
+ <div class="certificate">
302
+ <div class="inner-border">
303
+ <div class="content">
304
+ <div class="header">
305
+ <div class="logo-section">
306
+ <img src="https://www.gradio.app/_app/immutable/assets/gradio-logo-with-title.3SNGTZpF.svg" alt="Gradio" class="gradio-logo">
307
+ <img src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo-with-title.svg" alt="Hugging Face" class="hf-logo">
308
+ </div>
309
+ <div class="event-title">Gradio Agents & MCP Hackathon 2025</div>
310
+ <div class="certificate-type">Certificate of Participation</div>
311
+ </div>
312
+
313
+ <div class="main-content">
314
+ <div class="certifies-text">This certifies that</div>
315
+
316
+ <div class="participant-name" id="participantName">
317
+ {participant_name}
318
+ </div>
319
+
320
+ <div class="description">
321
+ has successfully participated in and completed the <strong>Gradio Agents & MCP Hackathon 2025</strong>,
322
+ a global developer event focused on building AI agents using Gradio and Model Context Protocol.
323
+ </div>
324
+
325
+ <div class="project-info">
326
+ <div class="project-title">Project: <span id="projectTitle">{project_name}</span></div>
327
+ <div class="track-info">Track: <span id="trackName">{track_name}</span></div>
328
+ </div>
329
+
330
+ <div class="stats">
331
+ <div class="stat">
332
+ <div class="stat-number">4,200+</div>
333
+ <div>Participants</div>
334
+ </div>
335
+ <div class="stat">
336
+ <div class="stat-number">630+</div>
337
+ <div>Submissions</div>
338
+ </div>
339
+ <div class="stat">
340
+ <div class="stat-number">$16,500</div>
341
+ <div>Total Prizes</div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+
346
+ <div class="footer">
347
+ <div class="date-section">
348
+ <div class="date">Event Date</div>
349
+ <div class="date-value">June 2-8, 2025</div>
350
+ </div>
351
+
352
+ <div class="signatures-section">
353
+ <div class="signature-line"></div>
354
+ <div class="signature-title">Gradio & Hugging Face Team</div>
355
+ </div>
356
+
357
+ <div class="verification-section">
358
+ <div class="certificate-id">Certificate ID</div>
359
+ <div class="id-value" id="certificateId">{certificate_id}</div>
360
+ </div>
361
+ </div>
362
+
363
+ <div class="sponsors">
364
+ <div class="sponsors-title">Proudly Sponsored By</div>
365
+ <div class="sponsor-logos">
366
+ <div class="sponsor-logo">Anthropic</div>
367
+ <div class="sponsor-logo">OpenAI</div>
368
+ <div class="sponsor-logo">Nebius</div>
369
+ <div class="sponsor-logo">SambaNova</div>
370
+ <div class="sponsor-logo">LlamaIndex</div>
371
+ <div class="sponsor-logo">Hyperbolic</div>
372
+ <div class="sponsor-logo">Modal Labs</div>
373
+ <div class="sponsor-logo">Mistral AI</div>
374
+ <div class="sponsor-logo">Hugging Face</div>
375
+ </div>
376
+ </div>
377
+ </div>
378
+ </div>
379
+ </div>
380
+ </body>
381
+ </html>"""
382
+
383
+ gradio_client = Client("https://ysharma-hackathon-certificate-html-to-image.hf.space/",hf_token=hf_token)
384
+
385
+ def load_user_data(oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None):
386
+ """Load user data from the private dataset"""
387
+ if not oauth_token or not profile:
388
+ return None, None, "❌ Please log in first to access your certificate data."
389
+
390
+ try:
391
+ # Get username from profile
392
+ username = profile.username if hasattr(profile, 'username') else profile.name
393
+
394
+ # Get HF_TOKEN from environment variable
395
+ hf_token = os.environ.get('HF_TOKEN')
396
+ if not hf_token:
397
+ return None, None, "❌ HF_TOKEN environment variable not set. Please contact administrators."
398
+
399
+ # Load the private dataset
400
+ dataset = load_dataset("ysharma/hackathon-data-for-certificates", token=hf_token, split="train")
401
+
402
+ # Convert to pandas for easier filtering
403
+ df = dataset.to_pandas()
404
+
405
+ # Find user by HF username
406
+ user_row = df[df['HF-username'] == username]
407
+
408
+ if user_row.empty:
409
+ return None, None, f"❌ Unable to find your project in the hackathon database. Username searched: {username}. Please contact the organizers on our Discord: https://discord.gg/Qe3jMKVczR"
410
+
411
+ # Get user data
412
+ user_data = user_row.iloc[0]
413
+ project_name = user_data['Space-Name']
414
+ track = user_data['Hackathon-Track']
415
+
416
+ # Handle "No Track" case
417
+ if track == "No Track":
418
+ track = "Agent Demo Track"
419
+
420
+ return project_name, track, f"βœ… Found your project: {project_name} in track: {track}"
421
+
422
+ except Exception as e:
423
+ return None, None, f"❌ Error loading data: {str(e)}. Please contact the organizers on our Discord: https://discord.gg/Qe3jMKVczR"
424
+
425
+ def show_login_status(profile: gr.OAuthProfile | None):
426
+ """Display login status"""
427
+ if profile:
428
+ username = profile.username if hasattr(profile, 'username') else profile.name
429
+ return f"βœ… Logged in as: {username}"
430
+ else:
431
+ return "❌ Please log in with your Hugging Face account"
432
+
433
+ def show_main_interface(profile: gr.OAuthProfile | None):
434
+ """Show/hide main interface based on login status"""
435
+ if profile:
436
+ return gr.update(visible=True)
437
+ else:
438
+ return gr.update(visible=False)
439
+
440
+ def get_participant_name(profile: gr.OAuthProfile | None):
441
+ """Get participant name from profile"""
442
+ print(f">>>>>>inside get_participant_name :: profile: {profile}")
443
+ if profile:
444
+ name = profile.name if hasattr(profile, 'name') else ""
445
+ username = profile.username if hasattr(profile, 'username') else ""
446
+ return name, username
447
+ return "", ""
448
+
449
+ def get_user_project_data(oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None):
450
+ """Get user project data for project name field"""
451
+ if not oauth_token or not profile:
452
+ return ""
453
+
454
+ project_name, track, status = load_user_data(oauth_token, profile)
455
+ return project_name or ""
456
+
457
+ def get_user_track_data(oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None):
458
+ """Get user track data for track field"""
459
+ if not oauth_token or not profile:
460
+ return ""
461
+
462
+ project_name, track, status = load_user_data(oauth_token, profile)
463
+ return track or ""
464
+
465
+ def get_data_status(oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None):
466
+ """Get data loading status message"""
467
+ if not oauth_token or not profile:
468
+ return "❌ Please log in first"
469
+
470
+ project_name, track, status = load_user_data(oauth_token, profile)
471
+ return status
472
+
473
+
474
+
475
+ def generate_linkedin_url(participant_name, project_name, track_name, certificate_id):
476
+ """Generate LinkedIn Add to Profile URL with pre-populated certificate details"""
477
+
478
+ # Certificate details for LinkedIn
479
+ cert_name = "Gradio Agents & MCP Hackathon 2025"
480
+ organization_name = "Hugging Face"
481
+ issue_year = "2025"
482
+ issue_month = "6" # June
483
+
484
+ # Build the LinkedIn URL parameters
485
+ params = {
486
+ 'startTask': 'CERTIFICATION_NAME',
487
+ 'name': cert_name,
488
+ 'organizationName': organization_name,
489
+ 'issueYear': issue_year,
490
+ 'issueMonth': issue_month,
491
+ 'certId': certificate_id
492
+ }
493
+
494
+ # URL encode the parameters
495
+ encoded_params = urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
496
+ linkedin_url = f"https://www.linkedin.com/profile/add?{encoded_params}"
497
+
498
+ return linkedin_url
499
+
500
+ def create_certificate(participant_name: str, project_name: str, track_name: str,
501
+ oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None):
502
+ """Generate the certificate HTML and create LinkedIn integration"""
503
+
504
+ print(f">>>>>>1. Inside create_certificate :: profile: {profile}")
505
+ if not oauth_token or not profile:
506
+ return None, "❌ Please log in first to generate your certificate.", ""
507
+
508
+ if not participant_name.strip():
509
+ participant_name = profile.name if hasattr(profile, 'name') else "Participant"
510
+
511
+ if not project_name.strip():
512
+ return None, "❌ Please enter a project name.", ""
513
+
514
+ if not track_name.strip():
515
+ track_name = "Agent Demo Track"
516
+
517
+ # Generate unique certificate ID
518
+ certificate_id = f"GRADIO2025-{str(uuid.uuid4())[:8].upper()}"
519
+
520
+ # Use string replacement instead of format to avoid CSS conflicts
521
+ certificate_html = CERTIFICATE_HTML_TEMPLATE.replace("{participant_name}", participant_name)
522
+ certificate_html = certificate_html.replace("{project_name}", project_name)
523
+ certificate_html = certificate_html.replace("{track_name}", track_name)
524
+ certificate_html = certificate_html.replace("{certificate_id}", certificate_id)
525
+
526
+ # Save to a temporary HTML file
527
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.html', encoding='utf-8') as f:
528
+ f.write(certificate_html)
529
+ html_temp_path = f.name
530
+
531
+ # Generate certificate image
532
+ image_temp_path = gradio_client.predict(
533
+ html_file=handle_file(html_temp_path),
534
+ api_name="/predict"
535
+ )[0]
536
+
537
+ certificate_image = Image.open(image_temp_path)
538
+ print(f">>>>>>2. Inside create_certificate :: profile: {profile}")
539
+ _, hf_username = get_participant_name(profile)
540
+ print(f">>>>>>hf_username: {hf_username}")
541
+
542
+ # Upload certificate and get status
543
+ upload_status = certificate_upload(certificate_image, hf_username)
544
+
545
+ # Generate LinkedIn URL
546
+ linkedin_url = generate_linkedin_url(participant_name, project_name, track_name, certificate_id)
547
+
548
+ return image_temp_path, f"{upload_status}", linkedin_url
549
+
550
+ def certificate_upload(certificate_image, hf_username):
551
+ """Upload to dataset and return status"""
552
+ success, message = upload_user_certificate(certificate_image, hf_username)
553
+
554
+ if success:
555
+ return f"βœ… Certificate generated and saved! {message}"
556
+ else:
557
+ return f"⚠️ Certificate generated but upload failed: {message}"
558
+
559
+ # Create the Gradio interface
560
+ with gr.Blocks(title="Gradio Agents & MCP Hackathon 2025 - Certificate Generator", ) as demo:
561
+
562
+ gr.Markdown("""
563
+ # πŸŽ“ Gradio Agents & MCP Hackathon 2025
564
+ ### Certificate Generator - Generate your personalized certificate of participation for the Gradio Agents & MCP Hackathon 2025!
565
+ """)
566
+
567
+ # Login section
568
+ with gr.Row():
569
+ with gr.Column():
570
+ login_btn = gr.LoginButton(value="πŸ” Sign in with Hugging Face", variant="primary")
571
+ login_status = gr.Markdown("❌ Please log in with your Hugging Face account")
572
+
573
+ # Main interface (initially hidden)
574
+ with gr.Column(visible=False) as main_interface:
575
+
576
+ gr.Markdown("### πŸ“ Certificate Information")
577
+
578
+ # Status message for data fetching
579
+ data_status = gr.Markdown("")
580
+
581
+ with gr.Row():
582
+ with gr.Column():
583
+ participant_name = gr.Textbox(
584
+ label="πŸ‘€ Participant Name",
585
+ placeholder="Your name (will be auto-filled from HF profile)",
586
+ info="This will appear on your certificate"
587
+ )
588
+
589
+ participant_username = gr.Textbox(
590
+ label="πŸ‘€ Participant username",
591
+ visible=False,
592
+ )
593
+
594
+ project_name = gr.Textbox(
595
+ label="πŸš€ Project Name",
596
+ placeholder="Your project name will be auto-loaded",
597
+ info="Edit this if you want to change the project name on your certificate"
598
+ )
599
+
600
+ track_name = gr.Textbox(
601
+ label="🎯 Hackathon Track",
602
+ placeholder="Your track will be auto-loaded",
603
+ info="Edit this if you want to change the track name on your certificate"
604
+ )
605
+
606
+ with gr.Row():
607
+ generate_btn = gr.Button("πŸŽ“ Generate Certificate", variant="primary", size="lg")
608
+
609
+ # Output section
610
+ with gr.Row():
611
+ with gr.Column():
612
+ certificate_file = gr.File(
613
+ label="πŸ“„ Download Your Certificate",
614
+ type="filepath",
615
+ interactive=False
616
+ )
617
+ generation_status = gr.Markdown("")
618
+
619
+ # LinkedIn integration section
620
+ with gr.Row():
621
+ with gr.Column():
622
+ gr.Markdown("### πŸ”— LinkedIn Integration")
623
+
624
+ linkedin_url_display = gr.Textbox(
625
+ label="LinkedIn Add to Profile URL",
626
+ placeholder="Generate your certificate first to get the LinkedIn URL",
627
+ interactive=False,
628
+ info="This URL will pre-populate your LinkedIn certification form"
629
+ )
630
+
631
+ linkedin_btn = gr.HTML("""
632
+ <div id="linkedin-button-container" style="margin-top: 10px;">
633
+ <p>Generate your certificate first to enable LinkedIn integration</p>
634
+ </div>
635
+ """)
636
+
637
+ gr.Markdown("""
638
+ πŸ“‹ **How it works:**
639
+ 1. The app fetches your name, project name, and Hackathon track details. You can update this information. **Please ensure the details are accurate before proceeding to generate the certificate.**
640
+ 2. Click "Generate Certificate" to create your personalized certificate. You can't make multiple entries; regenerating gives a new image but doesn't replace it in the dataset. The dataset is mainly for records, so no need to worry if you have entered incorrect info initially.
641
+ 2. Download the certificate image file
642
+ 3. Click "Add to LinkedIn Profile" - this opens LinkedIn with pre-filled details:
643
+ - βœ… Certificate name: "Gradio Agents & MCP Hackathon 2025"
644
+ - βœ… Organization: "Hugging Face"
645
+ - βœ… Issue date: June 2025
646
+ - βœ… Certificate ID
647
+ 4. **Upload your certificate image** in the "Certification media" field
648
+ 5. **Add skills**: MCP, AI Agents, Gradio, Hugging Face
649
+ 6. Click "Add" to save to your LinkedIn profile!
650
+
651
+ πŸ’‘ **Note**: LinkedIn doesn't allow automatic image uploads for security reasons, so you'll need to manually upload your certificate image.
652
+ """, elem_id="instructions")
653
+
654
+ # Hidden state to store LinkedIn URL
655
+ linkedin_url_state = gr.State("")
656
+
657
+ # Event handlers - using the correct OAuth pattern
658
+
659
+ # Update login status
660
+ demo.load(
661
+ fn=show_login_status,
662
+ inputs=None,
663
+ outputs=[login_status]
664
+ )
665
+
666
+ # Show/hide main interface based on login
667
+ demo.load(
668
+ fn=show_main_interface,
669
+ inputs=None,
670
+ outputs=[main_interface]
671
+ )
672
+
673
+ # Auto-populate participant name
674
+ demo.load(
675
+ fn=get_participant_name,
676
+ inputs=None,
677
+ outputs=[participant_name, participant_username]
678
+ )
679
+
680
+ # Auto-populate project data
681
+ demo.load(
682
+ fn=get_user_project_data,
683
+ inputs=None,
684
+ outputs=[project_name]
685
+ )
686
+
687
+ # Auto-populate track data
688
+ demo.load(
689
+ fn=get_user_track_data,
690
+ inputs=None,
691
+ outputs=[track_name]
692
+ )
693
+
694
+ # Show data status
695
+ demo.load(
696
+ fn=get_data_status,
697
+ inputs=None,
698
+ outputs=[data_status]
699
+ )
700
+
701
+ # Function to update LinkedIn button
702
+ def update_linkedin_button(linkedin_url):
703
+ if linkedin_url:
704
+ return f"""
705
+ <div style="margin-top: 10px;">
706
+ <a href="{linkedin_url}" target="_blank" style="display: inline-block; text-decoration: none;">
707
+ <div style="background: #0077B5; color: white; padding: 12px 24px; border-radius: 8px; font-weight: 600; font-size: 16px; cursor: pointer; border: none; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: background 0.2s;">
708
+ πŸ”— Add to LinkedIn Profile
709
+ </div>
710
+ </a>
711
+ <div style="margin-top: 8px; padding: 8px; background: #e3f2fd; border-radius: 6px; font-size: 12px; color: #1565c0;">
712
+ <strong>Ready to add to LinkedIn!</strong> Click the button to open LinkedIn with pre-filled details, then upload your certificate image manually.
713
+ </div>
714
+ </div>
715
+ """
716
+ else:
717
+ return """
718
+ <div style="margin-top: 10px;">
719
+ <p style="color: #666; font-style: italic;">Generate your certificate first to enable LinkedIn integration</p>
720
+ </div>
721
+ """
722
+
723
+ # Generate certificate button
724
+ generate_btn.click(
725
+ fn=create_certificate,
726
+ inputs=[participant_name, project_name, track_name],
727
+ outputs=[certificate_file, generation_status, linkedin_url_state]
728
+ ).then(
729
+ fn=lambda url: (url, update_linkedin_button(url)),
730
+ inputs=[linkedin_url_state],
731
+ outputs=[linkedin_url_display, linkedin_btn]
732
+ )
733
+
734
+ demo.launch()