Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> | |
| <title>Eval Sweep β Pass@k Dashboard</title> | |
| <style> | |
| :root { | |
| --bg: #0e1116; | |
| --panel: #161b22; | |
| --panel2: #1e242c; | |
| --border: #2c333d; | |
| --text: #d7dadf; | |
| --muted: #8a9099; | |
| --accent: #59a6ff; | |
| --pass: #3ec97a; | |
| --fail: #e25c5c; | |
| --warn: #e6b455; | |
| --gray: #565d68; | |
| } | |
| * { box-sizing: border-box; } | |
| * { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.18) transparent; } | |
| *::-webkit-scrollbar { width: 8px; height: 8px; } | |
| *::-webkit-scrollbar-track { background: transparent; } | |
| *::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.16); border-radius: 4px; | |
| border: 2px solid transparent; background-clip: content-box; } | |
| *::-webkit-scrollbar-thumb:hover { background-color: rgba(255,255,255,0.32); } | |
| *::-webkit-scrollbar-corner { background: transparent; } | |
| html, body { height: 100%; margin: 0; } | |
| body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif; | |
| background: var(--bg); color: var(--text); font-size: 14px; line-height: 1.5; | |
| display: flex; flex-direction: column; min-height: 100vh; overflow: hidden; } | |
| header { background: var(--panel); border-bottom: 1px solid var(--border); flex: 0 0 auto; } | |
| .hdr-bar { padding: 12px 20px; display: flex; align-items: center; gap: 18px; flex-wrap: wrap; } | |
| .hdr-bar h1 { margin: 0; font-size: 16px; font-weight: 600; } | |
| .hdr-bar .sub { color: var(--muted); font-size: 12px; } | |
| .hdr-bar .pills { display: flex; gap: 7px; flex-wrap: wrap; margin-left: auto; } | |
| .hdr-bar .pill-stat { background: var(--panel2); padding: 4px 11px; border-radius: 4px; | |
| font-size: 12px; color: var(--text); border: 1px solid var(--border); } | |
| .hdr-bar .pill-stat b { color: var(--accent); font-weight: 600; } | |
| /* Dataset segmented control β sits next to the H1, switches the whole UI. */ | |
| .ds-tabs { display: inline-flex; gap: 0; background: var(--panel2); border: 1px solid var(--border); | |
| border-radius: 6px; overflow: hidden; } | |
| .ds-tabs button { background: transparent; color: var(--muted); border: 0; padding: 6px 14px; | |
| font: inherit; font-size: 13px; cursor: pointer; transition: all 0.15s; } | |
| .ds-tabs button:hover { color: var(--text); } | |
| .ds-tabs button.active { background: var(--accent); color: #fff; font-weight: 600; } | |
| /* Info icon next to the benchmark toggle β clicks open a popover with the benchmark's | |
| description + source links. */ | |
| .info-btn { background: var(--panel2); color: var(--muted); border: 1px solid var(--border); | |
| width: 26px; height: 26px; border-radius: 50%; cursor: pointer; display: inline-flex; | |
| align-items: center; justify-content: center; font-size: 13px; font-weight: 600; | |
| padding: 0; line-height: 1; transition: all 0.15s; } | |
| .info-btn:hover { background: var(--border); color: var(--text); } | |
| .info-popover { position: absolute; top: 100%; left: 0; margin-top: 6px; max-width: 420px; | |
| background: var(--panel); border: 1px solid var(--border); border-radius: 6px; | |
| padding: 14px 16px; box-shadow: 0 8px 24px rgba(0,0,0,0.5); z-index: 100; | |
| font-size: 13px; line-height: 1.55; } | |
| .info-popover.hidden { display: none; } | |
| .info-popover h4 { margin: 0 0 8px 0; font-size: 14px; color: var(--text); } | |
| .info-popover p { margin: 0 0 10px 0; color: var(--muted); } | |
| .info-popover a { color: var(--accent); text-decoration: none; } | |
| .info-popover a:hover { text-decoration: underline; } | |
| .info-popover .src-list { display: flex; flex-direction: column; gap: 4px; } | |
| .info-popover .src-list .lbl { color: var(--muted); font-size: 11px; text-transform: uppercase; | |
| letter-spacing: 0.5px; margin-right: 6px; } | |
| .bench-toggle-wrap { position: relative; display: inline-flex; align-items: center; gap: 8px; } | |
| /* Full-page loading overlay β shown during initial fetch + on dataset switch. */ | |
| .loader-overlay { position: fixed; inset: 0; background: rgba(15, 17, 21, 0.85); z-index: 9999; | |
| display: flex; flex-direction: column; align-items: center; justify-content: center; | |
| gap: 14px; transition: opacity 0.2s; } | |
| .loader-overlay.hidden { opacity: 0; pointer-events: none; } | |
| .loader-spinner { width: 38px; height: 38px; border: 3px solid var(--panel2); | |
| border-top-color: var(--accent); border-radius: 50%; | |
| animation: spin 0.8s linear infinite; } | |
| .loader-msg { color: var(--muted); font-size: 12px; letter-spacing: 0.4px; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| .hdr-bar .toggle { background: var(--panel2); color: var(--text); border: 1px solid var(--border); | |
| padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; } | |
| .hdr-bar .toggle:hover { background: var(--border); } | |
| /* Details panel β caps its own height + scrolls internally so the rest of the | |
| page (filters, heatmap) stays in view. Without max-height it pushes the | |
| heatmap off-screen and the page becomes unscrollable. */ | |
| .hdr-expanded { padding: 4px 18px 14px; border-top: 1px solid var(--border); | |
| max-height: 70vh; overflow-y: auto; } | |
| .hdr-expanded.hidden { display: none; } | |
| .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 8px; margin-top: 10px; } | |
| .stat { background: var(--panel2); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); } | |
| .stat .num { font-size: 18px; font-weight: 600; } | |
| .stat .lbl { color: var(--muted); font-size: 11px; margin-top: 2px; } | |
| .agg-tables { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 12px; } | |
| .agg-table { background: var(--panel2); padding: 10px; border-radius: 6px; border: 1px solid var(--border); } | |
| .agg-table h4 { margin: 0 0 6px; font-size: 12px; color: var(--muted); } | |
| .agg-table table { width: 100%; border-collapse: collapse; font-size: 11px; } | |
| .agg-table th { text-align: left; padding: 3px 5px; color: var(--muted); border-bottom: 1px solid var(--border); } | |
| .agg-table td { padding: 3px 5px; border-bottom: 1px solid var(--border); font-family: ui-monospace, SF Mono, Menlo, monospace; } | |
| .agg-table .pct { color: var(--accent); } | |
| /* Bar-chart blocks inside the details panel. Pure CSS β pass rate per | |
| model / harness / difficulty, with one bar per row + a small numeric label. */ | |
| .charts { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 12px; } | |
| .chart { background: var(--panel2); padding: 12px; border-radius: 6px; border: 1px solid var(--border); } | |
| .chart h4 { margin: 0 0 10px; font-size: 12px; color: var(--muted); display: flex; justify-content: space-between; } | |
| .chart h4 .sub { color: var(--gray); font-weight: 400; font-size: 10px; } | |
| .chart .row { display: grid; grid-template-columns: 110px 1fr 64px; gap: 8px; align-items: center; margin-bottom: 5px; } | |
| .chart .row:last-child { margin-bottom: 0; } | |
| .chart .row .lbl { font-size: 11px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: ui-monospace, SF Mono, Menlo, monospace; } | |
| .chart .row .bar-wrap { height: 14px; background: rgba(255,255,255,0.04); border-radius: 3px; overflow: hidden; position: relative; } | |
| .chart .row .bar { height: 100%; background: linear-gradient(90deg, var(--accent), #7fc1ff); border-radius: 3px; transition: width 0.3s; } | |
| .chart .row .num { font-size: 11px; color: var(--muted); font-family: ui-monospace, SF Mono, Menlo, monospace; text-align: right; } | |
| .chart .row .num b { color: var(--accent); font-weight: 600; } | |
| /* Stacked-bar variant for pass@k progression */ | |
| .chart .row .stacked { display: flex; height: 14px; border-radius: 3px; overflow: hidden; | |
| background: rgba(255,255,255,0.04); } | |
| .chart .row .stacked > span { height: 100%; } | |
| .chart .row .stacked .s1 { background: rgba(62,201,122,0.85); } | |
| .chart .row .stacked .s2 { background: rgba(62,201,122,0.62); } | |
| .chart .row .stacked .s3 { background: rgba(62,201,122,0.42); } | |
| .chart .row .stacked .s4 { background: rgba(62,201,122,0.26); } | |
| .chart-legend { display: flex; gap: 8px; font-size: 10px; color: var(--muted); margin-top: 6px; } | |
| .chart-legend .swatch { width: 10px; height: 10px; display: inline-block; vertical-align: middle; margin-right: 3px; border-radius: 2px; } | |
| /* βββ Dense details panel ββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| /* Compact KPI cards along the top of the panel. */ | |
| .stats { grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 8px; margin-top: 10px; } | |
| .stat { padding: 8px 12px; } | |
| .stat .num { font-size: 17px; } | |
| .stat .lbl { font-size: 12px; margin-top: 2px; } | |
| /* Marginal tables (per-model, per-harness) with @1/@2/@3/@4 heat-tinted cells. */ | |
| .marg-card { grid-column: 1 / -1; background: var(--panel2); padding: 12px 14px; | |
| border-radius: 6px; border: 1px solid var(--border); } | |
| .marg-card h4 { margin: 0 0 10px; font-size: 13px; color: var(--muted); | |
| display: flex; justify-content: space-between; } | |
| .marg-card h4 .sub { color: var(--gray); font-weight: 400; font-size: 11px; } | |
| .marg-wrap { overflow-x: auto; } | |
| .marg-table { border-collapse: collapse; font-size: 13px; width: 100%; | |
| font-family: ui-monospace, SF Mono, Menlo, monospace; } | |
| .marg-table th { text-align: left; padding: 6px 10px; color: var(--muted); | |
| font-weight: 500; border-bottom: 1px solid var(--border); white-space: nowrap; | |
| font-size: 12px; } | |
| .marg-table th.kc { text-align: center; } | |
| .marg-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; } | |
| .marg-table td.lbl { color: var(--text); } | |
| .marg-table td.num { text-align: right; color: var(--muted); } | |
| .marg-table td.kc { text-align: center; min-width: 90px; position: relative; padding: 6px 4px; } | |
| .marg-table td.kc .kc-num { position: relative; font-weight: 600; font-size: 13px; color: var(--text); } | |
| .marg-table td.kc .kc-sub { position: relative; font-size: 10px; color: var(--muted); } | |
| /* Tiny stacked sparkline column showing @1β@k progression */ | |
| .marg-table td.spark { padding: 6px 8px; min-width: 100px; } | |
| .marg-table td.spark .sparkbar { display: flex; height: 10px; border-radius: 2px; overflow: hidden; | |
| background: rgba(255,255,255,0.04); } | |
| .marg-table td.spark .sparkbar > span { height: 100%; } | |
| .marg-table td.spark .sparkbar .s1 { background: rgba(62,201,122,0.85); } | |
| .marg-table td.spark .sparkbar .s2 { background: rgba(62,201,122,0.62); } | |
| .marg-table td.spark .sparkbar .s3 { background: rgba(62,201,122,0.42); } | |
| .marg-table td.spark .sparkbar .s4 { background: rgba(62,201,122,0.26); } | |
| /* Heat-tinted matrices β one cell per (axis-A, axis-B) pair. */ | |
| .matrices { display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); | |
| gap: 12px; grid-column: 1 / -1; } | |
| .matrix-card { background: var(--panel2); padding: 12px 14px; | |
| border-radius: 6px; border: 1px solid var(--border); } | |
| .matrix-card h4 { margin: 0 0 10px; font-size: 13px; color: var(--muted); | |
| display: flex; justify-content: space-between; } | |
| .matrix-card h4 .sub { color: var(--gray); font-weight: 400; font-size: 11px; } | |
| .matrix-card .matrix-wrap { overflow-x: auto; } | |
| .matrix-card table { border-collapse: collapse; font-size: 12px; | |
| font-family: ui-monospace, SF Mono, Menlo, monospace; } | |
| .matrix-card th, .matrix-card td { padding: 6px 9px; border: 1px solid var(--border); | |
| text-align: center; white-space: nowrap; } | |
| .matrix-card th { background: var(--panel); color: var(--muted); font-weight: 500; font-size: 12px; } | |
| .matrix-card td.lbl { background: var(--panel); color: var(--text); text-align: left; } | |
| .matrix-card td.cell { color: var(--text); } | |
| .matrix-card td.cell b { font-size: 13px; } | |
| .matrix-card td.cell .det { color: rgba(255,255,255,0.6); font-size: 11px; } | |
| .matrix-card td.empty { background: transparent; color: var(--gray); } | |
| /* βββ Desktop only β show a friendly gate on small viewports βββββββββββββ | |
| The dashboard is data-dense (multi-column tables, heatmaps, side panels) and | |
| does not condense gracefully below ~900px. Rather than mangle layout on | |
| phones, we show a single message and hide the app. */ | |
| .desktop-only-gate { display: none; position: fixed; inset: 0; z-index: 99999; | |
| background: var(--bg); color: var(--text); flex-direction: column; | |
| align-items: center; justify-content: center; gap: 16px; | |
| padding: 28px; text-align: center; font-size: 15px; line-height: 1.55; } | |
| .desktop-only-gate .icon { font-size: 42px; } | |
| .desktop-only-gate h2 { margin: 0; font-size: 18px; color: var(--text); } | |
| .desktop-only-gate p { margin: 0; color: var(--muted); max-width: 360px; } | |
| @media (max-width: 900px) { | |
| .desktop-only-gate { display: flex; } | |
| /* Hide everything else underneath; the gate fully covers the page. */ | |
| body > header, body > main, body > .filters, body > .loader-overlay { display: none ; } | |
| } | |
| .filters { padding: 8px 18px; background: var(--panel); border-bottom: 1px solid var(--border); | |
| display: flex; gap: 10px; flex-wrap: wrap; align-items: center; flex: 0 0 auto; } | |
| .filters input, .filters select { background: var(--panel2); color: var(--text); | |
| border: 1px solid var(--border); padding: 4px 8px; border-radius: 4px; font-size: 12px; } | |
| .filters input[type=search] { width: 240px; } | |
| .filters label { color: var(--muted); font-size: 12px; } | |
| .filters input, .filters select { font-size: 13px; padding: 5px 10px; } | |
| .filters input[type=search] { width: 280px; } | |
| .view-tabs { display: flex; gap: 0; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; } | |
| .view-tabs button { background: var(--panel2); color: var(--muted); border: 0; padding: 4px 12px; cursor: pointer; font-size: 11px; } | |
| .view-tabs button.active { background: var(--accent); color: white; } | |
| main { flex: 1 1 auto; display: flex; flex-direction: row; min-height: 0; overflow: hidden; } | |
| .view-pane { flex: 1 1 auto; overflow: auto; min-width: 0; min-height: 0; } | |
| .view-pane.hidden { display: none; } | |
| .side-pane { flex: 0 0 640px; overflow: hidden; border-left: 1px solid var(--border); | |
| background: var(--panel); display: flex; flex-direction: column; min-height: 0; | |
| position: relative; } | |
| .side-resizer { position: absolute; left: -3px; top: 0; bottom: 0; width: 7px; cursor: col-resize; | |
| z-index: 10; background: transparent; transition: background 0.15s; } | |
| .side-resizer:hover, .side-resizer.active { background: rgba(89,166,255,0.35); } | |
| body.resizing { cursor: col-resize ; user-select: none; } | |
| body.resizing * { user-select: none; pointer-events: none; } | |
| body.resizing .side-resizer { pointer-events: auto; } | |
| .side-pane.hidden { display: none; } | |
| .side-head { padding: 8px 14px; background: var(--panel2); border-bottom: 1px solid var(--border); | |
| display: flex; gap: 10px; align-items: center; flex: 0 0 auto; } | |
| .side-head h2 { font-size: 13px; margin: 0; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| .side-head button { background: var(--panel); color: var(--text); border: 1px solid var(--border); | |
| padding: 3px 10px; border-radius: 3px; cursor: pointer; font-size: 11px; } | |
| .side-head button:hover { background: var(--border); } | |
| .side-body { padding: 12px 16px; overflow-y: auto; flex: 1 1 auto; min-height: 0; } | |
| .heatmap-wrap { padding: 0; } | |
| table.heatmap { border-collapse: separate; border-spacing: 0; font-size: 12px; | |
| table-layout: fixed; width: max-content; } | |
| table.heatmap col.cg-task { width: 320px; } | |
| table.heatmap col.cg-h { width: 90px; } | |
| table.heatmap col.cg-cell { width: 64px; } | |
| table.heatmap th, table.heatmap td { border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); | |
| padding: 0; vertical-align: middle; overflow: hidden; } | |
| table.heatmap thead th { position: sticky; top: 0; z-index: 3; background: var(--panel); padding: 4px 6px; | |
| text-align: center; font-weight: 600; color: var(--text); font-size: 10.5px; | |
| white-space: nowrap; } | |
| table.heatmap th.col-task, table.heatmap td.col-task { | |
| position: sticky; left: 0; z-index: 5; | |
| background-color: #0e1116; | |
| text-align: left; padding: 4px 8px; | |
| border-right: 2px solid var(--border); | |
| } | |
| table.heatmap thead th.col-task { z-index: 7; background-color: #161b22; } | |
| table.heatmap th.col-h, table.heatmap td.col-h { | |
| position: sticky; left: 320px; z-index: 5; | |
| background-color: #0e1116; | |
| padding: 0 8px; | |
| font-family: ui-monospace, monospace; font-size: 10.5px; | |
| border-right: 2px solid var(--border); color: var(--muted); text-align: left; | |
| } | |
| table.heatmap thead th.col-h { z-index: 7; background-color: #161b22; } | |
| table.heatmap td.col-task { font-size: 12px; line-height: 1.4; vertical-align: middle; } | |
| table.heatmap td.col-task .tid { font-family: ui-monospace, monospace; font-size: 11px; color: var(--muted); } | |
| table.heatmap td.col-task .q { color: var(--text); margin-top: 2px; | |
| display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } | |
| table.heatmap td.col-task .grad-mini { font-size: 9.5px; padding: 1px 5px; border-radius: 3px; margin-right: 4px; } | |
| .gm-yes { background: rgba(62,201,122,0.18); color: var(--pass); } | |
| .gm-partial { background: rgba(230,180,85,0.18); color: var(--warn); } | |
| .gm-no { background: rgba(226,92,92,0.18); color: var(--fail); } | |
| table.heatmap td.cell { width: 64px; height: 26px; cursor: pointer; text-align: center; | |
| font-family: ui-monospace, monospace; font-size: 11px; | |
| transition: filter 0.1s; } | |
| table.heatmap td.cell:hover { filter: brightness(1.4); outline: 1px solid var(--accent); outline-offset: -1px; } | |
| td.cell.empty { background: transparent; color: var(--gray); cursor: default; } | |
| td.cell.empty:hover { filter: none; outline: 0; } | |
| /* Pass@k color gradient β green darker β lighter as k grows */ | |
| td.cell.pass-1 { background: rgba(62,201,122,0.55); color: white; font-weight: 600; } | |
| td.cell.pass-2 { background: rgba(62,201,122,0.40); color: white; font-weight: 600; } | |
| td.cell.pass-3 { background: rgba(62,201,122,0.28); color: white; font-weight: 600; } | |
| td.cell.pass-4 { background: rgba(62,201,122,0.18); color: white; font-weight: 600; } | |
| td.cell.fail { background: rgba(226,92,92,0.25); color: rgba(255,255,255,0.85); } | |
| td.cell.no_answer { background: rgba(226,92,92,0.12); color: rgba(226,92,92,0.7); } | |
| td.cell.err { background: rgba(230,180,85,0.18); color: var(--warn); } | |
| tr.task-spacer td { border: 0 ; background-color: #0e1116; height: 8px; padding: 0; } | |
| tr.selected td.col-task, tr.selected td.col-h { background: rgba(89,166,255,0.10); } | |
| tr.selected td.col-task { border-left: 3px solid var(--accent); padding-left: 5px; } | |
| /* List view */ | |
| #view-list { padding: 0; } | |
| .task-row { padding: 8px 14px; border-bottom: 1px solid var(--border); cursor: pointer; | |
| display: grid; grid-template-columns: 28px 1fr auto; gap: 10px; align-items: center; } | |
| .task-row:hover { background: var(--panel); } | |
| .task-row.selected { background: var(--panel2); border-left: 3px solid var(--accent); padding-left: 11px; } | |
| .diff { font-size: 10px; text-transform: uppercase; font-weight: 600; padding: 1px 6px; | |
| border-radius: 3px; letter-spacing: 0.5px; display: inline-block; } | |
| .diff.easy { background: rgba(62, 201, 122, 0.15); color: var(--pass); } | |
| .diff.medium { background: rgba(230, 180, 85, 0.15); color: var(--warn); } | |
| .diff.hard { background: rgba(226, 92, 92, 0.15); color: var(--fail); } | |
| .task-row .id { font-family: ui-monospace, monospace; font-size: 11px; color: var(--muted); } | |
| .task-row .q { color: var(--text); margin-top: 2px; } | |
| .grad-badge { font-size: 10px; padding: 2px 7px; border-radius: 3px; font-weight: 600; white-space: nowrap; } | |
| .grad-yes { background: rgba(62, 201, 122, 0.2); color: var(--pass); } | |
| .grad-partial { background: rgba(230, 180, 85, 0.2); color: var(--warn); } | |
| .grad-no { background: rgba(226, 92, 92, 0.2); color: var(--fail); } | |
| /* Detail */ | |
| .side-body h2 { font-size: 14px; margin: 0 0 6px; } | |
| .side-body .question { background: var(--panel2); padding: 10px; border-radius: 5px; | |
| border-left: 3px solid var(--accent); margin-bottom: 10px; } | |
| .side-body .meta { color: var(--muted); font-size: 11px; margin-bottom: 8px; } | |
| .side-body .meta b { color: var(--text); } | |
| .side-body .gold { background: rgba(62,201,122,0.08); padding: 6px 10px; border-radius: 4px; | |
| font-family: ui-monospace, monospace; font-size: 12px; margin-bottom: 10px; | |
| border-left: 3px solid var(--pass); } | |
| .trial-table { width: 100%; border-collapse: collapse; font-size: 11px; margin-top: 6px; } | |
| .trial-table th { text-align: left; padding: 5px 6px; color: var(--muted); font-weight: 500; | |
| border-bottom: 1px solid var(--border); font-size: 10px; text-transform: uppercase; } | |
| .trial-table td { padding: 5px 6px; border-bottom: 1px solid var(--border); vertical-align: middle; } | |
| .trial-table tr:hover { background: var(--panel2); } | |
| .trial-table .k { font-family: ui-monospace, monospace; font-weight: 600; } | |
| .trial-table .reward.pass { color: var(--pass); font-weight: 600; } | |
| .trial-table .reward.fail { color: var(--fail); } | |
| .trial-table .view-btn { background: var(--panel2); color: var(--accent); border: 1px solid var(--border); | |
| padding: 2px 8px; border-radius: 3px; cursor: pointer; font-size: 10px; } | |
| .trial-table .view-btn:hover { background: var(--border); } | |
| .agent-pill { font-size: 10px; padding: 1px 5px; border-radius: 3px; background: var(--panel2); | |
| border: 1px solid var(--border); font-family: ui-monospace, monospace; } | |
| .msg { margin-bottom: 8px; border-radius: 5px; overflow: hidden; } | |
| .msg .msg-head { padding: 4px 10px; font-size: 10px; text-transform: uppercase; font-weight: 600; | |
| letter-spacing: 0.5px; } | |
| .msg .msg-body { padding: 8px 12px; white-space: pre-wrap; word-break: break-word; | |
| font-family: ui-monospace, SF Mono, Menlo, monospace; font-size: 11.5px; line-height: 1.5; } | |
| .msg.system .msg-head { background: #2c3a4f; color: #b6c4e2; } | |
| .msg.system .msg-body { background: rgba(44, 58, 79, 0.3); } | |
| .msg.user .msg-head { background: #1f3a4d; color: #9bcce8; } | |
| .msg.user .msg-body { background: rgba(31, 58, 77, 0.3); } | |
| .msg.assistant .msg-head { background: #2e4030; color: #a8d5af; } | |
| .msg.assistant .msg-body { background: rgba(46, 64, 48, 0.25); } | |
| .msg.tool .msg-head { background: #4a3e23; color: #e6c98a; } | |
| .msg.tool .msg-body { background: rgba(74, 62, 35, 0.25); } | |
| .tool-call { margin-top: 6px; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; background: var(--panel2); } | |
| .tool-call .tc-head { padding: 5px 10px; background: rgba(89,166,255,0.08); display: flex; gap: 10px; | |
| align-items: center; font-size: 11px; flex-wrap: wrap; border-bottom: 1px solid var(--border); } | |
| .tool-call .tc-name { color: var(--accent); font-weight: 600; font-family: ui-monospace, monospace; } | |
| .tool-call .tc-id { color: var(--gray); font-size: 9.5px; font-family: ui-monospace, monospace; } | |
| .tool-call .tc-meta { color: var(--muted); font-size: 10.5px; font-family: ui-monospace, monospace; | |
| margin-left: auto; } | |
| .tool-call .tc-code { margin: 0; padding: 8px 12px; background: #0a0d12; font-family: ui-monospace, SF Mono, Menlo, monospace; | |
| font-size: 11.5px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; | |
| max-height: 360px; overflow: auto; color: #e8eaef; border: 0; } | |
| .tool-call .tc-code.lang-python { border-left: 3px solid #4584b6; } | |
| .tool-call .tc-code.lang-bash { border-left: 3px solid #4eaa25; } | |
| .tool-call .tc-code.lang-json { border-left: 3px solid #cb7d29; } | |
| .tool-output { margin: 0; padding: 8px 12px; background: rgba(0,0,0,0.35); | |
| font-family: ui-monospace, SF Mono, Menlo, monospace; font-size: 11.5px; line-height: 1.5; | |
| white-space: pre-wrap; word-break: break-word; max-height: 360px; overflow: auto; | |
| color: #c9d0d8; border-top: 1px solid var(--border); } | |
| .tool-output.empty { color: var(--gray); font-style: italic; } | |
| .tool-output-head { padding: 4px 10px; background: rgba(74,62,35,0.25); color: #e6c98a; | |
| font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; | |
| display: flex; gap: 8px; } | |
| .tool-output-head .rc { margin-left: auto; color: var(--muted); } | |
| .turn { border: 1px solid var(--border); border-radius: 6px; padding: 0; margin-bottom: 10px; | |
| background: var(--panel); overflow: hidden; } | |
| .turn-head { padding: 4px 12px; background: var(--panel2); color: var(--muted); font-size: 10px; | |
| text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; | |
| border-bottom: 1px solid var(--border); } | |
| .turn-thinking { padding: 8px 12px; color: #a8d5af; white-space: pre-wrap; word-break: break-word; | |
| font-size: 12px; line-height: 1.55; } | |
| .pill { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; | |
| background: var(--panel2); color: var(--muted); border: 1px solid var(--border); margin-right: 4px; } | |
| .pill.k { font-family: ui-monospace, monospace; } | |
| .pill.ok { background: rgba(62,201,122,0.15); color: var(--pass); border-color: transparent; } | |
| .pill.err { background: rgba(226,92,92,0.15); color: var(--fail); border-color: transparent; } | |
| .pill.warn { background: rgba(230,180,85,0.15); color: var(--warn); border-color: transparent; } | |
| .grader-box { background: var(--panel2); padding: 8px 10px; border-radius: 4px; font-family: ui-monospace, monospace; | |
| font-size: 11px; white-space: pre-wrap; color: var(--muted); margin-bottom: 8px; } | |
| details { margin: 10px 0; } | |
| details summary { cursor: pointer; color: var(--muted); padding: 4px 0; } | |
| details > pre { background: var(--panel2); padding: 8px; border-radius: 4px; overflow-x: auto; | |
| font-size: 11px; max-height: 300px; } | |
| .legend { display: inline-flex; gap: 8px; align-items: center; font-size: 10.5px; color: var(--muted); margin-left: 8px; } | |
| .legend .swatch { display: inline-block; width: 14px; height: 12px; border-radius: 2px; margin-right: 3px; vertical-align: middle; } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Desktop-only gate β shown under 900px viewport, hides everything else. --> | |
| <div class="desktop-only-gate"> | |
| <div class="icon">π₯οΈ</div> | |
| <h2>Please view on desktop</h2> | |
| <p>This dashboard is data-dense β heatmaps, multi-column tables, and side-by-side trajectory panels. It's optimized for a wider screen. Open this page on a laptop or larger display to use it.</p> | |
| </div> | |
| <!-- Full-page loader, removed once the first dataset's summary lands. --> | |
| <div class="loader-overlay" id="loader"> | |
| <div class="loader-spinner"></div> | |
| <div class="loader-msg" id="loader-msg">loadingβ¦</div> | |
| </div> | |
| <header> | |
| <div class="hdr-bar"> | |
| <div class="bench-toggle-wrap"> | |
| <div class="ds-tabs" id="ds-tabs"></div> | |
| <button class="info-btn" id="info-btn" title="benchmark info" onclick="toggleInfoPopover()">β</button> | |
| <div class="info-popover hidden" id="info-popover"></div> | |
| </div> | |
| <div class="pills" id="hdr-pills"></div> | |
| <button class="toggle" onclick="toggleHeader()" id="toggle-btn">βΌ details</button> | |
| </div> | |
| <div class="hdr-expanded hidden" id="hdr-expanded"> | |
| <div class="stats" id="stats"></div> | |
| <div class="charts" id="charts"></div> | |
| </div> | |
| </header> | |
| <div class="filters"> | |
| <input type="search" id="search" placeholder="Search task id, question, kaggleβ¦" /> | |
| <label>Difficulty</label> | |
| <select id="diff"> | |
| <option value="">all</option> | |
| <option value="0">L0</option><option value="1">L1</option> | |
| <option value="2">L2</option><option value="3">L3</option> | |
| <option value="4">L4</option> | |
| </select> | |
| <label>Status</label> | |
| <select id="status"><option value="">all</option><option value="grad">graduated (β₯2 harnesses)</option><option value="partial">any pass</option><option value="fail">all failed</option></select> | |
| <label>Pass on harness</label> | |
| <select id="harness"><option value="">any</option></select> | |
| <div class="view-tabs" style="margin-left:auto"> | |
| <button id="tab-heatmap" class="active" onclick="setView('heatmap')">heatmap</button> | |
| <button id="tab-list" onclick="setView('list')">list</button> | |
| </div> | |
| <span class="legend"> | |
| <span><span class="swatch" style="background:rgba(62,201,122,0.55)"></span>pass@1</span> | |
| <span><span class="swatch" style="background:rgba(62,201,122,0.40)"></span>@2</span> | |
| <span><span class="swatch" style="background:rgba(62,201,122,0.28)"></span>@3</span> | |
| <span><span class="swatch" style="background:rgba(62,201,122,0.18)"></span>@4</span> | |
| <span><span class="swatch" style="background:rgba(226,92,92,0.25)"></span>fail</span> | |
| <span><span class="swatch" style="background:transparent;border:1px solid var(--border)"></span>not run</span> | |
| </span> | |
| <span id="count" style="color:var(--muted);font-size:11px"></span> | |
| </div> | |
| <main> | |
| <div class="view-pane" id="view-heatmap"> | |
| <div class="heatmap-wrap" id="heatmap-wrap"></div> | |
| </div> | |
| <div class="view-pane hidden" id="view-list"></div> | |
| <aside class="side-pane hidden" id="side-pane"> | |
| <div class="side-resizer" id="side-resizer" title="drag to resize"></div> | |
| <div class="side-head"> | |
| <button id="side-back" onclick="sideBack()" style="display:none">β back</button> | |
| <h2 id="side-title"></h2> | |
| <button onclick="sideClose()">close β</button> | |
| </div> | |
| <div class="side-body" id="side-body"></div> | |
| </aside> | |
| </main> | |
| <script> | |
| let DATA = null; | |
| let selectedTaskIdx = null; | |
| let currentView = 'heatmap'; | |
| function fmtCost(c) { return '$' + (c||0).toFixed(4); } | |
| function fmtT(n) { if(!n) return '0'; if(n>=1e6) return (n/1e6).toFixed(1)+'M'; if(n>=1e3) return (n/1e3).toFixed(1)+'k'; return ''+n; } | |
| function fmtDur(s) { s = s||0; if(s<60) return s.toFixed(0)+'s'; return (s/60).toFixed(1)+'m'; } | |
| function escape(s) { return String(s == null ? '' : s) | |
| .replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>') | |
| .replace(/"/g,'"'); } | |
| function gradeClass(t) { if (t.graduated) return 'grad-yes'; if (t.any_pass) return 'grad-partial'; return 'grad-no'; } | |
| function gradeLabel(t) { | |
| if (t.graduated) return `β graduated @k${t.best_passed_at}`; | |
| if (t.any_pass) return `~ partial (${t.passing_harnesses.length} harness)`; | |
| return 'β all failed'; | |
| } | |
| function gradeMiniClass(t) { if (t.graduated) return 'gm-yes'; if (t.any_pass) return 'gm-partial'; return 'gm-no'; } | |
| function gradeMiniLabel(t) { | |
| if (t.graduated) return 'β' + (t.best_passed_at != null ? `@${t.best_passed_at}` : ''); | |
| if (t.any_pass) return '~'; | |
| return 'β'; | |
| } | |
| function renderHeaderStats() { | |
| const s = DATA.summary; | |
| document.getElementById('hdr-pills').innerHTML = [ | |
| ['Tasks', s.tasks_total], | |
| ['Any pass', s.tasks_any_pass], | |
| ['Graduated', s.tasks_graduated], | |
| ['Failed', s.tasks_failed], | |
| ['Attempts', s.attempts_total], | |
| ['Tok in/out', fmtT(s.tokens_prompt) + '/' + fmtT(s.tokens_completion)], | |
| ].map(([l,v]) => `<span class="pill-stat">${l} <b>${v}</b></span>`).join(''); | |
| const stats = [ | |
| ['Tasks', s.tasks_total], | |
| ['Graduated (β₯2 harnesses)', s.tasks_graduated], | |
| ['Any pass (β₯1)', s.tasks_any_pass], | |
| ['Failed (0)', s.tasks_failed], | |
| ['Attempts', s.attempts_total], | |
| ['Attempts passed', s.attempts_passed], | |
| ['Tokens (in/out)', `${fmtT(s.tokens_prompt)} / ${fmtT(s.tokens_completion)}`], | |
| ]; | |
| document.getElementById('stats').innerHTML = stats.map(([l,n]) => | |
| `<div class="stat"><div class="num">${n}</div><div class="lbl">${l}</div></div>` | |
| ).join(''); | |
| const byModel = DATA.by_model.slice(); | |
| const byAgent = DATA.by_agent.slice(); | |
| renderCharts(s, byModel, byAgent); | |
| // populate harness dropdown | |
| const hsel = document.getElementById('harness'); | |
| for (const a of DATA.agents_order) { | |
| const o = document.createElement('option'); | |
| o.value = a; o.textContent = a; | |
| hsel.appendChild(o); | |
| } | |
| } | |
| function toggleHeader() { | |
| const ex = document.getElementById('hdr-expanded'); | |
| ex.classList.toggle('hidden'); | |
| document.getElementById('toggle-btn').textContent = | |
| ex.classList.contains('hidden') ? 'βΌ details' : 'β² hide details'; | |
| } | |
| // βββ Dense details panel βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Compact, no-repeat layout: | |
| // 1. KPI strip (rendered in renderHeaderStats above) | |
| // 2. Per-model marginal table β Cells | @1 | @2 | @3 | @4 | spark | Tok i/o | |
| // 3. Per-harness marginal table β same shape | |
| // 4. Three matrices: ModelΓHarness, ModelΓDifficulty, HarnessΓDifficulty | |
| function renderCharts(s, byModel, byAgent) { | |
| const charts = document.getElementById('charts'); | |
| if (!charts) return; | |
| // ββ Compute @1..@k_max counts per (model, agent) by walking task cells ββββ | |
| // by_model / by_agent only carry @1 and @k_max; we want all four. Also need | |
| // joint counts for the three matrices. Single pass over DATA.tasks β cells. | |
| const KMAX = s.k_max || 4; | |
| const harnessOrder = (DATA.agents_order && DATA.agents_order.length) | |
| ? DATA.agents_order.slice() | |
| : byAgent.map(a => a.agent); | |
| const agentLabel = {}; | |
| for (const a of byAgent) agentLabel[a.agent] = a.agent_label || a.agent; | |
| const blank = () => ({cells: 0, k: Array(KMAX).fill(0)}); // k[i] = # cells that passed at attempt β€ i+1 | |
| const modelAgg = {}; | |
| const agentAgg = {}; | |
| const diffAgg = {}; | |
| const mxhAgg = {}; // model β agent β blank | |
| const mxdAgg = {}; // model β diff β blank | |
| const hxdAgg = {}; // agent β diff β blank | |
| const diffOrder = []; // preserve first-seen order from tasks | |
| for (const t of (DATA.tasks || [])) { | |
| const dkey = (t.difficulty || ('L' + (t.difficulty_level ?? 0))).toString(); | |
| if (!diffAgg[dkey]) { diffAgg[dkey] = blank(); diffOrder.push(dkey); } | |
| for (const ck of Object.keys(t.cells || {})) { | |
| const c = t.cells[ck]; | |
| const m = c.model, a = c.agent; | |
| modelAgg[m] = modelAgg[m] || blank(); | |
| agentAgg[a] = agentAgg[a] || blank(); | |
| mxhAgg[m] = mxhAgg[m] || {}; mxhAgg[m][a] = mxhAgg[m][a] || blank(); | |
| mxdAgg[m] = mxdAgg[m] || {}; mxdAgg[m][dkey] = mxdAgg[m][dkey] || blank(); | |
| hxdAgg[a] = hxdAgg[a] || {}; hxdAgg[a][dkey] = hxdAgg[a][dkey] || blank(); | |
| const slots = [modelAgg[m], agentAgg[a], diffAgg[dkey], | |
| mxhAgg[m][a], mxdAgg[m][dkey], hxdAgg[a][dkey]]; | |
| for (const sl of slots) sl.cells += 1; | |
| // c.passed_at = attempt at which the cell first passed (1..k_max) or null. | |
| const pa = c.passed_at; | |
| if (pa != null && pa >= 1 && pa <= KMAX) { | |
| for (const sl of slots) for (let k = pa - 1; k < KMAX; k++) sl.k[k] += 1; | |
| } | |
| } | |
| } | |
| // Heat color helper β redβamberβgreen by rate. | |
| const heatBg = (pct) => { | |
| if (pct == null || isNaN(pct)) return 'transparent'; | |
| const p = Math.max(0, Math.min(1, pct / 100)); | |
| const r = Math.round(226 + (62 - 226) * p); | |
| const g = Math.round(92 + (201 - 92) * p); | |
| const b = Math.round(92 + (122 - 92) * p); | |
| const a = 0.18 + p * 0.55; | |
| return `rgba(${r},${g},${b},${a.toFixed(3)})`; | |
| }; | |
| // ββ Per-(model|harness) marginal table cells: heat-tinted @k cell + count ββ | |
| function kcell(agg, k) { | |
| if (!agg || !agg.cells) return `<td class="kc empty">β</td>`; | |
| const cnt = agg.k[k - 1] || 0; | |
| const pct = cnt / agg.cells * 100; | |
| return `<td class="kc" style="background:${heatBg(pct)}"> | |
| <div class="kc-num">${pct.toFixed(0)}%</div> | |
| <div class="kc-sub">${cnt}/${agg.cells}</div> | |
| </td>`; | |
| } | |
| function sparkCell(agg) { | |
| if (!agg || !agg.cells) return '<td class="spark"></td>'; | |
| const denom = agg.cells; | |
| const bands = []; | |
| let prev = 0; | |
| for (let i = 0; i < KMAX; i++) { | |
| const inc = (agg.k[i] - prev) / denom * 100; | |
| bands.push(`<span class="s${Math.min(i+1, 4)}" style="width:${Math.max(0, inc)}%" title="@${i+1} +${inc.toFixed(0)}%"></span>`); | |
| prev = agg.k[i]; | |
| } | |
| return `<td class="spark"><div class="sparkbar">${bands.join('')}</div></td>`; | |
| } | |
| function tokCell(model_or_agent) { | |
| return `<td class="num">${fmtT(model_or_agent.tokens_in)}/${fmtT(model_or_agent.tokens_out)}</td>`; | |
| } | |
| const kHeads = Array.from({length: KMAX}, (_, i) => | |
| `<th class="kc">@${i + 1}</th>`).join(''); | |
| const modelTable = ` | |
| <div class="marg-card"> | |
| <h4>By model <span class="sub">pass@k heat-tinted Β· spark = @1β@${KMAX} progression</span></h4> | |
| <div class="marg-wrap"> | |
| <table class="marg-table"> | |
| <thead><tr><th>Model</th><th class="num">Cells</th>${kHeads}<th>Ξ@1β@${KMAX}</th><th class="num">Tok i/o</th></tr></thead> | |
| <tbody> | |
| ${byModel.map(m => { | |
| const agg = modelAgg[m.model]; | |
| return `<tr> | |
| <td class="lbl">${escape(m.model_short)}</td> | |
| <td class="num">${m.cells}</td> | |
| ${Array.from({length: KMAX}, (_,i) => kcell(agg, i+1)).join('')} | |
| ${sparkCell(agg)} | |
| ${tokCell(m)} | |
| </tr>`; | |
| }).join('')} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div>`; | |
| const agentTable = ` | |
| <div class="marg-card"> | |
| <h4>By harness <span class="sub">pass@k heat-tinted Β· spark = @1β@${KMAX} progression</span></h4> | |
| <div class="marg-wrap"> | |
| <table class="marg-table"> | |
| <thead><tr><th>Harness</th><th class="num">Cells</th>${kHeads}<th>Ξ@1β@${KMAX}</th><th class="num">Tok i/o</th></tr></thead> | |
| <tbody> | |
| ${byAgent.map(a => { | |
| const agg = agentAgg[a.agent]; | |
| return `<tr> | |
| <td class="lbl">${escape(a.agent_label)}</td> | |
| <td class="num">${a.cells}</td> | |
| ${Array.from({length: KMAX}, (_,i) => kcell(agg, i+1)).join('')} | |
| ${sparkCell(agg)} | |
| ${tokCell(a)} | |
| </tr>`; | |
| }).join('')} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div>`; | |
| // ββ Three matrices (MΓH, MΓD, HΓD) β pass@k_max % heat-tinted ββββββββββββ | |
| function matrix(rowKeys, rowLabel, colKeys, colLabel, agg) { | |
| const head = `<tr><th></th>${colKeys.map(c => | |
| `<th>${escape(colLabel(c))}</th>`).join('')}</tr>`; | |
| const body = rowKeys.map(rk => { | |
| const cells = colKeys.map(ck => { | |
| const slot = (agg[rk] || {})[ck]; | |
| if (!slot || !slot.cells) return '<td class="empty">β</td>'; | |
| const cnt = slot.k[KMAX - 1] || 0; | |
| const pct = cnt / slot.cells * 100; | |
| return `<td class="cell" style="background:${heatBg(pct)}"> | |
| <b>${pct.toFixed(0)}%</b><div class="det">${cnt}/${slot.cells}</div> | |
| </td>`; | |
| }).join(''); | |
| return `<tr><td class="lbl">${escape(rowLabel(rk))}</td>${cells}</tr>`; | |
| }).join(''); | |
| return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`; | |
| } | |
| const modelKeys = byModel.map(m => m.model); | |
| const modelLbl = (k) => (byModel.find(m => m.model === k) || {model_short: k}).model_short; | |
| const harnessLbl = (k) => agentLabel[k] || k; | |
| const matrices = ` | |
| <div class="matrices"> | |
| <div class="matrix-card"> | |
| <h4>Model Γ Harness <span class="sub">pass@${KMAX}</span></h4> | |
| <div class="matrix-wrap">${matrix(modelKeys, modelLbl, harnessOrder, harnessLbl, mxhAgg)}</div> | |
| </div> | |
| <div class="matrix-card"> | |
| <h4>Model Γ Difficulty <span class="sub">pass@${KMAX}</span></h4> | |
| <div class="matrix-wrap">${matrix(modelKeys, modelLbl, diffOrder, (d) => d, mxdAgg)}</div> | |
| </div> | |
| <div class="matrix-card"> | |
| <h4>Harness Γ Difficulty <span class="sub">pass@${KMAX}</span></h4> | |
| <div class="matrix-wrap">${matrix(harnessOrder, harnessLbl, diffOrder, (d) => d, hxdAgg)}</div> | |
| </div> | |
| </div>`; | |
| charts.innerHTML = modelTable + agentTable + matrices; | |
| } | |
| function applyFilters(tasks) { | |
| const q = document.getElementById('search').value.toLowerCase(); | |
| const diff = document.getElementById('diff').value; | |
| const status = document.getElementById('status').value; | |
| const harness = document.getElementById('harness').value; | |
| return tasks.filter(t => { | |
| if (q && !(t.id.toLowerCase().includes(q) | |
| || (t.question||'').toLowerCase().includes(q) | |
| || (t.kaggle||'').toLowerCase().includes(q))) return false; | |
| if (diff !== "" && String(t.difficulty_level) !== diff) return false; | |
| if (status === 'grad' && !t.graduated) return false; | |
| if (status === 'partial' && (t.graduated || !t.any_pass)) return false; | |
| if (status === 'fail' && t.any_pass) return false; | |
| if (harness && !t.passing_harnesses.includes(harness)) return false; | |
| return true; | |
| }); | |
| } | |
| function cellClass(c) { | |
| if (!c || c.n_attempts === 0) return 'empty'; | |
| if (c.passed_at != null) return 'pass-' + Math.min(c.passed_at, 4); | |
| // never passed β inspect the last attempt's error_kind for a finer color | |
| const last = c.attempts[c.attempts.length - 1]; | |
| if (last && last.error_kind === 'no_answer') return 'no_answer'; | |
| if (last && last.error_kind && last.error_kind !== 'ok' && last.error_kind !== 'wrong_answer') return 'err'; | |
| return 'fail'; | |
| } | |
| function cellSymbol(c) { | |
| if (!c || c.n_attempts === 0) return 'Β·'; | |
| if (c.passed_at != null) return String(c.passed_at); | |
| const last = c.attempts[c.attempts.length - 1]; | |
| if (last && last.error_kind === 'no_answer') return 'β '; | |
| if (last && last.error_kind && last.error_kind !== 'ok' && last.error_kind !== 'wrong_answer') return 'β '; | |
| return 'β'; | |
| } | |
| function renderHeatmap() { | |
| const visible = applyFilters(DATA.tasks); | |
| document.getElementById('count').textContent = `${visible.length} of ${DATA.tasks.length} tasks`; | |
| const models = DATA.models_order; | |
| const agents = DATA.agents_order; | |
| let cgroup = `<col class="cg-task"><col class="cg-h">`; | |
| for (const _ of models) cgroup += `<col class="cg-cell">`; | |
| let head = `<tr><th class="col-task">Task</th><th class="col-h">Harness</th>`; | |
| for (const m of models) { | |
| head += `<th>${escape(m)}</th>`; | |
| } | |
| head += `</tr>`; | |
| const nCols = 2 + models.length; | |
| let body = ''; | |
| visible.forEach((t, ti) => { | |
| if (ti > 0) body += `<tr class="task-spacer"><td colspan="${nCols}"></td></tr>`; | |
| const realIdx = DATA.tasks.indexOf(t); | |
| const selCls = realIdx === selectedTaskIdx ? 'selected' : ''; | |
| agents.forEach((a, ai) => { | |
| const isFirst = ai === 0; | |
| let row = `<tr class="${selCls}" data-task="${realIdx}">`; | |
| if (isFirst) { | |
| row += `<td class="col-task" rowspan="${agents.length}" onclick="selectTask(${realIdx})" style="cursor:pointer"> | |
| <div><span class="diff ${escape(t.difficulty_class)}">${t.difficulty || ("L" + t.difficulty_level)}</span> | |
| <span class="grad-mini ${gradeMiniClass(t)}">${gradeMiniLabel(t)}</span> | |
| <span class="tid">${escape(t.id)}</span></div> | |
| <div class="q">${escape(t.question)}</div> | |
| </td>`; | |
| } | |
| row += `<td class="col-h">${escape(a)}</td>`; | |
| for (const m of models) { | |
| const key = (t.cell_map[a] || {})[m]; | |
| const c = key ? t.cells[key] : null; | |
| const cls = cellClass(c); | |
| const sym = cellSymbol(c); | |
| if (c) { | |
| const passLabel = c.passed_at != null ? `pass@${c.passed_at}` : 'never passed'; | |
| const lastAtt = c.attempts[c.attempts.length - 1]; | |
| const tooltip = `${m} / ${a}\n${passLabel} (${c.n_attempts} attempt${c.n_attempts===1?'':'s'})\n${lastAtt ? 'last: ' + lastAtt.error_kind + ' $' + lastAtt.cost.toFixed(4) : ''}`; | |
| row += `<td class="cell ${cls}" title="${escape(tooltip)}" onclick="openCellDetail(${realIdx},'${escape(key)}')">${sym}</td>`; | |
| } else { | |
| row += `<td class="cell empty">Β·</td>`; | |
| } | |
| } | |
| row += `</tr>`; | |
| body += row; | |
| }); | |
| }); | |
| document.getElementById('heatmap-wrap').innerHTML = | |
| `<table class="heatmap"><colgroup>${cgroup}</colgroup><thead>${head}</thead><tbody>${body}</tbody></table>`; | |
| } | |
| function renderList() { | |
| const visible = applyFilters(DATA.tasks); | |
| document.getElementById('count').textContent = `${visible.length} of ${DATA.tasks.length} tasks`; | |
| const harnessOrder = DATA.agents_order; | |
| const html = visible.map(t => { | |
| const realIdx = DATA.tasks.indexOf(t); | |
| const harness = harnessOrder.map(a => { | |
| // Best cell across models for this harness | |
| let bestPa = null; let bestModel = null; | |
| for (const m of DATA.models_order) { | |
| const key = (t.cell_map[a] || {})[m]; | |
| if (!key) continue; | |
| const c = t.cells[key]; | |
| if (c && c.passed_at != null && (bestPa == null || c.passed_at < bestPa)) { | |
| bestPa = c.passed_at; bestModel = m; | |
| } | |
| } | |
| const has = harnessOrder.some(_ => (t.cell_map[a] || {})[Object.keys(t.cell_map[a]||{})[0] || ''] != null) || Object.keys(t.cell_map[a] || {}).length > 0; | |
| if (bestPa != null) { | |
| return `<span class="pill ok" title="${escape(bestModel)}">${a} β@${bestPa}</span>`; | |
| } | |
| if (!has) return `<span class="pill">${a} β</span>`; | |
| return `<span class="pill err">${a} β</span>`; | |
| }).join(''); | |
| return `<div class="task-row ${realIdx===selectedTaskIdx?'selected':''}" onclick="selectTask(${realIdx})"> | |
| <span class="diff ${escape(t.difficulty_class)}">${t.difficulty || ("L" + t.difficulty_level)}</span> | |
| <div> | |
| <div class="id">${escape(t.id)}</div> | |
| <div class="q">${escape(t.question).slice(0,160)}${(t.question||'').length>160?'β¦':''}</div> | |
| <div style="margin-top:5px">${harness}</div> | |
| </div> | |
| <div style="text-align:right"> | |
| <div class="grad-badge ${gradeClass(t)}">${gradeLabel(t)}</div> | |
| <div style="color:var(--muted);font-size:10px;margin-top:4px">${fmtCost(t.total_cost)} Β· ${t.total_attempts} attempts</div> | |
| </div> | |
| </div>`; | |
| }).join(''); | |
| document.getElementById('view-list').innerHTML = html || `<div style="padding:20px;color:var(--muted)">No tasks match filters.</div>`; | |
| } | |
| function renderCurrent() { | |
| if (currentView === 'heatmap') renderHeatmap(); | |
| else renderList(); | |
| } | |
| function setView(v) { | |
| currentView = v; | |
| document.getElementById('view-heatmap').classList.toggle('hidden', v !== 'heatmap'); | |
| document.getElementById('view-list').classList.toggle('hidden', v !== 'list'); | |
| document.getElementById('tab-heatmap').classList.toggle('active', v === 'heatmap'); | |
| document.getElementById('tab-list').classList.toggle('active', v === 'list'); | |
| renderCurrent(); | |
| } | |
| let sideStack = []; | |
| function openSide() { document.getElementById('side-pane').classList.remove('hidden'); } | |
| function sideClose() { | |
| document.getElementById('side-pane').classList.add('hidden'); | |
| sideStack = []; | |
| selectedTaskIdx = null; | |
| document.getElementById('side-back').style.display = 'none'; | |
| renderCurrent(); | |
| } | |
| function sideBack() { | |
| if (sideStack.length <= 1) { sideClose(); return; } | |
| sideStack.pop(); | |
| applySideTop(); | |
| } | |
| function applySideTop() { | |
| const top = sideStack[sideStack.length - 1]; | |
| document.getElementById('side-back').style.display = sideStack.length > 1 ? '' : 'none'; | |
| if (top.kind === 'task') renderTaskDetail(DATA.tasks[top.taskIdx]); | |
| else if (top.kind === 'cell') renderCellDetail(top.taskIdx, top.cellKey); | |
| else if (top.kind === 'trace') renderTraceSide(top.taskIdx, top.cellKey, top.attemptIdx); | |
| } | |
| function selectTask(idx) { | |
| selectedTaskIdx = idx; | |
| sideStack = [{kind: 'task', taskIdx: idx}]; | |
| openSide(); | |
| applySideTop(); | |
| renderCurrent(); | |
| } | |
| function openCellDetail(taskIdx, cellKey) { | |
| selectedTaskIdx = taskIdx; | |
| if (sideStack.length === 0) sideStack.push({kind: 'task', taskIdx}); | |
| sideStack.push({kind: 'cell', taskIdx, cellKey}); | |
| openSide(); | |
| applySideTop(); | |
| renderCurrent(); | |
| } | |
| function openTrace(taskIdx, cellKey, attemptIdx) { | |
| selectedTaskIdx = taskIdx; | |
| if (sideStack.length === 0) sideStack.push({kind: 'task', taskIdx}); | |
| sideStack.push({kind: 'trace', taskIdx, cellKey, attemptIdx}); | |
| openSide(); | |
| applySideTop(); | |
| renderCurrent(); | |
| } | |
| function renderTaskDetail(t) { | |
| const taskIdx = DATA.tasks.indexOf(t); | |
| // Build per-cell rows (one per (model, agent) cell with attempts) | |
| const cellRows = Object.entries(t.cells).map(([key, c]) => { | |
| const passLabel = c.passed_at != null ? `<span class="pill ok">pass@${c.passed_at}</span>` : `<span class="pill err">β</span>`; | |
| const totalCost = c.attempts.reduce((s, a) => s + a.cost, 0); | |
| return `<tr onclick="openCellDetail(${taskIdx}, '${escape(key)}')" style="cursor:pointer"> | |
| <td><span class="pill k">${escape(c.model)}</span></td> | |
| <td><span class="agent-pill">${escape(c.agent)}</span></td> | |
| <td>${passLabel}</td> | |
| <td>${c.n_attempts}</td> | |
| <td>${fmtCost(totalCost)}</td> | |
| <td><button class="view-btn">view β</button></td> | |
| </tr>`; | |
| }).join(''); | |
| document.getElementById('side-title').textContent = t.id; | |
| document.getElementById('side-body').innerHTML = ` | |
| <div class="meta"> | |
| <span class="diff ${escape(t.difficulty_class)}">${t.difficulty || ("L" + t.difficulty_level)}</span> Β· | |
| <b>Kaggle:</b> ${escape(t.kaggle || 'β')} Β· | |
| <b>Reward mode:</b> ${escape(t.reward_mode || '?')} | |
| </div> | |
| <div class="question">${escape(t.question)}</div> | |
| <div class="gold"><b>Gold:</b> ${escape(t.answer)}</div> | |
| <div class="meta"> | |
| <span class="grad-badge ${gradeClass(t)}">${gradeLabel(t)}</span> | |
| <b>Passing harnesses:</b> ${t.passing_harnesses.join(', ') || 'β'} | |
| <b>Cost:</b> ${fmtCost(t.total_cost)} <b>Attempts:</b> ${t.total_attempts} | |
| </div> | |
| <table class="trial-table"> | |
| <thead><tr><th>Model</th><th>Harness</th><th>Result</th><th>Attempts</th><th>Cost</th><th></th></tr></thead> | |
| <tbody>${cellRows || '<tr><td colspan="6" style="color:var(--muted)">(no cells)</td></tr>'}</tbody> | |
| </table> | |
| `; | |
| } | |
| function renderCellDetail(taskIdx, cellKey) { | |
| const t = DATA.tasks[taskIdx]; | |
| const c = t.cells[cellKey]; | |
| const passLabel = c.passed_at != null | |
| ? `<span class="pill ok">PASS @k${c.passed_at}</span>` | |
| : `<span class="pill err">FAIL after ${c.n_attempts} attempts</span>`; | |
| document.getElementById('side-title').innerHTML = | |
| `${escape(t.id)} Β· ${escape(c.model)} Β· ${escape(c.agent)} ${passLabel}`; | |
| const attRows = c.attempts.map((a, i) => { | |
| const rcls = a.reward >= 1 ? 'pass' : 'fail'; | |
| const sym = a.reward >= 1 ? 'β' : 'β'; | |
| const errPill = a.error_kind && a.error_kind !== 'ok' | |
| ? `<span class="pill err">${escape(a.error_kind)}</span>` : ''; | |
| return `<tr onclick="openTrace(${taskIdx}, '${escape(cellKey)}', ${i})" style="cursor:pointer"> | |
| <td><span class="pill k">k=${a.attempt}</span></td> | |
| <td class="reward ${rcls}">${sym} ${a.reward.toFixed(2)}</td> | |
| <td>${fmtCost(a.cost)}</td> | |
| <td>${fmtT(a.prompt_tokens)}/${fmtT(a.completion_tokens)}</td> | |
| <td>${fmtDur(a.elapsed_sec)}</td> | |
| <td>${errPill}</td> | |
| <td><button class="view-btn">trace β</button></td> | |
| </tr>`; | |
| }).join(''); | |
| document.getElementById('side-body').innerHTML = ` | |
| <div class="meta"> | |
| <b>Kaggle:</b> ${escape(t.kaggle || 'β')} Β· | |
| <b>Question:</b> ${escape(t.question)} | |
| </div> | |
| <div class="gold"><b>Gold:</b> ${escape(t.answer)}</div> | |
| <table class="trial-table"> | |
| <thead><tr><th>k</th><th>Reward</th><th>Cost</th><th>Tok i/o</th><th>Dur</th><th>Error</th><th></th></tr></thead> | |
| <tbody>${attRows}</tbody> | |
| </table> | |
| `; | |
| } | |
| function renderTraceSide(taskIdx, cellKey, attemptIdx) { | |
| const t = DATA.tasks[taskIdx]; | |
| const c = t.cells[cellKey]; | |
| const a = c.attempts[attemptIdx]; | |
| const passClass = a.reward >= 1 ? 'ok' : 'err'; | |
| document.getElementById('side-title').innerHTML = | |
| `${escape(t.id)} Β· k=${a.attempt} Β· ${escape(c.model)} Β· ${escape(c.agent)} | |
| <span class="pill ${passClass}">${a.reward >= 1 ? 'PASS' : 'FAIL'} ${a.reward.toFixed(2)}</span> | |
| <span class="pill">${fmtCost(a.cost)} Β· ${fmtT(a.prompt_tokens)}/${fmtT(a.completion_tokens)} tok</span>`; | |
| const logPathHtml = a.log_path | |
| ? `<div class="meta"><b>Sweep log:</b> <code style="font-size:10px">${escape(a.log_path)}</code></div>` : ''; | |
| document.getElementById('side-body').innerHTML = logPathHtml + | |
| '<div style="color:var(--muted);padding:10px">loading traceβ¦</div>'; | |
| fetch('api/' + CURRENT_DS + '/trace/' + a.tid).then(r => r.json()).then(tr => { | |
| const msgs = renderTrajectory(tr.trajectory || []); | |
| const graderHtml = tr.grader_text | |
| ? `<h4 style="margin:6px 0;color:var(--muted);font-size:11px;text-transform:uppercase">Grader output</h4> | |
| <div class="grader-box">${escape(tr.grader_text)}</div>` : ''; | |
| const ocHtml = tr.opencode_log | |
| ? `<details><summary>opencode.txt (${tr.opencode_log.length} chars)</summary> | |
| <pre>${escape(tr.opencode_log)}</pre></details>` : ''; | |
| const logHtml = tr.trial_log_tail | |
| ? `<details><summary>trial.log (last 80 lines)</summary> | |
| <pre>${escape(tr.trial_log_tail)}</pre></details>` : ''; | |
| document.getElementById('side-body').innerHTML = logPathHtml + graderHtml + msgs + ocHtml + logHtml; | |
| }).catch(e => { | |
| document.getElementById('side-body').innerHTML = logPathHtml + | |
| '<div style="color:#f88;padding:10px">failed to load trace: ' + escape(e) + '</div>'; | |
| }); | |
| } | |
| function renderTrajectory(traj) { | |
| const resultsById = {}; | |
| for (const m of traj) { | |
| if (m.role === 'tool' && m.tool_call_id) resultsById[m.tool_call_id] = m; | |
| } | |
| let out = ''; | |
| let turnIdx = 0; | |
| for (let i = 0; i < traj.length; i++) { | |
| const m = traj[i]; | |
| if (m.role === 'tool') continue; | |
| if (m.role === 'system' || m.role === 'user') { | |
| out += renderSimpleMessage(m); | |
| } else if (m.role === 'assistant') { | |
| turnIdx += 1; | |
| out += renderAssistantTurn(m, resultsById, turnIdx); | |
| } else { | |
| out += renderSimpleMessage(m); | |
| } | |
| } | |
| return out || `<div style="color:var(--muted);padding:10px">(no trajectory captured)</div>`; | |
| } | |
| function renderSimpleMessage(m) { | |
| const body = escape(m.content || '(empty)'); | |
| return `<div class="msg ${m.role}"> | |
| <div class="msg-head">${m.role}</div> | |
| <div class="msg-body">${body}</div> | |
| </div>`; | |
| } | |
| function renderAssistantTurn(m, resultsById, idx) { | |
| const thinking = (m.content || '').trim(); | |
| const calls = m.tool_calls || []; | |
| if (!calls.length) { | |
| return `<div class="msg assistant"> | |
| <div class="msg-head">assistant Β· turn ${idx}</div> | |
| <div class="msg-body">${escape(thinking || '(no content)')}</div> | |
| </div>`; | |
| } | |
| const thinkingHtml = thinking ? `<div class="turn-thinking">${escape(thinking)}</div>` : ''; | |
| const callsHtml = calls.map(tc => renderToolCall(tc, resultsById[tc.id])).join(''); | |
| return `<div class="turn"> | |
| <div class="turn-head">βΆ turn ${idx} Β· assistant</div> | |
| ${thinkingHtml} | |
| ${callsHtml} | |
| </div>`; | |
| } | |
| function renderToolCall(tc, resultMsg) { | |
| let parsed = null; | |
| try { parsed = JSON.parse(tc.arguments || '{}'); } | |
| catch { parsed = null; } | |
| let codeBody = ''; | |
| let langHint = ''; | |
| let metaParts = []; | |
| if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { | |
| if (typeof parsed.code === 'string') { codeBody = parsed.code; langHint = 'python'; } | |
| else if (typeof parsed.command === 'string') { codeBody = parsed.command; langHint = 'bash'; } | |
| else if (typeof parsed.cell === 'string') { codeBody = parsed.cell; langHint = 'python'; } | |
| else if (typeof parsed.answer === 'string') { codeBody = parsed.answer; langHint = ''; } | |
| const bodyKeys = new Set(['code', 'command', 'cell', 'answer']); | |
| for (const [k, v] of Object.entries(parsed)) { | |
| if (bodyKeys.has(k)) continue; | |
| let val; | |
| if (typeof v === 'string') val = v.length > 120 ? v.slice(0,120) + 'β¦' : v; | |
| else val = JSON.stringify(v); | |
| if (val != null && val !== '') metaParts.push(`${k}=${val}`); | |
| } | |
| if (!codeBody && Object.keys(parsed).length === 0) { /* empty */ } | |
| else if (!codeBody) { | |
| codeBody = JSON.stringify(parsed, null, 2); | |
| langHint = 'json'; | |
| } | |
| } else { | |
| codeBody = tc.arguments || ''; | |
| langHint = 'json'; | |
| } | |
| const meta = metaParts.length ? `<span class="tc-meta">${escape(metaParts.join(' Β· '))}</span>` : ''; | |
| const idLabel = tc.id ? `<span class="tc-id">${escape(tc.id.slice(0,16))}</span>` : ''; | |
| const codeBlock = codeBody | |
| ? `<pre class="tc-code lang-${langHint}">${escape(codeBody)}</pre>` | |
| : `<div style="padding:8px 12px;color:var(--gray);font-style:italic">(no arguments)</div>`; | |
| let resultHtml = ''; | |
| if (resultMsg) { | |
| const content = resultMsg.content || ''; | |
| const lines = content.split('\n'); | |
| const lineInfo = `${lines.length} line${lines.length===1?'':'s'} Β· ${content.length} char${content.length===1?'':'s'}`; | |
| const truncMatch = content.match(/\[truncated, full length (\d+)\]/); | |
| const truncated = truncMatch ? `<span style="color:var(--warn)">truncated Β· full ${truncMatch[1]}ch</span>` : ''; | |
| const empty = content.trim() === '' ? ' empty' : ''; | |
| resultHtml = ` | |
| <div class="tool-output-head">output <span class="rc">${lineInfo} ${truncated}</span></div> | |
| <pre class="tool-output${empty}">${empty ? '(empty)' : escape(content)}</pre> | |
| `; | |
| } else { | |
| resultHtml = `<div class="tool-output-head" style="color:var(--gray)">(no matching tool result)</div>`; | |
| } | |
| return `<div class="tool-call"> | |
| <div class="tc-head"> | |
| <span class="tc-name">β ${escape(tc.name || '?')}()</span> | |
| ${idLabel} | |
| ${meta} | |
| </div> | |
| ${codeBlock} | |
| ${resultHtml} | |
| </div>`; | |
| } | |
| ['search','diff','status','harness'].forEach(id => document.getElementById(id).addEventListener('input', renderCurrent)); | |
| document.addEventListener('keydown', e => { if (e.key==='Escape') sideClose(); }); | |
| const SIDE_MIN = 380; | |
| const SIDE_MAX_FRAC = 0.85; | |
| function sideMax() { return Math.max(SIDE_MIN + 100, Math.floor(window.innerWidth * SIDE_MAX_FRAC)); } | |
| function clampSideWidth(w) { return Math.min(sideMax(), Math.max(SIDE_MIN, w)); } | |
| function applySideWidth(w) { | |
| const sp = document.getElementById('side-pane'); | |
| sp.style.flex = `0 0 ${clampSideWidth(w)}px`; | |
| } | |
| try { | |
| const stored = parseInt(localStorage.getItem('sidePaneWidth') || '', 10); | |
| if (stored && !isNaN(stored)) applySideWidth(stored); | |
| } catch {} | |
| (function setupResizer() { | |
| const handle = document.getElementById('side-resizer'); | |
| const sp = document.getElementById('side-pane'); | |
| let dragging = false, startX = 0, startW = 0; | |
| handle.addEventListener('mousedown', e => { | |
| dragging = true; startX = e.clientX; startW = sp.getBoundingClientRect().width; | |
| document.body.classList.add('resizing'); | |
| handle.classList.add('active'); | |
| e.preventDefault(); | |
| }); | |
| document.addEventListener('mousemove', e => { | |
| if (!dragging) return; | |
| const newW = startW + (startX - e.clientX); | |
| applySideWidth(newW); | |
| }); | |
| document.addEventListener('mouseup', () => { | |
| if (!dragging) return; | |
| dragging = false; | |
| document.body.classList.remove('resizing'); | |
| handle.classList.remove('active'); | |
| try { localStorage.setItem('sidePaneWidth', String(sp.getBoundingClientRect().width)); } catch {} | |
| }); | |
| window.addEventListener('resize', () => { | |
| const cur = sp.getBoundingClientRect().width; | |
| applySideWidth(cur); | |
| }); | |
| })(); | |
| // βββ Benchmark + sweep selector + bootstrap ββββββββββββββββββββββββββββββββ | |
| // `BENCHMARKS` is loaded from /api/benchmarks at boot. Each entry has: | |
| // { label, description, source: {harbor, hf_dataset, paper, ...}, sweeps: [<key>] } | |
| let BENCHMARKS = {}; | |
| let CURRENT_BENCH = null; // benchmark key | |
| let CURRENT_DS = null; // sweep key (the actual data key /api/<ds>/summary) | |
| // Display order preference; unknown benchmarks appended alphabetically. | |
| const BENCH_ORDER = ['data-agent-bench', 'dabstep-bench']; | |
| function setLoader(visible, msg) { | |
| const el = document.getElementById('loader'); | |
| if (msg) document.getElementById('loader-msg').textContent = msg; | |
| el.classList.toggle('hidden', !visible); | |
| } | |
| function orderBenchmarks(keys) { | |
| const known = BENCH_ORDER.filter(k => keys.includes(k)); | |
| const extra = keys.filter(k => !BENCH_ORDER.includes(k)).sort(); | |
| return [...known, ...extra]; | |
| } | |
| function renderBenchTabs() { | |
| const wrap = document.getElementById('ds-tabs'); | |
| const keys = orderBenchmarks(Object.keys(BENCHMARKS)); | |
| wrap.innerHTML = keys.map(k => { | |
| const label = (BENCHMARKS[k] && BENCHMARKS[k].label) || k; | |
| const cls = k === CURRENT_BENCH ? 'active' : ''; | |
| return `<button class="${cls}" data-bench="${k}" onclick="switchBenchmark('${k}')">${escape(label)}</button>`; | |
| }).join(''); | |
| } | |
| // Switching benchmark: defaults to that benchmark's first sweep. | |
| function switchBenchmark(benchKey) { | |
| if (benchKey === CURRENT_BENCH) return; | |
| const bench = BENCHMARKS[benchKey]; | |
| if (!bench || !bench.sweeps || !bench.sweeps.length) return; | |
| CURRENT_BENCH = benchKey; | |
| CURRENT_DS = bench.sweeps[0]; | |
| document.querySelectorAll('#ds-tabs button').forEach(b => | |
| b.classList.toggle('active', b.dataset.bench === benchKey)); | |
| // Hide stale side pane + info popover. | |
| const side = document.getElementById('side-pane'); if (side) side.classList.add('hidden'); | |
| document.getElementById('info-popover').classList.add('hidden'); | |
| loadSummary(CURRENT_DS); | |
| } | |
| function loadSummary(ds) { | |
| setLoader(true, `loading ${(BENCHMARKS[CURRENT_BENCH] && BENCHMARKS[CURRENT_BENCH].label) || ds}β¦`); | |
| return fetch(`api/${ds}/summary`) | |
| .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }) | |
| .then(d => { | |
| DATA = d; | |
| renderHeaderStats(); | |
| renderCurrent(); | |
| setLoader(false); | |
| }) | |
| .catch(e => { | |
| setLoader(true, `Failed to load ${ds}: ${e}`); | |
| }); | |
| } | |
| // βββ Info popover β shows benchmark description + source links βββββββββββββ | |
| function toggleInfoPopover() { | |
| const pop = document.getElementById('info-popover'); | |
| if (!CURRENT_BENCH || !BENCHMARKS[CURRENT_BENCH]) return; | |
| if (!pop.classList.contains('hidden')) { | |
| pop.classList.add('hidden'); | |
| return; | |
| } | |
| const b = BENCHMARKS[CURRENT_BENCH]; | |
| const src = b.source || {}; | |
| const SRC_LABELS = { | |
| harbor: 'Harbor task suite', | |
| hf_dataset: 'Hugging Face dataset', | |
| paper: 'Paper', | |
| leaderboard: 'Leaderboard', | |
| }; | |
| const links = Object.keys(src).map(k => { | |
| const url = src[k].startsWith('http') ? src[k] | |
| : (k === 'harbor' || k === 'hf_dataset') | |
| ? `https://huggingface.co/datasets/${src[k]}` | |
| : src[k]; | |
| return `<div><span class="lbl">${SRC_LABELS[k] || k}:</span><a href="${url}" target="_blank" rel="noopener">${escape(src[k])}</a></div>`; | |
| }).join(''); | |
| pop.innerHTML = | |
| `<h4>${escape(b.label || CURRENT_BENCH)}</h4> | |
| <p>${escape(b.description || '')}</p> | |
| <div class="src-list">${links}</div>`; | |
| pop.classList.remove('hidden'); | |
| } | |
| // Click-outside-to-close for the popover. | |
| document.addEventListener('click', e => { | |
| const pop = document.getElementById('info-popover'); | |
| const btn = document.getElementById('info-btn'); | |
| if (pop && !pop.contains(e.target) && e.target !== btn) { | |
| pop.classList.add('hidden'); | |
| } | |
| }); | |
| // Bootstrap: fetch benchmark registry, pick first preferred, load its first sweep. | |
| fetch('api/benchmarks').then(r => r.json()).then(reg => { | |
| BENCHMARKS = reg || {}; | |
| const keys = orderBenchmarks(Object.keys(BENCHMARKS)); | |
| if (!keys.length) { | |
| setLoader(true, 'No benchmarks found on server.'); | |
| return; | |
| } | |
| CURRENT_BENCH = keys[0]; | |
| const sweeps = BENCHMARKS[CURRENT_BENCH].sweeps || []; | |
| if (!sweeps.length) { | |
| setLoader(true, `Benchmark ${CURRENT_BENCH} has no sweeps yet.`); | |
| return; | |
| } | |
| CURRENT_DS = sweeps[0]; | |
| renderBenchTabs(); | |
| loadSummary(CURRENT_DS); | |
| }).catch(e => { | |
| setLoader(true, `Failed to load benchmark registry: ${e}`); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |