wop's picture
Update script.js
5efa657 verified
Raw
History Blame Contribute Delete
23.2 kB
(() => {
"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) =>
({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[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>`;
});
})();