Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import pandas as pd | |
| import tempfile | |
| import os | |
| import uuid | |
| import urllib.parse | |
| from datasets import load_dataset | |
| from datetime import datetime | |
| from gradio_client import Client, handle_file | |
| from certificate_upload_module import upload_user_certificate | |
| from PIL import Image | |
| hf_token = os.getenv("HF_TOKEN") | |
| # HTML template for the certificate (unchanged) | |
| CERTIFICATE_HTML_TEMPLATE = """<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Gradio Agents & MCP Hackathon 2025 - Certificate of Participation</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Inter:wght@300;400;500;600&display=swap'); | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| width: 2000px; | |
| height: 1414px; | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| } | |
| .certificate { | |
| width: 2000px; | |
| height: 1414px; | |
| background: linear-gradient(135deg, #FF6B35 0%, #F7931E 50%, #FF8C00 100%); | |
| position: relative; | |
| margin: 0; | |
| border-radius: 0; | |
| overflow: hidden; | |
| box-shadow: 0 33px 100px rgba(0,0,0,0.3); | |
| print-color-adjust: exact; | |
| -webkit-print-color-adjust: exact; | |
| } | |
| .certificate::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| 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; | |
| opacity: 0.3; | |
| } | |
| .inner-border { | |
| position: absolute; | |
| top: 50px; | |
| left: 50px; | |
| right: 50px; | |
| bottom: 50px; | |
| border: 5px solid rgba(255,255,255,0.8); | |
| border-radius: 0; | |
| background: rgba(255,255,255,0.97); | |
| } | |
| .content { | |
| position: relative; | |
| padding: 100px 133px; | |
| height: 100%; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: space-between; | |
| text-align: center; | |
| z-index: 2; | |
| } | |
| .header { | |
| margin-bottom: 33px; | |
| } | |
| .logo-section { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 33px; | |
| margin-bottom: 33px; | |
| } | |
| .gradio-logo { | |
| height: 66px; | |
| } | |
| .hf-logo { | |
| height: 58px; | |
| } | |
| .logo-text { | |
| font-size: 46px; | |
| font-weight: bold; | |
| color: #E85D04; | |
| font-family: 'Inter', sans-serif; | |
| } | |
| .event-title { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 60px; | |
| font-weight: 700; | |
| color: #2d3748; | |
| margin-bottom: 17px; | |
| line-height: 1.2; | |
| } | |
| .certificate-type { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 30px; | |
| color: #E85D04; | |
| font-weight: 500; | |
| letter-spacing: 3.3px; | |
| text-transform: uppercase; | |
| } | |
| .main-content { | |
| flex-grow: 1; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| margin: 66px 0; | |
| } | |
| .certifies-text { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 33px; | |
| color: #4a5568; | |
| margin-bottom: 33px; | |
| } | |
| .participant-name { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 80px; | |
| font-weight: 700; | |
| color: #2d3748; | |
| margin: 33px 0; | |
| padding: 17px 0; | |
| border-bottom: 5px solid #E85D04; | |
| border-top: 2px solid #e2e8f0; | |
| background: linear-gradient(90deg, transparent 0%, rgba(232, 93, 4, 0.1) 50%, transparent 100%); | |
| } | |
| .description { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 30px; | |
| color: #4a5568; | |
| line-height: 1.6; | |
| max-width: 1333px; | |
| margin: 50px auto; | |
| } | |
| .project-info { | |
| background: rgba(232, 93, 4, 0.1); | |
| padding: 33px; | |
| border-radius: 17px; | |
| margin: 33px 0; | |
| border-left: 7px solid #E85D04; | |
| } | |
| .project-title { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 26px; | |
| color: #2d3748; | |
| font-weight: 600; | |
| } | |
| .track-info { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 23px; | |
| color: #E85D04; | |
| margin-top: 8px; | |
| } | |
| .footer { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr 1fr; | |
| gap: 66px; | |
| align-items: end; | |
| margin-top: 50px; | |
| } | |
| .date-section { | |
| text-align: left; | |
| } | |
| .signatures-section { | |
| text-align: center; | |
| } | |
| .verification-section { | |
| text-align: right; | |
| } | |
| .date, .certificate-id { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 23px; | |
| color: #4a5568; | |
| margin-bottom: 8px; | |
| } | |
| .date-value, .id-value { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 26px; | |
| font-weight: 600; | |
| color: #2d3748; | |
| } | |
| .signature-line { | |
| border-top: 3px solid #2d3748; | |
| width: 333px; | |
| margin: 33px auto 17px; | |
| } | |
| .signature-title { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 20px; | |
| color: #4a5568; | |
| text-transform: uppercase; | |
| letter-spacing: 1.7px; | |
| } | |
| .sponsors { | |
| margin-top: 66px; | |
| padding-top: 50px; | |
| border-top: 2px solid #e2e8f0; | |
| } | |
| .sponsors-title { | |
| font-family: 'Inter', sans-serif; | |
| font-size: 20px; | |
| color: #4a5568; | |
| text-transform: uppercase; | |
| letter-spacing: 1.7px; | |
| margin-bottom: 25px; | |
| } | |
| .sponsor-logos { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 25px; | |
| } | |
| .sponsor-logo { | |
| background: #E85D04; | |
| color: white; | |
| padding: 13px 26px; | |
| border-radius: 33px; | |
| font-family: 'Inter', sans-serif; | |
| font-size: 20px; | |
| font-weight: 600; | |
| } | |
| .stats { | |
| display: flex; | |
| justify-content: center; | |
| gap: 50px; | |
| margin: 33px 0; | |
| font-family: 'Inter', sans-serif; | |
| font-size: 23px; | |
| color: #E85D04; | |
| } | |
| .stat { | |
| text-align: center; | |
| } | |
| .stat-number { | |
| font-weight: 700; | |
| font-size: 30px; | |
| color: #2d3748; | |
| } | |
| @media print { | |
| body { margin: 0; } | |
| .certificate { | |
| margin: 0; | |
| box-shadow: none; | |
| page-break-inside: avoid; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="certificate"> | |
| <div class="inner-border"> | |
| <div class="content"> | |
| <div class="header"> | |
| <div class="logo-section"> | |
| <img src="https://www.gradio.app/_app/immutable/assets/gradio-logo-with-title.3SNGTZpF.svg" alt="Gradio" class="gradio-logo"> | |
| <img src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo-with-title.svg" alt="Hugging Face" class="hf-logo"> | |
| </div> | |
| <div class="event-title">Gradio Agents & MCP Hackathon 2025</div> | |
| <div class="certificate-type">Certificate of Participation</div> | |
| </div> | |
| <div class="main-content"> | |
| <div class="certifies-text">This certifies that</div> | |
| <div class="participant-name" id="participantName"> | |
| {participant_name} | |
| </div> | |
| <div class="description"> | |
| has successfully participated in and completed the <strong>Gradio Agents & MCP Hackathon 2025</strong>, | |
| a global developer event focused on building AI agents using Gradio and Model Context Protocol. | |
| </div> | |
| <div class="project-info"> | |
| <div class="project-title">Project: <span id="projectTitle">{project_name}</span></div> | |
| <div class="track-info">Track: <span id="trackName">{track_name}</span></div> | |
| </div> | |
| <div class="stats"> | |
| <div class="stat"> | |
| <div class="stat-number">4,200+</div> | |
| <div>Participants</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-number">630+</div> | |
| <div>Submissions</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-number">$16,500</div> | |
| <div>Total Prizes</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="footer"> | |
| <div class="date-section"> | |
| <div class="date">Event Date</div> | |
| <div class="date-value">June 2-8, 2025</div> | |
| </div> | |
| <div class="signatures-section"> | |
| <div class="signature-line"></div> | |
| <div class="signature-title">Gradio & Hugging Face Team</div> | |
| </div> | |
| <div class="verification-section"> | |
| <div class="certificate-id">Certificate ID</div> | |
| <div class="id-value" id="certificateId">{certificate_id}</div> | |
| </div> | |
| </div> | |
| <div class="sponsors"> | |
| <div class="sponsors-title">Proudly Sponsored By</div> | |
| <div class="sponsor-logos"> | |
| <div class="sponsor-logo">Anthropic</div> | |
| <div class="sponsor-logo">OpenAI</div> | |
| <div class="sponsor-logo">Nebius</div> | |
| <div class="sponsor-logo">SambaNova</div> | |
| <div class="sponsor-logo">LlamaIndex</div> | |
| <div class="sponsor-logo">Hyperbolic</div> | |
| <div class="sponsor-logo">Modal Labs</div> | |
| <div class="sponsor-logo">Mistral AI</div> | |
| <div class="sponsor-logo">Hugging Face</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </body> | |
| </html>""" | |
| gradio_client = Client("https://ysharma-hackathon-certificate-html-to-image.hf.space/",hf_token=hf_token) | |
| def load_user_data(oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None): | |
| """Load user data from the private dataset""" | |
| if not oauth_token or not profile: | |
| return None, None, "β Please log in first to access your certificate data." | |
| try: | |
| # Get username from profile | |
| username = profile.username if hasattr(profile, 'username') else profile.name | |
| # Get HF_TOKEN from environment variable | |
| hf_token = os.environ.get('HF_TOKEN') | |
| if not hf_token: | |
| return None, None, "β HF_TOKEN environment variable not set. Please contact administrators." | |
| # Load the private dataset | |
| dataset = load_dataset("ysharma/hackathon-data-for-certificates", token=hf_token, split="train") | |
| # Convert to pandas for easier filtering | |
| df = dataset.to_pandas() | |
| # Find user by HF username | |
| user_row = df[df['HF-username'] == username] | |
| if user_row.empty: | |
| 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" | |
| # Get user data | |
| user_data = user_row.iloc[0] | |
| project_name = user_data['Space-Name'] | |
| track = user_data['Hackathon-Track'] | |
| # Handle "No Track" case | |
| if track == "No Track": | |
| track = "Agent Demo Track" | |
| return project_name, track, f"β Found your project: {project_name} in track: {track}" | |
| except Exception as e: | |
| return None, None, f"β Error loading data: {str(e)}. Please contact the organizers on our Discord: https://discord.gg/Qe3jMKVczR" | |
| def show_login_status(profile: gr.OAuthProfile | None): | |
| """Display login status""" | |
| if profile: | |
| username = profile.username if hasattr(profile, 'username') else profile.name | |
| return f"β Logged in as: {username}" | |
| else: | |
| return "β Please log in with your Hugging Face account" | |
| def show_main_interface(profile: gr.OAuthProfile | None): | |
| """Show/hide main interface based on login status""" | |
| if profile: | |
| return gr.update(visible=True) | |
| else: | |
| return gr.update(visible=False) | |
| def get_participant_name(profile: gr.OAuthProfile | None): | |
| """Get participant name from profile""" | |
| print(f">>>>>>inside get_participant_name :: profile: {profile}") | |
| if profile: | |
| name = profile.name if hasattr(profile, 'name') else "" | |
| username = profile.username if hasattr(profile, 'username') else "" | |
| return name, username | |
| return "", "" | |
| def get_user_project_data(oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None): | |
| """Get user project data for project name field""" | |
| if not oauth_token or not profile: | |
| return "" | |
| project_name, track, status = load_user_data(oauth_token, profile) | |
| return project_name or "" | |
| def get_user_track_data(oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None): | |
| """Get user track data for track field""" | |
| if not oauth_token or not profile: | |
| return "" | |
| project_name, track, status = load_user_data(oauth_token, profile) | |
| return track or "" | |
| def get_data_status(oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None): | |
| """Get data loading status message""" | |
| if not oauth_token or not profile: | |
| return "β Please log in first" | |
| project_name, track, status = load_user_data(oauth_token, profile) | |
| return status | |
| def generate_linkedin_url(participant_name, project_name, track_name, certificate_id): | |
| """Generate LinkedIn Add to Profile URL with pre-populated certificate details""" | |
| # Certificate details for LinkedIn | |
| cert_name = "Gradio Agents & MCP Hackathon 2025" | |
| organization_name = "Hugging Face" | |
| issue_year = "2025" | |
| issue_month = "6" # June | |
| # Build the LinkedIn URL parameters | |
| params = { | |
| 'startTask': 'CERTIFICATION_NAME', | |
| 'name': cert_name, | |
| 'organizationName': organization_name, | |
| 'issueYear': issue_year, | |
| 'issueMonth': issue_month, | |
| 'certId': certificate_id | |
| } | |
| # URL encode the parameters | |
| encoded_params = urllib.parse.urlencode(params, quote_via=urllib.parse.quote) | |
| linkedin_url = f"https://www.linkedin.com/profile/add?{encoded_params}" | |
| return linkedin_url | |
| def create_certificate(participant_name: str, project_name: str, track_name: str, | |
| oauth_token: gr.OAuthToken | None, profile: gr.OAuthProfile | None): | |
| """Generate the certificate HTML and create LinkedIn integration""" | |
| print(f">>>>>>1. Inside create_certificate :: profile: {profile}") | |
| if not oauth_token or not profile: | |
| return None, "β Please log in first to generate your certificate.", "" | |
| if not participant_name.strip(): | |
| participant_name = profile.name if hasattr(profile, 'name') else "Participant" | |
| if not project_name.strip(): | |
| return None, "β Please enter a project name.", "" | |
| if not track_name.strip(): | |
| track_name = "Agent Demo Track" | |
| # Generate unique certificate ID | |
| certificate_id = f"GRADIO2025-{str(uuid.uuid4())[:8].upper()}" | |
| # Use string replacement instead of format to avoid CSS conflicts | |
| certificate_html = CERTIFICATE_HTML_TEMPLATE.replace("{participant_name}", participant_name) | |
| certificate_html = certificate_html.replace("{project_name}", project_name) | |
| certificate_html = certificate_html.replace("{track_name}", track_name) | |
| certificate_html = certificate_html.replace("{certificate_id}", certificate_id) | |
| # Save to a temporary HTML file | |
| with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.html', encoding='utf-8') as f: | |
| f.write(certificate_html) | |
| html_temp_path = f.name | |
| # Generate certificate image | |
| image_temp_path = gradio_client.predict( | |
| html_file=handle_file(html_temp_path), | |
| api_name="/predict" | |
| )[0] | |
| certificate_image = Image.open(image_temp_path) | |
| print(f">>>>>>2. Inside create_certificate :: profile: {profile}") | |
| _, hf_username = get_participant_name(profile) | |
| print(f">>>>>>hf_username: {hf_username}") | |
| # Upload certificate and get status | |
| upload_status = certificate_upload(certificate_image, hf_username) | |
| # Generate LinkedIn URL | |
| linkedin_url = generate_linkedin_url(participant_name, project_name, track_name, certificate_id) | |
| return image_temp_path, f"{upload_status}", linkedin_url | |
| def certificate_upload(certificate_image, hf_username): | |
| """Upload to dataset and return status""" | |
| success, message = upload_user_certificate(certificate_image, hf_username) | |
| if success: | |
| return f"β Certificate generated and saved! {message}" | |
| else: | |
| return f"β οΈ Certificate generated but upload failed: {message}" | |
| # Create the Gradio interface | |
| with gr.Blocks(title="Gradio Agents & MCP Hackathon 2025 - Certificate Generator", ) as demo: | |
| gr.Markdown(""" | |
| # π Gradio Agents & MCP Hackathon 2025 | |
| ### Certificate Generator - Generate your personalized certificate of participation for the Gradio Agents & MCP Hackathon 2025! | |
| """) | |
| # Login section | |
| with gr.Row(): | |
| with gr.Column(): | |
| login_btn = gr.LoginButton(value="π Sign in with Hugging Face", variant="primary") | |
| login_status = gr.Markdown("β Please log in with your Hugging Face account") | |
| # Main interface (initially hidden) | |
| with gr.Column(visible=False) as main_interface: | |
| gr.Markdown("### π Certificate Information") | |
| # Status message for data fetching | |
| data_status = gr.Markdown("") | |
| with gr.Row(): | |
| with gr.Column(): | |
| participant_name = gr.Textbox( | |
| label="π€ Participant Name", | |
| placeholder="Your name (will be auto-filled from HF profile)", | |
| info="This will appear on your certificate" | |
| ) | |
| participant_username = gr.Textbox( | |
| label="π€ Participant username", | |
| visible=False, | |
| ) | |
| project_name = gr.Textbox( | |
| label="π Project Name", | |
| placeholder="Your project name will be auto-loaded", | |
| info="Edit this if you want to change the project name on your certificate" | |
| ) | |
| track_name = gr.Textbox( | |
| label="π― Hackathon Track", | |
| placeholder="Your track will be auto-loaded", | |
| info="Edit this if you want to change the track name on your certificate" | |
| ) | |
| with gr.Row(): | |
| generate_btn = gr.Button("π Generate Certificate", variant="primary", size="lg") | |
| # Output section | |
| with gr.Row(): | |
| with gr.Column(): | |
| certificate_file = gr.File( | |
| label="π Download Your Certificate", | |
| type="filepath", | |
| interactive=False | |
| ) | |
| generation_status = gr.Markdown("") | |
| # LinkedIn integration section | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### π LinkedIn Integration") | |
| linkedin_url_display = gr.Textbox( | |
| label="LinkedIn Add to Profile URL", | |
| placeholder="Generate your certificate first to get the LinkedIn URL", | |
| interactive=False, | |
| info="This URL will pre-populate your LinkedIn certification form" | |
| ) | |
| linkedin_btn = gr.HTML(""" | |
| <div id="linkedin-button-container" style="margin-top: 10px;"> | |
| <p>Generate your certificate first to enable LinkedIn integration</p> | |
| </div> | |
| """) | |
| gr.Markdown(""" | |
| π **How it works:** | |
| 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.** | |
| 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. | |
| 2. Download the certificate image file | |
| 3. Click "Add to LinkedIn Profile" - this opens LinkedIn with pre-filled details: | |
| - β Certificate name: "Gradio Agents & MCP Hackathon 2025" | |
| - β Organization: "Hugging Face" | |
| - β Issue date: June 2025 | |
| - β Certificate ID | |
| 4. **Upload your certificate image** in the "Certification media" field | |
| 5. **Add skills**: MCP, AI Agents, Gradio, Hugging Face | |
| 6. Click "Add" to save to your LinkedIn profile! | |
| π‘ **Note**: LinkedIn doesn't allow automatic image uploads for security reasons, so you'll need to manually upload your certificate image. | |
| """, elem_id="instructions") | |
| # Hidden state to store LinkedIn URL | |
| linkedin_url_state = gr.State("") | |
| # Event handlers - using the correct OAuth pattern | |
| # Update login status | |
| demo.load( | |
| fn=show_login_status, | |
| inputs=None, | |
| outputs=[login_status] | |
| ) | |
| # Show/hide main interface based on login | |
| demo.load( | |
| fn=show_main_interface, | |
| inputs=None, | |
| outputs=[main_interface] | |
| ) | |
| # Auto-populate participant name | |
| demo.load( | |
| fn=get_participant_name, | |
| inputs=None, | |
| outputs=[participant_name, participant_username] | |
| ) | |
| # Auto-populate project data | |
| demo.load( | |
| fn=get_user_project_data, | |
| inputs=None, | |
| outputs=[project_name] | |
| ) | |
| # Auto-populate track data | |
| demo.load( | |
| fn=get_user_track_data, | |
| inputs=None, | |
| outputs=[track_name] | |
| ) | |
| # Show data status | |
| demo.load( | |
| fn=get_data_status, | |
| inputs=None, | |
| outputs=[data_status] | |
| ) | |
| # Function to update LinkedIn button | |
| def update_linkedin_button(linkedin_url): | |
| if linkedin_url: | |
| return f""" | |
| <div style="margin-top: 10px;"> | |
| <a href="{linkedin_url}" target="_blank" style="display: inline-block; text-decoration: none;"> | |
| <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;"> | |
| π Add to LinkedIn Profile | |
| </div> | |
| </a> | |
| <div style="margin-top: 8px; padding: 8px; background: #e3f2fd; border-radius: 6px; font-size: 12px; color: #1565c0;"> | |
| <strong>Ready to add to LinkedIn!</strong> Click the button to open LinkedIn with pre-filled details, then upload your certificate image manually. | |
| </div> | |
| </div> | |
| """ | |
| else: | |
| return """ | |
| <div style="margin-top: 10px;"> | |
| <p style="color: #666; font-style: italic;">Generate your certificate first to enable LinkedIn integration</p> | |
| </div> | |
| """ | |
| # Generate certificate button | |
| generate_btn.click( | |
| fn=create_certificate, | |
| inputs=[participant_name, project_name, track_name], | |
| outputs=[certificate_file, generation_status, linkedin_url_state] | |
| ).then( | |
| fn=lambda url: (url, update_linkedin_button(url)), | |
| inputs=[linkedin_url_state], | |
| outputs=[linkedin_url_display, linkedin_btn] | |
| ) | |
| demo.launch() |