SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
11.0 KB · 192 lines tsx
Raw Blame History
1import Link from 'next/link';2import { Legend, LineChart } from '@/components/charts/charts';3import { CompareButton } from '@/components/compare/compare-button';4import { ConfidenceBadge } from '@/components/ui/badges';5import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';6import { EntityLink } from '@/components/ui/entity';7import { Pagination } from '@/components/ui/pagination';8import { SourceCell } from '@/components/ui/provenance';9import { Note } from '@/components/ui/section';10import { EmptyState } from '@/components/ui/unavailable';11import { cn } from '@/lib/cn';12import { fmtDate, fmtInt, fmtScore } from '@/lib/format';13import { routes } from '@/lib/site';14import type { BenchmarkResult } from '@/lib/types';1516/** Per-model identifiers (one row each) are useless as filters; everything else becomes a chip candidate. */17const PER_ROW_KEYS = new Set(['aa_slug', 'model_tag', 'model_id', 'run_id', 'submission_id', 'date', 'submitted_at', 'url']);1819/** Distinct config values seen on the page (value → count), skipping per-row identifiers and singletons. */20export function configChips(results: BenchmarkResult[], max = 10): { value: string; key: string; count: number }[] {21  const counts = new Map<string, { key: string; count: number }>();22  for (const r of results) {23    for (const [k, v] of Object.entries(r.config ?? {})) {24      if (PER_ROW_KEYS.has(k) || v === null || v === undefined || v === '') continue;25      const s = typeof v === 'object' ? JSON.stringify(v) : String(v);26      if (s.length > 40) continue;27      const cur = counts.get(s);28      if (cur) cur.count += 1;29      else counts.set(s, { key: k, count: 1 });30    }31  }32  return [...counts.entries()]33    .map(([value, x]) => ({ value, key: x.key, count: x.count }))34    .filter((c) => c.count > 1 && c.count < results.length)35    .sort((a, b) => b.count - a.count || a.value.localeCompare(b.value))36    .slice(0, max);37}3839export function configSummary(c: Record<string, unknown>): string {40  return Object.entries(c ?? {})41    .filter(([, v]) => v !== null && v !== undefined && v !== '')42    .slice(0, 4)43    .map(([k, v]) => `${k}=${typeof v === 'object' ? JSON.stringify(v) : String(v)}`)44    .join(' · ');45}4647export function ConfigChips({ slug, chips, active, model }: { slug: string; chips: ReturnType<typeof configChips>; active?: string; model?: string }) {48  if (!chips.length && !active) return null;49  const cls = (on: boolean) => cn('inline-flex h-8 items-center gap-1.5 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');50  return (51    <nav aria-label="Configuration filter" className="no-scrollbar -mx-4 flex gap-1.5 overflow-x-auto px-4 md:mx-0 md:flex-wrap md:px-0">52      <Link href={routes.benchmark(slug, { model })} className={cls(!active)} aria-current={!active ? 'true' : undefined}>53        All configs54      </Link>55      {active && !chips.some((c) => c.value === active) && (56        <Link href={routes.benchmark(slug, { model })} className={cls(true)} aria-current="true" title="Remove this filter">57          <span className="mono">{active}</span> ×58        </Link>59      )}60      {chips.map((c) => (61        <Link key={c.value} href={routes.benchmark(slug, { config: active === c.value ? undefined : c.value, model })} className={cls(active === c.value)} aria-current={active === c.value ? 'true' : undefined} title={`config.${c.key} contains “${c.value}” (${c.count} rows on this page)`}>62          <span className="text-ink-3 opacity-80">{c.key}</span> <span className="mono">{c.value}</span>63        </Link>64      ))}65    </nav>66  );67}6869/**70 * Leaderboard table: rank · model (+org) · score with a relative bar · config · evaluated · source · compare/history.71 * Bars are relative to the best score of the page (per direction) — a reading aid, not a normalisation.72 */73export function Leaderboard({ slug, results, total, limit, offset, config, model, unit, makeHref }: { slug: string; results: BenchmarkResult[]; total: number; limit: number; offset: number; config?: string; model?: string; unit?: string | null; makeHref: (offset: number) => string }) {74  if (!results.length) return <EmptyState title={config ? 'No result matches this configuration filter' : 'No benchmark results recorded'}>{config ? <Link href={routes.benchmark(slug, { model })} className="link">Clear the filter</Link> : 'Results appear when a tier 1–3 source publishes them; we never copy scores without a source.'}</EmptyState>;75  const hib = results[0]?.higher_is_better !== false;76  const scores = results.map((r) => r.score).filter((s) => Number.isFinite(s));77  const max = Math.max(...scores);78  const min = Math.min(...scores);79  const width = (s: number) => {80    if (!Number.isFinite(s) || max <= 0) return 0;81    const v = hib ? s / max : min > 0 ? min / s : 0;82    return Math.max(2, Math.min(100, v * 100));83  };84  const u = unit ?? results[0]?.unit ?? null;85  return (86    <>87      <DataTable caption="Leaderboard">88        <thead>89          <tr>90            <Th className="w-10">#</Th>91            <Th>Model</Th>92            <Th num>Score</Th>93            <Th>Config</Th>94            <Th>Evaluated</Th>95            <Th>Source</Th>96            <Th className="w-40"><span className="sr-only">Actions</span></Th>97          </tr>98        </thead>99        <tbody>100          {results.length === 0 && <EmptyRow cols={7} />}101          {results.map((r, i) => {102            const rank = offset + i + 1;103            const isModel = model && r.model.slug === model;104            return (105              <tr key={r.id} className={isModel ? 'bg-accent-soft/40' : undefined}>106                <Td className="tnum text-ink-3" hideStack>{fmtInt(rank)}</Td>107                <Td primary>108                  <span className="flex flex-wrap items-center gap-x-2 gap-y-0.5">109                    <span className="tnum text-xs text-ink-3 md:hidden">#{fmtInt(rank)}</span>110                    <EntityLink e={r.model} />111                  </span>112                  {r.model.organization && <span className="block text-xs text-ink-3">{r.model.organization.name}</span>}113                </Td>114                <Td num label="Score" className="tnum font-medium">115                  <span className="inline-flex flex-col items-end gap-1">116                    <span>117                      {fmtScore(r.score)}118                      {u === '%' ? '%' : u ? <span className="text-ink-3"> {u}</span> : null}119                    </span>120                    <span className="block h-1 w-24 overflow-hidden rounded-sm bg-surface-2" aria-hidden>121                      <span className="block h-full" style={{ width: `${width(r.score)}%`, background: 'var(--type-benchmark)' }} />122                    </span>123                  </span>124                </Td>125                <Td label="Config" className="mono max-w-[18rem] truncate text-xs text-ink-3" title={JSON.stringify(r.config)}>{configSummary(r.config) || '—'}</Td>126                <Td label="Evaluated" className="tnum text-ink-2 whitespace-nowrap" title={r.evaluated_at ? undefined : `Observed ${fmtDate(r.observed_at)}; the source gave no evaluation date`}>127                  {r.evaluated_at ? fmtDate(r.evaluated_at) : <span className="text-ink-3">— <span className="text-[11px]">(obs. {fmtDate(r.observed_at)})</span></span>}128                </Td>129                <Td label="Source">130                  <SourceCell url={r.source_url} tier={r.tier} /> <ConfidenceBadge confidence={r.confidence !== 'high' && r.confidence !== 'medium' ? r.confidence : null} />131                </Td>132                <Td className="text-right">133                  <span className="inline-flex flex-wrap items-center justify-end gap-1.5">134                    <Link href={routes.benchmark(slug, { config, model: isModel ? undefined : r.model.slug })} className={cn('inline-flex h-7 items-center border px-1.5 text-xs whitespace-nowrap', isModel ? 'border-accent bg-accent-soft text-accent' : 'border-rule text-ink-2 hover:border-rule-strong hover:text-ink')} aria-pressed={!!isModel}>135                      History136                    </Link>137                    <CompareButton e={r.model} size="sm" />138                  </span>139                </Td>140              </tr>141            );142          })}143        </tbody>144      </DataTable>145      <Pagination total={total} limit={limit} offset={offset} makeHref={makeHref} className="mt-4" />146      <Note className="mt-3">147        Scores are reported as published, with their evaluation configuration (harness, prompting, judge). The bar is relative to the best score on this page{hib ? '' : ' (lower is better)'}. Results with different configs are not directly comparable — see <Link href="/methodology#benchmarks" className="link">methodology</Link>.148      </Note>149    </>150  );151}152153/** Score history for one model on this benchmark (all rows incl. superseded), as a time series. */154export function HistoryChart({ items, model, unit }: { items: BenchmarkResult[]; model: string; unit?: string | null }) {155  const name = items[0]?.model.name ?? model;156  const pts = items157    .map((r) => ({ x: new Date(r.evaluated_at ?? r.observed_at), y: r.score, r }))158    .filter((p) => !Number.isNaN(p.x.getTime()) && Number.isFinite(p.y))159    .sort((a, b) => a.x.getTime() - b.x.getTime());160  const u = unit ?? items[0]?.unit ?? null;161  const fmt = (v: number) => `${fmtScore(v)}${u === '%' ? '%' : ''}`;162  const days = new Set(pts.map((p) => p.x.toISOString().slice(0, 10)));163  return (164    <div>165      <p className="eyebrow mb-2">166        Score history · {name} <span className="tnum text-ink-3">{fmtInt(items.length)} {items.length === 1 ? 'row' : 'rows'}</span>167      </p>168      {pts.length < 2 || days.size < 2 ? (169        <Note>170          Not enough history to chart — {pts.length === 0 ? 'no result recorded for this model' : pts.length === 1 ? `a single observation (${fmt(pts[0]?.y ?? 0)} on ${fmtDate(pts[0]?.r.evaluated_at ?? pts[0]?.r.observed_at)})` : `${fmtInt(pts.length)} observations, all dated ${fmtDate(pts[0]?.r.evaluated_at ?? pts[0]?.r.observed_at)}`}. Rows under different configurations count separately; the list below shows each one.171        </Note>172      ) : (173        <>174          <LineChart series={[{ name, color: 'var(--type-benchmark)', points: pts.map((p) => ({ x: p.x, y: p.y })) }]} height={200} yFormat={fmt} showDots yLabel={`Score history for ${name}`} />175          <Legend series={[{ name, color: 'var(--type-benchmark)' }]} className="mt-2" />176        </>177      )}178      {pts.length > 0 && (179        <ul className="mt-3 divide-y divide-rule border-y border-rule text-sm">180          {[...pts].reverse().slice(0, 12).map((p) => (181            <li key={p.r.id} className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-baseline gap-x-3 py-1.5">182              <span className="tnum font-medium">{fmt(p.y)}</span>183              <span className="mono truncate text-xs text-ink-3" title={JSON.stringify(p.r.config)}>{configSummary(p.r.config) || '—'}</span>184              <span className="tnum text-xs text-ink-2">{fmtDate(p.r.evaluated_at ?? p.r.observed_at)}</span>185            </li>186          ))}187        </ul>188      )}189    </div>190  );191}192