ha251 commited on
Commit
c7ef4e9
·
verified ·
1 Parent(s): 80ad682

Update miniapp_leaderboard.py

Browse files
Files changed (1) hide show
  1. miniapp_leaderboard.py +169 -130
miniapp_leaderboard.py CHANGED
@@ -50,7 +50,7 @@ NUMERIC_COLS = [
50
  "Lifestyle",
51
  ]
52
 
53
- # Avg 最左 + 多级表头展示用
54
  DISPLAY_ORDER = [
55
  "Avg",
56
  "Model name",
@@ -211,7 +211,7 @@ def _submitted_today(username: str) -> bool:
211
  return any(str(v).startswith(today) for v in user_rows["Submitted at"].tolist())
212
 
213
 
214
- # ---------- HTML Leaderboard helpers ----------
215
  def _fmt_cell(v):
216
  if v is None or (isinstance(v, float) and pd.isna(v)):
217
  return ""
@@ -221,12 +221,10 @@ def _fmt_cell(v):
221
 
222
 
223
  def _apply_search_and_sort(df: pd.DataFrame, search_text: str, sort_col: str, sort_dir: str) -> pd.DataFrame:
224
- # 搜索仅 Model name
225
  s = (search_text or "").strip().lower()
226
  if s:
227
  df = df[df["Model name"].astype(str).str.lower().str.contains(s, na=False)]
228
 
229
- # 排序
230
  sort_col = sort_col if sort_col in df.columns else "Avg"
231
  asc = sort_dir == "asc"
232
  df = df.sort_values(by=[sort_col], ascending=asc, kind="stable", na_position="last")
@@ -236,13 +234,15 @@ def _apply_search_and_sort(df: pd.DataFrame, search_text: str, sort_col: str, so
236
  def _render_leaderboard_html(df: pd.DataFrame, sort_col: str, sort_dir: str) -> str:
237
  import html as _html
238
 
239
- def th(label, col=None, cls=""):
240
  if col:
241
  arrow = ""
242
  if col == sort_col:
243
- arrow = " " if sort_dir == "asc" else " "
244
- return f'<th class="clickable {cls}" data-col="{_html.escape(col)}">{_html.escape(label)}{arrow}</th>'
245
- return f'<th class="{cls}">{_html.escape(label)}</th>'
 
 
246
 
247
  trs = []
248
  for _, r in df.iterrows():
@@ -250,44 +250,46 @@ def _render_leaderboard_html(df: pd.DataFrame, sort_col: str, sort_dir: str) ->
250
  for c in DISPLAY_ORDER:
251
  val = _fmt_cell(r.get(c, ""))
252
  if c == "Model name":
253
- tds.append(f'<td class="model">{_html.escape(val)}</td>')
254
  else:
255
- tds.append(f'<td class="num">{_html.escape(val)}</td>')
256
- trs.append("<tr>" + "".join(tds) + "</tr>")
257
 
258
  return f"""
259
- <div class="lb-wrap">
260
- <table class="lb" id="lb_table">
261
- <thead>
262
- <tr class="top">
263
- {th("Avg. (%)", "Avg", "avg")}
264
- {th("Model", "Model name", "model")}
265
- <th class="group" colspan="9">Pass Rate (%)</th>
266
- </tr>
267
- <tr class="mid">
268
- <th></th>
269
- <th></th>
270
- <th class="group" colspan="3">Difficulty</th>
271
- <th class="group" colspan="6">Domain</th>
272
- </tr>
273
- <tr class="bot">
274
- <th></th>
275
- <th></th>
276
- {th("Easy", "Easy")}
277
- {th("Mid", "Mid")}
278
- {th("Hard", "Hard")}
279
- {th("Games", "Games")}
280
- {th("Science", "Science")}
281
- {th("Tools", "Tools")}
282
- {th("Humanities", "Humanities")}
283
- {th("Viz.", "Viz")}
284
- {th("Lifestyle", "Lifestyle")}
285
- </tr>
286
- </thead>
287
- <tbody>
288
- {''.join(trs)}
289
- </tbody>
290
- </table>
 
 
291
  </div>
292
  """
293
 
@@ -316,7 +318,6 @@ def submit(
316
  sort_dir: str,
317
  profile: gr.OAuthProfile | None,
318
  ):
319
- # Login required to submit (in Spaces)
320
  if IN_SPACES and (profile is None or not getattr(profile, "username", None)):
321
  return "You must log in to submit.", render_lb(search_text, sort_col, sort_dir)
322
 
@@ -387,68 +388,103 @@ def submit(
387
 
388
 
389
  CSS = r"""
390
- /* 顶部一行:搜索不明显 */
391
- #topbar { display:flex; align-items:center; gap:10px; }
392
- #searchbox { max-width: 320px; }
 
 
 
 
 
393
  #searchbox label { display:none !important; }
394
  #searchbox textarea, #searchbox input {
 
 
395
  border: 1px solid rgba(0,0,0,.10) !important;
396
  background: rgba(0,0,0,.02) !important;
397
  box-shadow: none !important;
398
  }
399
  #searchbox textarea::placeholder, #searchbox input::placeholder { color: rgba(0,0,0,.35); }
400
 
401
- /* 表格风格接近截图 */
402
- .lb-wrap { width: 100%; overflow-x: auto; }
403
- table.lb { width: 100%; border-collapse: collapse; font-family: serif; }
404
- table.lb thead th { text-align:center; font-weight:700; padding:10px 8px; border-bottom:1px solid #222; white-space:nowrap; }
405
- table.lb thead tr.top th { border-top:2px solid #222; font-size: 22px; }
406
- table.lb thead tr.mid th { border-bottom:1px solid #777; font-weight:700; padding-top:6px; padding-bottom:6px; font-size: 20px; }
407
- table.lb thead tr.bot th { border-bottom:2px solid #222; padding-top:8px; padding-bottom:8px; font-size: 20px; }
408
-
409
- table.lb tbody td { padding:10px 10px; border-bottom:1px solid rgba(0,0,0,.08); font-family: sans-serif; font-size: 14px; }
410
- table.lb tbody td.num { text-align:right; }
411
- table.lb tbody td.model { text-align:left; min-width:260px; }
412
-
413
- th.group { font-weight:800; letter-spacing:.2px; }
414
- th.avg { min-width:100px; }
415
- th.model { text-align:left; min-width:260px; }
416
-
417
- /* 点击排序 */
418
- th.clickable { user-select:none; }
419
- th.clickable:hover { background: rgba(0,0,0,.04); cursor:pointer; }
 
 
 
 
 
 
 
 
420
 
421
- /* 提交区紧凑 */
422
- #submitbox { max-width: 720px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  """
424
 
425
-
426
  with gr.Blocks(title=f"{APP_NAME} leaderboard") as demo:
427
- gr.Markdown(f"## {APP_NAME} leaderboard")
428
-
429
- with gr.Row(elem_id="topbar"):
430
- search_text = gr.Textbox(
431
- elem_id="searchbox",
432
- placeholder="Search model…",
433
- show_label=False,
434
- container=False,
435
- scale=1,
436
- )
437
- refresh_btn = gr.Button("Refresh", scale=0)
438
-
439
- # 排序状态
440
- sort_col = gr.State("Avg")
441
- sort_dir = gr.State("desc")
442
-
443
- # HTML leaderboard
444
- lb_html = gr.HTML(value=render_lb("", "Avg", "desc"))
445
-
446
- # elem_id 精确定位的隐藏输入:JS 写入点击列名
447
- clicked_col = gr.Textbox(visible=False, elem_id="clicked_col")
448
-
449
- # JS:绑定表头点击,把 data-col 写入隐藏 textbox
450
- gr.HTML(
451
- """
452
  <script>
453
  (function(){
454
  function bindClicks(){
@@ -466,51 +502,54 @@ with gr.Blocks(title=f"{APP_NAME} leaderboard") as demo:
466
  });
467
  }
468
 
469
- // 首次 + 每次页面更新后都尝试重新绑定
470
  const obs = new MutationObserver(()=>bindClicks());
471
  obs.observe(document.body, {subtree:true, childList:true});
472
- setTimeout(bindClicks, 300);
473
  })();
474
  </script>
475
  """
476
- )
477
 
478
- # 搜索/刷新:重新渲染 HTML
479
- search_text.change(render_lb, inputs=[search_text, sort_col, sort_dir], outputs=[lb_html])
480
- refresh_btn.click(render_lb, inputs=[search_text, sort_col, sort_dir], outputs=[lb_html])
481
 
482
- # 点击列名:更新排序状态 + 重新渲染
483
- def _on_click(col, cur_col, cur_dir, s):
484
- new_col, new_dir = toggle_sort(col, cur_col, cur_dir)
485
- return new_col, new_dir, render_lb(s, new_col, new_dir)
486
 
487
- clicked_col.change(
488
- _on_click,
489
- inputs=[clicked_col, sort_col, sort_dir, search_text],
490
- outputs=[sort_col, sort_dir, lb_html],
491
- )
492
 
493
- gr.Markdown(
494
- "### Submission\n"
495
- "- Submit **Model API URL** and **API key** only\n"
496
- "- Requires login (in Spaces)\n"
497
- "- One submission per user per day\n"
498
- "- HF account must be older than 4 months\n"
499
- "- API key **will not be stored**"
500
- )
 
 
 
 
501
 
502
- with gr.Column(elem_id="submitbox"):
503
- model_api = gr.Textbox(label="Model API URL (required)", placeholder="https://...")
504
- api_key = gr.Textbox(label="API key (required)", type="password", placeholder="Will not be stored")
505
- with gr.Row():
506
- gr.LoginButton()
507
- submit_btn = gr.Button("Submit", variant="primary")
508
- status = gr.Markdown()
509
-
510
- submit_btn.click(
511
- submit,
512
- inputs=[model_api, api_key, search_text, sort_col, sort_dir],
513
- outputs=[status, lb_html],
514
- )
 
 
515
 
516
  demo.launch(css=CSS, ssr_mode=False)
 
50
  "Lifestyle",
51
  ]
52
 
53
+ # 展示顺序:Avg 最左
54
  DISPLAY_ORDER = [
55
  "Avg",
56
  "Model name",
 
211
  return any(str(v).startswith(today) for v in user_rows["Submitted at"].tolist())
212
 
213
 
214
+ # ---------- HTML Leaderboard ----------
215
  def _fmt_cell(v):
216
  if v is None or (isinstance(v, float) and pd.isna(v)):
217
  return ""
 
221
 
222
 
223
  def _apply_search_and_sort(df: pd.DataFrame, search_text: str, sort_col: str, sort_dir: str) -> pd.DataFrame:
 
224
  s = (search_text or "").strip().lower()
225
  if s:
226
  df = df[df["Model name"].astype(str).str.lower().str.contains(s, na=False)]
227
 
 
228
  sort_col = sort_col if sort_col in df.columns else "Avg"
229
  asc = sort_dir == "asc"
230
  df = df.sort_values(by=[sort_col], ascending=asc, kind="stable", na_position="last")
 
234
  def _render_leaderboard_html(df: pd.DataFrame, sort_col: str, sort_dir: str) -> str:
235
  import html as _html
236
 
237
+ def th(label, col=None, align_left=False, cls=""):
238
  if col:
239
  arrow = ""
240
  if col == sort_col:
241
+ arrow = " " if sort_dir == "asc" else " "
242
+ al = " left" if align_left else ""
243
+ return f'<th class="h clickable{al} {cls}" data-col="{_html.escape(col)}">{_html.escape(label)}{arrow}</th>'
244
+ al = " left" if align_left else ""
245
+ return f'<th class="h{al} {cls}">{_html.escape(label)}</th>'
246
 
247
  trs = []
248
  for _, r in df.iterrows():
 
250
  for c in DISPLAY_ORDER:
251
  val = _fmt_cell(r.get(c, ""))
252
  if c == "Model name":
253
+ tds.append(f'<td class="cell model">{_html.escape(val)}</td>')
254
  else:
255
+ tds.append(f'<td class="cell num">{_html.escape(val)}</td>')
256
+ trs.append("<tr class='r'>" + "".join(tds) + "</tr>")
257
 
258
  return f"""
259
+ <div class="lb-card">
260
+ <div class="lb-scroll">
261
+ <table class="lb" id="lb_table">
262
+ <thead>
263
+ <tr class="t1">
264
+ {th("Avg. (%)", "Avg", cls="avg")}
265
+ {th("Model", "Model name", align_left=True, cls="model")}
266
+ <th class="h group" colspan="9">Pass Rate (%)</th>
267
+ </tr>
268
+ <tr class="t2">
269
+ <th class="h"></th>
270
+ <th class="h"></th>
271
+ <th class="h group" colspan="3">Difficulty</th>
272
+ <th class="h group" colspan="6">Domain</th>
273
+ </tr>
274
+ <tr class="t3">
275
+ <th class="h"></th>
276
+ <th class="h"></th>
277
+ {th("Easy", "Easy")}
278
+ {th("Mid", "Mid")}
279
+ {th("Hard", "Hard")}
280
+ {th("Games", "Games")}
281
+ {th("Science", "Science")}
282
+ {th("Tools", "Tools")}
283
+ {th("Humanities", "Humanities")}
284
+ {th("Viz.", "Viz")}
285
+ {th("Lifestyle", "Lifestyle")}
286
+ </tr>
287
+ </thead>
288
+ <tbody>
289
+ {''.join(trs)}
290
+ </tbody>
291
+ </table>
292
+ </div>
293
  </div>
294
  """
295
 
 
318
  sort_dir: str,
319
  profile: gr.OAuthProfile | None,
320
  ):
 
321
  if IN_SPACES and (profile is None or not getattr(profile, "username", None)):
322
  return "You must log in to submit.", render_lb(search_text, sort_col, sort_dir)
323
 
 
388
 
389
 
390
  CSS = r"""
391
+ /* Blocks 内容更贴边、更像网站 */
392
+ .gradio-container { max-width: 100% !important; }
393
+ #page { padding: 18px 18px 28px 18px; }
394
+
395
+ /* 顶部一行 */
396
+ #topbar { display:flex; align-items:center; justify-content:space-between; gap:12px; }
397
+ #titleline { font-weight: 700; font-size: 20px; line-height: 1; }
398
+ #searchbox { width: 280px; }
399
  #searchbox label { display:none !important; }
400
  #searchbox textarea, #searchbox input {
401
+ height: 36px !important;
402
+ border-radius: 10px !important;
403
  border: 1px solid rgba(0,0,0,.10) !important;
404
  background: rgba(0,0,0,.02) !important;
405
  box-shadow: none !important;
406
  }
407
  #searchbox textarea::placeholder, #searchbox input::placeholder { color: rgba(0,0,0,.35); }
408
 
409
+ /* 卡片式表格 */
410
+ .lb-card{
411
+ width: 100%;
412
+ border: 1px solid rgba(0,0,0,.08);
413
+ border-radius: 14px;
414
+ background: white;
415
+ }
416
+ .lb-scroll { width: 100%; overflow-x: auto; }
417
+ table.lb { width: 100%; border-collapse: separate; border-spacing: 0; min-width: 1100px; }
418
+ table.lb thead th.h{
419
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
420
+ font-weight: 700;
421
+ font-size: 13px;
422
+ color: rgba(0,0,0,.75);
423
+ padding: 10px 12px;
424
+ text-align: center;
425
+ border-bottom: 1px solid rgba(0,0,0,.08);
426
+ background: rgba(0,0,0,.02);
427
+ white-space: nowrap;
428
+ }
429
+ table.lb thead tr.t1 th { border-bottom: 1px solid rgba(0,0,0,.10); }
430
+ table.lb thead tr.t2 th { background: rgba(0,0,0,.015); }
431
+ table.lb thead th.left{ text-align:left; }
432
+ table.lb thead th.group{
433
+ font-weight: 800;
434
+ color: rgba(0,0,0,.80);
435
+ }
436
 
437
+ table.lb tbody td.cell{
438
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
439
+ font-size: 13px;
440
+ padding: 10px 12px;
441
+ border-bottom: 1px solid rgba(0,0,0,.06);
442
+ background: white;
443
+ }
444
+ table.lb tbody tr.r:hover td { background: rgba(0,0,0,.02); }
445
+ td.num { text-align:right; }
446
+ td.model { text-align:left; min-width: 280px; }
447
+ th.avg, td.avg { min-width: 90px; }
448
+
449
+ th.clickable { cursor: pointer; user-select: none; }
450
+ th.clickable:hover { background: rgba(0,0,0,.05); }
451
+
452
+ /* 提交区:全宽卡片 */
453
+ #submit_card{
454
+ width: 100%;
455
+ border: 1px solid rgba(0,0,0,.08);
456
+ border-radius: 14px;
457
+ padding: 14px;
458
+ background: white;
459
+ }
460
+ #submit_card .prose { margin: 0 0 10px 0; color: rgba(0,0,0,.72); font-size: 13px; }
461
  """
462
 
 
463
  with gr.Blocks(title=f"{APP_NAME} leaderboard") as demo:
464
+ with gr.Column(elem_id="page"):
465
+ # 顶部栏:左标题,右搜索 + 刷新(同一行,不显眼)
466
+ with gr.Row(elem_id="topbar"):
467
+ gr.Markdown(f"<div id='titleline'>{APP_NAME} leaderboard</div>")
468
+ with gr.Row():
469
+ search_text = gr.Textbox(
470
+ elem_id="searchbox",
471
+ placeholder="Search model…",
472
+ show_label=False,
473
+ container=False,
474
+ scale=1,
475
+ )
476
+ refresh_btn = gr.Button("Refresh", scale=0)
477
+
478
+ sort_col = gr.State("Avg")
479
+ sort_dir = gr.State("desc")
480
+
481
+ lb_html = gr.HTML(value=render_lb("", "Avg", "desc"))
482
+
483
+ clicked_col = gr.Textbox(visible=False, elem_id="clicked_col")
484
+
485
+ # JS:表头点击 -> 写入隐藏 textbox -> 触发 change
486
+ gr.HTML(
487
+ """
 
488
  <script>
489
  (function(){
490
  function bindClicks(){
 
502
  });
503
  }
504
 
 
505
  const obs = new MutationObserver(()=>bindClicks());
506
  obs.observe(document.body, {subtree:true, childList:true});
507
+ setTimeout(bindClicks, 250);
508
  })();
509
  </script>
510
  """
511
+ )
512
 
513
+ search_text.change(render_lb, inputs=[search_text, sort_col, sort_dir], outputs=[lb_html])
514
+ refresh_btn.click(render_lb, inputs=[search_text, sort_col, sort_dir], outputs=[lb_html])
 
515
 
516
+ def _on_click(col, cur_col, cur_dir, s):
517
+ new_col, new_dir = toggle_sort(col, cur_col, cur_dir)
518
+ return new_col, new_dir, render_lb(s, new_col, new_dir)
 
519
 
520
+ clicked_col.change(
521
+ _on_click,
522
+ inputs=[clicked_col, sort_col, sort_dir, search_text],
523
+ outputs=[sort_col, sort_dir, lb_html],
524
+ )
525
 
526
+ # 提交:全宽卡片 + 两列输入
527
+ gr.HTML(
528
+ """
529
+ <div id="submit_card">
530
+ <div class="prose">
531
+ <b>Submission</b> Submit <b>Model API URL</b> and <b>API key</b> only.
532
+ Requires login (Spaces). One submission per user per day. Account must be older than 4 months.
533
+ API key will <b>not</b> be stored.
534
+ </div>
535
+ </div>
536
+ """
537
+ )
538
 
539
+ # Column/Row 做“占满横向”的提交表单
540
+ with gr.Column():
541
+ with gr.Row():
542
+ model_api = gr.Textbox(label="Model API URL", placeholder="https://...", scale=3)
543
+ api_key = gr.Textbox(label="API key", type="password", placeholder="Will not be stored", scale=2)
544
+ with gr.Row():
545
+ gr.LoginButton()
546
+ submit_btn = gr.Button("Submit", variant="primary")
547
+ status = gr.Markdown()
548
+
549
+ submit_btn.click(
550
+ submit,
551
+ inputs=[model_api, api_key, search_text, sort_col, sort_dir],
552
+ outputs=[status, lb_html],
553
+ )
554
 
555
  demo.launch(css=CSS, ssr_mode=False)