File size: 8,658 Bytes
dfd58db 7008665 dfd58db 7008665 dfd58db ed2b0a4 dfd58db ed2b0a4 dfd58db 7008665 dfd58db 7008665 dfd58db 7008665 dfd58db ed2b0a4 28b36fa ed2b0a4 28b36fa ed2b0a4 28b36fa ed2b0a4 28b36fa ed2b0a4 dfd58db ed2b0a4 dfd58db ed2b0a4 dfd58db ed2b0a4 dfd58db 7008665 dfd58db 7008665 dfd58db 7008665 dfd58db 1971f1d dfd58db 0157e15 dfd58db ed2b0a4 dfd58db 7008665 dfd58db ed2b0a4 dfd58db ed2b0a4 11e61ce ed2b0a4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | """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 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 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'<a href="{linkedin_url(name)}" 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, "π 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")),
)
|