Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
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() |