Spaces:
Running
Running
| (() => { | |
| "use strict"; | |
| // --------------------------------------------------------------------- | |
| // State | |
| // --------------------------------------------------------------------- | |
| const state = { | |
| data: null, | |
| gen: null, // "7-2026" | "6-2026" | "all"; default = data.latest_generation | |
| search: "", | |
| sort: "avg-desc", | |
| expanded: null, // "modelId" whose detail row is open | |
| }; | |
| const $ = (sel) => document.querySelector(sel); | |
| // --------------------------------------------------------------------- | |
| // Data helpers | |
| // --------------------------------------------------------------------- | |
| const numeric = (v) => typeof v === "number" && isFinite(v); | |
| // Category score resolution. `score` (v2 uniform key) wins; the legacy | |
| // metric-name keys keep 6-2026 entries rendering unchanged. | |
| const catVal = (c) => | |
| [c.score, c.hybrid_score, c.exact_match, c.soft_score_norm, c.acc_norm, c.soft_score] | |
| .find(numeric); | |
| function benchesFor(gen) { | |
| const all = state.data.benchmarks.filter((b) => !b.unavailable); | |
| const list = gen === "all" ? all : all.filter((b) => (b.generation || "6-2026") === gen); | |
| return list.slice().sort((a, b) => (a.tier - b.tier) || String(a.generation).localeCompare(String(b.generation))); | |
| } | |
| function runScore(model, benchId) { | |
| const run = model.runs && model.runs[benchId]; | |
| return run && numeric(run.score) ? run.score : null; | |
| } | |
| function avgScore(model, benches) { | |
| const vals = benches.map((b) => runScore(model, b.id)).filter(numeric); | |
| return vals.length ? vals.reduce((s, v) => s + v, 0) / vals.length : null; | |
| } | |
| function visibleModels(benches) { | |
| let models = state.data.models.filter((m) => | |
| benches.some((b) => runScore(m, b.id) !== null) | |
| ); | |
| if (state.search) { | |
| const q = state.search.toLowerCase(); | |
| models = models.filter( | |
| (m) => (m.name || "").toLowerCase().includes(q) || (m.org || "").toLowerCase().includes(q) | |
| ); | |
| } | |
| const key = state.sort; | |
| models.sort((a, b) => { | |
| if (key === "name-asc") return (a.name || "").localeCompare(b.name || ""); | |
| if (key.startsWith("params")) { | |
| const pa = numeric(a.params_b) ? a.params_b : Infinity; | |
| const pb = numeric(b.params_b) ? b.params_b : Infinity; | |
| return key === "params-asc" ? pa - pb : (pb === Infinity ? -1 : pb) - (pa === Infinity ? -1 : pa); | |
| } | |
| const aa = avgScore(a, benches), ab = avgScore(b, benches); | |
| const va = aa === null ? -1 : aa, vb = ab === null ? -1 : ab; | |
| return key === "avg-asc" ? va - vb : vb - va; | |
| }); | |
| return models; | |
| } | |
| // --------------------------------------------------------------------- | |
| // Presentation helpers | |
| // --------------------------------------------------------------------- | |
| const esc = (s) => | |
| String(s).replace(/[&<>"']/g, (ch) => | |
| ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch])); | |
| function cssRgb(varName) { | |
| return getComputedStyle(document.documentElement).getPropertyValue(varName) | |
| .split(",").map((x) => parseFloat(x)); | |
| } | |
| // Gamma-curved color ramp: most cells stay near the base text color; only | |
| // genuinely strong scores go vividly green. | |
| function scoreColor(value, min, max) { | |
| const base = cssRgb("--score-base"), best = cssRgb("--score-best"); | |
| if (!numeric(value) || max <= min) return null; | |
| const p = Math.pow((value - min) / (max - min), 2.5); | |
| const mix = base.map((b, i) => Math.round(b + (best[i] - b) * p)); | |
| return `rgb(${mix.join(",")})`; | |
| } | |
| function orgColor(org) { | |
| let h = 0; | |
| for (const ch of String(org || "?")) h = (h * 31 + ch.charCodeAt(0)) % 360; | |
| return `hsl(${h}, 55%, 55%)`; | |
| } | |
| const fmtScore = (v) => (numeric(v) ? v.toFixed(3) : null); | |
| // params_b is always in billions. Format at whichever scale keeps 2-3 | |
| // significant figures, matching the reference site (40.1M, 919k, 23.7k...). | |
| function fmtParams(p) { | |
| if (!numeric(p)) return "—"; | |
| const abs = Math.abs(p); | |
| if (abs >= 1) return `${p.toFixed(p >= 100 ? 0 : 1)}B`; | |
| if (abs >= 0.001) return `${(p * 1000).toFixed((p * 1000) >= 100 ? 0 : 1)}M`; | |
| if (abs >= 0.000001) return `${(p * 1e6).toFixed((p * 1e6) >= 100 ? 0 : 1)}k`; | |
| return `${Math.round(p * 1e9)}`; | |
| } | |
| function shortLabel(bench) { | |
| return bench.label.replace(" (6-2026)", ""); | |
| } | |
| // --------------------------------------------------------------------- | |
| // Capability radar (replaces the old per-tier category-bar tables) | |
| // --------------------------------------------------------------------- | |
| // One shape per model: average category score across whichever benches are | |
| // currently in view. Much faster to read than a stack of sliders per tier. | |
| function categoryAverages(model, benches) { | |
| const sums = {}, counts = {}; | |
| for (const b of benches) { | |
| const run = model.runs && model.runs[b.id]; | |
| if (!run || !run.categories) continue; | |
| for (const [cat, c] of Object.entries(run.categories)) { | |
| const v = catVal(c); | |
| if (!numeric(v)) continue; | |
| sums[cat] = (sums[cat] || 0) + v; | |
| counts[cat] = (counts[cat] || 0) + 1; | |
| } | |
| } | |
| const cats = state.data.categories || Object.keys(sums); | |
| return cats | |
| .filter((c) => counts[c]) | |
| .map((c) => ({ cat: c, value: sums[c] / counts[c] })); | |
| } | |
| function radarSvg(points, size = 220) { | |
| const n = points.length; | |
| if (n < 3) { | |
| return `<p class="radar-empty">Not enough category data for this view.</p>`; | |
| } | |
| const cx = size / 2, cy = size / 2, r = size / 2 - 30; | |
| const angle = (i) => (Math.PI * 2 * i) / n - Math.PI / 2; | |
| const pt = (i, frac) => [cx + Math.cos(angle(i)) * r * frac, cy + Math.sin(angle(i)) * r * frac]; | |
| // background rings at 25/50/75/100% | |
| const rings = [0.25, 0.5, 0.75, 1].map((frac) => { | |
| const path = points.map((_, i) => pt(i, frac).join(",")).join(" "); | |
| return `<polygon points="${path}" class="radar-ring" />`; | |
| }).join(""); | |
| // spokes | |
| const spokes = points.map((_, i) => { | |
| const [x, y] = pt(i, 1); | |
| return `<line x1="${cx}" y1="${cy}" x2="${x}" y2="${y}" class="radar-spoke" />`; | |
| }).join(""); | |
| // data polygon | |
| const dataPath = points.map((p, i) => pt(i, Math.max(0.03, p.value)).join(",")).join(" "); | |
| // labels | |
| const labels = points.map((p, i) => { | |
| const [x, y] = pt(i, 1.18); | |
| const anchor = Math.cos(angle(i)) > 0.15 ? "start" : Math.cos(angle(i)) < -0.15 ? "end" : "middle"; | |
| const short = p.cat.replace(/-/g, " "); | |
| return `<text x="${x}" y="${y}" text-anchor="${anchor}" class="radar-label" dominant-baseline="middle">${esc(short)}</text>`; | |
| }).join(""); | |
| return `<svg viewBox="0 0 ${size} ${size}" class="radar-svg" role="img" aria-label="Category capability radar"> | |
| ${rings}${spokes} | |
| <polygon points="${dataPath}" class="radar-fill" /> | |
| ${labels} | |
| </svg>`; | |
| } | |
| function capabilityHtml(model, benches) { | |
| const points = categoryAverages(model, benches); | |
| const svg = radarSvg(points); | |
| // compact per-tier headline strip alongside the shape | |
| const tierRows = benches.map((b) => { | |
| const run = model.runs && model.runs[b.id]; | |
| const v = run && numeric(run.score) ? run.score : null; | |
| return `<div class="tier-row"> | |
| <span class="tier-name" title="${esc(b.description || "")}">${esc(shortLabel(b))}</span> | |
| <div class="tier-bar"><i style="width:${v !== null ? Math.round(v * 100) : 0}%"></i></div> | |
| <span class="tier-val">${v !== null ? v.toFixed(3) : "—"}</span> | |
| </div>`; | |
| }).join(""); | |
| return `<div class="capability-panel"> | |
| <div class="radar-wrap">${svg}</div> | |
| <div class="tier-strip">${tierRows || "<em>No tier scores yet.</em>"}</div> | |
| </div>`; | |
| } | |
| // --------------------------------------------------------------------- | |
| // Rendering — text-QA leaderboard | |
| // --------------------------------------------------------------------- | |
| function renderStats(models, benches) { | |
| const latest = state.data.latest_generation; | |
| const genLabel = state.gen === "all" ? "all generations" : state.gen; | |
| let bestModel = null, bestAvg = null; | |
| for (const m of models) { | |
| const a = avgScore(m, benches); | |
| if (a !== null && (bestAvg === null || a > bestAvg)) { bestAvg = a; bestModel = m; } | |
| } | |
| $("#stat-row").innerHTML = ` | |
| <div class="stat-pill"><div class="label">Models</div> | |
| <div class="value">${models.length}</div></div> | |
| <div class="stat-pill"><div class="label">Benchmarks</div> | |
| <div class="value">${benches.length}</div></div> | |
| <div class="stat-pill"><div class="label">Best avg</div> | |
| <div class="value${bestAvg !== null ? " good" : ""}">${bestAvg !== null ? bestAvg.toFixed(3) : "—"}</div> | |
| <div class="sub">${bestModel ? esc(bestModel.name) : "no runs yet"}</div></div> | |
| <div class="stat-pill"><div class="label">Updated</div> | |
| <div class="value">${esc(latest)}</div><div class="sub">${esc(state.data.updated)}</div></div>`; | |
| } | |
| function renderChips() { | |
| const latest = state.data.latest_generation; | |
| const gens = [latest, "6-2026", "all"]; | |
| $("#gen-chips").innerHTML = gens | |
| .map((g) => { | |
| const label = g === "all" ? "All" : g; | |
| const note = g === latest ? '<span class="chip-note">latest</span>' : ""; | |
| return `<button type="button" class="chip${state.gen === g ? " active" : ""}" | |
| data-gen="${esc(g)}" role="tab" aria-selected="${state.gen === g}">${esc(label)}${note}</button>`; | |
| }) | |
| .join(""); | |
| document.querySelectorAll("#gen-chips .chip").forEach((chip) => | |
| chip.addEventListener("click", () => { | |
| state.gen = chip.dataset.gen; | |
| state.expanded = null; | |
| render(); | |
| }) | |
| ); | |
| } | |
| function orgTag(org) { | |
| if (!org) return ""; | |
| const c = orgColor(org); | |
| return `<span class="org-tag" style="border:1px solid ${c}33;background:${c}22;color:${c}"> | |
| <span class="dot" style="background:${c}"></span>${esc(org)}</span>`; | |
| } | |
| function renderTable(models, benches) { | |
| const head = $("#lb-head"); | |
| const body = $("#lb-body"); | |
| head.innerHTML = `<tr> | |
| <th>#</th><th>Model</th><th>Org</th><th class="num">Params</th><th class="num">Avg</th> | |
| ${benches.map((b) => `<th class="num" title="${esc(b.description || "")}">${esc(shortLabel(b))}</th>`).join("")} | |
| <th></th></tr>`; | |
| // column ranges for the color ramp + best-value highlight | |
| const ranges = benches.map((b) => { | |
| const vals = models.map((m) => runScore(m, b.id)).filter(numeric); | |
| return { min: Math.min(...vals), max: Math.max(...vals), best: vals.length ? Math.max(...vals) : null }; | |
| }); | |
| const avgs = models.map((m) => avgScore(m, benches)).filter(numeric); | |
| const avgRange = { min: Math.min(...avgs), max: Math.max(...avgs), best: avgs.length ? Math.max(...avgs) : null }; | |
| const rows = []; | |
| models.forEach((m, i) => { | |
| const rank = i + 1; | |
| const avg = avgScore(m, benches); | |
| const open = state.expanded === m.id; | |
| const avgColor = scoreColor(avg, avgRange.min, avgRange.max); | |
| rows.push(`<tr class="model-row${open ? " open" : ""}" data-id="${esc(m.id)}"> | |
| <td class="rank rank-${rank <= 3 ? rank : "n"}">#${rank}</td> | |
| <td class="model-cell">${m.url ? `<a href="${esc(m.url)}" target="_blank" rel="noopener" onclick="event.stopPropagation()">${esc(m.name)}</a>` : esc(m.name)}</td> | |
| <td>${orgTag(m.org)}</td> | |
| <td class="num">${fmtParams(m.params_b)}</td> | |
| <td class="num score${numeric(avg) && avg === avgRange.best ? " best" : ""}" | |
| ${avgColor ? `style="color:${avgColor}"` : ""}>${fmtScore(avg) ?? '<span class="na">—</span>'}</td> | |
| ${benches.map((b, j) => { | |
| const v = runScore(m, b.id); | |
| const color = scoreColor(v, ranges[j].min, ranges[j].max); | |
| const best = numeric(v) && v === ranges[j].best; | |
| return `<td class="num score${best ? " best" : ""}" ${color ? `style="color:${color}"` : ""}> | |
| ${fmtScore(v) ?? '<span class="na">TBD</span>'}</td>`; | |
| }).join("")} | |
| <td><span class="expand-hint">▶</span></td></tr>`); | |
| if (open) { | |
| rows.push(`<tr class="detail-row"><td colspan="${6 + benches.length}">${capabilityHtml(m, benches)}</td></tr>`); | |
| } | |
| }); | |
| body.innerHTML = rows.join(""); | |
| document.querySelectorAll("#lb-body tr.model-row").forEach((tr) => | |
| tr.addEventListener("click", () => { | |
| state.expanded = state.expanded === tr.dataset.id ? null : tr.dataset.id; | |
| render(); | |
| }) | |
| ); | |
| } | |
| function renderCards(models, benches) { | |
| $("#model-cards").innerHTML = models | |
| .map((m, i) => { | |
| const rank = i + 1; | |
| const avg = avgScore(m, benches); | |
| const open = state.expanded === m.id; | |
| return `<div class="m-card${open ? " open" : ""}" data-id="${esc(m.id)}"> | |
| <div class="m-card-top"> | |
| <span style="display:flex;align-items:center;gap:9px;min-width:0;"> | |
| <span class="rank rank-${rank <= 3 ? rank : "n"}">#${rank}</span> | |
| ${m.url ? `<a href="${esc(m.url)}" target="_blank" rel="noopener" onclick="event.stopPropagation()">${esc(m.name)}</a>` : `<span>${esc(m.name)}</span>`} | |
| </span> | |
| <span class="expand-hint">▶</span> | |
| </div> | |
| <div class="m-card-meta">${orgTag(m.org)}<span class="params">${fmtParams(m.params_b)}</span></div> | |
| <div class="m-scores"> | |
| <div class="m-score"><div class="k">Avg</div><div class="v">${fmtScore(avg) ?? "—"}</div></div> | |
| ${benches.map((b) => { | |
| const v = runScore(m, b.id); | |
| return `<div class="m-score"><div class="k">${esc(shortLabel(b))}</div><div class="v">${fmtScore(v) ?? "—"}</div></div>`; | |
| }).join("")} | |
| </div> | |
| ${open ? `<div class="m-card-detail">${capabilityHtml(m, benches)}</div>` : ""} | |
| </div>`; | |
| }) | |
| .join(""); | |
| document.querySelectorAll("#model-cards .m-card").forEach((card) => | |
| card.addEventListener("click", () => { | |
| state.expanded = state.expanded === card.dataset.id ? null : card.dataset.id; | |
| render(); | |
| }) | |
| ); | |
| } | |
| // --------------------------------------------------------------------- | |
| // Rendering — text-to-image mini leaderboard (independent track) | |
| // --------------------------------------------------------------------- | |
| function t2iBenches() { | |
| return (state.data.t2i_benchmarks || []).filter((b) => !b.unavailable); | |
| } | |
| function t2iRunValue(model, benchId) { | |
| const run = model.runs && model.runs[benchId]; | |
| return run && numeric(run.score) ? run.score : null; | |
| } | |
| // FID: show at full reported precision (no artificial rounding — 4.8724 | |
| // stays 4.8724, 39.54 stays 39.54). CLIP: stored 0-1, display as a percent | |
| // to one decimal, matching the reference site's "28.0%" formatting. | |
| function fmtT2iValue(benchId, v) { | |
| if (!numeric(v)) return null; | |
| if (benchId === "t2i-clip-coco") return `${(v * 100).toFixed(1)}%`; | |
| // strip trailing zeros beyond what was actually reported, cap at 4dp | |
| return String(Math.round(v * 10000) / 10000); | |
| } | |
| // Composite score: each benchmark is min-max normalized across the visible | |
| // set (inverted for lower_is_better), then averaged and scaled to 0-100. | |
| // This mirrors the reference leaderboard's own "Overall Score" so ranking | |
| // reflects both benchmarks together instead of just the first column. | |
| function t2iComposite(model, benches, ranges) { | |
| const parts = []; | |
| benches.forEach((b, j) => { | |
| const v = t2iRunValue(model, b.id); | |
| if (!numeric(v)) return; | |
| const { min, max } = ranges[j]; | |
| if (max === min) return; | |
| let p = (v - min) / (max - min); | |
| if (b.lower_is_better) p = 1 - p; | |
| parts.push(p); | |
| }); | |
| if (!parts.length) return null; | |
| return (parts.reduce((s, v) => s + v, 0) / parts.length) * 100; | |
| } | |
| function renderT2i() { | |
| const section = $("#t2i-section"); | |
| const benches = t2iBenches(); | |
| const models = (state.data.t2i_models || []).filter((m) => | |
| benches.some((b) => t2iRunValue(m, b.id) !== null) | |
| ); | |
| if (!benches.length) { | |
| section.hidden = true; | |
| return; | |
| } | |
| section.hidden = false; | |
| const head = $("#t2i-head"); | |
| head.innerHTML = `<tr> | |
| <th>#</th><th>Model</th><th>Org</th><th class="num">Params</th> | |
| <th class="num" title="Normalized blend of all benchmarks below (0-100, higher is better)">Score</th> | |
| <th>Res.</th> | |
| ${benches.map((b) => `<th class="num" title="${esc(b.description || "")}">${esc(b.label)}${b.lower_is_better ? " ↓" : ""}</th>`).join("")} | |
| <th>Released</th></tr>`; | |
| if (!models.length) { | |
| $("#t2i-body").innerHTML = ""; | |
| $("#t2i-cards").innerHTML = ""; | |
| $("#t2i-empty").hidden = false; | |
| return; | |
| } | |
| $("#t2i-empty").hidden = true; | |
| // ranges computed once, over the full visible set, before sorting | |
| const ranges = benches.map((b) => { | |
| const vals = models.map((m) => t2iRunValue(m, b.id)).filter(numeric); | |
| const best = vals.length ? (b.lower_is_better ? Math.min(...vals) : Math.max(...vals)) : null; | |
| return { min: Math.min(...vals), max: Math.max(...vals), best }; | |
| }); | |
| const withScore = models.map((m) => ({ m, score: t2iComposite(m, benches, ranges) })); | |
| // sort by composite score desc; ties broken by the first (primary) bench | |
| withScore.sort((a, b) => { | |
| const sa = a.score === null ? -1 : a.score, sb = b.score === null ? -1 : b.score; | |
| if (sb !== sa) return sb - sa; | |
| const primary = benches[0]; | |
| const va = t2iRunValue(a.m, primary.id), vb = t2iRunValue(b.m, primary.id); | |
| const aa = va === null ? Infinity : va, bb = vb === null ? Infinity : vb; | |
| return primary.lower_is_better ? aa - bb : bb - aa; | |
| }); | |
| const scores = withScore.map((x) => x.score).filter(numeric); | |
| const scoreBest = scores.length ? Math.max(...scores) : null; | |
| $("#t2i-body").innerHTML = withScore.map(({ m, score }, i) => { | |
| const rank = i + 1; | |
| const flagNote = benches.map((b) => m.runs && m.runs[b.id] && m.runs[b.id].notes).find(Boolean); | |
| return `<tr${flagNote ? ` title="${esc(flagNote)}"` : ""}> | |
| <td class="rank rank-${rank <= 3 ? rank : "n"}">#${rank}</td> | |
| <td class="model-cell">${m.url ? `<a href="${esc(m.url)}" target="_blank" rel="noopener">${esc(m.name)}</a>` : esc(m.name)}${flagNote ? ' <span class="flag-dot" title="' + esc(flagNote) + '">⚠</span>' : ""}</td> | |
| <td>${orgTag(m.org)}</td> | |
| <td class="num">${fmtParams(m.params_b)}</td> | |
| <td class="num score composite${numeric(score) && score === scoreBest ? " best" : ""}">${numeric(score) ? score.toFixed(1) : '<span class="na">—</span>'}</td> | |
| <td class="res">${esc(m.resolution || "—")}</td> | |
| ${benches.map((b, j) => { | |
| const v = t2iRunValue(m, b.id); | |
| const best = numeric(v) && v === ranges[j].best; | |
| return `<td class="num score${best ? " best" : ""}">${fmtT2iValue(b.id, v) ?? '<span class="na">TBD</span>'}</td>`; | |
| }).join("")} | |
| <td class="res">${esc(m.release_date || "—")}</td> | |
| </tr>`; | |
| }).join(""); | |
| // mobile cards, tappable like the main table's mobile cards | |
| $("#t2i-cards").innerHTML = withScore.map(({ m, score }, i) => { | |
| const rank = i + 1; | |
| const flagNote = benches.map((b) => m.runs && m.runs[b.id] && m.runs[b.id].notes).find(Boolean); | |
| const open = state.expanded === `t2i:${m.id}`; | |
| return `<div class="t2i-m-card${open ? " open" : ""}" data-id="${esc(m.id)}"> | |
| <div class="m-card-top t2i-m-card-top"> | |
| <span style="display:flex;align-items:center;gap:9px;min-width:0;"> | |
| <span class="rank rank-${rank <= 3 ? rank : "n"}">#${rank}</span> | |
| ${m.url ? `<a href="${esc(m.url)}" target="_blank" rel="noopener" onclick="event.stopPropagation()">${esc(m.name)}</a>` : `<span>${esc(m.name)}</span>`} | |
| ${flagNote ? ' <span class="flag-dot" title="' + esc(flagNote) + '">⚠</span>' : ""} | |
| </span> | |
| <span class="expand-hint">▶</span> | |
| </div> | |
| <div class="t2i-m-card-meta">${orgTag(m.org)} | |
| <span class="params">${fmtParams(m.params_b)}</span> | |
| <span class="res">${esc(m.resolution || "—")}</span> | |
| <span class="date">${esc(m.release_date || "—")}</span> | |
| </div> | |
| <div class="t2i-m-scores"> | |
| <div class="t2i-m-score composite"><div class="k">Score</div><div class="v${numeric(score) && score === scoreBest ? " best" : ""}">${numeric(score) ? score.toFixed(1) : "—"}</div></div> | |
| ${benches.map((b, j) => { | |
| const v = t2iRunValue(m, b.id); | |
| const best = numeric(v) && v === ranges[j].best; | |
| return `<div class="t2i-m-score"><div class="k">${esc(b.label)}</div><div class="v${best ? " best" : ""}">${fmtT2iValue(b.id, v) ?? "—"}</div></div>`; | |
| }).join("")} | |
| </div> | |
| ${open ? `<div class="t2i-m-card-detail">${flagNote ? `<p style="font-size:0.78rem;color:var(--muted);line-height:1.5;">${esc(flagNote)}</p>` : `<p style="font-size:0.78rem;color:var(--muted-2);font-style:italic;">No additional notes.</p>`}</div>` : ""} | |
| </div>`; | |
| }).join(""); | |
| document.querySelectorAll("#t2i-cards .t2i-m-card").forEach((card) => | |
| card.addEventListener("click", () => { | |
| const key = `t2i:${card.dataset.id}`; | |
| state.expanded = state.expanded === key ? null : key; | |
| renderT2i(); | |
| }) | |
| ); | |
| } | |
| // --------------------------------------------------------------------- | |
| // Boot / orchestration | |
| // --------------------------------------------------------------------- | |
| function render() { | |
| const benches = benchesFor(state.gen); | |
| const models = visibleModels(benches); | |
| renderChips(); | |
| renderStats(models, benches); | |
| const empty = models.length === 0; | |
| $("#empty-state").hidden = !empty; | |
| $("#lb").style.display = empty ? "none" : ""; | |
| $("#model-cards").style.visibility = empty ? "hidden" : ""; | |
| if (!empty) { | |
| renderTable(models, benches); | |
| renderCards(models, benches); | |
| } | |
| renderT2i(); | |
| } | |
| async function boot() { | |
| const resp = await fetch("models.json"); | |
| state.data = await resp.json(); | |
| state.gen = state.data.latest_generation || "all"; | |
| $("#model-search").addEventListener("input", (e) => { | |
| state.search = e.target.value.trim(); | |
| render(); | |
| }); | |
| $("#sort-select").addEventListener("change", (e) => { | |
| state.sort = e.target.value; | |
| render(); | |
| }); | |
| $("#show-legacy").addEventListener("click", () => { | |
| state.gen = "6-2026"; | |
| render(); | |
| }); | |
| const scheme = window.matchMedia("(prefers-color-scheme: light)"); | |
| (scheme.addEventListener || scheme.addListener).call(scheme, "change", render); | |
| render(); | |
| } | |
| boot().catch((err) => { | |
| $("#lb-body").innerHTML = `<tr><td colspan="9">Failed to load models.json: ${esc(err.message)}</td></tr>`; | |
| }); | |
| })(); |