Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
|
| 2 |
"""
|
| 3 |
Judges Copilot Β· Gradio x Hugging Face Hackathon 2026
|
| 4 |
-
Single
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
import os, re, random
|
|
@@ -21,9 +22,11 @@ api = HfApi(token=HF_TOKEN)
|
|
| 21 |
CSS = """
|
| 22 |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
|
| 23 |
|
| 24 |
-
*, *::before, *::after { box-sizing: border-box;
|
| 25 |
|
| 26 |
-
body, .gradio-container, .gradio-container > div,
|
|
|
|
|
|
|
| 27 |
background: #0b0b18 !important;
|
| 28 |
font-family: 'Inter', sans-serif !important;
|
| 29 |
color: #d0d0ee !important;
|
|
@@ -38,7 +41,7 @@ body, .gradio-container, .gradio-container > div, .app {
|
|
| 38 |
|
| 39 |
footer, .footer { display: none !important; }
|
| 40 |
|
| 41 |
-
/* inputs */
|
| 42 |
textarea, input[type="text"] {
|
| 43 |
background: #13132a !important;
|
| 44 |
border: 1px solid #2a2a50 !important;
|
|
@@ -55,184 +58,200 @@ textarea:focus, input[type="text"]:focus {
|
|
| 55 |
textarea::placeholder { color: #404070 !important; }
|
| 56 |
label span, .label-wrap span {
|
| 57 |
color: #8080b0 !important;
|
| 58 |
-
font-size: .
|
| 59 |
-
font-weight:
|
| 60 |
}
|
| 61 |
|
| 62 |
-
/* button */
|
| 63 |
button.cta {
|
| 64 |
background: #F5A623 !important;
|
| 65 |
color: #0b0b18 !important;
|
| 66 |
-
font-weight:
|
| 67 |
font-size: 1rem !important;
|
| 68 |
border: none !important;
|
| 69 |
-
border-radius:
|
| 70 |
padding: 14px 40px !important;
|
| 71 |
width: 100% !important;
|
| 72 |
cursor: pointer !important;
|
|
|
|
| 73 |
transition: opacity .2s, transform .15s !important;
|
| 74 |
-
margin-top:
|
| 75 |
}
|
| 76 |
button.cta:hover { opacity: .88 !important; transform: translateY(-1px) !important; }
|
| 77 |
|
| 78 |
-
/* tabs */
|
| 79 |
.tab-nav button {
|
| 80 |
background: transparent !important;
|
| 81 |
color: #5858a0 !important;
|
| 82 |
border: none !important;
|
| 83 |
border-bottom: 2px solid transparent !important;
|
| 84 |
-
font-weight: 600 !important;
|
|
|
|
| 85 |
padding: 10px 16px !important;
|
| 86 |
}
|
| 87 |
.tab-nav button.selected { color: #F5A623 !important; border-bottom-color: #F5A623 !important; }
|
| 88 |
.tab-nav { border-bottom: 1px solid #1e1e3a !important; margin-bottom: 14px !important; }
|
| 89 |
.tabitem { background: transparent !important; padding: 0 !important; }
|
| 90 |
|
| 91 |
-
/* dataframe */
|
| 92 |
-
.dataframe table {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
.dataframe thead th {
|
| 94 |
-
background: #18183a !important;
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
}
|
| 99 |
.dataframe tbody td {
|
| 100 |
-
padding: 9px 13px !important;
|
| 101 |
-
|
|
|
|
|
|
|
| 102 |
}
|
| 103 |
.dataframe tbody tr:hover td { background: #141430 !important; }
|
| 104 |
.dataframe tbody tr:first-child td { color: #fff !important; font-weight: 600 !important; }
|
| 105 |
|
| 106 |
-
/* markdown */
|
| 107 |
-
.prose h2 { color: #fff !important; font-size: 1.3rem !important; margin: 1rem 0 .4rem !important; }
|
| 108 |
-
.prose h3 { color: #e8c870 !important; font-size: .
|
| 109 |
.prose strong { color: #F5A623 !important; }
|
| 110 |
.prose a { color: #80b0f0 !important; }
|
| 111 |
-
.prose blockquote {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
.prose hr { border: none !important; border-top: 1px solid #1e1e3a !important; }
|
| 113 |
.prose, .prose p, .prose li { color: #b8b8e0 !important; }
|
| 114 |
|
| 115 |
-
/* confetti */
|
| 116 |
@keyframes cffall {
|
| 117 |
-
0% { transform: translateY(-5vh) rotate(0deg);
|
| 118 |
85% { opacity: 1; }
|
| 119 |
100% { transform: translateY(105vh) rotate(600deg); opacity: 0; }
|
| 120 |
}
|
| 121 |
.cf {
|
| 122 |
-
position: fixed; top: 0;
|
|
|
|
| 123 |
animation: cffall linear forwards;
|
| 124 |
z-index: 99999; pointer-events: none;
|
| 125 |
}
|
| 126 |
"""
|
| 127 |
|
| 128 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 129 |
-
# Static HTML
|
| 130 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 131 |
HERO_HTML = """
|
| 132 |
<div style="
|
| 133 |
-
background:
|
| 134 |
-
text-align:
|
| 135 |
-
|
| 136 |
-
|
| 137 |
">
|
| 138 |
<div style="
|
| 139 |
display:inline-block;
|
| 140 |
-
background:rgba(245,166,35,.12);
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
padding:6px 18px;
|
| 145 |
-
font-size:.72rem; font-weight:700;
|
| 146 |
-
letter-spacing:.1em; text-transform:uppercase;
|
| 147 |
-
margin-bottom:24px;
|
| 148 |
">π Hackathon Judge's Co-Pilot</div>
|
| 149 |
|
| 150 |
<h1 style="
|
| 151 |
-
font-size:clamp(
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
margin:0 auto 18px; letter-spacing:-.01em;
|
| 155 |
-
font-family:'Inter',sans-serif;
|
| 156 |
">
|
| 157 |
You Are About To Judge<br>
|
| 158 |
<span style="color:#F5A623;">50+ Projects.</span>
|
| 159 |
</h1>
|
| 160 |
|
| 161 |
<p style="
|
| 162 |
-
font-size:
|
| 163 |
-
|
| 164 |
-
margin:0 auto 28px;
|
| 165 |
-
font-family:'Inter',sans-serif;
|
| 166 |
">
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
</p>
|
| 171 |
|
| 172 |
<div style="
|
| 173 |
background:#111128; border:1px solid #252548;
|
| 174 |
-
border-radius:14px 14px 14px 3px;
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
margin:0 auto; line-height:1.7;
|
| 178 |
-
text-align:left; font-family:'Inter',sans-serif;
|
| 179 |
">
|
| 180 |
-
π
|
| 181 |
-
|
| 182 |
</div>
|
| 183 |
</div>
|
| 184 |
"""
|
| 185 |
|
| 186 |
-
|
| 187 |
-
<div style="
|
| 188 |
-
|
| 189 |
-
font-size:.
|
| 190 |
-
letter-spacing:.
|
| 191 |
-
|
| 192 |
-
">
|
| 193 |
-
|
|
|
|
|
|
|
| 194 |
</div>
|
| 195 |
"""
|
| 196 |
|
| 197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
<div style="
|
| 199 |
-
|
| 200 |
-
font-size:.
|
| 201 |
letter-spacing:.1em; text-transform:uppercase;
|
| 202 |
-
|
| 203 |
-
">
|
| 204 |
-
π Results
|
| 205 |
-
</div>
|
| 206 |
"""
|
| 207 |
|
| 208 |
HANDOFF_HTML = """
|
| 209 |
<div style="
|
| 210 |
-
background:
|
| 211 |
-
border:
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
text-align: center;
|
| 215 |
-
margin-top: 24px;
|
| 216 |
-
font-family: 'Inter', sans-serif;
|
| 217 |
">
|
| 218 |
-
<div style="font-size:2.
|
| 219 |
<h3 style="
|
| 220 |
-
font-size:1.
|
| 221 |
-
|
| 222 |
">Now, We Are Left To Be Judged β Only By You.</h3>
|
| 223 |
-
<p style="font-size:.
|
| 224 |
We helped you judge everyone else.<br>
|
| 225 |
Now this tool itself is a submission,<br>
|
| 226 |
sitting in the same pile, waiting for the same verdict.<br><br>
|
| 227 |
-
<strong style="color:#fff;
|
| 228 |
</p>
|
| 229 |
</div>
|
| 230 |
"""
|
| 231 |
|
| 232 |
FOOTER_HTML = """
|
| 233 |
<div style="
|
| 234 |
-
text-align:center; padding:
|
| 235 |
-
font-size:.
|
| 236 |
border-top:1px solid #111128; margin-top:20px;
|
| 237 |
font-family:'Inter',sans-serif;
|
| 238 |
">
|
|
@@ -329,9 +348,9 @@ def fetch_repo_data(repo_type, repo_id):
|
|
| 329 |
data["error"] = f"Could not reach this {repo_type} ({type(exc).__name__})."
|
| 330 |
return data
|
| 331 |
try:
|
| 332 |
-
path = hf_hub_download(repo_id=repo_id,filename="README.md",
|
| 333 |
-
repo_type=repo_type,token=HF_TOKEN)
|
| 334 |
-
with open(path,encoding="utf-8",errors="replace") as fh:
|
| 335 |
data["readme"] = fh.read()
|
| 336 |
except Exception:
|
| 337 |
data["readme"] = ""
|
|
@@ -339,12 +358,12 @@ def fetch_repo_data(repo_type, repo_id):
|
|
| 339 |
|
| 340 |
def fetch_app_code(repo_type, repo_id, files):
|
| 341 |
if repo_type != "space": return ""
|
| 342 |
-
entry = next((f for f in files if f.lower() in ("app.py","main.py")),None)
|
| 343 |
if not entry: return ""
|
| 344 |
try:
|
| 345 |
-
path = hf_hub_download(repo_id=repo_id,filename=entry,
|
| 346 |
-
repo_type="space",token=HF_TOKEN)
|
| 347 |
-
with open(path,encoding="utf-8",errors="replace") as fh:
|
| 348 |
return fh.read()
|
| 349 |
except Exception:
|
| 350 |
return ""
|
|
@@ -404,16 +423,16 @@ def generate_justification(criterion, score, readme_lower, checklist):
|
|
| 404 |
if found_kw:
|
| 405 |
parts.append(f"README mentions _{', '.join(found_kw)}_")
|
| 406 |
else:
|
| 407 |
-
parts.append(f"no {criterion.lower()} keywords
|
| 408 |
present = [c for c in sig["checks"] if checklist.get(c)]
|
| 409 |
missing = [c for c in sig["checks"] if not checklist.get(c)]
|
| 410 |
if present: parts.append("β
" + ", ".join(present))
|
| 411 |
if missing: parts.append("β missing " + ", ".join(missing))
|
| 412 |
else:
|
| 413 |
words = re.findall(r"[a-z]{4,}", criterion.lower())
|
| 414 |
-
hits
|
| 415 |
parts.append(f"found: _{', '.join(hits[:3])}_" if hits else "no matching keywords")
|
| 416 |
-
if len(readme_lower) > 1500: parts.append("well-documented")
|
| 417 |
elif len(readme_lower) < 100: parts.append("very sparse README")
|
| 418 |
return " Β· ".join(parts) if parts else "scored on README signals"
|
| 419 |
|
|
@@ -442,12 +461,13 @@ def analyze_submission(raw, criteria_list):
|
|
| 442 |
title_m = re.search(r"^#\s+(.+)$", body, re.MULTILINE)
|
| 443 |
title = title_m.group(1).strip() if title_m else repo_id
|
| 444 |
|
| 445 |
-
|
| 446 |
for ln in body.splitlines():
|
| 447 |
ln = ln.strip()
|
| 448 |
-
if not ln or ln.startswith("#") or ln.startswith("![") or ln.startswith("[!["):
|
| 449 |
-
|
| 450 |
-
|
|
|
|
| 451 |
|
| 452 |
readme_lower = readme.lower()
|
| 453 |
checklist = {
|
|
@@ -482,19 +502,72 @@ def make_confetti():
|
|
| 482 |
l = random.randint(1,99)
|
| 483 |
d = round(random.uniform(0,2.2),2)
|
| 484 |
dur = round(random.uniform(2.5,5),2)
|
| 485 |
-
pieces.append(
|
|
|
|
|
|
|
|
|
|
| 486 |
return "<div>" + "".join(pieces) + "</div>"
|
| 487 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
def build_leaderboard(results, criteria_list):
|
| 489 |
-
cols = ["π
|
| 490 |
rows = []
|
| 491 |
-
|
| 492 |
-
|
|
|
|
| 493 |
row += [r["scores"].get(c,"-") for c in criteria_list]
|
| 494 |
row.append(r["likes"])
|
| 495 |
rows.append(row)
|
| 496 |
return pd.DataFrame(rows, columns=cols)
|
| 497 |
|
|
|
|
| 498 |
def build_criteria_table(results, criteria_list):
|
| 499 |
ok = [r for r in results if not r["error"]]
|
| 500 |
if not ok: return pd.DataFrame()
|
|
@@ -508,35 +581,15 @@ def build_criteria_table(results, criteria_list):
|
|
| 508 |
rows.append(row)
|
| 509 |
return pd.DataFrame(rows, columns=cols)
|
| 510 |
|
| 511 |
-
def build_winners(results, criteria_list):
|
| 512 |
-
ok = [r for r in results if not r["error"]]
|
| 513 |
-
if len(ok) < 2:
|
| 514 |
-
return "_Need at least 2 valid submissions to show category winners._"
|
| 515 |
-
medals = ["π₯","π₯","π₯"]
|
| 516 |
-
lines = ["## π
Category Winners\n"]
|
| 517 |
-
for c in criteria_list:
|
| 518 |
-
ranked = sorted(ok, key=lambda r: r["scores"].get(c,0), reverse=True)
|
| 519 |
-
lines.append(f"### {c}")
|
| 520 |
-
for i,r in enumerate(ranked[:3]):
|
| 521 |
-
s = r["scores"].get(c,0)
|
| 522 |
-
j = r["justifications"].get(c,"")
|
| 523 |
-
lines.append(f"{medals[i]} **{r['title']}** β {s}/10 \n _{j}_")
|
| 524 |
-
lines.append("")
|
| 525 |
-
return "\n".join(lines)
|
| 526 |
|
| 527 |
def build_briefing(results, criteria_list):
|
| 528 |
-
lines = [
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
lines.append(f"### π₯ Top Pick: [{top['title']}]({top['url']}) β **{top['overall']}/10**")
|
| 536 |
-
lines.append(f"> {top['summary']}\n")
|
| 537 |
-
lines.append("---\n")
|
| 538 |
-
for i,r in enumerate(results,1):
|
| 539 |
-
lines.append(f"### {i}. {r['title']}")
|
| 540 |
if r["url"]: lines.append(f"π [{r['url']}]({r['url']})")
|
| 541 |
if r["error"]:
|
| 542 |
lines.append(f"β οΈ _{r['summary']}_\n")
|
|
@@ -555,14 +608,16 @@ def build_briefing(results, criteria_list):
|
|
| 555 |
lines.append("\n---\n")
|
| 556 |
return "\n\n".join(lines)
|
| 557 |
|
|
|
|
| 558 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 559 |
# Main function
|
| 560 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 561 |
def run_analysis(urls_text, criteria_text):
|
| 562 |
-
empty = ("
|
| 563 |
-
|
| 564 |
if not urls_text or not urls_text.strip():
|
| 565 |
-
return
|
|
|
|
| 566 |
|
| 567 |
criteria_list = [c.strip() for c in re.split(r"[,\n]", criteria_text or "") if c.strip()]
|
| 568 |
if not criteria_list:
|
|
@@ -572,7 +627,10 @@ def run_analysis(urls_text, criteria_text):
|
|
| 572 |
for line in urls_text.splitlines():
|
| 573 |
line = line.strip()
|
| 574 |
if not line: continue
|
| 575 |
-
is_listing = (
|
|
|
|
|
|
|
|
|
|
| 576 |
if is_listing:
|
| 577 |
for sid in discover_from_hackathon_url(line):
|
| 578 |
if sid not in seen:
|
|
@@ -582,61 +640,67 @@ def run_analysis(urls_text, criteria_text):
|
|
| 582 |
seen.add(line); targets.append(line)
|
| 583 |
|
| 584 |
if not targets:
|
| 585 |
-
return ("### π No valid links found. Paste Space URLs or a hackathon tag page.",
|
| 586 |
-
pd.DataFrame(), pd.DataFrame(), ""
|
| 587 |
|
| 588 |
try:
|
| 589 |
results = [analyze_submission(t, criteria_list) for t in targets]
|
| 590 |
results.sort(key=lambda r: r["overall"], reverse=True)
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
|
|
|
|
|
|
| 598 |
except Exception as exc:
|
| 599 |
-
return (f"### β οΈ Error: `{exc}`", pd.DataFrame(), pd.DataFrame(), ""
|
|
|
|
| 600 |
|
| 601 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 602 |
-
# UI
|
| 603 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 604 |
with gr.Blocks(theme=gr.themes.Base(), css=CSS, title="Judges Copilot") as demo:
|
| 605 |
|
| 606 |
gr.HTML(HERO_HTML)
|
| 607 |
-
gr.HTML(DIVIDER_HTML)
|
| 608 |
|
| 609 |
# ββ Inputs βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
|
|
|
|
|
|
|
|
|
| 626 |
|
| 627 |
-
# ββ Confetti
|
| 628 |
confetti_out = gr.HTML(value="")
|
| 629 |
|
| 630 |
-
# ββ
|
| 631 |
-
gr.HTML(
|
|
|
|
|
|
|
|
|
|
| 632 |
|
| 633 |
with gr.Tabs():
|
| 634 |
-
with gr.Tab("
|
| 635 |
-
leaderboard_out = gr.Dataframe(label="
|
| 636 |
with gr.Tab("π― Criteria Breakdown"):
|
| 637 |
criteria_out = gr.Dataframe(label="Pass / Fail per Criterion (β
β₯7 Β· π‘ 5-6 Β· β <5)")
|
| 638 |
-
with gr.Tab("π Category Winners"):
|
| 639 |
-
winners_out = gr.Markdown(value="")
|
| 640 |
|
| 641 |
briefing_out = gr.Markdown(value="")
|
| 642 |
|
|
@@ -647,7 +711,7 @@ with gr.Blocks(theme=gr.themes.Base(), css=CSS, title="Judges Copilot") as demo:
|
|
| 647 |
analyse_btn.click(
|
| 648 |
fn=run_analysis,
|
| 649 |
inputs=[urls_box, criteria_box],
|
| 650 |
-
outputs=[briefing_out, leaderboard_out, criteria_out,
|
| 651 |
)
|
| 652 |
|
| 653 |
if __name__ == "__main__":
|
|
|
|
| 1 |
|
| 2 |
"""
|
| 3 |
Judges Copilot Β· Gradio x Hugging Face Hackathon 2026
|
| 4 |
+
Single page. Paste a hackathon page URL or individual links.
|
| 5 |
+
Add your criteria. Get 1st / 2nd / 3rd with full reasons.
|
| 6 |
"""
|
| 7 |
|
| 8 |
import os, re, random
|
|
|
|
| 22 |
CSS = """
|
| 23 |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
|
| 24 |
|
| 25 |
+
*, *::before, *::after { box-sizing: border-box; }
|
| 26 |
|
| 27 |
+
body, .gradio-container, .gradio-container > div,
|
| 28 |
+
.gradio-container .wrap, .gradio-container .gap,
|
| 29 |
+
.gradio-container .contain, .app {
|
| 30 |
background: #0b0b18 !important;
|
| 31 |
font-family: 'Inter', sans-serif !important;
|
| 32 |
color: #d0d0ee !important;
|
|
|
|
| 41 |
|
| 42 |
footer, .footer { display: none !important; }
|
| 43 |
|
| 44 |
+
/* ββ inputs ββ */
|
| 45 |
textarea, input[type="text"] {
|
| 46 |
background: #13132a !important;
|
| 47 |
border: 1px solid #2a2a50 !important;
|
|
|
|
| 58 |
textarea::placeholder { color: #404070 !important; }
|
| 59 |
label span, .label-wrap span {
|
| 60 |
color: #8080b0 !important;
|
| 61 |
+
font-size: .84rem !important;
|
| 62 |
+
font-weight: 600 !important;
|
| 63 |
}
|
| 64 |
|
| 65 |
+
/* ββ analyse button ββ */
|
| 66 |
button.cta {
|
| 67 |
background: #F5A623 !important;
|
| 68 |
color: #0b0b18 !important;
|
| 69 |
+
font-weight: 800 !important;
|
| 70 |
font-size: 1rem !important;
|
| 71 |
border: none !important;
|
| 72 |
+
border-radius: 12px !important;
|
| 73 |
padding: 14px 40px !important;
|
| 74 |
width: 100% !important;
|
| 75 |
cursor: pointer !important;
|
| 76 |
+
letter-spacing: .02em !important;
|
| 77 |
transition: opacity .2s, transform .15s !important;
|
| 78 |
+
margin-top: 6px !important;
|
| 79 |
}
|
| 80 |
button.cta:hover { opacity: .88 !important; transform: translateY(-1px) !important; }
|
| 81 |
|
| 82 |
+
/* ββ tabs ββ */
|
| 83 |
.tab-nav button {
|
| 84 |
background: transparent !important;
|
| 85 |
color: #5858a0 !important;
|
| 86 |
border: none !important;
|
| 87 |
border-bottom: 2px solid transparent !important;
|
| 88 |
+
font-weight: 600 !important;
|
| 89 |
+
font-size: .87rem !important;
|
| 90 |
padding: 10px 16px !important;
|
| 91 |
}
|
| 92 |
.tab-nav button.selected { color: #F5A623 !important; border-bottom-color: #F5A623 !important; }
|
| 93 |
.tab-nav { border-bottom: 1px solid #1e1e3a !important; margin-bottom: 14px !important; }
|
| 94 |
.tabitem { background: transparent !important; padding: 0 !important; }
|
| 95 |
|
| 96 |
+
/* ββ dataframe ββ */
|
| 97 |
+
.dataframe table {
|
| 98 |
+
background: #0f0f26 !important;
|
| 99 |
+
border-collapse: collapse !important;
|
| 100 |
+
width: 100% !important;
|
| 101 |
+
}
|
| 102 |
.dataframe thead th {
|
| 103 |
+
background: #18183a !important;
|
| 104 |
+
color: #F5A623 !important;
|
| 105 |
+
padding: 10px 13px !important;
|
| 106 |
+
font-size: .78rem !important;
|
| 107 |
+
font-weight: 700 !important;
|
| 108 |
+
letter-spacing: .05em !important;
|
| 109 |
+
text-transform: uppercase !important;
|
| 110 |
+
border-bottom: 2px solid #252548 !important;
|
| 111 |
}
|
| 112 |
.dataframe tbody td {
|
| 113 |
+
padding: 9px 13px !important;
|
| 114 |
+
border-bottom: 1px solid #18183a !important;
|
| 115 |
+
font-size: .82rem !important;
|
| 116 |
+
color: #b8b8e0 !important;
|
| 117 |
}
|
| 118 |
.dataframe tbody tr:hover td { background: #141430 !important; }
|
| 119 |
.dataframe tbody tr:first-child td { color: #fff !important; font-weight: 600 !important; }
|
| 120 |
|
| 121 |
+
/* ββ markdown ββ */
|
| 122 |
+
.prose h2 { color: #fff !important; font-size: 1.3rem !important; margin: 1.1rem 0 .4rem !important; }
|
| 123 |
+
.prose h3 { color: #e8c870 !important; font-size: .97rem !important; margin: 1rem 0 .3rem !important; }
|
| 124 |
.prose strong { color: #F5A623 !important; }
|
| 125 |
.prose a { color: #80b0f0 !important; }
|
| 126 |
+
.prose blockquote {
|
| 127 |
+
border-left: 3px solid #F5A623 !important;
|
| 128 |
+
padding-left: 12px !important;
|
| 129 |
+
color: #8888b8 !important;
|
| 130 |
+
}
|
| 131 |
.prose hr { border: none !important; border-top: 1px solid #1e1e3a !important; }
|
| 132 |
.prose, .prose p, .prose li { color: #b8b8e0 !important; }
|
| 133 |
|
| 134 |
+
/* ββ confetti ββ */
|
| 135 |
@keyframes cffall {
|
| 136 |
+
0% { transform: translateY(-5vh) rotate(0deg); opacity: 1; }
|
| 137 |
85% { opacity: 1; }
|
| 138 |
100% { transform: translateY(105vh) rotate(600deg); opacity: 0; }
|
| 139 |
}
|
| 140 |
.cf {
|
| 141 |
+
position: fixed; top: 0;
|
| 142 |
+
font-size: 1.5rem;
|
| 143 |
animation: cffall linear forwards;
|
| 144 |
z-index: 99999; pointer-events: none;
|
| 145 |
}
|
| 146 |
"""
|
| 147 |
|
| 148 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 149 |
+
# Static HTML
|
| 150 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 151 |
HERO_HTML = """
|
| 152 |
<div style="
|
| 153 |
+
background:linear-gradient(180deg,#0b0b18 0%,#10102a 100%);
|
| 154 |
+
text-align:center; padding:56px 24px 44px;
|
| 155 |
+
border-bottom:1px solid #1a1a34;
|
| 156 |
+
font-family:'Inter',sans-serif;
|
| 157 |
">
|
| 158 |
<div style="
|
| 159 |
display:inline-block;
|
| 160 |
+
background:rgba(245,166,35,.12); color:#F5A623;
|
| 161 |
+
border:1px solid rgba(245,166,35,.3); border-radius:100px;
|
| 162 |
+
padding:6px 18px; font-size:.71rem; font-weight:700;
|
| 163 |
+
letter-spacing:.1em; text-transform:uppercase; margin-bottom:22px;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
">π Hackathon Judge's Co-Pilot</div>
|
| 165 |
|
| 166 |
<h1 style="
|
| 167 |
+
font-size:clamp(1.9rem,5.5vw,3.6rem); font-weight:800;
|
| 168 |
+
line-height:1.08; color:#fff; max-width:660px;
|
| 169 |
+
margin:0 auto 16px; letter-spacing:-.01em;
|
|
|
|
|
|
|
| 170 |
">
|
| 171 |
You Are About To Judge<br>
|
| 172 |
<span style="color:#F5A623;">50+ Projects.</span>
|
| 173 |
</h1>
|
| 174 |
|
| 175 |
<p style="
|
| 176 |
+
font-size:.97rem; color:#686898; max-width:450px;
|
| 177 |
+
line-height:1.8; margin:0 auto 24px;
|
|
|
|
|
|
|
| 178 |
">
|
| 179 |
+
Paste the hackathon submissions page β or individual links.
|
| 180 |
+
Set your criteria. Get a full ranked briefing with
|
| 181 |
+
1st, 2nd, 3rd and a reason for every score.
|
| 182 |
</p>
|
| 183 |
|
| 184 |
<div style="
|
| 185 |
background:#111128; border:1px solid #252548;
|
| 186 |
+
border-radius:14px 14px 14px 3px; padding:14px 20px;
|
| 187 |
+
font-size:.87rem; color:#9090c0; max-width:370px;
|
| 188 |
+
margin:0 auto; line-height:1.7; text-align:left;
|
|
|
|
|
|
|
| 189 |
">
|
| 190 |
+
π We read the READMEs. We check the files.
|
| 191 |
+
We score every criterion. You just decide.
|
| 192 |
</div>
|
| 193 |
</div>
|
| 194 |
"""
|
| 195 |
|
| 196 |
+
INPUT_HEADER_HTML = """
|
| 197 |
+
<div style="font-family:'Inter',sans-serif; padding:30px 0 6px;">
|
| 198 |
+
<p style="
|
| 199 |
+
font-size:.71rem; color:#F5A623; font-weight:700;
|
| 200 |
+
letter-spacing:.1em; text-transform:uppercase; margin-bottom:6px;
|
| 201 |
+
">Step 1 β Links</p>
|
| 202 |
+
<p style="font-size:.84rem; color:#5858a0; line-height:1.6; margin:0;">
|
| 203 |
+
Paste the <strong style="color:#c0c0e8;">hackathon submissions page URL</strong>
|
| 204 |
+
(auto-discovers all entries) β or paste individual Space links, one per line.
|
| 205 |
+
</p>
|
| 206 |
</div>
|
| 207 |
"""
|
| 208 |
|
| 209 |
+
CRITERIA_HEADER_HTML = """
|
| 210 |
+
<div style="font-family:'Inter',sans-serif; padding:20px 0 6px;">
|
| 211 |
+
<p style="
|
| 212 |
+
font-size:.71rem; color:#F5A623; font-weight:700;
|
| 213 |
+
letter-spacing:.1em; text-transform:uppercase; margin-bottom:6px;
|
| 214 |
+
">Step 2 β Your Criteria</p>
|
| 215 |
+
<p style="font-size:.84rem; color:#5858a0; line-height:1.6; margin:0;">
|
| 216 |
+
Add, remove, or rename any criteria β these become your scoring rubric.
|
| 217 |
+
</p>
|
| 218 |
+
</div>
|
| 219 |
+
"""
|
| 220 |
+
|
| 221 |
+
RESULTS_LABEL_HTML = """
|
| 222 |
<div style="
|
| 223 |
+
font-family:'Inter',sans-serif;
|
| 224 |
+
font-size:.71rem; color:#F5A623; font-weight:700;
|
| 225 |
letter-spacing:.1em; text-transform:uppercase;
|
| 226 |
+
padding:28px 0 10px;
|
| 227 |
+
">π Results</div>
|
|
|
|
|
|
|
| 228 |
"""
|
| 229 |
|
| 230 |
HANDOFF_HTML = """
|
| 231 |
<div style="
|
| 232 |
+
background:linear-gradient(135deg,#0f0f26,#180f28);
|
| 233 |
+
border:1px solid rgba(245,166,35,.2); border-radius:16px;
|
| 234 |
+
padding:30px 24px; text-align:center; margin-top:24px;
|
| 235 |
+
font-family:'Inter',sans-serif;
|
|
|
|
|
|
|
|
|
|
| 236 |
">
|
| 237 |
+
<div style="font-size:2.4rem;margin-bottom:10px;">π</div>
|
| 238 |
<h3 style="
|
| 239 |
+
font-size:1.25rem; font-weight:800; color:#fff;
|
| 240 |
+
margin-bottom:12px; letter-spacing:-.01em;
|
| 241 |
">Now, We Are Left To Be Judged β Only By You.</h3>
|
| 242 |
+
<p style="font-size:.9rem; color:#686898; line-height:1.85; max-width:420px; margin:0 auto;">
|
| 243 |
We helped you judge everyone else.<br>
|
| 244 |
Now this tool itself is a submission,<br>
|
| 245 |
sitting in the same pile, waiting for the same verdict.<br><br>
|
| 246 |
+
<strong style="color:#fff;">The judge is you.</strong>
|
| 247 |
</p>
|
| 248 |
</div>
|
| 249 |
"""
|
| 250 |
|
| 251 |
FOOTER_HTML = """
|
| 252 |
<div style="
|
| 253 |
+
text-align:center; padding:22px 16px;
|
| 254 |
+
font-size:.74rem; color:#2e2e58;
|
| 255 |
border-top:1px solid #111128; margin-top:20px;
|
| 256 |
font-family:'Inter',sans-serif;
|
| 257 |
">
|
|
|
|
| 348 |
data["error"] = f"Could not reach this {repo_type} ({type(exc).__name__})."
|
| 349 |
return data
|
| 350 |
try:
|
| 351 |
+
path = hf_hub_download(repo_id=repo_id, filename="README.md",
|
| 352 |
+
repo_type=repo_type, token=HF_TOKEN)
|
| 353 |
+
with open(path, encoding="utf-8", errors="replace") as fh:
|
| 354 |
data["readme"] = fh.read()
|
| 355 |
except Exception:
|
| 356 |
data["readme"] = ""
|
|
|
|
| 358 |
|
| 359 |
def fetch_app_code(repo_type, repo_id, files):
|
| 360 |
if repo_type != "space": return ""
|
| 361 |
+
entry = next((f for f in files if f.lower() in ("app.py","main.py")), None)
|
| 362 |
if not entry: return ""
|
| 363 |
try:
|
| 364 |
+
path = hf_hub_download(repo_id=repo_id, filename=entry,
|
| 365 |
+
repo_type="space", token=HF_TOKEN)
|
| 366 |
+
with open(path, encoding="utf-8", errors="replace") as fh:
|
| 367 |
return fh.read()
|
| 368 |
except Exception:
|
| 369 |
return ""
|
|
|
|
| 423 |
if found_kw:
|
| 424 |
parts.append(f"README mentions _{', '.join(found_kw)}_")
|
| 425 |
else:
|
| 426 |
+
parts.append(f"no {criterion.lower()} keywords found")
|
| 427 |
present = [c for c in sig["checks"] if checklist.get(c)]
|
| 428 |
missing = [c for c in sig["checks"] if not checklist.get(c)]
|
| 429 |
if present: parts.append("β
" + ", ".join(present))
|
| 430 |
if missing: parts.append("β missing " + ", ".join(missing))
|
| 431 |
else:
|
| 432 |
words = re.findall(r"[a-z]{4,}", criterion.lower())
|
| 433 |
+
hits = [w for w in words if w in readme_lower]
|
| 434 |
parts.append(f"found: _{', '.join(hits[:3])}_" if hits else "no matching keywords")
|
| 435 |
+
if len(readme_lower) > 1500: parts.append("well-documented README")
|
| 436 |
elif len(readme_lower) < 100: parts.append("very sparse README")
|
| 437 |
return " Β· ".join(parts) if parts else "scored on README signals"
|
| 438 |
|
|
|
|
| 461 |
title_m = re.search(r"^#\s+(.+)$", body, re.MULTILINE)
|
| 462 |
title = title_m.group(1).strip() if title_m else repo_id
|
| 463 |
|
| 464 |
+
content_lines = []
|
| 465 |
for ln in body.splitlines():
|
| 466 |
ln = ln.strip()
|
| 467 |
+
if not ln or ln.startswith("#") or ln.startswith("![") or ln.startswith("[!["):
|
| 468 |
+
continue
|
| 469 |
+
content_lines.append(ln)
|
| 470 |
+
summary = re.sub(r"\s+"," "," ".join(content_lines))[:300] if content_lines else "No description found."
|
| 471 |
|
| 472 |
readme_lower = readme.lower()
|
| 473 |
checklist = {
|
|
|
|
| 502 |
l = random.randint(1,99)
|
| 503 |
d = round(random.uniform(0,2.2),2)
|
| 504 |
dur = round(random.uniform(2.5,5),2)
|
| 505 |
+
pieces.append(
|
| 506 |
+
f'<span class="cf" style="left:{l}%;'
|
| 507 |
+
f'animation-duration:{dur}s;animation-delay:{d}s">{e}</span>'
|
| 508 |
+
)
|
| 509 |
return "<div>" + "".join(pieces) + "</div>"
|
| 510 |
|
| 511 |
+
|
| 512 |
+
def build_podium_html(results):
|
| 513 |
+
"""Big 1st / 2nd / 3rd winner cards at the top of results."""
|
| 514 |
+
ok = [r for r in results if not r["error"]]
|
| 515 |
+
if not ok:
|
| 516 |
+
return ""
|
| 517 |
+
|
| 518 |
+
medals = [("π₯","1st","#F5A623","rgba(245,166,35,.15)"),
|
| 519 |
+
("π₯","2nd","#b0b8d0","rgba(176,184,208,.12)"),
|
| 520 |
+
("π₯","3rd","#cd7f32","rgba(205,127,50,.12)")]
|
| 521 |
+
cards = []
|
| 522 |
+
for i, r in enumerate(ok[:3]):
|
| 523 |
+
emoji, place, color, bg = medals[i]
|
| 524 |
+
cards.append(f"""
|
| 525 |
+
<div style="
|
| 526 |
+
background:{bg}; border:1px solid {color}44;
|
| 527 |
+
border-radius:14px; padding:20px 16px; text-align:center;
|
| 528 |
+
font-family:'Inter',sans-serif; flex:1; min-width:160px;
|
| 529 |
+
">
|
| 530 |
+
<div style="font-size:2rem;margin-bottom:6px;">{emoji}</div>
|
| 531 |
+
<div style="font-size:.7rem;color:{color};font-weight:700;
|
| 532 |
+
letter-spacing:.1em;text-transform:uppercase;margin-bottom:8px;">
|
| 533 |
+
{place} Place
|
| 534 |
+
</div>
|
| 535 |
+
<div style="font-size:.95rem;font-weight:700;color:#fff;
|
| 536 |
+
margin-bottom:6px;line-height:1.3;">
|
| 537 |
+
{r["title"]}
|
| 538 |
+
</div>
|
| 539 |
+
<div style="font-size:1.3rem;font-weight:800;color:{color};">
|
| 540 |
+
{r["overall"]}/10
|
| 541 |
+
</div>
|
| 542 |
+
{"<a href='" + r['url'] + "' target='_blank' style='font-size:.75rem;color:#6080c0;'>View β</a>" if r["url"] else ""}
|
| 543 |
+
</div>
|
| 544 |
+
""")
|
| 545 |
+
|
| 546 |
+
return f"""
|
| 547 |
+
<div style="padding:24px 0 8px;font-family:'Inter',sans-serif;">
|
| 548 |
+
<div style="font-size:.71rem;color:#F5A623;font-weight:700;
|
| 549 |
+
letter-spacing:.1em;text-transform:uppercase;margin-bottom:14px;">
|
| 550 |
+
π Final Rankings
|
| 551 |
+
</div>
|
| 552 |
+
<div style="display:flex;gap:12px;flex-wrap:wrap;">
|
| 553 |
+
{"".join(cards)}
|
| 554 |
+
</div>
|
| 555 |
+
</div>
|
| 556 |
+
"""
|
| 557 |
+
|
| 558 |
+
|
| 559 |
def build_leaderboard(results, criteria_list):
|
| 560 |
+
cols = ["π
","Project","β Overall"] + criteria_list + ["β€οΈ Likes"]
|
| 561 |
rows = []
|
| 562 |
+
medals = ["π₯","π₯","π₯"]
|
| 563 |
+
for i, r in enumerate(results,1):
|
| 564 |
+
row = [medals[i-1] if i <= 3 else str(i), r["title"], f"{r['overall']}/10"]
|
| 565 |
row += [r["scores"].get(c,"-") for c in criteria_list]
|
| 566 |
row.append(r["likes"])
|
| 567 |
rows.append(row)
|
| 568 |
return pd.DataFrame(rows, columns=cols)
|
| 569 |
|
| 570 |
+
|
| 571 |
def build_criteria_table(results, criteria_list):
|
| 572 |
ok = [r for r in results if not r["error"]]
|
| 573 |
if not ok: return pd.DataFrame()
|
|
|
|
| 581 |
rows.append(row)
|
| 582 |
return pd.DataFrame(rows, columns=cols)
|
| 583 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
|
| 585 |
def build_briefing(results, criteria_list):
|
| 586 |
+
lines = ["## π Full Judge's Briefing",
|
| 587 |
+
f"_Analysed **{len(results)}** submission(s) Β· **{len(criteria_list)}** criteria_\n",
|
| 588 |
+
"---\n"]
|
| 589 |
+
for i, r in enumerate(results,1):
|
| 590 |
+
medals = {1:"π₯",2:"π₯",3:"π₯"}
|
| 591 |
+
prefix = medals.get(i, f"**#{i}**")
|
| 592 |
+
lines.append(f"### {prefix} {r['title']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
if r["url"]: lines.append(f"π [{r['url']}]({r['url']})")
|
| 594 |
if r["error"]:
|
| 595 |
lines.append(f"β οΈ _{r['summary']}_\n")
|
|
|
|
| 608 |
lines.append("\n---\n")
|
| 609 |
return "\n\n".join(lines)
|
| 610 |
|
| 611 |
+
|
| 612 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 613 |
# Main function
|
| 614 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 615 |
def run_analysis(urls_text, criteria_text):
|
| 616 |
+
empty = ("", "", pd.DataFrame(), pd.DataFrame(), "")
|
| 617 |
+
|
| 618 |
if not urls_text or not urls_text.strip():
|
| 619 |
+
return ("### π Paste links and hit Analyse to get started.",
|
| 620 |
+
"", pd.DataFrame(), pd.DataFrame(), "")
|
| 621 |
|
| 622 |
criteria_list = [c.strip() for c in re.split(r"[,\n]", criteria_text or "") if c.strip()]
|
| 623 |
if not criteria_list:
|
|
|
|
| 627 |
for line in urls_text.splitlines():
|
| 628 |
line = line.strip()
|
| 629 |
if not line: continue
|
| 630 |
+
is_listing = (
|
| 631 |
+
("huggingface.co" in line and "?" in line and "/spaces/" not in line)
|
| 632 |
+
or "?tag=" in line
|
| 633 |
+
)
|
| 634 |
if is_listing:
|
| 635 |
for sid in discover_from_hackathon_url(line):
|
| 636 |
if sid not in seen:
|
|
|
|
| 640 |
seen.add(line); targets.append(line)
|
| 641 |
|
| 642 |
if not targets:
|
| 643 |
+
return ("### π No valid links found. Paste Space URLs or a hackathon tag page URL.",
|
| 644 |
+
"", pd.DataFrame(), pd.DataFrame(), "")
|
| 645 |
|
| 646 |
try:
|
| 647 |
results = [analyze_submission(t, criteria_list) for t in targets]
|
| 648 |
results.sort(key=lambda r: r["overall"], reverse=True)
|
| 649 |
+
|
| 650 |
+
podium_html = build_podium_html(results)
|
| 651 |
+
briefing_md = build_briefing(results, criteria_list)
|
| 652 |
+
leaderboard = build_leaderboard(results, criteria_list)
|
| 653 |
+
criteria_df = build_criteria_table(results, criteria_list)
|
| 654 |
+
confetti = make_confetti() if any(not r["error"] for r in results) else ""
|
| 655 |
+
|
| 656 |
+
return briefing_md, podium_html, leaderboard, criteria_df, confetti
|
| 657 |
+
|
| 658 |
except Exception as exc:
|
| 659 |
+
return (f"### β οΈ Error: `{exc}`", "", pd.DataFrame(), pd.DataFrame(), "")
|
| 660 |
+
|
| 661 |
|
| 662 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 663 |
+
# UI
|
| 664 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 665 |
with gr.Blocks(theme=gr.themes.Base(), css=CSS, title="Judges Copilot") as demo:
|
| 666 |
|
| 667 |
gr.HTML(HERO_HTML)
|
|
|
|
| 668 |
|
| 669 |
# ββ Inputs βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 670 |
+
gr.HTML(INPUT_HEADER_HTML)
|
| 671 |
+
urls_box = gr.Textbox(
|
| 672 |
+
label="Hackathon submissions page URL β or individual Space links (one per line)",
|
| 673 |
+
placeholder=(
|
| 674 |
+
"Option A (auto-discovers all): https://huggingface.co/spaces?tag=gradio-hackathon-2026\n"
|
| 675 |
+
"Option B (individual): https://huggingface.co/spaces/username/project-name\n"
|
| 676 |
+
" another-user/another-space"
|
| 677 |
+
),
|
| 678 |
+
lines=5,
|
| 679 |
+
)
|
| 680 |
+
|
| 681 |
+
gr.HTML(CRITERIA_HEADER_HTML)
|
| 682 |
+
criteria_box = gr.Textbox(
|
| 683 |
+
label="Your judging criteria β edit, add, or remove any (comma-separated)",
|
| 684 |
+
value="Innovation, Technical Execution, Usability & Design, Real-World Impact, Presentation",
|
| 685 |
+
lines=3,
|
| 686 |
+
)
|
| 687 |
+
|
| 688 |
+
analyse_btn = gr.Button("β‘ Analyse & Rank Submissions", elem_classes=["cta"])
|
| 689 |
|
| 690 |
+
# ββ Confetti βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 691 |
confetti_out = gr.HTML(value="")
|
| 692 |
|
| 693 |
+
# ββ Podium (1st / 2nd / 3rd) βββββββββββββββββββββββββββββββββ
|
| 694 |
+
podium_out = gr.HTML(value="")
|
| 695 |
+
|
| 696 |
+
# ββ Detailed results βββββββββββββββββββββββββββββββββββββββββ
|
| 697 |
+
gr.HTML(RESULTS_LABEL_HTML)
|
| 698 |
|
| 699 |
with gr.Tabs():
|
| 700 |
+
with gr.Tab("π Full Leaderboard"):
|
| 701 |
+
leaderboard_out = gr.Dataframe(label="All submissions ranked")
|
| 702 |
with gr.Tab("π― Criteria Breakdown"):
|
| 703 |
criteria_out = gr.Dataframe(label="Pass / Fail per Criterion (β
β₯7 Β· π‘ 5-6 Β· β <5)")
|
|
|
|
|
|
|
| 704 |
|
| 705 |
briefing_out = gr.Markdown(value="")
|
| 706 |
|
|
|
|
| 711 |
analyse_btn.click(
|
| 712 |
fn=run_analysis,
|
| 713 |
inputs=[urls_box, criteria_box],
|
| 714 |
+
outputs=[briefing_out, podium_out, leaderboard_out, criteria_out, confetti_out],
|
| 715 |
)
|
| 716 |
|
| 717 |
if __name__ == "__main__":
|