abidlabs HF Staff commited on
Commit
7008665
Β·
verified Β·
1 Parent(s): c8bd3fa

Fix renderer: in-Space chromium + partner logos (app.py)

Browse files
Files changed (1) hide show
  1. app.py +48 -23
app.py CHANGED
@@ -4,26 +4,62 @@
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 is rendered to PNG in-process
8
- (certificate.py) with the participant's verified-claim stats.
9
  """
10
 
11
  import json
12
  import os
 
 
13
  import tempfile
14
  import urllib.parse
15
 
16
  import gradio as gr
17
 
18
- from certificate import render_certificate
19
-
20
  DISCUSSIONS_URL = "https://huggingface.co/spaces/ICML-2026-agent-repro/challenge/discussions"
 
21
 
22
  _HERE = os.path.dirname(__file__)
 
 
23
  with open(os.path.join(_HERE, "eligible.json"), encoding="utf-8") as f:
24
  ELIGIBLE = json.load(f)
25
 
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def plural(n, word):
28
  return f"{n} {word}" + ("" if n == 1 else "s")
29
 
@@ -35,17 +71,17 @@ def stats_line(rec):
35
 
36
  def on_load(profile: gr.OAuthProfile | None):
37
  if profile is None:
38
- return (gr.update(visible=False), gr.update(visible=False),
39
  "### πŸ‘‹ Sign in with Hugging Face to check your eligibility.", "")
40
  rec = ELIGIBLE.get(profile.username.lower())
41
  if rec is None:
42
- return (gr.update(visible=False), gr.update(visible=True),
43
  f"### Sorry @{profile.username} β€” no verified logbook found.\n"
44
  "Certificates go to participants with at least one claim judged "
45
  "**verified** by the Logbook Judge before the Aug 2 deadline. "
46
  f"If you believe this is an error, please open a thread in the "
47
  f"[challenge discussions]({DISCUSSIONS_URL}).", "")
48
- return (gr.update(visible=True), gr.update(visible=False),
49
  f"### πŸŽ‰ Congratulations @{profile.username} β€” you qualify!\n"
50
  f"Your record: **{stats_line(rec)}** ({plural(rec['logbooks'], 'logbook')} judged). "
51
  "Adjust how your name should appear, then generate your certificate.",
@@ -70,11 +106,11 @@ def create_certificate(display_name, profile: gr.OAuthProfile | None):
70
  if rec is None:
71
  return None, "❌ No verified logbook found for your account.", gr.update(visible=False)
72
  name = (display_name or "").strip() or profile.name or profile.username
73
- out = os.path.join(tempfile.mkdtemp(), "icml-2026-certificate.png")
74
  try:
75
- image_path = render_certificate(name, stats_line(rec), out)
76
  except Exception as e:
77
- return None, f"❌ Rendering failed: {e}", gr.update(visible=False)
78
  btn = (f'<a href="{linkedin_url(name)}" target="_blank" '
79
  f'style="display:inline-block;background:#0a66c2;color:#fff;font-weight:700;'
80
  f'padding:10px 22px;border-radius:8px;text-decoration:none">Add to LinkedIn profile β†’</a>')
@@ -86,14 +122,6 @@ THEME = gr.themes.Base(
86
  neutral_hue=gr.themes.colors.stone,
87
  )
88
 
89
- # The page is a light "paper" design, so pin the dark-mode palette to the light
90
- # one. Without this, a visitor whose browser prefers dark mode gets dark-theme
91
- # text colors on the light background forced by the CSS below β€” near-invisible
92
- # headings and body copy.
93
- for _attr in list(vars(THEME)):
94
- if _attr.endswith("_dark") and hasattr(THEME, _attr[:-5]):
95
- setattr(THEME, _attr, getattr(THEME, _attr[:-5]))
96
-
97
  CSS = """
98
  .gradio-container {
99
  background-color: #fdfcf9 !important;
@@ -116,14 +144,11 @@ with gr.Blocks(title="ICML 2026 Open Reproductions β€” Certificates", theme=THEM
116
  with gr.Column(visible=False) as main_box:
117
  name_box = gr.Textbox(label="Name on the certificate")
118
  gen_btn = gr.Button("Generate my certificate πŸŽ“", variant="primary")
119
- cert_image = gr.Image(label="Your certificate", type="filepath",
120
- interactive=False, format="png")
121
  note = gr.Markdown()
122
  linkedin = gr.HTML(visible=False)
123
- with gr.Column(visible=False) as contact_box:
124
- pass
125
 
126
- demo.load(on_load, inputs=None, outputs=[main_box, contact_box, status, name_box])
127
  gen_btn.click(create_certificate, inputs=[name_box], outputs=[cert_image, note, linkedin])
128
 
129
  demo.launch()
 
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 to PNG with headless
8
+ Chromium (installed via packages.txt) β€” no external renderer dependency.
9
  """
10
 
11
  import json
12
  import os
13
+ import shutil
14
+ import subprocess
15
  import tempfile
16
  import urllib.parse
17
 
18
  import gradio as gr
19
 
 
 
20
  DISCUSSIONS_URL = "https://huggingface.co/spaces/ICML-2026-agent-repro/challenge/discussions"
21
+ CANVAS = (2000, 1414) # must match the body size in certificate_template.html
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
 
30
+ def find_chromium():
31
+ candidates = [
32
+ "chromium", "chromium-browser", "google-chrome", "google-chrome-stable",
33
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", # local dev
34
+ ]
35
+ for c in candidates:
36
+ path = shutil.which(c) or (c if os.path.exists(c) else None)
37
+ if path:
38
+ return path
39
+ raise RuntimeError("No Chromium binary found β€” is `chromium` in packages.txt?")
40
+
41
+
42
+ def render_html(html: str) -> str:
43
+ out = os.path.join(tempfile.mkdtemp(), "certificate.png")
44
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".html", encoding="utf-8") as f:
45
+ f.write(html)
46
+ src = f.name
47
+ try:
48
+ subprocess.run(
49
+ [find_chromium(), "--headless=new", "--no-sandbox", "--disable-gpu",
50
+ "--hide-scrollbars", "--force-device-scale-factor=1",
51
+ f"--window-size={CANVAS[0]},{CANVAS[1]}", f"--screenshot={out}",
52
+ f"file://{src}"],
53
+ check=True, capture_output=True, timeout=120,
54
+ )
55
+ finally:
56
+ try:
57
+ os.unlink(src)
58
+ except OSError:
59
+ pass
60
+ return out
61
+
62
+
63
  def plural(n, word):
64
  return f"{n} {word}" + ("" if n == 1 else "s")
65
 
 
71
 
72
  def on_load(profile: gr.OAuthProfile | None):
73
  if profile is None:
74
+ return (gr.update(visible=False),
75
  "### πŸ‘‹ Sign in with Hugging Face to check your eligibility.", "")
76
  rec = ELIGIBLE.get(profile.username.lower())
77
  if rec is None:
78
+ return (gr.update(visible=False),
79
  f"### Sorry @{profile.username} β€” no verified logbook found.\n"
80
  "Certificates go to participants with at least one claim judged "
81
  "**verified** by the Logbook Judge before the Aug 2 deadline. "
82
  f"If you believe this is an error, please open a thread in the "
83
  f"[challenge discussions]({DISCUSSIONS_URL}).", "")
84
+ return (gr.update(visible=True),
85
  f"### πŸŽ‰ Congratulations @{profile.username} β€” you qualify!\n"
86
  f"Your record: **{stats_line(rec)}** ({plural(rec['logbooks'], 'logbook')} judged). "
87
  "Adjust how your name should appear, then generate your certificate.",
 
106
  if rec is None:
107
  return None, "❌ No verified logbook found for your account.", gr.update(visible=False)
108
  name = (display_name or "").strip() or profile.name or profile.username
109
+ html = TEMPLATE.replace("{participant_name}", name).replace("{stats_line}", stats_line(rec))
110
  try:
111
+ image_path = render_html(html)
112
  except Exception as e:
113
+ return None, f"❌ Rendering failed: {e}. Please try again in a minute.", gr.update(visible=False)
114
  btn = (f'<a href="{linkedin_url(name)}" target="_blank" '
115
  f'style="display:inline-block;background:#0a66c2;color:#fff;font-weight:700;'
116
  f'padding:10px 22px;border-radius:8px;text-decoration:none">Add to LinkedIn profile β†’</a>')
 
122
  neutral_hue=gr.themes.colors.stone,
123
  )
124
 
 
 
 
 
 
 
 
 
125
  CSS = """
126
  .gradio-container {
127
  background-color: #fdfcf9 !important;
 
144
  with gr.Column(visible=False) as main_box:
145
  name_box = gr.Textbox(label="Name on the certificate")
146
  gen_btn = gr.Button("Generate my certificate πŸŽ“", variant="primary")
147
+ cert_image = gr.Image(label="Your certificate", type="filepath", interactive=False)
 
148
  note = gr.Markdown()
149
  linkedin = gr.HTML(visible=False)
 
 
150
 
151
+ demo.load(on_load, inputs=None, outputs=[main_box, status, name_box])
152
  gen_btn.click(create_certificate, inputs=[name_box], outputs=[cert_image, note, linkedin])
153
 
154
  demo.launch()