abidlabs HF Staff commited on
Commit
dfd58db
Β·
verified Β·
1 Parent(s): 47589bc

Add app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -0
app.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ICML 2026 Open Reproductions β€” Certificate Generator
2
+ =======================================================
3
+ 1. Sign in with Hugging Face (OAuth).
4
+ 2. Eligibility is checked against the frozen challenge verdicts: every
5
+ participant with at least one claim judged `verified` qualifies
6
+ (baked into eligible.json at challenge close β€” 267 participants).
7
+ 3. The display name is editable; the certificate renders (HTML -> PNG via the
8
+ shared renderer Space) with the participant's verified-claim stats.
9
+ """
10
+
11
+ import inspect
12
+ import json
13
+ import os
14
+ import tempfile
15
+ import urllib.parse
16
+
17
+ import gradio as gr
18
+ from gradio_client import Client, handle_file
19
+
20
+ RENDERER_SPACE = "https://ysharma-hackathon-certificate-html-to-image.hf.space/"
21
+ DISCUSSIONS_URL = "https://huggingface.co/spaces/ICML-2026-agent-repro/challenge/discussions"
22
+
23
+ _HERE = os.path.dirname(__file__)
24
+ with open(os.path.join(_HERE, "certificate_template.html"), encoding="utf-8") as f:
25
+ TEMPLATE = f.read()
26
+ with open(os.path.join(_HERE, "eligible.json"), encoding="utf-8") as f:
27
+ ELIGIBLE = json.load(f)
28
+
29
+ _client = None
30
+
31
+
32
+ def get_renderer():
33
+ global _client
34
+ if _client is None:
35
+ params = inspect.signature(Client.__init__).parameters
36
+ kwargs = {}
37
+ token = os.getenv("HF_TOKEN")
38
+ if token:
39
+ kwargs["hf_token" if "hf_token" in params else "token"] = token
40
+ _client = Client(RENDERER_SPACE, **kwargs)
41
+ return _client
42
+
43
+
44
+ def plural(n, word):
45
+ return f"{n} {word}" + ("" if n == 1 else "s")
46
+
47
+
48
+ def stats_line(rec):
49
+ return (f"{plural(rec['verified_claims'], 'claim')} verified across "
50
+ f"{plural(rec['verified_papers'], 'paper')}")
51
+
52
+
53
+ def on_load(profile: gr.OAuthProfile | None):
54
+ if profile is None:
55
+ return (gr.update(visible=False), gr.update(visible=False),
56
+ "### πŸ‘‹ Sign in with Hugging Face to check your eligibility.", "")
57
+ rec = ELIGIBLE.get(profile.username.lower())
58
+ if rec is None:
59
+ return (gr.update(visible=False), gr.update(visible=True),
60
+ f"### Sorry @{profile.username} β€” no verified logbook found.\n"
61
+ "Certificates go to participants with at least one claim judged "
62
+ "**verified** by the Logbook Judge before the Aug 2 deadline. "
63
+ f"If you believe this is an error, please open a thread in the "
64
+ f"[challenge discussions]({DISCUSSIONS_URL}).", "")
65
+ return (gr.update(visible=True), gr.update(visible=False),
66
+ f"### πŸŽ‰ Congratulations @{profile.username} β€” you qualify!\n"
67
+ f"Your record: **{stats_line(rec)}** ({plural(rec['logbooks'], 'logbook')} judged). "
68
+ "Adjust how your name should appear, then generate your certificate.",
69
+ profile.name or profile.username)
70
+
71
+
72
+ def linkedin_url(name):
73
+ p = {
74
+ "startTask": "CERTIFICATION_NAME",
75
+ "name": "ICML 2026 Open Reproductions β€” Certificate of Participation",
76
+ "organizationName": "Hugging Face",
77
+ "issueYear": "2026", "issueMonth": "8",
78
+ "certUrl": "https://huggingface.co/spaces/ICML-2026-agent-repro/certificate-generator",
79
+ }
80
+ return "https://www.linkedin.com/profile/add?" + urllib.parse.urlencode(p)
81
+
82
+
83
+ def create_certificate(display_name, profile: gr.OAuthProfile | None):
84
+ if profile is None:
85
+ return None, "❌ Please sign in first.", gr.update(visible=False)
86
+ rec = ELIGIBLE.get(profile.username.lower())
87
+ if rec is None:
88
+ return None, "❌ No verified logbook found for your account.", gr.update(visible=False)
89
+ name = (display_name or "").strip() or profile.name or profile.username
90
+ html = TEMPLATE.replace("{participant_name}", name).replace("{stats_line}", stats_line(rec))
91
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".html", encoding="utf-8") as f:
92
+ f.write(html)
93
+ path = f.name
94
+ try:
95
+ image_path = get_renderer().predict(html_file=handle_file(path), api_name="/predict")[0]
96
+ except Exception as e:
97
+ return None, f"❌ Rendering failed: {e}. Please try again in a minute.", gr.update(visible=False)
98
+ btn = (f'<a href="{linkedin_url(name)}" target="_blank" '
99
+ f'style="display:inline-block;background:#0a66c2;color:#fff;font-weight:700;'
100
+ f'padding:10px 22px;border-radius:8px;text-decoration:none">Add to LinkedIn profile β†’</a>')
101
+ return image_path, "πŸŽ‰ Your certificate is ready β€” download it below.", gr.update(value=btn, visible=True)
102
+
103
+
104
+ THEME = gr.themes.Base(
105
+ primary_hue=gr.themes.colors.orange,
106
+ neutral_hue=gr.themes.colors.stone,
107
+ )
108
+
109
+ CSS = """
110
+ .gradio-container {
111
+ background-color: #fdfcf9 !important;
112
+ background-image:
113
+ linear-gradient(rgba(31,41,55,.045) 1px, transparent 1px),
114
+ linear-gradient(90deg, rgba(31,41,55,.045) 1px, transparent 1px);
115
+ background-size: 26px 26px;
116
+ }
117
+ #hero h1 { font-family: ui-serif, "Iowan Old Style", Georgia, serif; }
118
+ """
119
+
120
+ with gr.Blocks(title="ICML 2026 Open Reproductions β€” Certificates", theme=THEME, css=CSS) as demo:
121
+ gr.Markdown("# πŸŽ“ ICML 2026 Open Reproductions β€” Certificate of Participation", elem_id="hero")
122
+ gr.Markdown(
123
+ "Every participant with **at least one claim judged `verified`** in the challenge "
124
+ "(July 15 – August 2, 2026) can generate their certificate here."
125
+ )
126
+ login = gr.LoginButton()
127
+ status = gr.Markdown()
128
+ with gr.Column(visible=False) as main_box:
129
+ name_box = gr.Textbox(label="Name on the certificate")
130
+ gen_btn = gr.Button("Generate my certificate πŸŽ“", variant="primary")
131
+ cert_image = gr.Image(label="Your certificate", type="filepath", interactive=False)
132
+ note = gr.Markdown()
133
+ linkedin = gr.HTML(visible=False)
134
+ with gr.Column(visible=False) as contact_box:
135
+ pass
136
+
137
+ demo.load(on_load, inputs=None, outputs=[main_box, contact_box, status, name_box])
138
+ gen_btn.click(create_certificate, inputs=[name_box], outputs=[cert_image, note, linkedin])
139
+
140
+ demo.launch()