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%
5.2 KB · 85 lines tsx
Raw Blame History
1'use client';2import { InteractiveLineChart, TimelineLanes } from '@/components/charts';3import { Note } from '@/components/ui/section';4import { fmtDate, fmtInt } from '@/lib/format';5import { routes } from '@/lib/site';6import type { FamilyMember } from '@/lib/types';78/*9  Client blocks of the family page: release timeline (TimelineLanes by status) and benchmark progress10  (best rank of any member by member release date; ranks go down = better, so the axis is inverted).11*/1213const STATUS_LANES: { key: string; label: string; color: string }[] = [14  { key: 'active', label: 'Active', color: 'var(--positive)' },15  { key: 'preview', label: 'Preview', color: 'var(--accent)' },16  { key: 'announced', label: 'Announced', color: 'var(--accent)' },17  { key: 'deprecated', label: 'Deprecated', color: 'var(--warning)' },18  { key: 'retired', label: 'Retired', color: 'var(--danger)' },19  { key: 'other', label: 'Other', color: 'var(--ink-3)' },20];2122export function FamilyReleaseLanes({ members }: { members: FamilyMember[] }) {23  const dated = members.filter((m) => typeof m.key_facts?.release_date === 'string' || typeof m.model.attributes?.release_date === 'string');24  const undated = members.length - dated.length;25  if (dated.length === 0) return <Note>No member has a sourced release date yet — the timeline needs at least one.</Note>;26  const laneOf = (m: FamilyMember) => {27    const s = String(m.key_facts?.status ?? m.model.status ?? 'other');28    return STATUS_LANES.some((l) => l.key === s) ? s : 'other';29  };30  const used = new Set(dated.map(laneOf));31  const lanes = STATUS_LANES.filter((l) => used.has(l.key));32  const events = dated.map((m) => {33    const at = String(m.key_facts?.release_date ?? m.model.attributes?.release_date);34    const ranks = Object.values(m.benchmark_ranks ?? {});35    return { id: m.model.id, lane: laneOf(m), at, importance: ranks.length ? (Math.min(...ranks) <= 10 ? 3 : Math.min(...ranks) <= 50 ? 2 : 1) : 1, label: m.model.name, href: routes.entity(m.model), sub: `${fmtDate(at)}${ranks.length ? ` · best rank #${Math.min(...ranks)}` : ''}` };36  });37  return (38    <div data-family-timeline>39      <TimelineLanes lanes={lanes} events={events} title="Family releases by status" laneWidth={88} />40      <p className="mt-1 text-[11px] text-ink-3">41        {fmtInt(dated.length)} dated releases · dot size = best benchmark rank of the member42        {undated > 0 ? ` · ${fmtInt(undated)} member${undated === 1 ? '' : 's'} without a sourced release date not shown` : ''}43      </p>44    </div>45  );46}4748export function FamilyBenchmarkProgress({ members, benchNames }: { members: FamilyMember[]; benchNames: Record<string, string> }) {49  // per benchmark: (release date, best rank so far among members released up to that date)50  const perBench = new Map<string, { x: Date; y: number; model: string }[]>();51  const dated = members52    .map((m) => ({ m, at: typeof m.key_facts?.release_date === 'string' ? new Date(m.key_facts.release_date) : null }))53    .filter((x): x is { m: FamilyMember; at: Date } => !!x.at && !Number.isNaN(x.at.getTime()))54    .sort((a, b) => a.at.getTime() - b.at.getTime());55  for (const { m, at } of dated) {56    for (const [slug, rank] of Object.entries(m.benchmark_ranks ?? {})) {57      const arr = perBench.get(slug) ?? [];58      const best = arr.length ? Math.min(arr[arr.length - 1]!.y, rank) : rank;59      arr.push({ x: at, y: best, model: m.model.name });60      perBench.set(slug, arr);61    }62  }63  const charts = [...perBench.entries()].map(([slug, pts]) => ({ slug, pts: pts.filter((p, i, a) => i === 0 || p.x.getTime() !== a[i - 1]!.x.getTime() || p.y !== a[i - 1]!.y) })).filter((c) => new Set(c.pts.map((p) => p.x.toISOString().slice(0, 10))).size >= 2).sort((a, b) => a.pts[a.pts.length - 1]!.y - b.pts[b.pts.length - 1]!.y).slice(0, 6);64  if (!charts.length) return <Note>Benchmark progress needs at least two dated members with a rank on the same benchmark. Ranks (not scores) are what the family endpoint provides.</Note>;65  return (66    <div className="grid gap-6 md:grid-cols-2 xl:grid-cols-3" data-family-progress>67      {charts.map((c) => {68        const maxRank = Math.max(...c.pts.map((p) => p.y));69        return (70          <div key={c.slug}>71            <p className="mb-1 flex items-baseline justify-between gap-2 text-xs">72              <a href={routes.benchmark(c.slug)} className="truncate font-medium text-ink hover:text-accent">73                {benchNames[c.slug] ?? c.slug}74              </a>75              <span className="tnum text-ink-3">best #{c.pts[c.pts.length - 1]!.y}</span>76            </p>77            <InteractiveLineChart series={[{ name: 'Best rank so far', color: 'var(--type-benchmark)', points: c.pts.map((p) => ({ x: p.x, y: p.y })) }]} height={150} step showDots yDomain={[Math.max(2, Math.ceil(maxRank * 1.15)), 1]} yFormat={(v) => `#${Math.round(v)}`} yLabel={`Best rank of the family on ${benchNames[c.slug] ?? c.slug}`} />78          </div>79        );80      })}81      <p className="text-[11px] text-ink-3 md:col-span-2 xl:col-span-3">Best rank (primary comparability group, current leaderboard) reached by any member released up to each date — lower is better, axis inverted. Ranks are today's ranks, not the ranks at release time.</p>82    </div>83  );84}85