Update app.py
Browse files
app.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
| 1 |
|
| 2 |
"""
|
| 3 |
-
Judges Copilot
|
| 4 |
-
|
| 5 |
-
Add your criteria. Get 1st / 2nd / 3rd with full reasons.
|
| 6 |
"""
|
| 7 |
|
| 8 |
import os, re, random
|
|
@@ -62,7 +61,7 @@ label span, .label-wrap span {
|
|
| 62 |
font-weight: 600 !important;
|
| 63 |
}
|
| 64 |
|
| 65 |
-
/* ββ
|
| 66 |
button.cta {
|
| 67 |
background: #F5A623 !important;
|
| 68 |
color: #0b0b18 !important;
|
|
@@ -79,6 +78,25 @@ button.cta {
|
|
| 79 |
}
|
| 80 |
button.cta:hover { opacity: .88 !important; transform: translateY(-1px) !important; }
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
/* ββ tabs ββ */
|
| 83 |
.tab-nav button {
|
| 84 |
background: transparent !important;
|
|
@@ -131,10 +149,21 @@ button.cta:hover { opacity: .88 !important; transform: translateY(-1px) !importa
|
|
| 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%
|
| 137 |
-
85%
|
| 138 |
100% { transform: translateY(105vh) rotate(600deg); opacity: 0; }
|
| 139 |
}
|
| 140 |
.cf {
|
|
@@ -145,13 +174,22 @@ button.cta:hover { opacity: .88 !important; transform: translateY(-1px) !importa
|
|
| 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
|
| 155 |
border-bottom:1px solid #1a1a34;
|
| 156 |
font-family:'Inter',sans-serif;
|
| 157 |
">
|
|
@@ -173,35 +211,49 @@ HERO_HTML = """
|
|
| 173 |
</h1>
|
| 174 |
|
| 175 |
<p style="
|
| 176 |
-
font-size:.97rem; color:#686898; max-width:
|
| 177 |
-
line-height:1.8; margin:0 auto
|
| 178 |
">
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 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 |
-
|
| 197 |
-
<div style="font-family:'Inter',sans-serif; padding:
|
| 198 |
<p style="
|
| 199 |
font-size:.71rem; color:#F5A623; font-weight:700;
|
| 200 |
-
letter-spacing:.1em; text-transform:uppercase; margin-bottom:
|
| 201 |
-
">
|
| 202 |
-
<p style="font-size:.
|
| 203 |
-
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
</p>
|
| 206 |
</div>
|
| 207 |
"""
|
|
@@ -211,22 +263,13 @@ CRITERIA_HEADER_HTML = """
|
|
| 211 |
<p style="
|
| 212 |
font-size:.71rem; color:#F5A623; font-weight:700;
|
| 213 |
letter-spacing:.1em; text-transform:uppercase; margin-bottom:6px;
|
| 214 |
-
">
|
| 215 |
<p style="font-size:.84rem; color:#5858a0; line-height:1.6; margin:0;">
|
| 216 |
-
|
| 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);
|
|
@@ -260,7 +303,7 @@ FOOTER_HTML = """
|
|
| 260 |
"""
|
| 261 |
|
| 262 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 263 |
-
# Scoring
|
| 264 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 265 |
CRITERIA_SIGNALS = {
|
| 266 |
"innovation": {
|
|
@@ -314,15 +357,22 @@ CRITERIA_ALIASES = {
|
|
| 314 |
"completeness":"completeness","overall quality":"completeness",
|
| 315 |
}
|
| 316 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 318 |
# Hub helpers
|
| 319 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 320 |
def parse_hf_url(text: str):
|
| 321 |
text = text.strip().rstrip("/")
|
| 322 |
for pattern, rtype in [
|
| 323 |
-
(r"huggingface\.co/spaces/([\w.\-]+/[\w.\-]+)",
|
| 324 |
(r"huggingface\.co/datasets/([\w.\-]+/[\w.\-]+)", "dataset"),
|
| 325 |
-
(r"huggingface\.co/([\w.\-]+/[\w.\-]+)",
|
| 326 |
]:
|
| 327 |
m = re.search(pattern, text)
|
| 328 |
if m:
|
|
@@ -332,11 +382,26 @@ def parse_hf_url(text: str):
|
|
| 332 |
return "model", text
|
| 333 |
return None, None
|
| 334 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
def _get_repo_info(repo_type, repo_id):
|
| 336 |
-
if repo_type == "space":
|
| 337 |
if repo_type == "dataset": return api.dataset_info(repo_id)
|
| 338 |
return api.model_info(repo_id)
|
| 339 |
|
|
|
|
| 340 |
def fetch_repo_data(repo_type, repo_id):
|
| 341 |
data = {"readme":"","files":[],"likes":0,"error":""}
|
| 342 |
try:
|
|
@@ -356,6 +421,7 @@ def fetch_repo_data(repo_type, repo_id):
|
|
| 356 |
data["readme"] = ""
|
| 357 |
return data
|
| 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)
|
|
@@ -368,37 +434,60 @@ def fetch_app_code(repo_type, repo_id, files):
|
|
| 368 |
except Exception:
|
| 369 |
return ""
|
| 370 |
|
|
|
|
| 371 |
def discover_from_hackathon_url(url):
|
| 372 |
targets = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
tag_match = re.search(r"[?&]tag=([^&\s]+)", url)
|
| 374 |
if tag_match:
|
| 375 |
try:
|
| 376 |
for s in api.list_spaces(filter=tag_match.group(1), limit=100):
|
| 377 |
sid = getattr(s,"id",None)
|
| 378 |
-
if sid
|
| 379 |
-
|
|
|
|
|
|
|
| 380 |
except Exception:
|
| 381 |
pass
|
|
|
|
|
|
|
| 382 |
try:
|
| 383 |
resp = requests.get(url, timeout=12, headers={"User-Agent":"JudgesCopilot/1.0"})
|
| 384 |
if resp.ok:
|
| 385 |
found = re.findall(
|
| 386 |
r'href=["\'](?:https://huggingface\.co)?/spaces/([\w.\-]+/[\w.\-]+)["\']',
|
| 387 |
resp.text)
|
| 388 |
-
seen = set()
|
| 389 |
for sid in found:
|
| 390 |
if sid not in seen:
|
| 391 |
seen.add(sid); targets.append(sid)
|
| 392 |
except Exception:
|
| 393 |
pass
|
|
|
|
| 394 |
return targets
|
| 395 |
|
|
|
|
| 396 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 397 |
-
# Scoring
|
| 398 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 399 |
def _best_signal(criterion_lower):
|
| 400 |
return CRITERIA_SIGNALS.get(CRITERIA_ALIASES.get(criterion_lower))
|
| 401 |
|
|
|
|
| 402 |
def score_criterion(criterion, readme_lower, checklist, repo_type):
|
| 403 |
score = 5.0
|
| 404 |
sig = _best_signal(criterion.lower().strip())
|
|
@@ -415,6 +504,7 @@ def score_criterion(criterion, readme_lower, checklist, repo_type):
|
|
| 415 |
if repo_type == "space" and checklist.get("Uses Gradio"): score += 0.3
|
| 416 |
return round(max(1.0, min(10.0, score)), 1)
|
| 417 |
|
|
|
|
| 418 |
def generate_justification(criterion, score, readme_lower, checklist):
|
| 419 |
sig = _best_signal(criterion.lower().strip())
|
| 420 |
parts = []
|
|
@@ -430,12 +520,13 @@ def generate_justification(criterion, score, readme_lower, checklist):
|
|
| 430 |
if missing: parts.append("β missing " + ", ".join(missing))
|
| 431 |
else:
|
| 432 |
words = re.findall(r"[a-z]{4,}", criterion.lower())
|
| 433 |
-
hits
|
| 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 |
|
|
|
|
| 439 |
def analyze_submission(raw, criteria_list):
|
| 440 |
repo_type, repo_id = parse_hf_url(raw)
|
| 441 |
empty_j = {c:"" for c in criteria_list}
|
|
@@ -444,22 +535,22 @@ def analyze_submission(raw, criteria_list):
|
|
| 444 |
"scores":{c:0 for c in criteria_list},"justifications":empty_j,
|
| 445 |
"checklist":{},"summary":"Could not parse as a HF link.","error":True,"likes":0}
|
| 446 |
|
| 447 |
-
data
|
| 448 |
prefix = {"space":"spaces/","dataset":"datasets/","model":""}[repo_type]
|
| 449 |
-
url
|
| 450 |
|
| 451 |
if data["error"]:
|
| 452 |
return {"input":raw,"title":repo_id,"url":url,"overall":0,
|
| 453 |
"scores":{c:0 for c in criteria_list},"justifications":empty_j,
|
| 454 |
"checklist":{},"summary":data["error"],"error":True,"likes":0}
|
| 455 |
|
| 456 |
-
readme
|
| 457 |
-
files
|
| 458 |
app_code = fetch_app_code(repo_type, repo_id, files)
|
| 459 |
-
body
|
| 460 |
|
| 461 |
title_m = re.search(r"^#\s+(.+)$", body, re.MULTILINE)
|
| 462 |
-
title
|
| 463 |
|
| 464 |
content_lines = []
|
| 465 |
for ln in body.splitlines():
|
|
@@ -471,13 +562,13 @@ def analyze_submission(raw, criteria_list):
|
|
| 471 |
|
| 472 |
readme_lower = readme.lower()
|
| 473 |
checklist = {
|
| 474 |
-
"Has README":
|
| 475 |
"Has requirements.txt": any(f.lower()=="requirements.txt" for f in files),
|
| 476 |
-
"Has app entry point":
|
| 477 |
-
"Has LICENSE":
|
| 478 |
-
"Uses Gradio":
|
| 479 |
("import gradio","gr.Blocks","gr.Interface","gr.ChatInterface")),
|
| 480 |
-
"Demo media":
|
| 481 |
or "youtube" in readme_lower,
|
| 482 |
}
|
| 483 |
|
|
@@ -491,6 +582,7 @@ def analyze_submission(raw, criteria_list):
|
|
| 491 |
"scores":scores,"justifications":justifications,"checklist":checklist,
|
| 492 |
"summary":summary,"error":False,"likes":data["likes"]}
|
| 493 |
|
|
|
|
| 494 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 495 |
# Output builders
|
| 496 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -498,9 +590,9 @@ def make_confetti():
|
|
| 498 |
emojis = ["π","π","β¨","π","β","π","π₯³","π―","π«","π","π₯","π₯"]
|
| 499 |
pieces = []
|
| 500 |
for _ in range(28):
|
| 501 |
-
e
|
| 502 |
-
l
|
| 503 |
-
d
|
| 504 |
dur = round(random.uniform(2.5,5),2)
|
| 505 |
pieces.append(
|
| 506 |
f'<span class="cf" style="left:{l}%;'
|
|
@@ -510,50 +602,32 @@ def make_confetti():
|
|
| 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 |
-
|
| 517 |
-
|
| 518 |
-
|
| 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 |
-
|
| 527 |
-
|
| 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 |
-
|
| 533 |
-
{place} Place
|
| 534 |
-
</div>
|
| 535 |
<div style="font-size:.95rem;font-weight:700;color:#fff;
|
| 536 |
-
|
| 537 |
-
|
| 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 |
-
|
| 550 |
-
|
| 551 |
-
|
| 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):
|
|
@@ -587,8 +661,8 @@ def build_briefing(results, criteria_list):
|
|
| 587 |
f"_Analysed **{len(results)}** submission(s) Β· **{len(criteria_list)}** criteria_\n",
|
| 588 |
"---\n"]
|
| 589 |
for i, r in enumerate(results,1):
|
| 590 |
-
|
| 591 |
-
prefix =
|
| 592 |
lines.append(f"### {prefix} {r['title']}")
|
| 593 |
if r["url"]: lines.append(f"π [{r['url']}]({r['url']})")
|
| 594 |
if r["error"]:
|
|
@@ -596,9 +670,9 @@ def build_briefing(results, criteria_list):
|
|
| 596 |
else:
|
| 597 |
lines.append(f"_{r['summary']}_\n")
|
| 598 |
for c in criteria_list:
|
| 599 |
-
s
|
| 600 |
just = r["justifications"].get(c,"")
|
| 601 |
-
dot
|
| 602 |
lines.append(f"{dot} **{c}: {s}/10** β {just}")
|
| 603 |
lines.append(f"\n**Overall: {r['overall']}/10** Β· β€οΈ {r['likes']} likes")
|
| 604 |
present = [k for k,v in r["checklist"].items() if v]
|
|
@@ -610,13 +684,11 @@ def build_briefing(results, criteria_list):
|
|
| 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 ("### π
|
| 620 |
"", pd.DataFrame(), pd.DataFrame(), "")
|
| 621 |
|
| 622 |
criteria_list = [c.strip() for c in re.split(r"[,\n]", criteria_text or "") if c.strip()]
|
|
@@ -627,10 +699,13 @@ def run_analysis(urls_text, criteria_text):
|
|
| 627 |
for line in urls_text.splitlines():
|
| 628 |
line = line.strip()
|
| 629 |
if not line: continue
|
|
|
|
| 630 |
is_listing = (
|
| 631 |
-
(
|
|
|
|
| 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,18 +715,18 @@ def run_analysis(urls_text, criteria_text):
|
|
| 640 |
seen.add(line); targets.append(line)
|
| 641 |
|
| 642 |
if not targets:
|
| 643 |
-
return ("### π No valid links found.
|
| 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
|
| 651 |
-
briefing_md
|
| 652 |
-
leaderboard
|
| 653 |
-
criteria_df
|
| 654 |
-
confetti
|
| 655 |
|
| 656 |
return briefing_md, podium_html, leaderboard, criteria_df, confetti
|
| 657 |
|
|
@@ -659,6 +734,12 @@ def run_analysis(urls_text, criteria_text):
|
|
| 659 |
return (f"### β οΈ Error: `{exc}`", "", pd.DataFrame(), pd.DataFrame(), "")
|
| 660 |
|
| 661 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 663 |
# UI
|
| 664 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -666,54 +747,125 @@ with gr.Blocks(theme=gr.themes.Base(), css=CSS, title="Judges Copilot") as demo:
|
|
| 666 |
|
| 667 |
gr.HTML(HERO_HTML)
|
| 668 |
|
| 669 |
-
# ββ
|
| 670 |
-
gr.HTML(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
urls_box = gr.Textbox(
|
| 672 |
-
label="
|
| 673 |
placeholder=(
|
| 674 |
-
"
|
| 675 |
-
"
|
| 676 |
-
"
|
| 677 |
),
|
| 678 |
-
lines=
|
|
|
|
| 679 |
)
|
| 680 |
|
|
|
|
| 681 |
gr.HTML(CRITERIA_HEADER_HTML)
|
| 682 |
criteria_box = gr.Textbox(
|
| 683 |
-
label="
|
| 684 |
value="Innovation, Technical Execution, Usability & Design, Real-World Impact, Presentation",
|
| 685 |
-
lines=
|
|
|
|
| 686 |
)
|
| 687 |
|
| 688 |
-
analyse_btn = gr.Button("β‘ Analyse & Rank
|
| 689 |
|
| 690 |
-
# ββ
|
| 691 |
confetti_out = gr.HTML(value="")
|
| 692 |
-
|
| 693 |
-
# ββ Podium (1st / 2nd / 3rd) βββββββββββββββββββββββββββββββββ
|
| 694 |
podium_out = gr.HTML(value="")
|
| 695 |
|
| 696 |
-
|
| 697 |
-
|
|
|
|
| 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="
|
| 704 |
|
| 705 |
briefing_out = gr.Markdown(value="")
|
| 706 |
|
| 707 |
gr.HTML(HANDOFF_HTML)
|
| 708 |
gr.HTML(FOOTER_HTML)
|
| 709 |
|
| 710 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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__":
|
| 718 |
demo.launch()
|
| 719 |
|
|
|
|
| 1 |
|
| 2 |
"""
|
| 3 |
+
Judges Copilot Β· Gradio x Hugging Face Hackathon 2026
|
| 4 |
+
One click loads the whole hackathon. No copy-paste needed.
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
import os, re, random
|
|
|
|
| 61 |
font-weight: 600 !important;
|
| 62 |
}
|
| 63 |
|
| 64 |
+
/* ββ main CTA button ββ */
|
| 65 |
button.cta {
|
| 66 |
background: #F5A623 !important;
|
| 67 |
color: #0b0b18 !important;
|
|
|
|
| 78 |
}
|
| 79 |
button.cta:hover { opacity: .88 !important; transform: translateY(-1px) !important; }
|
| 80 |
|
| 81 |
+
/* ββ quick-start chip buttons ββ */
|
| 82 |
+
.chip > button {
|
| 83 |
+
background: rgba(245,166,35,.08) !important;
|
| 84 |
+
color: #F5A623 !important;
|
| 85 |
+
border: 1px solid rgba(245,166,35,.28) !important;
|
| 86 |
+
border-radius: 100px !important;
|
| 87 |
+
font-size: .82rem !important;
|
| 88 |
+
font-weight: 700 !important;
|
| 89 |
+
padding: 9px 20px !important;
|
| 90 |
+
white-space: nowrap !important;
|
| 91 |
+
transition: background .18s, border-color .18s, transform .15s !important;
|
| 92 |
+
letter-spacing: .01em !important;
|
| 93 |
+
}
|
| 94 |
+
.chip > button:hover {
|
| 95 |
+
background: rgba(245,166,35,.18) !important;
|
| 96 |
+
border-color: #F5A623 !important;
|
| 97 |
+
transform: translateY(-1px) !important;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
/* ββ tabs ββ */
|
| 101 |
.tab-nav button {
|
| 102 |
background: transparent !important;
|
|
|
|
| 149 |
.prose hr { border: none !important; border-top: 1px solid #1e1e3a !important; }
|
| 150 |
.prose, .prose p, .prose li { color: #b8b8e0 !important; }
|
| 151 |
|
| 152 |
+
/* ββ status banner ββ */
|
| 153 |
+
.status-banner {
|
| 154 |
+
background: rgba(245,166,35,.07);
|
| 155 |
+
border: 1px solid rgba(245,166,35,.2);
|
| 156 |
+
border-radius: 10px;
|
| 157 |
+
padding: 12px 18px;
|
| 158 |
+
font-size: .85rem;
|
| 159 |
+
color: #c0a060;
|
| 160 |
+
margin: 8px 0 4px;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
/* ββ confetti ββ */
|
| 164 |
@keyframes cffall {
|
| 165 |
+
0% { transform: translateY(-5vh) rotate(0deg); opacity: 1; }
|
| 166 |
+
85% { opacity: 1; }
|
| 167 |
100% { transform: translateY(105vh) rotate(600deg); opacity: 0; }
|
| 168 |
}
|
| 169 |
.cf {
|
|
|
|
| 174 |
}
|
| 175 |
"""
|
| 176 |
|
| 177 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 178 |
+
# Preset hackathons β add more here any time
|
| 179 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 180 |
+
PRESETS = {
|
| 181 |
+
"π Build Small Hackathon": "https://huggingface.co/build-small-hackathon",
|
| 182 |
+
"π¨ Gradio Hackathon 2026": "https://huggingface.co/spaces?tag=gradio-hackathon-2026",
|
| 183 |
+
"π€ Agents Hackathon": "https://huggingface.co/spaces?tag=agents-hackathon",
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 187 |
# Static HTML
|
| 188 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 189 |
HERO_HTML = """
|
| 190 |
<div style="
|
| 191 |
background:linear-gradient(180deg,#0b0b18 0%,#10102a 100%);
|
| 192 |
+
text-align:center; padding:56px 24px 40px;
|
| 193 |
border-bottom:1px solid #1a1a34;
|
| 194 |
font-family:'Inter',sans-serif;
|
| 195 |
">
|
|
|
|
| 211 |
</h1>
|
| 212 |
|
| 213 |
<p style="
|
| 214 |
+
font-size:.97rem; color:#686898; max-width:440px;
|
| 215 |
+
line-height:1.8; margin:0 auto 20px;
|
| 216 |
">
|
| 217 |
+
One click loads every submission. We read the READMEs,
|
| 218 |
+
check the files, score every criterion.
|
| 219 |
+
You just decide.
|
| 220 |
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
</div>
|
| 222 |
"""
|
| 223 |
|
| 224 |
+
QUICKSTART_LABEL_HTML = """
|
| 225 |
+
<div style="font-family:'Inter',sans-serif; padding:28px 0 10px;">
|
| 226 |
<p style="
|
| 227 |
font-size:.71rem; color:#F5A623; font-weight:700;
|
| 228 |
+
letter-spacing:.1em; text-transform:uppercase; margin-bottom:10px;
|
| 229 |
+
">β‘ Quick Start β one click, zero copy-paste</p>
|
| 230 |
+
<p style="font-size:.82rem; color:#404068; margin:0 0 12px;">
|
| 231 |
+
Click a hackathon below and we'll load every submission automatically.
|
| 232 |
+
</p>
|
| 233 |
+
</div>
|
| 234 |
+
"""
|
| 235 |
+
|
| 236 |
+
OR_DIVIDER_HTML = """
|
| 237 |
+
<div style="
|
| 238 |
+
display:flex; align-items:center; gap:12px;
|
| 239 |
+
font-family:'Inter',sans-serif;
|
| 240 |
+
font-size:.78rem; color:#252548;
|
| 241 |
+
padding: 18px 0 6px;
|
| 242 |
+
">
|
| 243 |
+
<div style="flex:1; height:1px; background:#1a1a34;"></div>
|
| 244 |
+
<span>or paste your own link</span>
|
| 245 |
+
<div style="flex:1; height:1px; background:#1a1a34;"></div>
|
| 246 |
+
</div>
|
| 247 |
+
"""
|
| 248 |
+
|
| 249 |
+
CUSTOM_LABEL_HTML = """
|
| 250 |
+
<div style="font-family:'Inter',sans-serif; padding:4px 0 6px;">
|
| 251 |
+
<p style="
|
| 252 |
+
font-size:.71rem; color:#5858a0; font-weight:700;
|
| 253 |
+
letter-spacing:.1em; text-transform:uppercase; margin-bottom:4px;
|
| 254 |
+
">Custom Links</p>
|
| 255 |
+
<p style="font-size:.82rem; color:#404068; margin:0;">
|
| 256 |
+
HF org URL Β· individual Space links (one per line) Β· ?tag= filter page
|
| 257 |
</p>
|
| 258 |
</div>
|
| 259 |
"""
|
|
|
|
| 263 |
<p style="
|
| 264 |
font-size:.71rem; color:#F5A623; font-weight:700;
|
| 265 |
letter-spacing:.1em; text-transform:uppercase; margin-bottom:6px;
|
| 266 |
+
">Your Criteria</p>
|
| 267 |
<p style="font-size:.84rem; color:#5858a0; line-height:1.6; margin:0;">
|
| 268 |
+
Edit, add, or remove β these become your scoring rubric.
|
| 269 |
</p>
|
| 270 |
</div>
|
| 271 |
"""
|
| 272 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
HANDOFF_HTML = """
|
| 274 |
<div style="
|
| 275 |
background:linear-gradient(135deg,#0f0f26,#180f28);
|
|
|
|
| 303 |
"""
|
| 304 |
|
| 305 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 306 |
+
# Scoring
|
| 307 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 308 |
CRITERIA_SIGNALS = {
|
| 309 |
"innovation": {
|
|
|
|
| 357 |
"completeness":"completeness","overall quality":"completeness",
|
| 358 |
}
|
| 359 |
|
| 360 |
+
_HF_SYSTEM_PATHS = {
|
| 361 |
+
"models","datasets","spaces","organizations","pricing",
|
| 362 |
+
"docs","blog","inference-endpoints","enterprise","join",
|
| 363 |
+
"login","register","settings","privacy","terms",
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
|
| 367 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 368 |
# Hub helpers
|
| 369 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 370 |
def parse_hf_url(text: str):
|
| 371 |
text = text.strip().rstrip("/")
|
| 372 |
for pattern, rtype in [
|
| 373 |
+
(r"huggingface\.co/spaces/([\w.\-]+/[\w.\-]+)", "space"),
|
| 374 |
(r"huggingface\.co/datasets/([\w.\-]+/[\w.\-]+)", "dataset"),
|
| 375 |
+
(r"huggingface\.co/([\w.\-]+/[\w.\-]+)", "model"),
|
| 376 |
]:
|
| 377 |
m = re.search(pattern, text)
|
| 378 |
if m:
|
|
|
|
| 382 |
return "model", text
|
| 383 |
return None, None
|
| 384 |
|
| 385 |
+
|
| 386 |
+
def _get_org_name(url: str):
|
| 387 |
+
"""Return org name if this is a HF org page, else None."""
|
| 388 |
+
if "huggingface.co" not in url:
|
| 389 |
+
return None
|
| 390 |
+
if re.search(r"huggingface\.co/(spaces|datasets|models)/[\w.\-]+/[\w.\-]+", url):
|
| 391 |
+
return None
|
| 392 |
+
m = re.search(r"huggingface\.co/(?:organizations/)?([\w.\-]+)(?:/.*)?$", url)
|
| 393 |
+
if not m:
|
| 394 |
+
return None
|
| 395 |
+
org = m.group(1)
|
| 396 |
+
return None if org in _HF_SYSTEM_PATHS else org
|
| 397 |
+
|
| 398 |
+
|
| 399 |
def _get_repo_info(repo_type, repo_id):
|
| 400 |
+
if repo_type == "space": return api.space_info(repo_id)
|
| 401 |
if repo_type == "dataset": return api.dataset_info(repo_id)
|
| 402 |
return api.model_info(repo_id)
|
| 403 |
|
| 404 |
+
|
| 405 |
def fetch_repo_data(repo_type, repo_id):
|
| 406 |
data = {"readme":"","files":[],"likes":0,"error":""}
|
| 407 |
try:
|
|
|
|
| 421 |
data["readme"] = ""
|
| 422 |
return data
|
| 423 |
|
| 424 |
+
|
| 425 |
def fetch_app_code(repo_type, repo_id, files):
|
| 426 |
if repo_type != "space": return ""
|
| 427 |
entry = next((f for f in files if f.lower() in ("app.py","main.py")), None)
|
|
|
|
| 434 |
except Exception:
|
| 435 |
return ""
|
| 436 |
|
| 437 |
+
|
| 438 |
def discover_from_hackathon_url(url):
|
| 439 |
targets = []
|
| 440 |
+
|
| 441 |
+
# 1. HF org page
|
| 442 |
+
org = _get_org_name(url)
|
| 443 |
+
if org:
|
| 444 |
+
try:
|
| 445 |
+
for s in api.list_spaces(author=org, limit=200):
|
| 446 |
+
sid = getattr(s, "id", None)
|
| 447 |
+
if sid and sid not in targets:
|
| 448 |
+
targets.append(sid)
|
| 449 |
+
if targets:
|
| 450 |
+
return targets
|
| 451 |
+
except Exception:
|
| 452 |
+
pass
|
| 453 |
+
|
| 454 |
+
# 2. ?tag= filter
|
| 455 |
tag_match = re.search(r"[?&]tag=([^&\s]+)", url)
|
| 456 |
if tag_match:
|
| 457 |
try:
|
| 458 |
for s in api.list_spaces(filter=tag_match.group(1), limit=100):
|
| 459 |
sid = getattr(s,"id",None)
|
| 460 |
+
if sid and sid not in targets:
|
| 461 |
+
targets.append(sid)
|
| 462 |
+
if targets:
|
| 463 |
+
return targets
|
| 464 |
except Exception:
|
| 465 |
pass
|
| 466 |
+
|
| 467 |
+
# 3. HTML scrape fallback
|
| 468 |
try:
|
| 469 |
resp = requests.get(url, timeout=12, headers={"User-Agent":"JudgesCopilot/1.0"})
|
| 470 |
if resp.ok:
|
| 471 |
found = re.findall(
|
| 472 |
r'href=["\'](?:https://huggingface\.co)?/spaces/([\w.\-]+/[\w.\-]+)["\']',
|
| 473 |
resp.text)
|
| 474 |
+
seen = set(targets)
|
| 475 |
for sid in found:
|
| 476 |
if sid not in seen:
|
| 477 |
seen.add(sid); targets.append(sid)
|
| 478 |
except Exception:
|
| 479 |
pass
|
| 480 |
+
|
| 481 |
return targets
|
| 482 |
|
| 483 |
+
|
| 484 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 485 |
+
# Scoring helpers
|
| 486 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 487 |
def _best_signal(criterion_lower):
|
| 488 |
return CRITERIA_SIGNALS.get(CRITERIA_ALIASES.get(criterion_lower))
|
| 489 |
|
| 490 |
+
|
| 491 |
def score_criterion(criterion, readme_lower, checklist, repo_type):
|
| 492 |
score = 5.0
|
| 493 |
sig = _best_signal(criterion.lower().strip())
|
|
|
|
| 504 |
if repo_type == "space" and checklist.get("Uses Gradio"): score += 0.3
|
| 505 |
return round(max(1.0, min(10.0, score)), 1)
|
| 506 |
|
| 507 |
+
|
| 508 |
def generate_justification(criterion, score, readme_lower, checklist):
|
| 509 |
sig = _best_signal(criterion.lower().strip())
|
| 510 |
parts = []
|
|
|
|
| 520 |
if missing: parts.append("β missing " + ", ".join(missing))
|
| 521 |
else:
|
| 522 |
words = re.findall(r"[a-z]{4,}", criterion.lower())
|
| 523 |
+
hits = [w for w in words if w in readme_lower]
|
| 524 |
parts.append(f"found: _{', '.join(hits[:3])}_" if hits else "no matching keywords")
|
| 525 |
if len(readme_lower) > 1500: parts.append("well-documented README")
|
| 526 |
elif len(readme_lower) < 100: parts.append("very sparse README")
|
| 527 |
return " Β· ".join(parts) if parts else "scored on README signals"
|
| 528 |
|
| 529 |
+
|
| 530 |
def analyze_submission(raw, criteria_list):
|
| 531 |
repo_type, repo_id = parse_hf_url(raw)
|
| 532 |
empty_j = {c:"" for c in criteria_list}
|
|
|
|
| 535 |
"scores":{c:0 for c in criteria_list},"justifications":empty_j,
|
| 536 |
"checklist":{},"summary":"Could not parse as a HF link.","error":True,"likes":0}
|
| 537 |
|
| 538 |
+
data = fetch_repo_data(repo_type, repo_id)
|
| 539 |
prefix = {"space":"spaces/","dataset":"datasets/","model":""}[repo_type]
|
| 540 |
+
url = f"https://huggingface.co/{prefix}{repo_id}"
|
| 541 |
|
| 542 |
if data["error"]:
|
| 543 |
return {"input":raw,"title":repo_id,"url":url,"overall":0,
|
| 544 |
"scores":{c:0 for c in criteria_list},"justifications":empty_j,
|
| 545 |
"checklist":{},"summary":data["error"],"error":True,"likes":0}
|
| 546 |
|
| 547 |
+
readme = data["readme"]
|
| 548 |
+
files = data["files"]
|
| 549 |
app_code = fetch_app_code(repo_type, repo_id, files)
|
| 550 |
+
body = re.sub(r"^---.*?---","",readme,count=1,flags=re.DOTALL).strip()
|
| 551 |
|
| 552 |
title_m = re.search(r"^#\s+(.+)$", body, re.MULTILINE)
|
| 553 |
+
title = title_m.group(1).strip() if title_m else repo_id
|
| 554 |
|
| 555 |
content_lines = []
|
| 556 |
for ln in body.splitlines():
|
|
|
|
| 562 |
|
| 563 |
readme_lower = readme.lower()
|
| 564 |
checklist = {
|
| 565 |
+
"Has README": bool(readme.strip()),
|
| 566 |
"Has requirements.txt": any(f.lower()=="requirements.txt" for f in files),
|
| 567 |
+
"Has app entry point": any(f.lower() in ("app.py","main.py") for f in files),
|
| 568 |
+
"Has LICENSE": any("license" in f.lower() for f in files),
|
| 569 |
+
"Uses Gradio": any(s in app_code for s in
|
| 570 |
("import gradio","gr.Blocks","gr.Interface","gr.ChatInterface")),
|
| 571 |
+
"Demo media": bool(re.search(r"\.(gif|mp4|png|jpe?g|webm)",readme,re.I))
|
| 572 |
or "youtube" in readme_lower,
|
| 573 |
}
|
| 574 |
|
|
|
|
| 582 |
"scores":scores,"justifications":justifications,"checklist":checklist,
|
| 583 |
"summary":summary,"error":False,"likes":data["likes"]}
|
| 584 |
|
| 585 |
+
|
| 586 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 587 |
# Output builders
|
| 588 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 590 |
emojis = ["π","π","β¨","π","β","π","π₯³","π―","π«","π","π₯","π₯"]
|
| 591 |
pieces = []
|
| 592 |
for _ in range(28):
|
| 593 |
+
e = random.choice(emojis)
|
| 594 |
+
l = random.randint(1,99)
|
| 595 |
+
d = round(random.uniform(0,2.2),2)
|
| 596 |
dur = round(random.uniform(2.5,5),2)
|
| 597 |
pieces.append(
|
| 598 |
f'<span class="cf" style="left:{l}%;'
|
|
|
|
| 602 |
|
| 603 |
|
| 604 |
def build_podium_html(results):
|
|
|
|
| 605 |
ok = [r for r in results if not r["error"]]
|
| 606 |
+
if not ok: return ""
|
| 607 |
+
medals = [("π₯","1st","#F5A623","rgba(245,166,35,.15)"),
|
| 608 |
+
("π₯","2nd","#b0b8d0","rgba(176,184,208,.12)"),
|
| 609 |
+
("π₯","3rd","#cd7f32","rgba(205,127,50,.12)")]
|
|
|
|
|
|
|
| 610 |
cards = []
|
| 611 |
for i, r in enumerate(ok[:3]):
|
| 612 |
emoji, place, color, bg = medals[i]
|
| 613 |
cards.append(f"""
|
| 614 |
+
<div style="background:{bg};border:1px solid {color}44;border-radius:14px;
|
| 615 |
+
padding:20px 16px;text-align:center;font-family:'Inter',sans-serif;
|
| 616 |
+
flex:1;min-width:160px;">
|
|
|
|
|
|
|
| 617 |
<div style="font-size:2rem;margin-bottom:6px;">{emoji}</div>
|
| 618 |
<div style="font-size:.7rem;color:{color};font-weight:700;
|
| 619 |
+
letter-spacing:.1em;text-transform:uppercase;margin-bottom:8px;">{place} Place</div>
|
|
|
|
|
|
|
| 620 |
<div style="font-size:.95rem;font-weight:700;color:#fff;
|
| 621 |
+
margin-bottom:6px;line-height:1.3;">{r["title"]}</div>
|
| 622 |
+
<div style="font-size:1.3rem;font-weight:800;color:{color};">{r["overall"]}/10</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
{"<a href='" + r['url'] + "' target='_blank' style='font-size:.75rem;color:#6080c0;'>View β</a>" if r["url"] else ""}
|
| 624 |
+
</div>""")
|
|
|
|
|
|
|
| 625 |
return f"""
|
| 626 |
<div style="padding:24px 0 8px;font-family:'Inter',sans-serif;">
|
| 627 |
<div style="font-size:.71rem;color:#F5A623;font-weight:700;
|
| 628 |
+
letter-spacing:.1em;text-transform:uppercase;margin-bottom:14px;">π Final Rankings</div>
|
| 629 |
+
<div style="display:flex;gap:12px;flex-wrap:wrap;">{"".join(cards)}</div>
|
| 630 |
+
</div>"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 631 |
|
| 632 |
|
| 633 |
def build_leaderboard(results, criteria_list):
|
|
|
|
| 661 |
f"_Analysed **{len(results)}** submission(s) Β· **{len(criteria_list)}** criteria_\n",
|
| 662 |
"---\n"]
|
| 663 |
for i, r in enumerate(results,1):
|
| 664 |
+
medals_map = {1:"π₯",2:"π₯",3:"π₯"}
|
| 665 |
+
prefix = medals_map.get(i, f"**#{i}**")
|
| 666 |
lines.append(f"### {prefix} {r['title']}")
|
| 667 |
if r["url"]: lines.append(f"π [{r['url']}]({r['url']})")
|
| 668 |
if r["error"]:
|
|
|
|
| 670 |
else:
|
| 671 |
lines.append(f"_{r['summary']}_\n")
|
| 672 |
for c in criteria_list:
|
| 673 |
+
s = r["scores"].get(c,0)
|
| 674 |
just = r["justifications"].get(c,"")
|
| 675 |
+
dot = "π’" if s>=7 else ("π‘" if s>=5 else "π΄")
|
| 676 |
lines.append(f"{dot} **{c}: {s}/10** β {just}")
|
| 677 |
lines.append(f"\n**Overall: {r['overall']}/10** Β· β€οΈ {r['likes']} likes")
|
| 678 |
present = [k for k,v in r["checklist"].items() if v]
|
|
|
|
| 684 |
|
| 685 |
|
| 686 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 687 |
+
# Main analysis function
|
| 688 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 689 |
def run_analysis(urls_text, criteria_text):
|
|
|
|
|
|
|
| 690 |
if not urls_text or not urls_text.strip():
|
| 691 |
+
return ("### π Click a hackathon above or paste a link and hit Analyse.",
|
| 692 |
"", pd.DataFrame(), pd.DataFrame(), "")
|
| 693 |
|
| 694 |
criteria_list = [c.strip() for c in re.split(r"[,\n]", criteria_text or "") if c.strip()]
|
|
|
|
| 699 |
for line in urls_text.splitlines():
|
| 700 |
line = line.strip()
|
| 701 |
if not line: continue
|
| 702 |
+
|
| 703 |
is_listing = (
|
| 704 |
+
_get_org_name(line) is not None
|
| 705 |
+
or ("huggingface.co" in line and "?" in line and "/spaces/" not in line)
|
| 706 |
or "?tag=" in line
|
| 707 |
)
|
| 708 |
+
|
| 709 |
if is_listing:
|
| 710 |
for sid in discover_from_hackathon_url(line):
|
| 711 |
if sid not in seen:
|
|
|
|
| 715 |
seen.add(line); targets.append(line)
|
| 716 |
|
| 717 |
if not targets:
|
| 718 |
+
return ("### π No valid links found. Try clicking a Quick Start button above.",
|
| 719 |
"", pd.DataFrame(), pd.DataFrame(), "")
|
| 720 |
|
| 721 |
try:
|
| 722 |
results = [analyze_submission(t, criteria_list) for t in targets]
|
| 723 |
results.sort(key=lambda r: r["overall"], reverse=True)
|
| 724 |
|
| 725 |
+
podium_html = build_podium_html(results)
|
| 726 |
+
briefing_md = build_briefing(results, criteria_list)
|
| 727 |
+
leaderboard = build_leaderboard(results, criteria_list)
|
| 728 |
+
criteria_df = build_criteria_table(results, criteria_list)
|
| 729 |
+
confetti = make_confetti() if any(not r["error"] for r in results) else ""
|
| 730 |
|
| 731 |
return briefing_md, podium_html, leaderboard, criteria_df, confetti
|
| 732 |
|
|
|
|
| 734 |
return (f"### β οΈ Error: `{exc}`", "", pd.DataFrame(), pd.DataFrame(), "")
|
| 735 |
|
| 736 |
|
| 737 |
+
def fill_and_run(preset_url, criteria_text):
|
| 738 |
+
"""Fill the URL box AND immediately run analysis β one click does both."""
|
| 739 |
+
briefing, podium, lb, crit_df, confetti = run_analysis(preset_url, criteria_text)
|
| 740 |
+
return preset_url, briefing, podium, lb, crit_df, confetti
|
| 741 |
+
|
| 742 |
+
|
| 743 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 744 |
# UI
|
| 745 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 747 |
|
| 748 |
gr.HTML(HERO_HTML)
|
| 749 |
|
| 750 |
+
# ββ Quick-start chips ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 751 |
+
gr.HTML(QUICKSTART_LABEL_HTML)
|
| 752 |
+
|
| 753 |
+
with gr.Row():
|
| 754 |
+
chip_bs = gr.Button("π Build Small Hackathon", size="sm", elem_classes=["chip"])
|
| 755 |
+
chip_gh = gr.Button("π¨ Gradio Hackathon 2026", size="sm", elem_classes=["chip"])
|
| 756 |
+
chip_ag = gr.Button("π€ Agents Hackathon", size="sm", elem_classes=["chip"])
|
| 757 |
+
|
| 758 |
+
# ββ Status line (shows which hackathon was loaded) ββββββββββββββββββββ
|
| 759 |
+
status_md = gr.Markdown(value="", visible=False)
|
| 760 |
+
|
| 761 |
+
# ββ Divider ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 762 |
+
gr.HTML(OR_DIVIDER_HTML)
|
| 763 |
+
|
| 764 |
+
# ββ Custom URL input ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 765 |
+
gr.HTML(CUSTOM_LABEL_HTML)
|
| 766 |
urls_box = gr.Textbox(
|
| 767 |
+
label="",
|
| 768 |
placeholder=(
|
| 769 |
+
"https://huggingface.co/your-org\n"
|
| 770 |
+
"https://huggingface.co/spaces/username/project\n"
|
| 771 |
+
"https://huggingface.co/spaces?tag=your-tag"
|
| 772 |
),
|
| 773 |
+
lines=3,
|
| 774 |
+
show_label=False,
|
| 775 |
)
|
| 776 |
|
| 777 |
+
# ββ Criteria ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 778 |
gr.HTML(CRITERIA_HEADER_HTML)
|
| 779 |
criteria_box = gr.Textbox(
|
| 780 |
+
label="",
|
| 781 |
value="Innovation, Technical Execution, Usability & Design, Real-World Impact, Presentation",
|
| 782 |
+
lines=2,
|
| 783 |
+
show_label=False,
|
| 784 |
)
|
| 785 |
|
| 786 |
+
analyse_btn = gr.Button("β‘ Analyse & Rank", elem_classes=["cta"])
|
| 787 |
|
| 788 |
+
# ββ Outputs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 789 |
confetti_out = gr.HTML(value="")
|
|
|
|
|
|
|
| 790 |
podium_out = gr.HTML(value="")
|
| 791 |
|
| 792 |
+
gr.HTML("""<div style="font-family:'Inter',sans-serif;font-size:.71rem;color:#F5A623;
|
| 793 |
+
font-weight:700;letter-spacing:.1em;text-transform:uppercase;padding:28px 0 10px;">
|
| 794 |
+
π Results</div>""")
|
| 795 |
|
| 796 |
with gr.Tabs():
|
| 797 |
with gr.Tab("π Full Leaderboard"):
|
| 798 |
leaderboard_out = gr.Dataframe(label="All submissions ranked")
|
| 799 |
with gr.Tab("π― Criteria Breakdown"):
|
| 800 |
+
criteria_out = gr.Dataframe(label="β
β₯7 Β· π‘ 5-6 Β· β <5")
|
| 801 |
|
| 802 |
briefing_out = gr.Markdown(value="")
|
| 803 |
|
| 804 |
gr.HTML(HANDOFF_HTML)
|
| 805 |
gr.HTML(FOOTER_HTML)
|
| 806 |
|
| 807 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 808 |
+
# Event wiring
|
| 809 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 810 |
+
|
| 811 |
+
# Manual analyse button
|
| 812 |
analyse_btn.click(
|
| 813 |
fn=run_analysis,
|
| 814 |
inputs=[urls_box, criteria_box],
|
| 815 |
outputs=[briefing_out, podium_out, leaderboard_out, criteria_out, confetti_out],
|
| 816 |
)
|
| 817 |
|
| 818 |
+
# ββ Chip: Build Small Hackathon ββββββββββββββββββββββββββββββββββββββ
|
| 819 |
+
chip_bs.click(
|
| 820 |
+
fn=lambda ct: (
|
| 821 |
+
PRESETS["π Build Small Hackathon"],
|
| 822 |
+
gr.update(value=f"β³ Loading all submissions from **Build Small Hackathon**β¦", visible=True),
|
| 823 |
+
),
|
| 824 |
+
inputs=[criteria_box],
|
| 825 |
+
outputs=[urls_box, status_md],
|
| 826 |
+
).then(
|
| 827 |
+
fn=fill_and_run,
|
| 828 |
+
inputs=[urls_box, criteria_box],
|
| 829 |
+
outputs=[urls_box, briefing_out, podium_out, leaderboard_out, criteria_out, confetti_out],
|
| 830 |
+
).then(
|
| 831 |
+
fn=lambda: gr.update(value="β
Done β scroll down for results", visible=True),
|
| 832 |
+
outputs=[status_md],
|
| 833 |
+
)
|
| 834 |
+
|
| 835 |
+
# ββ Chip: Gradio Hackathon βββββββββββββββββββββββββββββββββββββββββββ
|
| 836 |
+
chip_gh.click(
|
| 837 |
+
fn=lambda ct: (
|
| 838 |
+
PRESETS["π¨ Gradio Hackathon 2026"],
|
| 839 |
+
gr.update(value=f"β³ Loading all submissions from **Gradio Hackathon 2026**β¦", visible=True),
|
| 840 |
+
),
|
| 841 |
+
inputs=[criteria_box],
|
| 842 |
+
outputs=[urls_box, status_md],
|
| 843 |
+
).then(
|
| 844 |
+
fn=fill_and_run,
|
| 845 |
+
inputs=[urls_box, criteria_box],
|
| 846 |
+
outputs=[urls_box, briefing_out, podium_out, leaderboard_out, criteria_out, confetti_out],
|
| 847 |
+
).then(
|
| 848 |
+
fn=lambda: gr.update(value="β
Done β scroll down for results", visible=True),
|
| 849 |
+
outputs=[status_md],
|
| 850 |
+
)
|
| 851 |
+
|
| 852 |
+
# ββ Chip: Agents Hackathon βββββββββββββββββββββββββββββββββββββββββββ
|
| 853 |
+
chip_ag.click(
|
| 854 |
+
fn=lambda ct: (
|
| 855 |
+
PRESETS["π€ Agents Hackathon"],
|
| 856 |
+
gr.update(value=f"β³ Loading all submissions from **Agents Hackathon**β¦", visible=True),
|
| 857 |
+
),
|
| 858 |
+
inputs=[criteria_box],
|
| 859 |
+
outputs=[urls_box, status_md],
|
| 860 |
+
).then(
|
| 861 |
+
fn=fill_and_run,
|
| 862 |
+
inputs=[urls_box, criteria_box],
|
| 863 |
+
outputs=[urls_box, briefing_out, podium_out, leaderboard_out, criteria_out, confetti_out],
|
| 864 |
+
).then(
|
| 865 |
+
fn=lambda: gr.update(value="β
Done β scroll down for results", visible=True),
|
| 866 |
+
outputs=[status_md],
|
| 867 |
+
)
|
| 868 |
+
|
| 869 |
if __name__ == "__main__":
|
| 870 |
demo.launch()
|
| 871 |
|