"""ICML 2026 Open Reproductions — Certificate Generator
=======================================================
1. Sign in with Hugging Face (OAuth).
2. Eligibility is checked against the frozen challenge verdicts: every
participant with at least one claim judged `verified` qualifies
(baked into eligible.json at challenge close — 267 participants).
3. The display name is editable; the certificate renders to PNG with headless
Chromium (installed via packages.txt) — no external renderer dependency.
"""
import json
import os
import shutil
import subprocess
import tempfile
import urllib.parse
from html import escape
import gradio as gr
import uvicorn
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
DISCUSSIONS_URL = "https://huggingface.co/spaces/ICML-2026-agent-repro/challenge/discussions"
CANVAS = (2000, 1414) # must match the body size in certificate_template.html
_HERE = os.path.dirname(__file__)
with open(os.path.join(_HERE, "certificate_template.html"), encoding="utf-8") as f:
TEMPLATE = f.read()
with open(os.path.join(_HERE, "eligible.json"), encoding="utf-8") as f:
ELIGIBLE = json.load(f)
def find_chromium():
candidates = [
"chromium", "chromium-browser", "google-chrome", "google-chrome-stable",
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", # local dev
]
for c in candidates:
path = shutil.which(c) or (c if os.path.exists(c) else None)
if path:
return path
raise RuntimeError("No Chromium binary found — is `chromium` in packages.txt?")
def render_html(html: str) -> str:
out = os.path.join(tempfile.mkdtemp(), "certificate.png")
with tempfile.NamedTemporaryFile("w", delete=False, suffix=".html", encoding="utf-8") as f:
f.write(html)
src = f.name
try:
subprocess.run(
[find_chromium(), "--headless=new", "--no-sandbox", "--disable-gpu",
"--hide-scrollbars", "--force-device-scale-factor=1",
f"--window-size={CANVAS[0]},{CANVAS[1]}", f"--screenshot={out}",
f"file://{src}"],
check=True, capture_output=True, timeout=120,
)
finally:
try:
os.unlink(src)
except OSError:
pass
return out
def plural(n, word):
return f"{n} {word}" + ("" if n == 1 else "s")
def stats_line(rec):
return (f"{plural(rec['verified_claims'], 'claim')} verified across "
f"{plural(rec['verified_papers'], 'paper')}")
def auth_control(profile: gr.OAuthProfile | None):
"""Render an OAuth link that always leaves the embedded Space iframe."""
if profile is None:
href = "/oauth-login"
label = "🤗 Sign in with Hugging Face"
else:
href = "/logout?_target_url=/"
label = f"Logout (@{escape(profile.username)})"
return (
f''
f"{label}"
)
def on_load(profile: gr.OAuthProfile | None):
if profile is None:
return (auth_control(profile), gr.update(visible=False),
"### 👋 Sign in with Hugging Face to check your eligibility.", "")
rec = ELIGIBLE.get(profile.username.lower())
if rec is None:
return (auth_control(profile), gr.update(visible=False),
f"### Sorry @{profile.username} — no verified logbook found.\n"
"Certificates go to participants with at least one claim judged "
"**verified** by the Logbook Judge before the Aug 2 deadline. "
f"If you believe this is an error, please open a thread in the "
f"[challenge discussions]({DISCUSSIONS_URL}).", "")
return (auth_control(profile), gr.update(visible=True),
f"### 🎉 Congratulations @{profile.username} — you qualify!\n"
f"Your record: **{stats_line(rec)}** ({plural(rec['logbooks'], 'logbook')} judged). "
"Adjust how your name should appear, then generate your certificate.",
profile.name or profile.username)
def linkedin_url(name):
p = {
"startTask": "CERTIFICATION_NAME",
"name": "ICML 2026 Open Reproductions — Certificate of Participation",
"organizationName": "Hugging Face",
"issueYear": "2026", "issueMonth": "8",
"certUrl": "https://huggingface.co/spaces/ICML-2026-agent-repro/certificate-generator",
}
return "https://www.linkedin.com/profile/add?" + urllib.parse.urlencode(p)
def create_certificate(display_name, profile: gr.OAuthProfile | None):
if profile is None:
return None, "❌ Please sign in first.", gr.update(visible=False)
rec = ELIGIBLE.get(profile.username.lower())
if rec is None:
return None, "❌ No verified logbook found for your account.", gr.update(visible=False)
name = (display_name or "").strip() or profile.name or profile.username
html = TEMPLATE.replace("{participant_name}", name).replace("{stats_line}", stats_line(rec))
try:
image_path = render_html(html)
except Exception as e:
return None, f"❌ Rendering failed: {e}. Please try again in a minute.", gr.update(visible=False)
btn = (f'Add to LinkedIn profile →')
return image_path, "🎉 Your certificate is ready — download it or add it to your LinkedIn profile below.", gr.update(value=btn, visible=True)
THEME = gr.themes.Base(
primary_hue=gr.themes.colors.orange,
neutral_hue=gr.themes.colors.stone,
)
CSS = """
.gradio-container {
background-color: #fdfcf9 !important;
background-image:
linear-gradient(rgba(31,41,55,.045) 1px, transparent 1px),
linear-gradient(90deg, rgba(31,41,55,.045) 1px, transparent 1px);
background-size: 26px 26px;
}
.dark .gradio-container {
background-color: #17181c !important;
background-image:
linear-gradient(rgba(253,252,249,.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(253,252,249,.04) 1px, transparent 1px);
}
#hero h1 { font-family: ui-serif, "Iowan Old Style", Georgia, serif; }
"""
with gr.Blocks(title="ICML 2026 Open Reproductions — Certificates", theme=THEME, css=CSS) as demo:
gr.Markdown("# 🎓 ICML 2026 Open Reproductions — Certificate of Participation", elem_id="hero")
gr.Markdown(
"Every participant with **at least one claim judged `verified`** in the challenge "
"(July 15 – August 2, 2026) can generate their certificate here."
)
# This hidden component enables Gradio's OAuth routes. The visible control above
# uses a top-level navigation so browsers do not block the OAuth session cookie
# as a third-party cookie inside the Hugging Face Space iframe.
gr.LoginButton(visible=False)
auth = gr.HTML()
status = gr.Markdown()
with gr.Column(visible=False) as main_box:
name_box = gr.Textbox(label="Name on the certificate")
gen_btn = gr.Button("Generate my certificate 🎓", variant="primary")
cert_image = gr.Image(label="Your certificate", type="filepath", interactive=False)
note = gr.Markdown()
linkedin = gr.HTML(visible=False)
demo.load(on_load, inputs=None, outputs=[auth, main_box, status, name_box])
gen_btn.click(create_certificate, inputs=[name_box], outputs=[cert_image, note, linkedin])
app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
@app.get("/oauth-login", include_in_schema=False)
def oauth_login():
"""Start OAuth after removing session state left by an interrupted login."""
response = RedirectResponse("/login/huggingface?_target_url=/", status_code=303)
response.delete_cookie(
"session", path="/", secure=True, httponly=True, samesite="none"
)
return response
# Gradio 5.49.1's Spaces SSR proxy corrupts frontend asset URLs when a Blocks
# app is mounted at the root path. OAuth still needs the explicit FastAPI mount,
# so disable SSR here until the upstream root-mount routing bug is fixed.
app = gr.mount_gradio_app(app, demo, path="/", ssr_mode=False)
if __name__ == "__main__":
uvicorn.run(
app,
host=os.getenv("GRADIO_SERVER_NAME", "0.0.0.0"),
port=int(os.getenv("GRADIO_SERVER_PORT", "7860")),
)