DineshAI's picture
Add opt-in public certificate links
46daaad verified
Raw
History Blame
10.5 kB
"""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
from huggingface_hub import upload_file
DISCUSSIONS_URL = "https://huggingface.co/spaces/ICML-2026-agent-repro/challenge/discussions"
SPACE_URL = "https://huggingface.co/spaces/ICML-2026-agent-repro/certificate-generator"
CERTIFICATES_DATASET = os.getenv(
"CERTIFICATES_DATASET", "ICML-2026-agent-repro/certificates"
)
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 OAuth controls that work inside the sandboxed Space iframe."""
if profile is None:
href = "/oauth-login"
target = "_blank"
label = "πŸ€— Sign in with Hugging Face"
else:
href = "/logout?_target_url=/"
target = "_self"
label = f"Logout (@{escape(profile.username)})"
return (
f'<a href="{href}" target="{target}" rel="noopener" role="button" '
'style="display:inline-block;background:#111827;color:#fff;font-weight:700;'
'padding:10px 18px;border-radius:8px;text-decoration:none">'
f"{label}</a>"
)
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 publish_certificate(username, image_path):
path_in_repo = f"certificates/{username.lower()}.png"
upload_file(
path_or_fileobj=image_path,
path_in_repo=path_in_repo,
repo_id=CERTIFICATES_DATASET,
repo_type="dataset",
token=os.getenv("HF_TOKEN"),
)
encoded_path = urllib.parse.quote(path_in_repo, safe="/")
return (
f"https://huggingface.co/datasets/{CERTIFICATES_DATASET}/"
f"resolve/main/{encoded_path}"
)
def linkedin_url(cert_url, username):
p = {
"startTask": "CERTIFICATION_NAME",
"name": "ICML 2026 Open Reproductions β€” Certificate of Participation",
"organizationName": "Hugging Face",
"issueYear": "2026", "issueMonth": "8",
"certUrl": cert_url,
"certId": username,
}
return "https://www.linkedin.com/profile/add?" + urllib.parse.urlencode(p)
def create_certificate(display_name, make_public, 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)
cert_url = SPACE_URL
if make_public:
try:
cert_url = publish_certificate(profile.username, image_path)
except Exception as e:
print(f"Certificate upload failed for @{profile.username}: {e}")
note = ("⚠️ Your certificate is ready to download, but its public URL could not "
"be created. Please try generating it again in a minute.")
else:
note = ("πŸŽ‰ Your certificate is ready. Anyone with your "
f"[public certificate URL]({cert_url}) can view it.")
else:
note = ("πŸŽ‰ Your certificate is ready to download. It was not published; enable "
"the public sharing option and generate it again to create a permanent URL.")
btn = (f'<a href="{linkedin_url(cert_url, profile.username)}" target="_blank" '
f'style="display:inline-block;background:#0a66c2;color:#fff;font-weight:700;'
f'padding:10px 22px;border-radius:8px;text-decoration:none">Add to LinkedIn profile β†’</a>')
return image_path, note, 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")
public_share = gr.Checkbox(
value=False,
label="Create a public shareable certificate URL",
info=("Publishes your certificate image and displayed name in a public Hugging "
"Face dataset. Your username appears in the URL; generating again replaces "
"the image at that URL."),
)
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, public_share],
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")),
)