SPB Git

spb/ai-atlas

Public

web D1: models terminal 3.0, model page 3.0, artifacts, model diff, compare 3.0, benchmarks 2.0 (directory · leaderboard · matrix · cost-vs-performance), families, licences, per-type OG images, qa/d1.mjs

- /models: TerminalLayout with API facets (orgs, families, openness, canonical licences, modalities, status, years, identity trust), 7B/128K range inputs, include-artifacts toggle, understood-as chips, dense sortable table with column chooser (localStorage), keyboard row focus, inspector pane (mobile: sheet), Compare + Watch
- /models/[slug]: identity strip + SectionNav (Overview … Provenance), identity panel, openness block from ontology dimensions, grouped benchmarks with trust/comparability + vs-leader delta, deployments table, price-history step charts with evidence, version-history transitions, hardware fit (estimated), SVG lineage tree, artifacts by kind, change history + as-of; artifact and folded-variant slugs 308 to their canonical URL
- /artifacts/[slug], /models/[a]/diff/[b], /compare (modes, row groups, hide-identical, diff_only, use-case emphasis, per-cell evidence, stacked mobile layout)
- /benchmarks (family-grouped directory, honesty line), /benchmarks/[slug] (comparability-group picker, trust/org/comparable-only filters, one-row-per-model leaderboard, frontier over time, definition with evidence), /benchmarks/matrix (Heatmap by within-column rank), /benchmarks/[slug]/cost-vs-performance (Pareto, client-only scatter)
- /families, /families/[slug] (release lanes, members, lineage mini-graph, licence mix, benchmark progress), /licenses, /licenses/[key]
- lib: D1 blocks in types.ts / api.ts (apiD1)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Sep 12, 2026) parent f48531f Browse files

41 changed files +6,726 −487

41 changed files
added apps/web/qa/d1.mjs +196 −0
@@ -0,0 +1,196 @@
1+/**
2+ * Stream D1 QA sweep — models · artifacts · families · benchmarks · compare · licences.
3+ * Widths 320 · 360 · 375 · 390 · 430 · 768 · 1366 · 1440 · 1920, dark + light. Slugs are discovered live from the API.
4+ * Asserts: HTTP status (308 for merged / artifact slugs under /models), zero console errors, no horizontal overflow
5+ * (scrollWidth ≤ clientWidth), the evidence drawer opens from a model-page value, the matrix renders ≥ 1 heatmap cell,
6+ * the leaderboard has one row per model (no duplicate model slug in the first 50 rows).
7+ * Run: node qa/d1.mjs [BASE=http://localhost:8341] [API=http://127.0.0.1:8332] (FAST=1 → 390 + 1440 only)
8+ */
9+import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs';
10+import { mkdirSync } from 'node:fs';
11+
12+const BASE = process.argv[2] ?? 'http://localhost:8341';
13+const API = (process.argv[3] ?? 'http://127.0.0.1:8332') + '/api/v1';
14+const OUT = new URL('./screens/d1/', import.meta.url).pathname;
15+mkdirSync(OUT, { recursive: true });
16+const WIDTHS = process.env.FAST === '1' ? [390, 1440] : [320, 360, 375, 390, 430, 768, 1366, 1440, 1920];
17+const THEMES = ['dark', 'light'];
18+const j = async (u) => (await fetch(API + u)).json();
19+
20+// ---------------------------------------------------------------------------------------------------------- discover slugs
21+const top = await j('/models?limit=3&sort=quality');
22+const [a, b, c] = top.items.map((m) => m.slug);
23+const artRes = await j('/models?include=artifacts&limit=80');
24+// prefer an artifact whose canonical model is resolved (the page shows a prominent link + note); fall back to any artifact
25+const artifactRow = artRes.items.find((m) => m.entity_type === 'artifact' && m.canonical) ?? artRes.items.find((m) => m.entity_type === 'artifact');
26+const artifact = artifactRow?.slug;
27+const artifactHasCanonical = !!artifactRow?.canonical;
28+const detail = await j(`/models/${encodeURIComponent(a)}`);
29+let merged = null;
30+for (const alias of detail.identity?.api_aliases ?? []) {
31+ if (alias.includes('/') || alias === a) continue;
32+ const d = await j(`/models/${encodeURIComponent(alias)}`);
33+ if (d.redirected_from && d.slug === a) {
34+ merged = alias;
35+ break;
36+ }
37+}
38+const fam = (await j('/families?limit=1&sort=models')).items[0]?.slug;
39+const benches = (await j('/benchmarks')).items.filter((x) => Number(x.result_count) > 0);
40+const bench = benches.find((x) => x.slug === 'gpqa-diamond')?.slug ?? benches[0]?.slug;
41+const lic = (await j('/licenses')).items[0]?.key ?? 'Apache-2.0';
42+console.log(`slugs: model=${a} artifact=${artifact} merged=${merged} family=${fam} benchmark=${bench} licence=${lic}`);
43+
44+const PAGES = [
45+ '/models',
46+ '/models?openness=open-weights&min_params=7B&sort=params',
47+ `/models/${a}`,
48+ `/models/${a}/diff/${b}`,
49+ `/artifacts/${artifact}`,
50+ `/compare?ids=${a},${b},${c}`,
51+ '/benchmarks',
52+ `/benchmarks/${bench}`,
53+ '/benchmarks/matrix',
54+ `/benchmarks/${bench}/cost-vs-performance`,
55+ '/families',
56+ `/families/${fam}`,
57+ '/licenses',
58+ `/licenses/${encodeURIComponent(lic)}`,
59+];
60+// 404 favicon noise and 429s from the API's per-IP rate limiter (the sweep loads a page every second) are not page bugs.
61+const filterErrors = (errors) => errors.filter((e) => !/favicon|Failed to load resource: the server responded with a status of (404|429)/.test(e));
62+
63+let failures = 0;
64+const ok = (cond, msg) => {
65+ if (!cond) failures++;
66+ console.log(`${cond ? 'OK ' : 'FAIL'} ${msg}`);
67+};
68+
69+// ---------------------------------------------------------------------------------------------------------- redirects
70+for (const [path, expect] of [
71+ [`/models/${artifact}`, `/artifacts/${artifact}`],
72+ [`/models/${merged}`, `/models/${a}`],
73+]) {
74+ if (!path.includes('null')) {
75+ const r = await fetch(BASE + path, { redirect: 'manual' });
76+ const loc = r.headers.get('location') ?? '';
77+ ok((r.status === 308 || r.status === 301) && loc.endsWith(expect), `redirect ${path} → ${r.status} ${loc}`);
78+ } else console.log(`SKIP redirect (no slug discovered) ${path}`);
79+}
80+for (const path of ['/models/does-not-exist-xyz', '/artifacts/does-not-exist-xyz', '/families/does-not-exist-xyz', '/licenses/does-not-exist-xyz', '/benchmarks/does-not-exist-xyz']) {
81+ const r = await fetch(BASE + path);
82+ ok(r.status === 404, `404 ${path} → ${r.status}`);
83+}
84+for (const path of [`/models/${a}/opengraph-image`, `/artifacts/${artifact}/opengraph-image`, `/benchmarks/${bench}/opengraph-image`, `/families/${fam}/opengraph-image`, `/licenses/${encodeURIComponent(lic)}/opengraph-image`]) {
85+ const r = await fetch(BASE + path);
86+ ok(r.status === 200 && (r.headers.get('content-type') ?? '').startsWith('image/png'), `og ${path} → ${r.status} ${r.headers.get('content-type')}`);
87+}
88+
89+// ---------------------------------------------------------------------------------------------------------- sweep
90+const browser = await chromium.launch();
91+for (const theme of THEMES) {
92+ for (const width of WIDTHS) {
93+ const mobile = width < 768;
94+ const ctx = await browser.newContext({ viewport: { width, height: mobile ? 844 : 900 }, deviceScaleFactor: 1, isMobile: mobile, hasTouch: mobile, colorScheme: theme });
95+ await ctx.addInitScript((t) => localStorage.setItem('aia-theme', t), theme);
96+ const page = await ctx.newPage();
97+ for (const path of PAGES) {
98+ const errors = [];
99+ const onErr = (e) => errors.push(String(e));
100+ const onCon = (m) => {
101+ if (m.type() === 'error') errors.push(m.text());
102+ };
103+ page.on('pageerror', onErr);
104+ page.on('console', onCon);
105+ const t0 = Date.now();
106+ const res = await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 90000 }).catch((e) => ({ status: () => `ERR ${e.message.slice(0, 40)}` }));
107+ await page.waitForTimeout(400);
108+ const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth).catch(() => -1);
109+ const filtered = filterErrors(errors);
110+ const status = res.status();
111+ const good = status === 200 && overflow <= 0 && filtered.length === 0;
112+ if (!good) failures++;
113+ console.log(`${good ? 'OK ' : 'FAIL'} ${theme.padEnd(5)} ${String(width).padStart(4)} ${status} ${String(Date.now() - t0).padStart(5)}ms overflow=${overflow} errors=${filtered.length} ${path}${filtered.length ? ' :: ' + filtered[0].slice(0, 160) : ''}`);
114+ if ([390, 1440].includes(width) || !good) await page.screenshot({ path: `${OUT}${theme}-${width}-${path.replace(/[^a-z0-9]+/gi, '_').replace(/^_|_$/g, '')}.png`, fullPage: false }).catch(() => undefined);
115+ page.off('pageerror', onErr);
116+ page.off('console', onCon);
117+ }
118+ await ctx.close();
119+ }
120+}
121+
122+// ---------------------------------------------------------------------------------------------------------- flows (1440 dark)
123+{
124+ const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, colorScheme: 'dark' });
125+ await ctx.addInitScript(() => localStorage.setItem('aia-theme', 'dark'));
126+ const page = await ctx.newPage();
127+
128+ // evidence drawer from a model page value
129+ await page.goto(`${BASE}/models/${a}`, { waitUntil: 'networkidle' });
130+ const trigger = page.locator('[data-identity-strip] [data-evidence]').first();
131+ ok((await trigger.count()) > 0, 'model page: identity strip has evidence triggers');
132+ await trigger.click();
133+ const drawer = await page.waitForSelector('[data-evidence-drawer]', { timeout: 8000 }).catch(() => null);
134+ ok(!!drawer, 'model page: evidence drawer opens from an identity-strip value');
135+ const drawerText = drawer ? await drawer.innerText() : '';
136+ ok(/Source|Tier|Observed/i.test(drawerText), 'evidence drawer shows source · tier · observed');
137+ await page.screenshot({ path: `${OUT}flow-evidence-drawer.png` });
138+ await page.keyboard.press('Escape');
139+ // section nav present with sections in order
140+ const navItems = await page.$$eval('[data-section-nav] a', (as) => as.map((x) => x.textContent.trim()));
141+ ok(navItems[0] === 'Overview' && navItems.at(-1) === 'Provenance', `section nav order: ${navItems.join(' · ')}`);
142+ // every rendered section id has a nav item
143+ const sectionIds = await page.$$eval('section[data-section]', (ss) => ss.map((s) => s.id));
144+ const navIds = await page.$$eval('[data-section-nav] a', (as) => as.map((x) => x.getAttribute('href').slice(1)));
145+ ok(sectionIds.every((id) => navIds.includes(id)), `all ${sectionIds.length} sections are in the nav`);
146+
147+ // models list: inspector reflects the inspected row; column chooser
148+ await page.goto(`${BASE}/models`, { waitUntil: 'networkidle' });
149+ const second = page.locator('[data-models-table] tr[data-row]').nth(1);
150+ const secondSlug = await second.getAttribute('data-row');
151+ await second.locator('[data-inspect]').click();
152+ await page.waitForTimeout(200);
153+ const inspected = await page.getAttribute('aside[aria-label="Inspector"] [data-models-inspector]', 'data-inspected');
154+ ok(inspected === secondSlug, `models inspector shows the inspected row (${inspected})`);
155+ await page.click('[data-column-chooser]');
156+ const chooserLabels = await page.$$eval('[role="group"][aria-label="Visible columns"] label', (ls) => ls.length);
157+ ok(chooserLabels >= 6, `column chooser lists ${chooserLabels} columns`);
158+ const understood = await page.goto(`${BASE}/models?openness=open-weights&min_params=7B`, { waitUntil: 'networkidle' }).then(() => page.$$eval('[data-understood] a', (as) => as.map((x) => x.textContent.trim())));
159+ ok(understood.some((t) => /parameters ≥ 7B/.test(t)) && understood.some((t) => /Open weights/.test(t)), `understood-as chips: ${understood.join(' | ')}`);
160+ const countText = await page.textContent('[data-models-count]');
161+ ok(/canonical models/.test(countText ?? ''), `count label: ${countText?.trim().slice(0, 60)}`);
162+
163+ // matrix renders cells
164+ await page.goto(`${BASE}/benchmarks/matrix`, { waitUntil: 'networkidle' });
165+ const cells = await page.$$eval('table.heatmap td:not(.empty)', (tds) => tds.length);
166+ ok(cells >= 1, `matrix renders ${cells} heatmap cells`);
167+
168+ // leaderboard: one row per model
169+ await page.goto(`${BASE}/benchmarks/${bench}`, { waitUntil: 'networkidle' });
170+ const slugs = await page.$$eval('[data-leaderboard] tr[data-model]', (trs) => trs.slice(0, 50).map((t) => t.getAttribute('data-model')));
171+ ok(slugs.length > 0 && new Set(slugs).size === slugs.length, `leaderboard: ${slugs.length} rows, ${new Set(slugs).size} distinct models in the first 50`);
172+ const lbApi = await j(`/benchmarks/${bench}/leaderboard?limit=50`);
173+ ok(slugs[0] === lbApi.items[0]?.model.slug, `leaderboard first row matches the API leader (${slugs[0]})`);
174+
175+ // compare: hide-identical toggle + differences-only link
176+ await page.goto(`${BASE}/compare?ids=${a},${b},${c}`, { waitUntil: 'networkidle' });
177+ const before = await page.$$eval('[data-compare-table] tr[data-dim]', (r) => r.length);
178+ await page.click('[data-hide-identical]');
179+ await page.waitForTimeout(150);
180+ const after = await page.$$eval('[data-compare-table] tr[data-dim]', (r) => r.length);
181+ ok(after >= before, `compare: ${before} rows with identical hidden → ${after} rows shown when unhidden`);
182+ const diffOnlyHref = await page.getAttribute('[data-diff-only]', 'href');
183+ ok((diffOnlyHref ?? '').includes('diff_only=1'), `compare: differences-only link → ${diffOnlyHref}`);
184+ const evidenceCells = await page.$$eval('[data-compare-table] [data-evidence]', (x) => x.length);
185+ ok(evidenceCells > 0, `compare: ${evidenceCells} cells open evidence`);
186+
187+ // artifact page note + canonical link
188+ await page.goto(`${BASE}/artifacts/${artifact}`, { waitUntil: 'networkidle' });
189+ if (artifactHasCanonical) ok((await page.locator('[data-artifact-note]').count()) === 1, 'artifact page shows the "not an independent model" note with a canonical link');
190+ else ok((await page.locator('[data-artifact-header]').count()) === 1, `artifact page renders (canonical model unresolved for ${artifact} — honest fallback note)`);
191+ await ctx.close();
192+}
193+
194+await browser.close();
195+console.log(failures ? `\n${failures} failure(s)` : '\nall D1 checks OK');
196+process.exit(failures ? 1 : 0);
added apps/web/src/app/artifacts/[slug]/opengraph-image.tsx +24 −0
@@ -0,0 +1,24 @@
1+import { ImageResponse } from 'next/og';
2+import { Fallback, Wallpaper } from '@/components/brand/og';
3+import { apiD1, safe } from '@/lib/api';
4+import { fmtGb, fmtInt, num } from '@/lib/format';
5+import { routes, SITE_NAME } from '@/lib/site';
6+
7+export const runtime = 'nodejs';
8+export const alt = `Artifact on ${SITE_NAME}`;
9+export const size = { width: 1200, height: 630 };
10+export const contentType = 'image/png';
11+
12+export default async function ArtifactOgImage({ params }: { params: Promise<{ slug: string }> }) {
13+ const { slug } = await params;
14+ const d = await safe(apiD1.model(slug));
15+ if (!d || d.entity_type !== 'artifact') return new ImageResponse(<Fallback label="Artifact" />, { ...size });
16+ const a = d.attributes ?? {};
17+ const counters: [string, string][] = [];
18+ if (typeof a.quant_format === 'string') counters.push(['Format', String(a.quant_format).toUpperCase()]);
19+ if (num(a.file_size_gb) !== null) counters.push(['File size', fmtGb(a.file_size_gb, 1)]);
20+ if (num(a['metric.downloads']) !== null) counters.push(['Downloads', fmtInt(a['metric.downloads'])]);
21+ if (d.organization) counters.push(['Publisher', d.organization.name.slice(0, 18)]);
22+ const kind = d.artifact_kind ?? 'artifact';
23+ return new ImageResponse(<Wallpaper eyebrow={`Artifact · ${kind}`} title={d.name} subtitle={d.canonical ? `${kind.charAt(0).toUpperCase()}${kind.slice(1)} of ${d.canonical.name} — not an independent model` : 'Packaging of a canonical model'} counters={counters.slice(0, 4)} footer={`www.ai-atlas.co${routes.artifact(d.slug)}`} markPx={220} />, { ...size });
24+}
added apps/web/src/app/artifacts/[slug]/page.tsx +37 −0
@@ -0,0 +1,37 @@
1+import type { Metadata } from 'next';
2+import { notFound, permanentRedirect } from 'next/navigation';
3+import { ArtifactPage, describeArtifact } from '@/components/entity/artifact-page';
4+import { ApiError, apiD1, safe } from '@/lib/api';
5+import { routes, SITE_NAME, SITE_URL } from '@/lib/site';
6+import type { ModelDetail } from '@/lib/types';
7+
8+type Params = { params: Promise<{ slug: string }> };
9+
10+async function load(slug: string): Promise<ModelDetail> {
11+ try {
12+ return await apiD1.model(slug);
13+ } catch (e) {
14+ if (e instanceof ApiError && e.notFound) notFound();
15+ throw e;
16+ }
17+}
18+
19+export async function generateMetadata({ params }: Params): Promise<Metadata> {
20+ const { slug } = await params;
21+ const d = await safe(apiD1.model(slug));
22+ if (!d || d.entity_type !== 'artifact' || d.slug !== slug) return { title: 'Artifact', robots: { index: false } };
23+ const kind = d.artifact_kind ?? 'artifact';
24+ const title = `${d.name} — ${kind.charAt(0).toUpperCase()}${kind.slice(1)} of ${d.canonical?.name ?? 'a model'}`;
25+ const description = describeArtifact(d);
26+ const canonical = routes.artifact(d.slug);
27+ return { title, description, alternates: { canonical }, openGraph: { title: `${title} | ${SITE_NAME}`, description, url: `${SITE_URL}${canonical}`, type: 'article', siteName: SITE_NAME }, twitter: { card: 'summary_large_image', title, description } };
28+}
29+
30+export default async function ArtifactRoute({ params }: Params) {
31+ const { slug } = await params;
32+ const d = await load(slug);
33+ // A model slug under /artifacts → its canonical URL; unknown types → 404.
34+ if (d.entity_type !== 'artifact') permanentRedirect(routes.entity(d));
35+ if (d.slug !== slug) permanentRedirect(routes.artifact(d.slug));
36+ return <ArtifactPage d={d} canonical={routes.artifact(d.slug)} />;
37+}
added apps/web/src/app/benchmarks/[slug]/cost-vs-performance/page.tsx +257 −0
@@ -0,0 +1,257 @@
1+import type { Metadata } from 'next';
2+import { ScrollX } from '@/components/models/scroll-x';
3+import Link from 'next/link';
4+import { notFound } from 'next/navigation';
5+import type { ScatterPoint } from '@/components/charts';
6+import { ParetoChart } from '@/components/benchmarks/client-charts';
7+import { TrustBadge } from '@/components/models/badges';
8+import { configChipsOf, fmtScoreUnit, opennessLabel, xFormatter } from '@/components/models/shared';
9+import { Estimated } from '@/components/ui/badges';
10+import { DataTable, Td, Th } from '@/components/ui/data-table';
11+import { Container, Note, PageHeader } from '@/components/ui/section';
12+import { EmptyState, Unavailable } from '@/components/ui/unavailable';
13+import { ApiError, apiD1, safe } from '@/lib/api';
14+import { cn } from '@/lib/cn';
15+import { fmtInt } from '@/lib/format';
16+import { routes, SITE_NAME, SITE_URL } from '@/lib/site';
17+import type { ParetoPayload } from '@/lib/types';
18+
19+/*
20+ Cost vs performance for one benchmark (Pareto view from `/pareto`): X = cheapest current output price (log toggle) or another axis,
21+ Y = score in the comparability group, bubble = context or parameters (joined from a second /pareto call), frontier from the API.
22+*/
23+
24+type SP = Record<string, string | undefined>;
25+type Params = { params: Promise<{ slug: string }>; searchParams: Promise<SP> };
26+export const revalidate = 600;
27+const X_AXES = [
28+ { key: 'output_price', label: 'Output price' },
29+ { key: 'input_price', label: 'Input price' },
30+ { key: 'parameter_count', label: 'Parameters' },
31+ { key: 'context_length', label: 'Context' },
32+ { key: 'memory_estimate', label: 'Memory (est.)' },
33+];
34+
35+export async function generateMetadata({ params }: Params): Promise<Metadata> {
36+ const { slug } = await params;
37+ const d = await safe(apiD1.benchmark(slug));
38+ if (!d || d.entity_type !== 'benchmark') return { title: 'Cost vs performance', robots: { index: false } };
39+ const title = `${d.name} — Cost vs Performance (Pareto)`;
40+ const description = `Every canonical model with a current ${d.name} result plotted against its cheapest current output price (USD per 1M tokens), with the Pareto frontier — same comparability group only, trust level on every point. ${SITE_NAME}.`;
41+ const canonical = `${routes.benchmark(d.slug)}/cost-vs-performance`;
42+ return { title, description, alternates: { canonical }, openGraph: { title: `${title} | ${SITE_NAME}`, description, url: `${SITE_URL}${canonical}`, type: 'article' } };
43+}
44+
45+async function loadPareto(q: Record<string, string | number | undefined>): Promise<{ res: ParetoPayload | null; error: string | null }> {
46+ try {
47+ return { res: await apiD1.pareto(q), error: null };
48+ } catch (e) {
49+ if (e instanceof ApiError && e.notFound) notFound();
50+ if (e instanceof ApiError && (e.status === 400 || e.status === 422)) return { res: null, error: e.detail ?? 'Unsupported axis.' };
51+ return { res: null, error: null };
52+ }
53+}
54+
55+export default async function CostVsPerformancePage({ params, searchParams }: Params) {
56+ const { slug } = await params;
57+ const sp = await searchParams;
58+ const x = X_AXES.find((a) => a.key === sp.x) ?? X_AXES[0]!;
59+ const log = sp.log !== '0';
60+ const bubble = sp.bubble === 'params' ? 'params' : sp.bubble === 'none' ? 'none' : 'context';
61+ const metric = sp.metric?.trim() || undefined;
62+ const configKey = sp.config_key?.trim() || undefined;
63+ const org = sp.org?.trim() || undefined;
64+ const openness = sp.openness?.trim() || undefined;
65+ const base = { benchmark: slug, metric, config_key: configKey, org, openness };
66+ const [detail, { res, error }, bubbleRes] = await Promise.all([safe(apiD1.benchmark(slug)), loadPareto({ ...base, x: x.key }), bubble === 'none' ? Promise.resolve(null) : safe(apiD1.pareto({ ...base, x: bubble === 'params' ? 'parameter_count' : 'context_length' }))]);
67+ if (!detail || detail.entity_type !== 'benchmark') notFound();
68+ const canonical = `${routes.benchmark(detail.slug)}/cost-vs-performance`;
69+ const href = (patch: SP) => {
70+ const p = new URLSearchParams();
71+ for (const [k, v] of Object.entries({ x: x.key === 'output_price' ? undefined : x.key, log: log ? undefined : '0', bubble: bubble === 'context' ? undefined : bubble, metric, config_key: configKey, org, openness, ...patch })) if (v) p.set(k, v);
72+ const s = p.toString();
73+ return `${canonical}${s ? `?${s}` : ''}`;
74+ };
75+ const unit = typeof detail.attributes?.unit === 'string' ? (detail.attributes.unit as string) : null;
76+ const bubbleById = new Map<string, number>();
77+ for (const p of bubbleRes?.points ?? []) bubbleById.set(p.model.id, p.x);
78+ const frontierSet = new Set(res?.frontier ?? []);
79+ const points: ScatterPoint[] = (res?.points ?? []).map((p) => {
80+ const chips = configChipsOf(p.config, res?.group?.config ?? null, 3);
81+ return {
82+ id: p.id,
83+ x: p.x,
84+ y: p.y,
85+ r: bubble === 'none' ? 1 : bubbleById.get(p.model.id) ?? 1,
86+ label: p.model.name,
87+ sub: [p.model.organization, p.provider?.name ? `via ${p.provider.name}` : null, `rank ${p.rank}`, p.trust_level, chips.map((c) => `${c.key}=${c.value}`).join(' ') || null, p.estimated ? 'estimated' : null].filter(Boolean).join(' · '),
88+ href: `/models/${encodeURIComponent(p.model.slug)}`,
89+ color: p.model.openness && /open|restricted/.test(p.model.openness) ? 'var(--positive)' : 'var(--type-model)',
90+ group: p.model.openness ?? undefined,
91+ };
92+ });
93+ const frontierPts = points.filter((p) => frontierSet.has(p.id)).sort((a, b) => a.x - b.x);
94+ const hib = res?.group?.higher_is_better !== false;
95+ const yFmt = (v: number) => fmtScoreUnit(v, unit);
96+ const xFmt = xFormatter(x.key);
97+ const chip = (on: boolean) => cn('inline-flex h-8 items-center border px-2.5 text-xs whitespace-nowrap', on ? 'border-ink bg-ink text-canvas' : 'border-rule text-ink-2 hover:border-rule-strong hover:text-ink');
98+ const ld = { '@context': 'https://schema.org', '@type': 'Dataset', name: `${detail.name} — cost vs performance`, url: `${SITE_URL}${canonical}`, description: res?.methodology };
99+
100+ return (
101+ <Container wide>
102+ <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />
103+ <PageHeader
104+ eyebrow={
105+ <>
106+ <Link href={routes.benchmarks()} className="hover:text-ink">
107+ Benchmarks
108+ </Link>
109+ <span aria-hidden>/</span>
110+ <Link href={routes.benchmark(detail.slug)} className="hover:text-ink">
111+ {detail.name}
112+ </Link>
113+ </>
114+ }
115+ title={`${detail.name} — cost vs performance`}
116+ lede={res?.group ? `Best current row per canonical model in the group “${res.group.label}” (${fmtInt(res.group.model_count)} models) against ${res.x.label}. The dashed line is the Pareto frontier: no model is both better and cheaper than a point on it.` : 'Best current row per canonical model against its cheapest current price.'}
117+ aside={
118+ <Link href={routes.benchmark(detail.slug)} className="inline-flex h-9 items-center border border-rule px-2.5 text-sm text-ink-2 hover:border-rule-strong hover:text-ink">
119+ ← Leaderboard
120+ </Link>
121+ }
122+ >
123+ <div className="mt-5 flex flex-wrap items-center gap-x-6 gap-y-2 text-xs" data-pareto-controls>
124+ <span className="flex flex-wrap items-center gap-1.5">
125+ <span className="eyebrow mr-1">X</span>
126+ {X_AXES.map((a) => (
127+ <Link key={a.key} href={href({ x: a.key === 'output_price' ? undefined : a.key })} className={chip(a.key === x.key)} aria-current={a.key === x.key ? 'true' : undefined}>
128+ {a.label}
129+ </Link>
130+ ))}
131+ </span>
132+ <span className="flex items-center gap-1.5">
133+ <span className="eyebrow mr-1">Scale</span>
134+ <Link href={href({ log: undefined })} className={chip(log)} aria-pressed={log}>
135+ log
136+ </Link>
137+ <Link href={href({ log: '0' })} className={chip(!log)} aria-pressed={!log}>
138+ linear
139+ </Link>
140+ </span>
141+ <span className="flex items-center gap-1.5">
142+ <span className="eyebrow mr-1">Bubble</span>
143+ <Link href={href({ bubble: undefined })} className={chip(bubble === 'context')}>
144+ context
145+ </Link>
146+ <Link href={href({ bubble: 'params' })} className={chip(bubble === 'params')}>
147+ params
148+ </Link>
149+ <Link href={href({ bubble: 'none' })} className={chip(bubble === 'none')}>
150+ none
151+ </Link>
152+ </span>
153+ {res && res.groups.length > 1 && (
154+ <form action={canonical} method="get" className="flex items-center gap-1.5">
155+ {x.key !== 'output_price' && <input type="hidden" name="x" value={x.key} />}
156+ {!log && <input type="hidden" name="log" value="0" />}
157+ <span className="eyebrow mr-1">Group</span>
158+ <select name="group" defaultValue={res.group ? `${res.group.metric}|${res.group.config_key}` : ''} className="h-8 max-w-[18rem] border border-rule bg-surface px-2 text-xs text-ink" aria-label="Comparability group">
159+ {res.groups.map((g) => (
160+ <option key={g.config_key} value={`${g.metric}|${g.config_key}`}>
161+ {g.label} ({g.model_count})
162+ </option>
163+ ))}
164+ </select>
165+ <button type="submit" className="inline-flex h-8 items-center bg-ink px-2.5 text-xs font-medium text-canvas">
166+ Go
167+ </button>
168+ </form>
169+ )}
170+ </div>
171+ </PageHeader>
172+
173+ <div className="space-y-8 pb-16">
174+ {error ? (
175+ <EmptyState title="This axis is not available">{error}</EmptyState>
176+ ) : !res ? (
177+ <Unavailable what="Pareto view" />
178+ ) : points.length === 0 ? (
179+ <EmptyState title="No model has both a score in this group and a value on this axis">Try another axis or comparability group.</EmptyState>
180+ ) : (
181+ <>
182+ <section data-pareto-chart>
183+ <ParetoChart points={points} xKey={x.key} unit={unit} log={log} xLabel={res.x.label} yLabel={res.y.label} frontier={frontierPts.map((p) => ({ x: p.x, y: p.y }))} highlight={[...frontierSet]} />
184+ <p className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-[11px] text-ink-3">
185+ <span className="inline-flex items-center gap-1">
186+ <span className="inline-block size-2 rounded-full" style={{ background: 'var(--positive)' }} /> open / restricted weights
187+ </span>
188+ <span className="inline-flex items-center gap-1">
189+ <span className="inline-block size-2 rounded-full" style={{ background: 'var(--type-model)' }} /> closed
190+ </span>
191+ <span className="inline-flex items-center gap-1">
192+ <span className="inline-block h-[2px] w-4 border-t border-dashed border-accent-2" /> Pareto frontier ({fmtInt(frontierPts.length)} models)
193+ </span>
194+ {bubble !== 'none' && <span>bubble = {bubble === 'params' ? 'total parameters' : 'context window'}{bubbleRes ? '' : ' (unavailable — uniform)'}</span>}
195+ {x.key === 'memory_estimate' && <Estimated />}
196+ </p>
197+ </section>
198+ <section>
199+ <p className="eyebrow mb-2">
200+ Frontier models <span className="tnum text-ink-3">{fmtInt(frontierPts.length)}</span>
201+ </p>
202+ <ScrollX><DataTable caption="Pareto frontier" compact>
203+ <thead>
204+ <tr>
205+ <Th>Model</Th>
206+ <Th num>{res.y.label}</Th>
207+ <Th num>Rank</Th>
208+ <Th num>{res.x.label}</Th>
209+ <Th>Provider</Th>
210+ <Th>Trust</Th>
211+ </tr>
212+ </thead>
213+ <tbody>
214+ {(hib ? [...frontierPts].sort((a, b) => b.y - a.y) : [...frontierPts].sort((a, b) => a.y - b.y)).map((p) => {
215+ const raw = res.points.find((q) => q.id === p.id)!;
216+ return (
217+ <tr key={p.id}>
218+ <Td primary>
219+ <Link href={p.href ?? '#'} className="text-ink hover:text-accent hover:underline">
220+ {p.label}
221+ </Link>
222+ <span className="block text-[11px] text-ink-3">
223+ {raw.model.organization ?? ''}
224+ {raw.model.openness ? ` · ${opennessLabel(raw.model.openness)}` : ''}
225+ </span>
226+ </Td>
227+ <Td num label={res.y.label} className="tnum font-medium">
228+ {yFmt(p.y)}
229+ </Td>
230+ <Td num label="Rank" className="tnum text-ink-2">
231+ {fmtInt(raw.rank)}
232+ </Td>
233+ <Td num label={res.x.label} className="tnum text-accent-2">
234+ {xFmt(p.x)}
235+ {raw.estimated && <span className="ml-1 text-[10px] uppercase text-warning">est.</span>}
236+ </Td>
237+ <Td label="Provider" className="text-ink-2">
238+ {raw.provider ? <Link href={routes.entity(raw.provider)} className="hover:text-accent">{raw.provider.name}</Link> : '—'}
239+ </Td>
240+ <Td label="Trust">
241+ <TrustBadge level={raw.trust_level} />
242+ </Td>
243+ </tr>
244+ );
245+ })}
246+ </tbody>
247+ </DataTable></ScrollX>
248+ </section>
249+ <Note>
250+ <strong className="font-medium text-ink-2">Methodology.</strong> {res.methodology} Only the selected comparability group is plotted; points under other configurations are not mixed in. Price = cheapest current offer across providers at the time of the last crawl. Nothing is estimated except where marked.
251+ </Note>
252+ </>
253+ )}
254+ </div>
255+ </Container>
256+ );
257+}
added apps/web/src/app/benchmarks/[slug]/opengraph-image.tsx +27 −0
@@ -0,0 +1,27 @@
1+import { ImageResponse } from 'next/og';
2+import { Fallback, Wallpaper } from '@/components/brand/og';
3+import { fmtScoreUnit } from '@/components/models/shared';
4+import { apiD1, safe } from '@/lib/api';
5+import { fmtInt } from '@/lib/format';
6+import { routes, SITE_NAME } from '@/lib/site';
7+
8+export const runtime = 'nodejs';
9+export const alt = `Benchmark leaderboard on ${SITE_NAME}`;
10+export const size = { width: 1200, height: 630 };
11+export const contentType = 'image/png';
12+
13+/** Benchmark OG: "Current recorded leader: X (score)" + results · models · metric · trust. */
14+export default async function BenchmarkOgImage({ params }: { params: Promise<{ slug: string }> }) {
15+ const { slug } = await params;
16+ const [d, list] = await Promise.all([safe(apiD1.benchmark(slug)), safe(apiD1.benchmarks())]);
17+ if (!d) return new ImageResponse(<Fallback label="Benchmark" />, { ...size });
18+ const item = list?.items.find((b) => b.slug === d.slug || b.id === d.id) ?? null;
19+ const leader = item?.leader ?? d.leaderboard?.[0] ?? null;
20+ const counters: [string, string][] = [];
21+ counters.push(['Results', fmtInt(item?.result_count ?? d.result_count ?? 0)]);
22+ counters.push(['Models', fmtInt(item?.model_count ?? d.model_count ?? 0)]);
23+ if (d.metric ?? item?.metric) counters.push(['Metric', `${d.metric ?? item?.metric} ${(d.direction ?? item?.direction) === 'lower' ? '↓' : '↑'}`]);
24+ if (leader) counters.push(['Leader score', fmtScoreUnit(leader.score, leader.unit)]);
25+ const subtitle = leader ? `Current recorded leader: ${leader.model.name}${leader.model.organization ? ` (${leader.model.organization.name})` : ''} — ${leader.trust_label ?? leader.trust_level}` : 'No results recorded yet — sources being connected.';
26+ return new ImageResponse(<Wallpaper eyebrow={`Benchmark${d.category ? ` · ${d.category}` : ''}${d.variant && d.variant !== 'main' ? ` · ${d.variant}` : ''}`} title={d.name} subtitle={subtitle} counters={counters.slice(0, 4)} footer={`www.ai-atlas.co${routes.benchmark(d.slug)}`} markPx={220} />, { ...size });
27+}
modified apps/web/src/app/benchmarks/[slug]/page.tsx +321 −168
@@ -1,213 +1,366 @@
1 import { ExternalLink } from 'lucide-react'; 1 import { ExternalLink } from 'lucide-react';
2 import type { Metadata } from 'next'; 2 import type { Metadata } from 'next';
3 import Link from 'next/link'; 3 import Link from 'next/link';
4−import { permanentRedirect } from 'next/navigation'; 4+import { notFound, permanentRedirect } from 'next/navigation';
5−import { configChips, ConfigChips, HistoryChart, Leaderboard } from '@/components/benchmarks/leaderboard'; 5+import { HistoryChart } from '@/components/benchmarks/leaderboard';
6+import { FrontierChart, GroupPicker, Leaderboard2 } from '@/components/benchmarks/leaderboard2';
6 import { CompareTrayBar } from '@/components/compare/compare-tray-bar'; 7 import { CompareTrayBar } from '@/components/compare/compare-tray-bar';
7−import { RelationsBlock, SourcesTable, TimelineList } from '@/components/entity/blocks'; 8+import { ComparabilityLegend } from '@/components/entity/model-blocks';
8−import { HistoryPanel } from '@/components/entity/history'; 9+import { TerminalLayout } from '@/components/layout/terminal';
9−import { entityMetadata, loadEntity } from '@/components/entity/load';
10 import { ViewBeacon } from '@/components/layout/view-beacon'; 10 import { ViewBeacon } from '@/components/layout/view-beacon';
11+import { TrustBadge } from '@/components/models/badges';
12+import { fmtScoreUnit, TRUST_LONG } from '@/components/models/shared';
11 import { EntityBadge, StatusBadge } from '@/components/ui/badges'; 13 import { EntityBadge, StatusBadge } from '@/components/ui/badges';
12−import { QualityMark } from '@/components/ui/entity'; 14+import { EntityLink, QualityMark } from '@/components/ui/entity';
13 import { KeyValue, type KVRow } from '@/components/ui/key-value'; 15 import { KeyValue, type KVRow } from '@/components/ui/key-value';
14 import { Container, Note } from '@/components/ui/section'; 16 import { Container, Note } from '@/components/ui/section';
15−import { TabPanel, Tabs, type TabDef } from '@/components/ui/tabs';
16 import { Unavailable } from '@/components/ui/unavailable'; 17 import { Unavailable } from '@/components/ui/unavailable';
17−import { api, safe } from '@/lib/api'; 18+import { api, ApiError, apiD1, safe } from '@/lib/api';
18−import { fmtAgo, fmtDate, fmtInt, fmtScore } from '@/lib/format'; 19+import { cn } from '@/lib/cn';
20+import { fmtAgo, fmtDate, fmtInt, num } from '@/lib/format';
19 import { routes, SITE_NAME, SITE_URL } from '@/lib/site'; 21 import { routes, SITE_NAME, SITE_URL } from '@/lib/site';
22+import type { BenchmarkDetail, Group } from '@/lib/types';
20 23
21−type Params = { params: Promise<{ slug: string }>; searchParams: Promise<Record<string, string | undefined>> }; 24+/*
25+ Benchmark leaderboard page 2.0 — TerminalLayout: filters (comparability group · trust · organization · comparable-only) ·
26+ main (variant navigation, frontier over time, one-row-per-model leaderboard) · inspector (definition with evidence, trust mix, links).
27+*/
28+
29+type SP = Record<string, string | undefined>;
30+type Params = { params: Promise<{ slug: string }>; searchParams: Promise<SP> };
22 const LIMIT = 100; 31 const LIMIT = 100;
32+export const revalidate = 300;
33+
34+async function loadBenchmark(slug: string): Promise<BenchmarkDetail> {
35+ try {
36+ const d = await apiD1.benchmark(slug);
37+ if (d.entity_type !== 'benchmark') notFound();
38+ return d;
39+ } catch (e) {
40+ if (e instanceof ApiError && e.notFound) notFound();
41+ throw e;
42+ }
43+}
23 44
24 export async function generateMetadata({ params }: Params): Promise<Metadata> { 45 export async function generateMetadata({ params }: Params): Promise<Metadata> {
25 const { slug } = await params; 46 const { slug } = await params;
26− const m = await entityMetadata('benchmarks', slug); 47+ const d = await safe(apiD1.benchmark(slug));
27− if (!m.title || (m.robots as { index?: boolean } | undefined)?.index === false) return m; 48+ if (!d || d.entity_type !== 'benchmark' || d.slug !== slug) return { title: 'Benchmark', robots: { index: false } };
28− const name = String(m.title).split(' — ')[0]; 49+ const title = `${d.name} Leaderboard & Results`;
29− const title = `${name} leaderboard — results, configs and history`; 50+ const leader = d.leaderboard?.[0];
30− return { ...m, title, openGraph: { ...(m.openGraph ?? {}), title: `${title} | ${SITE_NAME}` }, twitter: { ...(m.twitter ?? {}), title } }; 51+ const description = `${d.name}${d.category ? ` (${d.category})` : ''}: ${fmtInt(d.result_count ?? 0)} current results across ${fmtInt(d.model_count ?? 0)} canonical models, metric ${d.metric ?? '—'}${leader ? `; current recorded leader ${leader.model.name} at ${fmtScoreUnit(leader.score, leader.unit)} (${leader.trust_label})` : ''}. Comparability groups, trust levels, frontier over time and per-model history on ${SITE_NAME}.`.slice(0, 300);
52+ const canonical = routes.benchmark(d.slug);
53+ return { title, description, alternates: { canonical }, openGraph: { title: `${title} | ${SITE_NAME}`, description, url: `${SITE_URL}${canonical}`, type: 'article', siteName: SITE_NAME }, twitter: { card: 'summary_large_image', title, description } };
31 } 54 }
32 55
33−/**
34− * Dedicated benchmark page: definition, leaderboard (paged, config-filterable), per-model history chart,
35− * timeline and sources. Static segment → shadows /[type]/[slug] for benchmarks only.
36− */
37 export default async function BenchmarkPage({ params, searchParams }: Params) { 56 export default async function BenchmarkPage({ params, searchParams }: Params) {
38 const { slug } = await params; 57 const { slug } = await params;
39 const sp = await searchParams; 58 const sp = await searchParams;
40− const d = await loadEntity('benchmarks', slug); 59+ const d = await loadBenchmark(slug);
41− if (d.slug !== slug) permanentRedirect(routes.benchmark(d.slug, { config: sp.config, model: sp.model })); 60+ if (d.slug !== slug) permanentRedirect(routes.benchmark(d.slug));
42− const canonical = routes.entity(d); 61+ const canonical = routes.benchmark(d.slug);
62+ // Group from `?group=metric|config_key` (select form) or `?metric=&config_key=` (links).
63+ let metric = sp.metric?.trim() || undefined;
64+ let configKey = sp.config_key?.trim() || undefined;
65+ if (sp.group?.includes('|')) [metric, configKey] = sp.group.split('|') as [string, string];
66+ const trust = sp.trust?.trim() || undefined;
67+ const org = sp.org?.trim() || undefined;
68+ const comparableOnly = sp.comparable_only === '1';
43 const offset = Math.max(0, Number(sp.offset) || 0); 69 const offset = Math.max(0, Number(sp.offset) || 0);
44− const config = sp.config?.trim() || undefined;
45 const model = sp.model?.trim() || undefined; 70 const model = sp.model?.trim() || undefined;
46− // History tab (same mode as EntityPage): full claim history always, "as of" state only when requested. 71+ const [lb, frontier, list, history] = await Promise.all([
47− const asofRaw = sp.asof?.trim() || undefined; 72+ safe(apiD1.leaderboard(d.slug, { metric, config_key: configKey, trust, org, comparable_only: comparableOnly ? 1 : undefined, limit: LIMIT, offset })),
48− const asofValid = asofRaw && /^\d{4}-\d{2}-\d{2}$/.test(asofRaw) ? asofRaw : undefined; 73+ safe(apiD1.frontier(d.slug, { metric, config_key: configKey })),
49− const property = sp.property?.trim() || undefined; 74+ safe(apiD1.benchmarks()),
50− const [page, history, claimHistory, asofPayload] = await Promise.all([
51− safe(api.benchmarkResults(d.slug, { limit: LIMIT, offset, config })),
52 model ? safe(api.benchmarkHistory(d.slug, model)) : Promise.resolve(null), 75 model ? safe(api.benchmarkHistory(d.slug, model)) : Promise.resolve(null),
53− safe(api.entityHistory(d.slug, property)),
54− asofValid ? safe(api.entityAsOf(d.slug, asofValid)) : Promise.resolve(null),
55 ]); 76 ]);
56− const claims = claimHistory?.items ?? null; 77+ const group: Group | null = lb?.group ?? d.primary_group ?? null;
57− // Chips come from an unfiltered sample so the filter can be changed while one is active. 78+ const groups = lb?.groups ?? d.groups ?? [];
58− const sample = config || offset ? await safe(api.benchmarkResults(d.slug, { limit: LIMIT })) : page;
59− const chips = configChips(sample?.items ?? []);
60 const a = d.attributes ?? {}; 79 const a = d.attributes ?? {};
61− const unit = typeof a.unit === 'string' ? a.unit : (page?.items[0]?.unit ?? null); 80+ const unit = typeof a.unit === 'string' ? a.unit : (lb?.items[0]?.unit ?? null);
62− const direction = page?.items[0] ? (page.items[0].higher_is_better === false ? 'Lower is better' : 'Higher is better') : null; 81+ const item = list?.items.find((b) => b.slug === d.slug) ?? null;
82+ const siblings = list?.items.filter((b) => (b.family ?? b.slug) === (d.family ?? item?.family ?? d.slug)).sort((x, y) => Number(!!y.attributes?.family_head) - Number(!!x.attributes?.family_head) || (num(y.result_count) ?? 0) - (num(x.result_count) ?? 0)) ?? [];
83+ const cur: SP = { metric, config_key: configKey, trust, org, comparable_only: comparableOnly ? '1' : undefined, model };
84+ const href = (patch: SP) => {
85+ const p = new URLSearchParams();
86+ for (const [k, v] of Object.entries({ ...cur, ...patch })) if (v) p.set(k, v);
87+ const s = p.toString();
88+ return `${canonical}${s ? `?${s}` : ''}`;
89+ };
63 const link = (k: string) => (typeof a[k] === 'string' && /^https?:\/\//.test(a[k] as string) ? (a[k] as string) : null); 90 const link = (k: string) => (typeof a[k] === 'string' && /^https?:\/\//.test(a[k] as string) ? (a[k] as string) : null);
64 const site = link('website') ?? link('official_url'); 91 const site = link('website') ?? link('official_url');
65 const defRows: KVRow[] = [ 92 const defRows: KVRow[] = [
66− { key: 'category', raw: a.category },
67 { key: 'task', raw: a.task }, 93 { key: 'task', raw: a.task },
68− { key: 'metric', raw: a.metric, value: typeof a.metric === 'string' ? <span>{a.metric}{unit && <span className="text-ink-3"> · {unit}</span>}</span> : undefined }, 94+ { key: 'metric', raw: a.metric_label ?? a.metric, value: typeof (a.metric_label ?? a.metric) === 'string' ? <span>{String(a.metric_label ?? a.metric)}{unit && <span className="text-ink-3"> · {unit}</span>}{a.higher_is_better === false ? <span className="text-ink-3"> · lower is better</span> : ''}</span> : undefined },
69− ...(direction ? [{ key: 'direction', label: 'Direction', value: <span>{direction}</span> }] : []), 95+ { key: 'metric_min', label: 'Metric range', raw: num(a.metric_min) !== null && num(a.metric_max) !== null ? `${a.metric_min} – ${a.metric_max}` : undefined },
70− { key: 'creator', raw: a.creator }, 96+ { key: 'variant', raw: a.variant },
97+ { key: 'version', raw: a.version },
98+ { key: 'harness', raw: a.harness },
99+ { key: 'comparability_note', label: 'Comparability note', raw: a.comparability_note },
100+ { key: 'known_limitations', raw: a.known_limitations },
71 { key: 'paper', raw: a.paper }, 101 { key: 'paper', raw: a.paper },
72 { key: 'paper_url', raw: a.paper_url }, 102 { key: 'paper_url', raw: a.paper_url },
73− { key: 'methodology', raw: a.methodology, value: link('methodology') ? <a href={link('methodology') as string} className="link break-all" target="_blank" rel="noopener noreferrer">{(link('methodology') as string).replace(/^https?:\/\/(www\.)?/, '')}</a> : undefined }, 103+ { key: 'website', raw: a.website },
74− { key: 'known_limitations', raw: a.known_limitations }, 104+ { key: 'creator', raw: a.creator },
75− { key: 'website', raw: a.website, value: site ? <a href={site} className="link break-all" target="_blank" rel="noopener noreferrer">{site.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '')}</a> : undefined },
76− { key: 'official_url', raw: link('website') ? undefined : a.official_url },
77− ];
78− const tabs: TabDef[] = [
79− { id: 'leaderboard', label: 'Leaderboard', count: page?.total ?? d.results?.length ?? undefined },
80− { id: 'definition', label: 'Definition' },
81− { id: 'relations', label: 'Relations', count: d.relations?.reduce((n, g) => n + g.items.length, 0) || undefined },
82− { id: 'history', label: 'History', count: property ? undefined : claims?.length || undefined },
83− { id: 'timeline', label: 'Timeline', count: d.timeline?.length || undefined },
84− { id: 'sources', label: 'Sources', count: d.sources?.length || undefined },
85 ]; 105 ];
86− const ld = { '@context': 'https://schema.org', '@type': 'Dataset', name: d.name, url: `${SITE_URL}${canonical}`, description: d.description ?? (typeof a.task === 'string' ? a.task : undefined), alternateName: d.aliases?.length ? d.aliases : undefined, creator: typeof a.creator === 'string' ? { '@type': 'Organization', name: a.creator } : d.organization ? { '@type': 'Organization', name: d.organization.name } : undefined, sameAs: site ? [site] : undefined, measurementTechnique: typeof a.metric === 'string' ? a.metric : undefined }; 106+ const trustMix = lb?.group?.trust_mix ?? d.trust_mix ?? item?.trust_mix ?? {};
87− const href = (o: number) => routes.benchmark(d.slug, { config, model }) + (o ? `${config || model ? '&' : '?'}offset=${o}` : ''); 107+ const filterCount = [trust, org, comparableOnly ? '1' : undefined, configKey].filter(Boolean).length;
108+ const ld = { '@context': 'https://schema.org', '@type': 'Dataset', name: `${d.name} leaderboard`, url: `${SITE_URL}${canonical}`, description: d.description ?? (typeof a.task === 'string' ? a.task : undefined), alternateName: d.aliases?.length ? d.aliases : undefined, measurementTechnique: typeof a.metric === 'string' ? a.metric : undefined, sameAs: [site, link('paper')].filter(Boolean), variableMeasured: typeof a.metric === 'string' ? a.metric : undefined };
109+ const empty = (num(d.result_count) ?? 0) === 0;
110+
111+ const filters = (
112+ <div className="space-y-5 pb-6 text-sm" data-leaderboard-filters>
113+ <div>
114+ <p className="eyebrow mb-1">Comparability group</p>
115+ <GroupPicker groups={groups} active={group} makeHref={(g) => href({ metric: g.metric, config_key: g.config_key, offset: undefined })} slug={d.slug} hidden={{ trust, org, comparable_only: cur.comparable_only }} />
116+ </div>
117+ {Object.keys(trustMix).length > 0 && (
118+ <div>
119+ <p className="eyebrow mb-1">Trust level</p>
120+ <ul className="space-y-px">
121+ <li>
122+ <Link href={href({ trust: undefined, offset: undefined })} className={cn('flex min-h-8 items-center justify-between px-1 text-[13px] hover:bg-surface-2', !trust ? 'bg-surface-2 font-medium text-ink' : 'text-ink-2')} aria-current={!trust ? 'true' : undefined}>
123+ <span>All</span>
124+ <span className="tnum text-xs text-ink-3">{fmtInt(Object.values(trustMix).reduce((n, v) => n + v, 0))}</span>
125+ </Link>
126+ </li>
127+ {Object.entries(trustMix)
128+ .sort((x, y) => y[1] - x[1])
129+ .map(([k, n]) => (
130+ <li key={k}>
131+ <Link href={href({ trust: trust === k ? undefined : k, offset: undefined })} className={cn('flex min-h-8 items-center justify-between gap-2 px-1 text-[13px] hover:bg-surface-2', trust === k ? 'bg-surface-2 font-medium text-ink' : 'text-ink-2')} aria-current={trust === k ? 'true' : undefined} title={TRUST_LONG[k] ?? k}>
132+ <TrustBadge level={k} />
133+ <span className="tnum text-xs text-ink-3">{fmtInt(n)}</span>
134+ </Link>
135+ </li>
136+ ))}
137+ </ul>
138+ </div>
139+ )}
140+ <form action={canonical} method="get" className="space-y-3">
141+ {metric && <input type="hidden" name="metric" value={metric} />}
142+ {configKey && <input type="hidden" name="config_key" value={configKey} />}
143+ {trust && <input type="hidden" name="trust" value={trust} />}
144+ <label className="block">
145+ <span className="eyebrow block pb-1">Organization slug</span>
146+ <input name="org" defaultValue={org ?? ''} placeholder="anthropic, openai…" className="h-9 w-full border border-rule bg-surface px-2 text-[13px] text-ink focus:border-accent focus:outline-none" />
147+ </label>
148+ <label className="flex min-h-9 items-center gap-2 text-[13px] text-ink-2">
149+ <input type="checkbox" name="comparable_only" value="1" defaultChecked={comparableOnly} className="size-4 accent-[var(--accent)]" /> Comparable rows only
150+ </label>
151+ <div className="flex gap-2">
152+ <button type="submit" className="inline-flex h-9 flex-1 items-center justify-center bg-ink px-3 text-[13px] font-medium text-canvas hover:opacity-90">
153+ Apply
154+ </button>
155+ <Link href={canonical} className="inline-flex h-9 items-center border border-rule px-3 text-[13px] text-ink-2 hover:text-ink">
156+ Reset
157+ </Link>
158+ </div>
159+ </form>
160+ <div>
161+ <p className="eyebrow mb-1">Views</p>
162+ <ul className="space-y-px text-[13px]">
163+ <li>
164+ <Link href={`${canonical}/cost-vs-performance${group ? `?metric=${encodeURIComponent(group.metric)}&config_key=${encodeURIComponent(group.config_key)}` : ''}`} className="flex min-h-8 items-center px-1 text-ink-2 hover:bg-surface-2 hover:text-ink">
165+ Cost vs performance →
166+ </Link>
167+ </li>
168+ <li>
169+ <Link href={`/benchmarks/matrix?benchmarks=${encodeURIComponent(d.slug)}`} className="flex min-h-8 items-center px-1 text-ink-2 hover:bg-surface-2 hover:text-ink">
170+ In the matrix →
171+ </Link>
172+ </li>
173+ <li>
174+ <Link href={`${routes.timeline({ entity: d.slug })}`} className="flex min-h-8 items-center px-1 text-ink-2 hover:bg-surface-2 hover:text-ink">
175+ Timeline →
176+ </Link>
177+ </li>
178+ </ul>
179+ </div>
180+ </div>
181+ );
182+
183+ const inspector = (
184+ <div className="space-y-5 text-sm" data-benchmark-definition>
185+ <div>
186+ <KeyValue rows={defRows} provenance={d.provenance} slug={d.slug} entity={{ name: d.name, entity_type: 'benchmark' }} dense />
187+ </div>
188+ {group && (
189+ <div>
190+ <p className="eyebrow mb-1">Active group</p>
191+ <p className="text-[13px] text-ink">{group.label}</p>
192+ <p className="tnum text-xs text-ink-3">
193+ {fmtInt(group.model_count)} models · {fmtInt(group.n)} current rows
194+ </p>
195+ </div>
196+ )}
197+ <div>
198+ <p className="eyebrow mb-1">Comparability</p>
199+ <ComparabilityLegend />
200+ </div>
201+ {d.aliases?.length > 0 && (
202+ <div>
203+ <p className="eyebrow mb-1">Also known as</p>
204+ <p className="text-xs text-ink-2">{d.aliases.join(', ')}</p>
205+ </div>
206+ )}
207+ <p className="mono break-all text-[11px] text-ink-3">
208+ slug {d.slug} · {d.id}
209+ </p>
210+ {lb?.methodology && <Note>{lb.methodology}</Note>}
211+ </div>
212+ );
88 213
89 return ( 214 return (
90− <Container wide> 215+ <>
91 <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} /> 216 <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />
92 <ViewBeacon path={canonical} /> 217 <ViewBeacon path={canonical} />
93− <nav aria-label="Breadcrumb" className="pt-5 text-xs text-ink-3"> 218+ <Container wide>
94− <ol className="flex flex-wrap items-center gap-1.5"> 219+ <nav aria-label="Breadcrumb" className="pt-5 text-xs text-ink-3">
95− <li><Link href="/" className="hover:text-ink">AI Atlas</Link></li> 220+ <ol className="flex flex-wrap items-center gap-1.5">
96− <li aria-hidden>/</li> 221+ <li>
97− <li><Link href={routes.benchmarks()} className="hover:text-ink">Benchmarks</Link></li> 222+ <Link href="/" className="hover:text-ink">
98− {typeof a.category === 'string' && ( 223+ AI Atlas
99− <> 224+ </Link>
100− <li aria-hidden>/</li> 225+ </li>
101− <li><Link href={`/benchmarks?category=${encodeURIComponent(a.category)}`} className="hover:text-ink">{a.category}</Link></li> 226+ <li aria-hidden>/</li>
102− </> 227+ <li>
103− )} 228+ <Link href={routes.benchmarks()} className="hover:text-ink">
104− <li aria-hidden>/</li> 229+ Benchmarks
105− <li className="text-ink-2">{d.name}</li> 230+ </Link>
106− </ol> 231+ </li>
107− </nav> 232+ {d.category && (
108− 233+ <>
109− <header className="pb-6 pt-4 md:pb-8 md:pt-5"> 234+ <li aria-hidden>/</li>
110− <div className="flex flex-wrap items-center gap-2"> 235+ <li>
111− <EntityBadge type={d.entity_type} /> 236+ <Link href={`/benchmarks?category=${encodeURIComponent(d.category)}`} className="hover:text-ink">
112− <StatusBadge status={d.status} /> 237+ {d.category}
113− {typeof a.category === 'string' && <span className="text-xs text-ink-3">category · {a.category}</span>} 238+ </Link>
114− </div> 239+ </li>
115− <div className="mt-3 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between"> 240+ </>
116− <div className="min-w-0"> 241+ )}
117− <h1 className="display text-[30px] md:text-[44px]">{d.name}</h1> 242+ <li aria-hidden>/</li>
118− <p className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[15px] text-ink-2"> 243+ <li className="text-ink-2">{d.name}</li>
119− {typeof a.creator === 'string' && <span className="font-medium text-ink">{a.creator}</span>} 244+ </ol>
120− {d.organization && ( 245+ </nav>
121− <Link href={routes.entity({ entity_type: 'company', slug: d.organization.slug })} className="font-medium text-ink hover:text-accent"> 246+ <header className="pb-4 pt-4 md:pt-5">
122− {d.organization.name} 247+ <div className="flex flex-wrap items-center gap-2">
123− </Link> 248+ <EntityBadge type="benchmark" />
124− )} 249+ <StatusBadge status={d.status} />
125− {site && ( 250+ {d.category && <span className="text-xs text-ink-3">category · {d.category}</span>}
126− <a href={site} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-ink-3 hover:text-accent"> 251+ {d.family && d.variant && (
127− {site.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '').slice(0, 48)} <ExternalLink className="size-3.5" aria-hidden /> 252+ <span className="text-xs text-ink-3">
128− </a> 253+ family · {d.family} · variant {d.variant}
129− )} 254+ </span>
130− </p> 255+ )}
131− {(d.description || typeof a.task === 'string') && <p className="mt-3 max-w-3xl text-[15px] leading-relaxed text-ink-2">{d.description ?? (a.task as string)}</p>}
132 </div> 256 </div>
133− <div className="shrink-0 text-xs text-ink-3 lg:text-right"> 257+ <div className="mt-3 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
134− <QualityMark q={d.quality?.score} label /> 258+ <div className="min-w-0">
135− <p className="mt-1" title={d.updated_at}>Updated {fmtAgo(d.updated_at)} · first seen {fmtDate(d.first_seen_at)}</p> 259+ <h1 className="display text-[30px] md:text-[44px]">{d.name}</h1>
136− <p className="mono mt-0.5 text-[11px]">{d.id}</p> 260+ <p className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[15px] text-ink-2">
261+ {typeof a.creator === 'string' && <span className="font-medium text-ink">{a.creator}</span>}
262+ {site && (
263+ <a href={site} target="_blank" rel="noopener noreferrer" className="inline-flex max-w-full min-w-0 items-center gap-1 text-ink-3 hover:text-accent">
264+ <span className="truncate">{site.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '').slice(0, 48)}</span> <ExternalLink className="size-3.5 shrink-0" aria-hidden />
265+ </a>
266+ )}
267+ </p>
268+ {(d.description || typeof a.task === 'string') && <p className="mt-3 max-w-3xl text-[15px] leading-relaxed text-ink-2">{d.description ?? (a.task as string)}</p>}
269+ </div>
270+ <div className="shrink-0 text-xs text-ink-3 lg:text-right">
271+ <QualityMark q={d.quality?.score} label />
272+ <p className="mt-1" title={d.updated_at}>
273+ Updated {fmtAgo(d.updated_at)} · first seen {fmtDate(d.first_seen_at)}
274+ </p>
275+ </div>
137 </div> 276 </div>
138− </div> 277+ <dl className="mt-4 grid grid-cols-2 gap-x-6 gap-y-3 border-y border-rule py-3 sm:grid-cols-4" data-benchmark-strip>
139− <dl className="mt-5 grid grid-cols-2 gap-x-6 gap-y-3 border-y border-rule py-3 sm:grid-cols-4"> 278+ <div>
140− <div><dt className="eyebrow">Metric</dt><dd className="mt-0.5 truncate text-[15px] font-medium text-ink">{typeof a.metric === 'string' ? a.metric : '—'}{unit && <span className="text-ink-3"> · {unit}</span>}</dd></div> 279+ <dt className="eyebrow">Metric</dt>
141− <div><dt className="eyebrow">Direction</dt><dd className="mt-0.5 truncate text-[15px] font-medium text-ink">{direction ?? '—'}</dd></div> 280+ <dd className="mt-0.5 truncate text-[15px] font-medium text-ink">
142− <div><dt className="eyebrow">Results</dt><dd className="tnum mt-0.5 text-[15px] font-medium text-ink">{sample ? fmtInt(sample.total) : '—'}{config && page && <span className="text-xs text-ink-3"> · {fmtInt(page.total)} filtered</span>}</dd></div> 281+ {d.metric ?? '—'}
143− <div><dt className="eyebrow">Leader</dt><dd className="mt-0.5 truncate text-[15px] font-medium text-ink">{sample?.items[0] ? <Link href={routes.entity(sample.items[0].model)} className="hover:text-accent">{sample.items[0].model.name} <span className="tnum text-ink-2">{fmtScore(sample.items[0].score)}{unit === '%' ? '%' : ''}</span></Link> : '—'}</dd></div> 282+ {unit && <span className="text-ink-3"> · {unit}</span>} <span className="text-ink-3">{d.direction === 'lower' ? '↓' : '↑'}</span>
144− </dl> 283+ </dd>
145− </header> 284+ </div>
285+ <div>
286+ <dt className="eyebrow">Current results</dt>
287+ <dd className="tnum mt-0.5 text-[15px] font-medium text-ink">{fmtInt(d.result_count ?? 0)}</dd>
288+ </div>
289+ <div>
290+ <dt className="eyebrow">Models</dt>
291+ <dd className="tnum mt-0.5 text-[15px] font-medium text-ink">{fmtInt(d.model_count ?? 0)}</dd>
292+ </div>
293+ <div>
294+ <dt className="eyebrow">Current leader</dt>
295+ <dd className="mt-0.5 truncate text-[15px] font-medium text-ink">
296+ {lb?.items[0] && offset === 0 ? (
297+ <>
298+ <EntityLink e={{ ...lb.items[0].model, entity_type: 'model' }} /> <span className="tnum text-ink-2">{fmtScoreUnit(lb.items[0].score, unit)}</span>
299+ </>
300+ ) : item?.leader ? (
301+ <>
302+ <EntityLink e={{ ...item.leader.model, entity_type: 'model' }} /> <span className="tnum text-ink-2">{fmtScoreUnit(item.leader.score, unit)}</span>
303+ </>
304+ ) : (
305+ <span className="text-ink-3">—</span>
306+ )}
307+ </dd>
308+ </div>
309+ </dl>
310+ {siblings.length > 1 && (
311+ <nav aria-label="Variants" className="no-scrollbar -mx-4 mt-3 flex gap-1.5 overflow-x-auto px-4 md:mx-0 md:flex-wrap md:px-0" data-variant-nav>
312+ <span className="eyebrow mr-1 flex items-center">Variants</span>
313+ {siblings.map((s) => (
314+ <Link key={s.slug} href={routes.benchmark(s.slug)} aria-current={s.slug === d.slug ? 'page' : undefined} className={cn('inline-flex h-8 items-center gap-1.5 border px-2.5 text-xs whitespace-nowrap', s.slug === d.slug ? 'border-ink bg-ink text-canvas' : 'border-rule text-ink-2 hover:border-rule-strong hover:text-ink')}>
315+ {s.variant ?? s.name}
316+ {s.attributes?.family_head ? <span className="opacity-70">· head</span> : null}
317+ <span className="tnum opacity-70">{fmtInt(s.result_count)}</span>
318+ </Link>
319+ ))}
320+ </nav>
321+ )}
322+ </header>
323+ </Container>
146 324
147− <Tabs tabs={tabs} className="pb-10"> 325+ <TerminalLayout filters={filters} filterCount={filterCount} inspector={inspector} inspectorTitle="Definition" storageKey="aia-leaderboard-inspector">
148− <TabPanel id="leaderboard"> 326+ <div className="space-y-8 pb-16">
149− <div className="space-y-6"> 327+ {empty && (
150− {model && ( 328+ <p className="border-l-2 border-warning bg-warning-soft/40 px-3 py-2 text-xs text-ink-2" data-honesty>
151− <section> 329+ No results recorded for this benchmark yet — its sources are being connected. The definition, aliases and variants are kept so links resolve; nothing is fabricated.
152− {history ? <HistoryChart items={history.items} model={model} unit={unit} /> : <Unavailable what="Result history" compact />} 330+ </p>
153− <p className="mt-2 text-xs"> 331+ )}
154− <Link href={routes.benchmark(d.slug, { config })} className="link">Back to the full leaderboard</Link> 332+ {model && (
155− </p> 333+ <section>
156− </section> 334+ {history ? <HistoryChart items={history.items} model={model} unit={unit} /> : <Unavailable what="Result history" compact />}
157− )} 335+ <p className="mt-2 text-xs">
158− <section className="space-y-3"> 336+ <Link href={href({ model: undefined })} className="link">
159− <div className="flex flex-wrap items-center justify-between gap-3"> 337+ Back to the leaderboard
160− <p className="eyebrow"> 338+ </Link>
161− Leaderboard {page && <span className="tnum text-ink-3">{fmtInt(page.total)} current {page.total === 1 ? 'result' : 'results'}{config ? ` · config contains “${config}”` : ''}</span>} 339+ </p>
162− </p>
163− <p className="text-xs text-ink-3">
164− Select models with <span className="mono">+</span>, then open <Link href={routes.compare()} className="link">Compare</Link>.
165− </p>
166− </div>
167− <ConfigChips slug={d.slug} chips={chips} active={config} model={model} />
168− {!page ? <Unavailable what="Leaderboard" /> : <Leaderboard slug={d.slug} results={page.items} total={page.total} limit={LIMIT} offset={offset} config={config} model={model} unit={unit} makeHref={href} />}
169− {chips.length > 0 && <Note>The config filter matches a value inside each result's configuration (server-side, `config=` on the API). Chips are the values shared by several rows on the first page; per-model identifiers are not offered.</Note>}
170 </section> 340 </section>
171− </div> 341+ )}
172− </TabPanel> 342+ {!empty && (
173− <TabPanel id="definition">
174− <div className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_22rem]">
175 <section> 343 <section>
176− <p className="eyebrow mb-2">Definition</p> 344+ <p className="eyebrow mb-2">
177− <KeyValue rows={defRows} provenance={d.provenance} /> 345+ Frontier over time {group && <span className="text-ink-3">· {group.label}</span>}
178− <Note className="mt-3"> 346+ </p>
179− Each value shows its source, tier and observation time. Missing rows mean no source stated them. <Link href="/methodology#benchmarks" className="link">How results are recorded →</Link> 347+ <FrontierChart frontier={frontier} group={group} unit={unit} />
180− </Note>
181 </section> 348 </section>
182− <aside className="space-y-8"> 349+ )}
183− {d.aliases?.length > 0 && ( 350+ <section>
184− <section> 351+ <div className="mb-2 flex flex-wrap items-center justify-between gap-2">
185− <p className="eyebrow mb-2">Also known as</p> 352+ <p className="eyebrow">
186− <p className="text-sm text-ink-2">{d.aliases.join(', ')}</p> 353+ Leaderboard {lb && <span className="tnum text-ink-3">{fmtInt(lb.total)} models{trust ? ` · trust ${trust}` : ''}{org ? ` · org ${org}` : ''}{comparableOnly ? ' · comparable only' : ''}</span>}
187− </section> 354+ </p>
188− )} 355+ <p className="text-xs text-ink-3">
189− <section> 356+ Select models with <span className="mono">+</span>, then <Link href={routes.compare()} className="link">Compare</Link>.
190− <p className="eyebrow mb-2">Identity</p> 357+ </p>
191− <p className="mono break-all text-[11px] text-ink-3">slug {d.slug}</p> 358+ </div>
192− <p className="mono break-all text-[11px] text-ink-3">{d.id}</p> 359+ {!lb ? <Unavailable what="Leaderboard" /> : <Leaderboard2 rows={lb.items} group={group} total={lb.total} limit={LIMIT} offset={offset} unit={unit} activeModel={model} makeHref={(o) => href({ offset: o ? String(o) : undefined })} historyHref={(s) => href({ model: model === s ? undefined : s })} />}
193− </section> 360+ </section>
194− </aside> 361+ </div>
195− </div> 362+ </TerminalLayout>
196− </TabPanel>
197− <TabPanel id="relations">
198− <RelationsBlock relations={d.relations ?? []} />
199− </TabPanel>
200− <TabPanel id="history">
201− <HistoryPanel d={d} asof={asofRaw} asofPayload={asofPayload} claims={claims} property={property} />
202− </TabPanel>
203− <TabPanel id="timeline">
204− <TimelineList events={d.timeline ?? []} slug={d.slug} />
205− </TabPanel>
206− <TabPanel id="sources">
207− <SourcesTable sources={d.sources ?? []} />
208− </TabPanel>
209− </Tabs>
210 <CompareTrayBar /> 363 <CompareTrayBar />
211− </Container> 364+ </>
212 ); 365 );
213 } 366 }
added apps/web/src/app/benchmarks/matrix/page.tsx +179 −0
@@ -0,0 +1,179 @@
1+import type { Metadata } from 'next';
2+import Link from 'next/link';
3+import { Heatmap } from '@/components/charts';
4+import { ComparabilityLegend } from '@/components/entity/model-blocks';
5+import { MatrixFilterRail } from '@/components/benchmarks/filter-rails';
6+import { TerminalLayout } from '@/components/layout/terminal';
7+import { TrustBadge } from '@/components/models/badges';
8+import { fmtScoreUnit, trustShort } from '@/components/models/shared';
9+import { Container, Note } from '@/components/ui/section';
10+import { EmptyState, Unavailable } from '@/components/ui/unavailable';
11+import { apiD1, safe } from '@/lib/api';
12+import { cn } from '@/lib/cn';
13+import { fmtInt, num } from '@/lib/format';
14+import { routes, SITE_NAME, SITE_URL } from '@/lib/site';
15+
16+export const metadata: Metadata = {
17+ title: 'Benchmark matrix — models × benchmarks, best comparable current score',
18+ description: 'Heatmap of canonical models against benchmarks: each cell is the best current score inside the comparability group, coloured by rank within the column. Pick benchmarks, filter by organization or family and release year, restrict to comparable rows.',
19+ alternates: { canonical: '/benchmarks/matrix' },
20+ openGraph: { title: `Benchmark matrix | ${SITE_NAME}`, url: `${SITE_URL}/benchmarks/matrix`, type: 'website' },
21+};
22+export const revalidate = 600;
23+
24+type SP = Record<string, string | undefined>;
25+const LIMITS = [30, 60, 100];
26+
27+export default async function MatrixPage({ searchParams }: { searchParams: Promise<SP> }) {
28+ const sp = await searchParams;
29+ const benchmarks = (sp.benchmarks ?? '').split(',').map((s) => s.trim()).filter(Boolean);
30+ const org = sp.org?.trim() || undefined;
31+ const family = sp.family?.trim() || undefined;
32+ const comparableOnly = sp.comparable_only === '1';
33+ const minCells = Math.min(12, Math.max(1, Number(sp.min_cells) || 3));
34+ const limit = LIMITS.includes(Number(sp.limit)) ? Number(sp.limit) : 60;
35+ const yearFrom = /^\d{4}$/.test(sp.year_from ?? '') ? Number(sp.year_from) : undefined;
36+ const yearTo = /^\d{4}$/.test(sp.year_to ?? '') ? Number(sp.year_to) : undefined;
37+ const [matrix, list] = await Promise.all([safe(apiD1.matrix({ benchmarks: benchmarks.length ? benchmarks.join(',') : undefined, org, family, comparable_only: comparableOnly ? 1 : undefined, min_cells: minCells, limit })), safe(apiD1.benchmarks())]);
38+ const withResults = (list?.items ?? []).filter((b) => (num(b.result_count) ?? 0) > 0).sort((x, y) => (num(y.result_count) ?? 0) - (num(x.result_count) ?? 0));
39+ // Release-year range filters rows in the page (the API has no date filter on the matrix; cells carry no dates).
40+ const rows = (matrix?.rows ?? []).filter((r) => {
41+ const y = r.model.release_date ? Number(r.model.release_date.slice(0, 4)) : null;
42+ if (yearFrom !== undefined && (y === null || y < yearFrom)) return false;
43+ if (yearTo !== undefined && (y === null || y > yearTo)) return false;
44+ return true;
45+ });
46+ const cols = matrix?.columns ?? [];
47+ const colById = new Map(cols.map((c) => [c.id, c]));
48+ const colIds = cols.map((c) => c.id);
49+ // Colour by within-column rank: 1 = best rank in the column, 0 = last among the models that have a cell.
50+ const colRanks = new Map<string, number[]>();
51+ for (const c of cols) colRanks.set(c.id, rows.map((r) => r.cells[c.id]?.rank).filter((x): x is number => typeof x === 'number').sort((a, b) => a - b));
52+ const cellFn = (rowKey: string, colKey: string) => {
53+ const r = rows.find((x) => x.model.id === rowKey);
54+ const c = r?.cells[colKey];
55+ const col = colById.get(colKey);
56+ if (!r || !c || !col) return { value: null, title: r && col ? `${r.model.name} · ${col.name}: no current result in this group` : undefined };
57+ const ranks = colRanks.get(colKey) ?? [];
58+ const pos = ranks.indexOf(c.rank);
59+ const value = ranks.length > 1 ? 1 - pos / (ranks.length - 1) : 1;
60+ const unit = typeof list?.items.find((b) => b.id === colKey)?.unit === 'string' ? (list?.items.find((b) => b.id === colKey)?.unit as string) : null;
61+ return {
62+ value,
63+ label: fmtScoreUnit(c.score, unit),
64+ href: `${routes.benchmark(col.slug)}?metric=${encodeURIComponent(col.metric)}&config_key=${encodeURIComponent(c.config_key)}`,
65+ title: `${r.model.name} · ${col.name}: ${fmtScoreUnit(c.score, unit)} · rank ${c.rank} of ${col.n_models} · ${trustShort(c.trust_level)} · ${c.comparability}`,
66+ };
67+ };
68+ const filterCount = [benchmarks.length ? '1' : undefined, org, family, comparableOnly ? '1' : undefined, yearFrom, yearTo].filter(Boolean).length;
69+ const trustLevels = [...new Set(rows.flatMap((r) => Object.values(r.cells).map((c) => c?.trust_level).filter(Boolean)))] as string[];
70+ const partially = rows.reduce((n, r) => n + Object.values(r.cells).filter((c) => c && c.comparability !== 'comparable').length, 0);
71+ const cellsN = rows.reduce((n, r) => n + r.n_cells, 0);
72+ const ld = { '@context': 'https://schema.org', '@type': 'Dataset', name: 'Benchmark matrix', url: `${SITE_URL}/benchmarks/matrix`, description: metadata.description };
73+
74+ const filters = <MatrixFilterRail benchmarks={withResults.map((b) => ({ slug: b.slug, name: b.name, models: num(b.model_count) ?? 0 }))} picked={benchmarks} org={org} family={family} yearFrom={sp.year_from} yearTo={sp.year_to} comparableOnly={comparableOnly} minCells={minCells} limit={limit} limits={LIMITS} />;
75+ const inspector = (
76+ <div className="space-y-5 text-sm" data-matrix-legend>
77+ <dl className="kv [&>div]:grid-cols-[7rem_minmax(0,1fr)] [&>div]:py-1">
78+ <div>
79+ <dt>Rows</dt>
80+ <dd className="tnum text-ink">
81+ {fmtInt(rows.length)} <span className="text-ink-3">of {fmtInt(matrix?.total_rows ?? 0)} eligible</span>
82+ </dd>
83+ </div>
84+ <div>
85+ <dt>Columns</dt>
86+ <dd className="tnum text-ink">{fmtInt(cols.length)}</dd>
87+ </div>
88+ <div>
89+ <dt>Cells</dt>
90+ <dd className="tnum text-ink">
91+ {fmtInt(cellsN)} {partially ? <span className="text-ink-3">· {fmtInt(partially)} partially comparable</span> : null}
92+ </dd>
93+ </div>
94+ </dl>
95+ <div>
96+ <p className="eyebrow mb-1">Colour</p>
97+ <p className="text-xs text-ink-2">Within-column rank of the model's best comparable current score: darkest = best rank in that column. Colours never compare across columns; the number in the cell is the score itself.</p>
98+ <div className="mt-2 flex h-2 overflow-hidden rounded-sm" aria-hidden>
99+ {[0.1, 0.3, 0.5, 0.7, 0.9].map((k) => (
100+ <span key={k} className="flex-1" style={{ background: `color-mix(in srgb, var(--type-benchmark) ${Math.round(8 + k * 72)}%, var(--surface))` }} />
101+ ))}
102+ </div>
103+ <p className="mt-0.5 flex justify-between text-[10px] text-ink-3">
104+ <span>last</span>
105+ <span>best</span>
106+ </p>
107+ </div>
108+ {trustLevels.length > 0 && (
109+ <div>
110+ <p className="eyebrow mb-1">Trust levels present</p>
111+ <p className="flex flex-wrap gap-1">
112+ {trustLevels.map((t) => (
113+ <TrustBadge key={t} level={t} />
114+ ))}
115+ </p>
116+ </div>
117+ )}
118+ <div>
119+ <p className="eyebrow mb-1">Comparability</p>
120+ <ComparabilityLegend />
121+ </div>
122+ {matrix?.methodology && <Note>{matrix.methodology}</Note>}
123+ <Note>
124+ `mean_rank` orders the rows; it is a sort key, not a score. <Link href="/methodology#benchmarks" className="link">Methodology →</Link>
125+ </Note>
126+ </div>
127+ );
128+
129+ return (
130+ <>
131+ <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />
132+ <Container wide>
133+ <header className="flex flex-col gap-3 pb-4 pt-6 md:flex-row md:items-end md:justify-between md:pt-8">
134+ <div className="min-w-0">
135+ <p className="eyebrow">
136+ <Link href={routes.benchmarks()} className="hover:text-ink">
137+ Benchmarks
138+ </Link>{' '}
139+ / Matrix
140+ </p>
141+ <h1 className="display mt-1 text-[26px] md:text-[34px]">Benchmark matrix</h1>
142+ <p className="mt-2 max-w-2xl text-sm text-ink-2">Canonical models × benchmarks. Each cell is the best current score in the benchmark's comparability group; hover for score, rank, trust and comparability, click to open the leaderboard.</p>
143+ </div>
144+ {matrix && (
145+ <p className="tnum text-sm text-ink-2">
146+ <span className="font-semibold text-ink">{fmtInt(rows.length)}</span> models × <span className="font-semibold text-ink">{fmtInt(cols.length)}</span> benchmarks
147+ </p>
148+ )}
149+ </header>
150+ </Container>
151+ <TerminalLayout filters={filters} filterCount={filterCount} inspector={inspector} inspectorTitle="Legend" storageKey="aia-matrix-inspector">
152+ <div className="pb-16">
153+ {!matrix ? (
154+ <Unavailable what="Benchmark matrix" />
155+ ) : rows.length === 0 || cols.length === 0 ? (
156+ <EmptyState title="No model has enough comparable cells for this selection">Lower “min cells”, widen the release range or pick other benchmarks.</EmptyState>
157+ ) : (
158+ <>
159+ <Heatmap
160+ rows={rows.map((r) => ({ key: r.model.id, label: r.model.name, href: `/models/${encodeURIComponent(r.model.slug)}`, sub: [r.model.organization, r.model.release_date ? r.model.release_date.slice(0, 4) : null].filter(Boolean).join(' · ') }))}
161+ cols={cols.map((c) => ({ key: c.id, label: c.name.length > 22 ? `${c.name.slice(0, 21)}…` : c.name, href: routes.benchmark(c.slug), sub: `${c.metric} · ${fmtInt(c.n_models)} models` }))}
162+ cell={cellFn}
163+ min={0}
164+ max={1}
165+ color="var(--type-benchmark)"
166+ caption="Benchmark matrix: models × benchmarks, best comparable current score, coloured by within-column rank"
167+ rowHeader="Model"
168+ className="max-h-[75dvh] overflow-auto scrollbar-thin"
169+ />
170+ <Note className={cn('mt-3')}>
171+ Rows are the models with at least {fmtInt(matrix.min_cells)} cells{comparableOnly ? ' (comparable rows only)' : ''}, ordered by mean rank; {yearFrom || yearTo ? `release-year filter applied in the page (${yearFrom ?? '…'} – ${yearTo ?? '…'}); ` : ''}columns are {benchmarks.length ? 'the picked benchmarks' : 'the 12 benchmarks with the most current results'}. Empty = no current result in the group, never interpolated.
172+ </Note>
173+ </>
174+ )}
175+ </div>
176+ </TerminalLayout>
177+ </>
178+ );
179+}
modified apps/web/src/app/benchmarks/page.tsx +200 −75
@@ -1,101 +1,226 @@
1 import type { Metadata } from 'next'; 1 import type { Metadata } from 'next';
2 import Link from 'next/link'; 2 import Link from 'next/link';
3−import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; 3+import { redirect } from 'next/navigation';
4+import { BenchmarksFilterRail } from '@/components/benchmarks/filter-rails';
5+import { TerminalLayout } from '@/components/layout/terminal';
6+import { ScrollX } from '@/components/models/scroll-x';
7+import { TrustBadge } from '@/components/models/badges';
8+import { fmtScoreUnit } from '@/components/models/shared';
9+import { ComparabilityLegend } from '@/components/entity/model-blocks';
4 import { EntityLink } from '@/components/ui/entity'; 10 import { EntityLink } from '@/components/ui/entity';
5−import { Container, Note, PageHeader } from '@/components/ui/section'; 11+import { Hint } from '@/components/ui/hint';
12+import { Container, Note } from '@/components/ui/section';
6 import { EmptyState, Unavailable } from '@/components/ui/unavailable'; 13 import { EmptyState, Unavailable } from '@/components/ui/unavailable';
7−import { api, safe } from '@/lib/api'; 14+import { apiD1, safe } from '@/lib/api';
8 import { cn } from '@/lib/cn'; 15 import { cn } from '@/lib/cn';
9−import { fmtAgo, fmtInt, fmtScore, num } from '@/lib/format'; 16+import { fmtAgo, fmtInt, num } from '@/lib/format';
10−import { routes } from '@/lib/site'; 17+import { routes, SITE_NAME, SITE_URL } from '@/lib/site';
18+import type { BenchmarkListItem } from '@/lib/types';
11 19
12−export const metadata: Metadata = { title: 'AI benchmarks & leaderboards', description: 'Benchmarks with their published results, evaluation configs and current leaders — never compared blindly across configs.', alternates: { canonical: '/benchmarks' } }; 20+export const metadata: Metadata = {
21+ title: 'AI benchmarks — leaderboards, comparability groups & current leaders',
22+ description: 'Every benchmark in the atlas grouped by family and variant: metric and direction, current results, distinct models, the current recorded leader with its trust level — one row per canonical model, never compared across configurations.',
23+ alternates: { canonical: '/benchmarks' },
24+ openGraph: { title: `AI benchmarks — leaderboards, comparability groups & current leaders | ${SITE_NAME}`, url: `${SITE_URL}/benchmarks`, type: 'website' },
25+};
13 export const revalidate = 300; 26 export const revalidate = 300;
14 27
15−export default async function BenchmarksPage({ searchParams }: { searchParams: Promise<{ category?: string }> }) { 28+type SP = { category?: string; view?: string; q?: string; with_results?: string };
16− const { category } = await searchParams; 29+
17− const res = await safe(api.benchmarks()); 30+function familyOf(b: BenchmarkListItem): string {
18− const all = (res?.items ?? []).slice().sort((a, b) => (num(b.result_count) ?? 0) - (num(a.result_count) ?? 0)); 31+ return b.family ?? b.slug;
32+}
33+
34+export default async function BenchmarksPage({ searchParams }: { searchParams: Promise<SP> }) {
35+ const sp = await searchParams;
36+ if (sp.view === 'matrix') redirect('/benchmarks/matrix');
37+ const category = sp.category?.trim() || undefined;
38+ const q = sp.q?.trim().toLowerCase() || undefined;
39+ const withResults = sp.with_results === '1';
40+ const [res, meth] = await Promise.all([safe(apiD1.benchmarks()), safe(apiD1.methodology())]);
41+ const all = res?.items ?? [];
19 const cats = new Map<string, number>(); 42 const cats = new Map<string, number>();
20− for (const b of all) { 43+ for (const b of all) if (b.category) cats.set(b.category, (cats.get(b.category) ?? 0) + 1);
21− const c = typeof b.attributes?.category === 'string' ? b.attributes.category : null;
22− if (c) cats.set(c, (cats.get(c) ?? 0) + 1);
23− }
24 const catList = [...cats.entries()].sort((x, y) => y[1] - x[1] || x[0].localeCompare(y[0])); 44 const catList = [...cats.entries()].sort((x, y) => y[1] - x[1] || x[0].localeCompare(y[0]));
25− const items = category ? all.filter((b) => b.attributes?.category === category) : all; 45+ let items = category ? all.filter((b) => b.category === category) : all;
26− const chip = (active: boolean) => cn('inline-flex h-8 items-center gap-1.5 border px-2.5 text-xs whitespace-nowrap', active ? 'border-ink bg-ink text-canvas' : 'border-rule text-ink-2 hover:border-rule-strong hover:text-ink'); 46+ if (q) items = items.filter((b) => `${b.name} ${b.family ?? ''} ${b.variant ?? ''}`.toLowerCase().includes(q));
47+ if (withResults) items = items.filter((b) => (num(b.result_count) ?? 0) > 0);
48+ const empty = all.filter((b) => (num(b.result_count) ?? 0) === 0).length;
49+ const totalResults = all.reduce((n, b) => n + (num(b.result_count) ?? 0), 0);
50+ // Family grouping: families by total results, head first, then variants by results.
51+ const fams = new Map<string, BenchmarkListItem[]>();
52+ for (const b of items) fams.set(familyOf(b), [...(fams.get(familyOf(b)) ?? []), b]);
53+ const famList = [...fams.entries()]
54+ .map(([f, list]) => ({ f, list: list.sort((a, b) => Number(!!b.attributes?.family_head) - Number(!!a.attributes?.family_head) || (num(b.result_count) ?? 0) - (num(a.result_count) ?? 0) || a.name.localeCompare(b.name)), total: list.reduce((n, b) => n + (num(b.result_count) ?? 0), 0) }))
55+ .sort((a, b) => b.total - a.total || a.f.localeCompare(b.f));
56+ const href = (patch: Record<string, string | undefined>) => {
57+ const p = new URLSearchParams();
58+ const cur = { category, q: sp.q, with_results: withResults ? '1' : undefined, ...patch };
59+ for (const [k, v] of Object.entries(cur)) if (v) p.set(k, v);
60+ const s = p.toString();
61+ return s ? `/benchmarks?${s}` : '/benchmarks';
62+ };
63+ const trustLevels = meth?.trust_levels ?? [];
64+ const filterCount = [category, q, withResults ? '1' : undefined].filter(Boolean).length;
65+ const ld = { '@context': 'https://schema.org', '@type': 'CollectionPage', name: 'AI benchmarks', url: `${SITE_URL}/benchmarks`, description: metadata.description, mainEntity: { '@type': 'ItemList', numberOfItems: all.length, itemListElement: all.slice(0, 12).map((b, i) => ({ '@type': 'ListItem', position: i + 1, name: b.name, url: `${SITE_URL}${routes.benchmark(b.slug)}` })) } };
66+
67+ const filters = <BenchmarksFilterRail q={sp.q} category={category} withResults={withResults} total={all.length} categories={catList.map(([c, n]) => ({ c, n }))} hrefAll={href({ category: undefined })} categoryHrefs={Object.fromEntries(catList.map(([c]) => [c, href({ category: category === c ? undefined : c })]))} />;
68+ const inspector = (
69+ <div className="space-y-5 text-sm" data-benchmark-inspector>
70+ <dl className="kv [&>div]:grid-cols-[8rem_minmax(0,1fr)] [&>div]:py-1">
71+ <div>
72+ <dt>Benchmarks</dt>
73+ <dd className="tnum text-ink">{fmtInt(all.length)}</dd>
74+ </div>
75+ <div>
76+ <dt>Current results</dt>
77+ <dd className="tnum text-ink">{fmtInt(totalResults)}</dd>
78+ </div>
79+ <div>
80+ <dt>Without results</dt>
81+ <dd className="tnum text-ink">{fmtInt(empty)}</dd>
82+ </div>
83+ </dl>
84+ <div>
85+ <p className="eyebrow mb-1">Trust levels</p>
86+ <ul className="space-y-1">
87+ {(trustLevels.length ? trustLevels : Object.entries({ 'official-benchmark': 'Official benchmark leaderboard', 'independent-evaluator': 'Independent third-party evaluator', community: 'Community-run leaderboard or submission' }).map(([key, label]) => ({ key, label }))).map((t) => (
88+ <li key={t.key} className="flex items-start gap-2 text-xs text-ink-2">
89+ <TrustBadge level={t.key} /> <span>{t.label}</span>
90+ </li>
91+ ))}
92+ </ul>
93+ </div>
94+ <div>
95+ <p className="eyebrow mb-1">Comparability</p>
96+ <ComparabilityLegend />
97+ </div>
98+ <Note>{res?.note ?? 'Leaderboards show one row per canonical model: its best current row inside the comparability group (metric × task-defining configuration).'}</Note>
99+ </div>
100+ );
101+
27 return ( 102 return (
28− <Container> 103+ <>
29− <PageHeader eyebrow="Benchmarks" title="Benchmarks" lede="Evaluation suites and the results published for them. Each result carries its configuration; leaders are shown per benchmark, not as a composite." aside={res ? <p className="tnum text-sm text-ink-3">{fmtInt(items.length)}{category ? ` of ${fmtInt(all.length)}` : ''} benchmarks</p> : undefined}> 104+ <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />
30− {catList.length > 0 && ( 105+ <Container wide>
31− <nav aria-label="Category" className="no-scrollbar -mx-4 mt-6 flex gap-1.5 overflow-x-auto px-4 md:mx-0 md:flex-wrap md:px-0"> 106+ <header className="flex flex-col gap-3 pb-4 pt-6 md:flex-row md:items-end md:justify-between md:pt-8">
32− <Link href={routes.benchmarks()} className={chip(!category)} aria-current={!category ? 'true' : undefined}> 107+ <div className="min-w-0">
33− All <span className="tnum opacity-70">{fmtInt(all.length)}</span> 108+ <p className="eyebrow">Benchmarks</p>
34− </Link> 109+ <h1 className="display mt-1 text-[26px] md:text-[34px]">Benchmarks</h1>
35− {catList.map(([c, n]) => ( 110+ <p className="mt-2 max-w-2xl text-sm text-ink-2">Evaluation suites grouped by family and variant. Each leaderboard is one row per canonical model inside a comparability group; leaders are per group, never a composite.</p>
36− <Link key={c} href={`/benchmarks?category=${encodeURIComponent(c)}`} className={chip(category === c)} aria-current={category === c ? 'true' : undefined}> 111+ </div>
37− {c} <span className="tnum opacity-70">{fmtInt(n)}</span> 112+ {res && (
38− </Link> 113+ <p className="tnum text-sm text-ink-2">
39− ))} 114+ <span className="font-semibold text-ink">{fmtInt(items.length)}</span> {category ? `of ${fmtInt(all.length)} ` : ''}benchmarks · <span className="font-semibold text-ink">{fmtInt(totalResults)}</span> current results
40− </nav> 115+ <Hint text="Current results = one row per model × benchmark × metric × configuration run; closed (superseded) rows are kept in history." align="right" />
41− )} 116+ </p>
42− </PageHeader> 117+ )}
43− <div className="pb-16"> 118+ </header>
119+ </Container>
120+ <TerminalLayout filters={filters} filterCount={filterCount} inspector={inspector} inspectorTitle="Legend" storageKey="aia-benchmarks-inspector">
44 {!res ? ( 121 {!res ? (
45 <Unavailable what="Benchmarks" /> 122 <Unavailable what="Benchmarks" />
46 ) : items.length === 0 ? ( 123 ) : items.length === 0 ? (
47− <EmptyState title={category ? `No benchmark in “${category}”` : 'No benchmarks recorded yet'}>{category && <Link href={routes.benchmarks()} className="link">Show all benchmarks</Link>}</EmptyState> 124+ <EmptyState title={category ? `No benchmark in “${category}”` : 'No benchmark matches'}>
125+ <Link href="/benchmarks" className="link">
126+ Show all benchmarks
127+ </Link>
128+ </EmptyState>
48 ) : ( 129 ) : (
49 <> 130 <>
50− <DataTable caption="Benchmarks"> 131+ {empty > 0 && !withResults && (
132+ <p className="mb-2 border-l-2 border-warning bg-warning-soft/40 px-3 py-2 text-xs text-ink-2" data-honesty>
133+ {fmtInt(empty)} benchmark{empty === 1 ? '' : 's'} ha{empty === 1 ? 's' : 've'} no results yet — sources being connected. They are listed so their definitions and aliases resolve; nothing is fabricated.
134+ </p>
135+ )}
136+ <ScrollX>
137+ <table className="data-table stack compact" data-benchmarks-table>
138+ <caption className="sr-only">Benchmarks by family</caption>
51 <thead> 139 <thead>
52 <tr> 140 <tr>
53− <Th>Benchmark</Th> 141+ <th scope="col">Family · variant</th>
54− <Th>Category</Th> 142+ <th scope="col">Metric</th>
55− <Th>Metric</Th> 143+ <th scope="col" className="num">
56− <Th num>Results</Th> 144+ Results
57− <Th num>Models</Th> 145+ </th>
58− <Th>Current leader</Th> 146+ <th scope="col" className="num">
59− <Th>Updated</Th> 147+ Models
148+ </th>
149+ <th scope="col">Current leader</th>
150+ <th scope="col">Updated</th>
60 </tr> 151 </tr>
61 </thead> 152 </thead>
62 <tbody> 153 <tbody>
63− {items.length === 0 && <EmptyRow cols={7}>No benchmarks recorded yet.</EmptyRow>} 154+ {famList.map((fam) => (
64− {items.map((b) => { 155+ <FamilyRows key={fam.f} fam={fam.f} list={fam.list} />
65− const a = b.attributes ?? {}; 156+ ))}
66− return (
67− <tr key={b.id}>
68− <Td primary>
69− <Link href={routes.benchmark(b.slug)} className="text-ink hover:text-accent hover:underline">{b.name}</Link>
70− {(b.description || typeof a.task === 'string') && <span className="block max-w-md truncate text-xs text-ink-3">{b.description ?? (a.task as string)}</span>}
71− </Td>
72− <Td label="Category" className="text-ink-2">{typeof a.category === 'string' ? <Link href={`/benchmarks?category=${encodeURIComponent(a.category)}`} className="hover:text-accent">{a.category}</Link> : <span className="text-ink-3">—</span>}</Td>
73− <Td label="Metric" className="text-ink-2">
74− {typeof a.metric === 'string' ? a.metric : <span className="text-ink-3">—</span>}
75− {typeof a.unit === 'string' && a.unit && <span className="text-ink-3"> ({a.unit})</span>}
76− </Td>
77− <Td num label="Results" className="tnum">{fmtInt(b.result_count)}</Td>
78− <Td num label="Models" className="tnum text-ink-2">{fmtInt(b.model_count)}</Td>
79− <Td label="Current leader">
80− {b.top ? (
81− <span>
82− <EntityLink e={b.top.model} className="font-medium" /> <span className="tnum text-ink-2">{fmtScore(b.top.score)}{a.unit === '%' ? '%' : ''}</span>
83− {b.top.model.organization && <span className="block text-xs text-ink-3">{b.top.model.organization.name}</span>}
84− </span>
85− ) : (
86− <span className="text-ink-3">—</span>
87− )}
88− </Td>
89− <Td label="Updated" className="text-ink-2" title={b.updated_at}>{fmtAgo(b.updated_at)}</Td>
90− </tr>
91− );
92− })}
93 </tbody> 157 </tbody>
94− </DataTable> 158+ </table>
95− <Note className="mt-3">Leader = best current result under the benchmark's default direction (higher or lower is better). Configs differ; open a benchmark for its leaderboard, config filter and per-model history.</Note> 159+ </ScrollX>
160+ <Note className="mt-3">Leader = best current row of the benchmark's primary comparability group (most-populated task configuration of the canonical metric), with the trust level of that row. Open a benchmark for the group picker, trust filter, frontier over time and per-model history.</Note>
96 </> 161 </>
97 )} 162 )}
98− </div> 163+ </TerminalLayout>
99− </Container> 164+ </>
165+ );
166+}
167+
168+function FamilyRows({ fam, list }: { fam: string; list: BenchmarkListItem[] }) {
169+ const grouped = list.length > 1;
170+ const head = list.find((b) => b.attributes?.family_head) ?? null;
171+ return (
172+ <>
173+ {grouped && (
174+ <tr className="bg-surface-2/40">
175+ <td colSpan={6} className="!py-1.5 text-[11px] font-semibold uppercase tracking-[0.08em] text-ink-3">
176+ {head?.family ?? fam} <span className="tnum font-normal normal-case tracking-normal">· {list.length} variants · {fmtInt(list.reduce((n, b) => n + (num(b.result_count) ?? 0), 0))} results</span>
177+ </td>
178+ </tr>
179+ )}
180+ {list.map((b) => {
181+ const empty = (num(b.result_count) ?? 0) === 0;
182+ const leader = b.leader;
183+ return (
184+ <tr key={b.id} className={cn(empty && 'text-ink-3')}>
185+ <td className={cn('primary', grouped && 'md:pl-5')}>
186+ <Link href={routes.benchmark(b.slug)} className="text-ink hover:text-accent hover:underline">
187+ {b.name}
188+ </Link>
189+ <span className="block text-[11px] text-ink-3">
190+ {grouped && b.variant ? `variant ${b.variant}` : b.category ?? ''}
191+ {grouped && b.variant && b.category ? ` · ${b.category}` : ''}
192+ {b.attributes?.family_head ? ' · family head' : ''}
193+ </span>
194+ </td>
195+ <td data-label="Metric" className="text-ink-2">
196+ {b.metric ?? '—'}
197+ <span className="text-ink-3"> {b.direction === 'lower' ? '↓ lower is better' : '↑'}</span>
198+ {b.groups.length > 1 && <span className="block text-[11px] text-ink-3">{fmtInt(b.groups.length)} comparability groups</span>}
199+ </td>
200+ <td data-label="Results" className="num tnum">
201+ {empty ? <span className="text-ink-3">0</span> : fmtInt(b.result_count)}
202+ </td>
203+ <td data-label="Models" className="num tnum text-ink-2">
204+ {fmtInt(b.model_count)}
205+ </td>
206+ <td data-label="Current leader">
207+ {leader ? (
208+ <span className="flex flex-wrap items-center gap-x-2 gap-y-0.5">
209+ <EntityLink e={{ ...leader.model, entity_type: 'model' }} className="font-medium" />
210+ <span className="tnum text-ink-2">{fmtScoreUnit(leader.score, leader.unit)}</span>
211+ <TrustBadge level={leader.trust_level} label={leader.trust_label} />
212+ {leader.model.organization && <span className="block w-full text-[11px] text-ink-3">{leader.model.organization.name}</span>}
213+ </span>
214+ ) : (
215+ <span className="text-xs text-ink-3">{empty ? 'no results yet' : '—'}</span>
216+ )}
217+ </td>
218+ <td data-label="Updated" className="text-xs text-ink-2" title={leader?.observed_at}>
219+ {leader ? fmtAgo(leader.observed_at) : '—'}
220+ </td>
221+ </tr>
222+ );
223+ })}
224+ </>
100 ); 225 );
101 } 226 }
modified apps/web/src/app/compare/page.tsx +58 −38
Large diff (148 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/families/[slug]/opengraph-image.tsx +22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/families/[slug]/page.tsx +313 −0
Large diff (313 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/families/page.tsx +159 −0
Large diff (159 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/licenses/[key]/opengraph-image.tsx +26 −0
Large diff (26 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/licenses/[key]/page.tsx +197 −0
Large diff (197 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/licenses/page.tsx +147 −0
Large diff (147 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/app/models/(list)/page.tsx +98 −146
Large diff (267 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/models/[slug]/diff/[b]/page.tsx +252 −0
Large diff (252 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/app/models/[slug]/opengraph-image.tsx +16 −37
Large diff (63 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/app/models/[slug]/page.tsx +35 −11
Large diff (57 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/benchmarks/client-charts.tsx +23 −0
Large diff (23 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/benchmarks/filter-rails.tsx +142 −0
Large diff (142 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/benchmarks/leaderboard2.tsx +204 −0
Large diff (204 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/compare/compare-terminal.tsx +386 −0
Large diff (386 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/entity/artifact-page.tsx +224 −0
Large diff (224 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/components/entity/blocks.tsx +11 −10
Large diff (85 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/components/entity/history.tsx +3 −2
Large diff (21 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/entity/model-blocks.tsx +479 −0
Large diff (479 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/entity/model-page.tsx +449 −0
Large diff (449 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/badges.tsx +82 −0
Large diff (82 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/family-blocks.tsx +84 −0
Large diff (84 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/lineage-tree.tsx +116 −0
Large diff (116 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/mini-graph.tsx +102 −0
Large diff (102 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/models-filters.tsx +173 −0
Large diff (173 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/models-terminal.tsx +370 −0
Large diff (370 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/openness-block.tsx +96 −0
Large diff (96 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/price-history.tsx +134 −0
Large diff (134 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/scroll-to-section.tsx +21 −0
Large diff (21 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/scroll-x.tsx +12 −0
Large diff (12 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/models/shared.ts +141 −0
Large diff (141 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/lib/api.ts +100 −0
Large diff (103 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/lib/types.ts +810 −0
Large diff (814 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme