HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import { Chip, TierBadge } from '@/components/ui/badges';2import { DataTable, Td, Th } from '@/components/ui/data-table';3import { EntityLink } from '@/components/ui/entity';4import { Note } from '@/components/ui/section';5import { cn } from '@/lib/cn';6import { fmtAgo, fmtDate, fmtScore, fmtUsdPerM, fmtValue, num } from '@/lib/format';7import { routes } from '@/lib/site';8import type { BenchmarkResult, CompareDimension, ComparePayload, Price, ProvenanceEntry } from '@/lib/types';9import { CompareButton } from './compare-button';1011/* ------------------------------------------------------------------------------------------------------ helpers */1213const PRICE_KEYS = new Set(['best_input_per_mtok', 'best_output_per_mtok', 'min_input_per_mtok', 'min_output_per_mtok']);14/** Numeric dimensions where lower is better. */15function lowerIsBetter(key: string): boolean {16 return PRICE_KEYS.has(key) || /tdp|price|latency/.test(key);17}1819function host(url: string | null | undefined): string | null {20 if (!url) return null;21 try {22 return new URL(url).hostname.replace(/^www\./, '');23 } catch {24 return null;25 }26}2728/** Compact per-cell provenance: T1 · host · 3 h ago (full text in title). */29function CellProvenance({ p }: { p: ProvenanceEntry }) {30 const h = p.source_name ?? host(p.url) ?? 'source';31 return (32 <span className="mt-0.5 hidden items-center gap-1 text-[11px] leading-4 text-ink-3 md:flex">33 <TierBadge tier={p.tier} />34 {p.url ? (35 <a href={p.url} target="_blank" rel="noopener noreferrer" className="max-w-[10rem] truncate hover:text-accent">36 {h}37 </a>38 ) : (39 <span className="max-w-[10rem] truncate">{h}</span>40 )}41 <span>· {fmtAgo(p.observed_at)}</span>42 </span>43 );44}4546function CellValue({ v, dim }: { v: unknown; dim: CompareDimension }) {47 if (v === null || v === undefined || v === '' || (Array.isArray(v) && v.length === 0)) return <span className="text-ink-3">Unavailable</span>;48 switch (dim.kind) {49 case 'date':50 return <>{fmtDate(String(v))}</>;51 case 'bool':52 return <>{v ? 'Yes' : 'No'}</>;53 case 'list': {54 const arr = Array.isArray(v) ? v : [v];55 return (56 <span className="flex flex-wrap gap-1">57 {arr.map((x, i) => (58 <Chip key={i}>{typeof x === 'object' && x ? JSON.stringify(x) : String(x)}</Chip>59 ))}60 </span>61 );62 }63 case 'number':64 if (PRICE_KEYS.has(dim.key)) return <span className="text-accent-2">{fmtUsdPerM(v)}</span>;65 return <>{fmtValue(v, dim.key)}</>;66 default:67 return <>{typeof v === 'string' ? v : fmtValue(v, dim.key)}</>;68 }69}7071const STICKY = 'sticky left-0 z-10 bg-canvas';72/** First-column width: table layout ignores max-width, so set width + min-width explicitly. */73const FIRST = 'w-[9rem] min-w-[9rem] md:w-[12rem] md:min-w-[12rem]';74/** `.table-scroll td { white-space: nowrap }` out-specifies utilities — wrap the first column inline. */75const WRAP = { whiteSpace: 'normal' } as const;7677/** Horizontal scroll wrapper without the negative page margins (so the sticky column pins at the very left edge). */78function ScrollTable({ children, caption, className }: { children: React.ReactNode; caption: string; className?: string }) {79 return (80 <div className="table-scroll">81 <DataTable stack={false} caption={caption} className={className}>82 {children}83 </DataTable>84 </div>85 );86}8788function EntityHead({ e }: { e: ComparePayload['items'][number]['entity'] }) {89 return (90 <span className="flex flex-col items-start gap-1 normal-case tracking-normal">91 <EntityLink e={e} className="text-sm font-semibold whitespace-normal" />92 {e.organization && <span className="text-[11px] font-normal text-ink-3">{e.organization.name}</span>}93 <CompareButton e={e} size="sm" label="Add" />94 </span>95 );96}9798/* ------------------------------------------------------------------------------------------------------ matrix */99100/** Dimension × entity matrix: sticky first column, horizontal scroll, kind-aware formatting, per-cell provenance. */101export function CompareMatrix({ res }: { res: ComparePayload }) {102 return (103 <>104 <ScrollTable caption="Comparison matrix" className="compare-matrix">105 <thead>106 <tr>107 <Th className={cn(STICKY, FIRST)} style={WRAP}>Dimension</Th>108 {res.items.map((it) => (109 <Th key={it.entity.id} className="min-w-[10rem] align-top">110 <EntityHead e={it.entity} />111 </Th>112 ))}113 </tr>114 </thead>115 <tbody>116 {res.dimensions.map((dim) => {117 const nums = res.items.map((it) => num(it.values[dim.key]));118 const present = nums.filter((n): n is number => n !== null);119 const best = dim.kind === 'number' && present.length > 1 ? (lowerIsBetter(dim.key) ? Math.min(...present) : Math.max(...present)) : null;120 return (121 <tr key={dim.key}>122 <Td className={cn(STICKY, FIRST, 'text-ink-2')} style={WRAP}>123 {dim.label}124 {dim.unit && <span className="block text-[11px] text-ink-3">{dim.unit}</span>}125 </Td>126 {res.items.map((it, i) => {127 const v = it.values[dim.key];128 const p = it.provenance?.[dim.key];129 const isBest = best !== null && nums[i] === best;130 return (131 <Td key={it.entity.id} className={cn('align-top', dim.kind === 'number' && 'tnum', dim.kind === 'list' && 'whitespace-normal')} title={p ? `${p.source_name ?? host(p.url) ?? ''} · tier ${p.tier} · ${fmtDate(p.observed_at)}` : undefined}>132 <span className={cn('block', isBest && 'font-semibold text-ink')}>133 <CellValue v={v} dim={dim} />134 </span>135 {p && <CellProvenance p={p} />}136 </Td>137 );138 })}139 </tr>140 );141 })}142 </tbody>143 </ScrollTable>144 <Note className="mt-3">Bold marks the best number in a row (highest, or lowest for prices); it is not a verdict. Each value carries its own source and tier; hover a cell for the observation date. Missing values are shown as unavailable, never estimated.</Note>145 </>146 );147}148149/* ------------------------------------------------------------------------------------------------------ shared benchmarks */150151function configSummary(c: Record<string, unknown>): string {152 return Object.entries(c ?? {})153 .filter(([, v]) => v !== null && v !== undefined && v !== '')154 .slice(0, 4)155 .map(([k, v]) => `${k}=${typeof v === 'object' ? JSON.stringify(v) : String(v)}`)156 .join(' · ');157}158159/** Benchmarks with a current result for every compared model (best result per model per benchmark). */160export function SharedBenchmarks({ res }: { res: ComparePayload }) {161 const perItem = res.items.map((it) => {162 const m = new Map<string, BenchmarkResult>();163 for (const r of it.results ?? []) {164 const prev = m.get(r.benchmark.slug);165 if (!prev || (r.higher_is_better === false ? r.score < prev.score : r.score > prev.score)) m.set(r.benchmark.slug, r);166 }167 return m;168 });169 const first = perItem[0];170 const shared = first ? [...first.values()].filter((r) => perItem.every((m) => m.has(r.benchmark.slug))) : [];171 const anyResults = res.items.some((it) => (it.results?.length ?? 0) > 0);172 if (shared.length === 0)173 return (174 <p className="text-sm text-ink-3">175 {anyResults ? 'No benchmark has a published result for every compared model.' : 'No benchmark results recorded for these models.'} Per-model results are on each model page (Benchmarks tab).176 </p>177 );178 shared.sort((a, b) => a.benchmark.name.localeCompare(b.benchmark.name));179 return (180 <>181 <ScrollTable caption="Shared benchmark results">182 <thead>183 <tr>184 <Th className={cn(STICKY, FIRST)} style={WRAP}>Benchmark</Th>185 {res.items.map((it) => (186 <Th key={it.entity.id} className="min-w-[8rem]">187 <EntityLink e={it.entity} className="text-sm font-semibold normal-case tracking-normal whitespace-normal" />188 </Th>189 ))}190 </tr>191 </thead>192 <tbody>193 {shared.map((row) => {194 const cells = perItem.map((m) => m.get(row.benchmark.slug) ?? null);195 const scores = cells.map((c) => (c ? c.score : null)).filter((s): s is number => s !== null);196 const best = scores.length > 1 ? (row.higher_is_better === false ? Math.min(...scores) : Math.max(...scores)) : null;197 return (198 <tr key={row.benchmark.slug}>199 <Td className={cn(STICKY, FIRST, 'text-ink-2')} style={WRAP}>200 <EntityLink e={row.benchmark} />201 <span className="block text-[11px] text-ink-3">202 {row.metric ?? '—'}203 {row.higher_is_better === false ? ' · lower is better' : ''}204 </span>205 </Td>206 {cells.map((c, i) => (207 <Td key={res.items[i]!.entity.id} className="tnum align-top" title={c ? configSummary(c.config) || undefined : undefined}>208 {c ? (209 <>210 <span className={cn('block', best !== null && c.score === best && 'font-semibold text-ink')}>211 {fmtScore(c.score)}212 {c.unit === '%' ? '%' : c.unit ? <span className="text-ink-3"> {c.unit}</span> : ''}213 </span>214 <span className="mt-0.5 hidden items-center gap-1 text-[11px] text-ink-3 md:flex">215 <TierBadge tier={c.tier} />216 <span className="max-w-[10rem] truncate">{configSummary(c.config) || (c.evaluated_at ? fmtDate(c.evaluated_at) : '')}</span>217 </span>218 </>219 ) : (220 <span className="text-ink-3">—</span>221 )}222 </Td>223 ))}224 </tr>225 );226 })}227 </tbody>228 </ScrollTable>229 <Note className="mt-3">Only benchmarks with a result for every compared model are shown (best current result per model). Configurations may differ — hover a score for its config; scores under different configs are not strictly comparable.</Note>230 </>231 );232}233234/* ------------------------------------------------------------------------------------------------------ prices */235236type Best = { input: number | null; output: number | null; observed: string; url: string | null; tier: number };237238/** Provider × entity: cheapest current input / output per provider for each compared model. */239export function ComparePrices({ res }: { res: ComparePayload }) {240 const providers = new Map<string, Price['provider']>();241 const perItem = res.items.map((it) => {242 const m = new Map<string, Best>();243 for (const p of it.prices ?? []) {244 providers.set(p.provider.slug, p.provider);245 const cur = m.get(p.provider.slug) ?? { input: null, output: null, observed: p.observed_at, url: p.source_url, tier: p.tier };246 const inp = num(p.input_per_mtok);247 const out = num(p.output_per_mtok);248 if (inp !== null && (cur.input === null || inp < cur.input)) cur.input = inp;249 if (out !== null && (cur.output === null || out < cur.output)) cur.output = out;250 if (p.observed_at > cur.observed) cur.observed = p.observed_at;251 m.set(p.provider.slug, cur);252 }253 return m;254 });255 if (providers.size === 0) return <p className="text-sm text-ink-3">No current prices recorded for these models.</p>;256 const rows = [...providers.values()].sort((a, b) => a.name.localeCompare(b.name));257 const bestOverall = res.items.map((_, i) => {258 let best: number | null = null;259 for (const b of perItem[i]!.values()) if (b.input !== null && (best === null || b.input < best)) best = b.input;260 return best;261 });262 return (263 <>264 <ScrollTable caption="Best current prices per provider (USD per 1M tokens)">265 <thead>266 <tr>267 <Th className={cn(STICKY, FIRST)} style={WRAP}>Provider</Th>268 {res.items.map((it) => (269 <Th key={it.entity.id} className="min-w-[9rem]">270 <EntityLink e={it.entity} className="text-sm font-semibold normal-case tracking-normal whitespace-normal" />271 <span className="block text-[10px] font-normal text-ink-3">in / out per 1M</span>272 </Th>273 ))}274 </tr>275 </thead>276 <tbody>277 {rows.map((prov) => (278 <tr key={prov.slug}>279 <Td className={cn(STICKY, FIRST)} style={WRAP}>280 <EntityLink e={prov} />281 </Td>282 {perItem.map((m, i) => {283 const b = m.get(prov.slug);284 if (!b) return <Td key={res.items[i]!.entity.id} className="text-ink-3">—</Td>;285 const cheapest = bestOverall[i] !== null && b.input === bestOverall[i];286 return (287 <Td key={res.items[i]!.entity.id} className="tnum align-top" title={`observed ${fmtDate(b.observed)}`}>288 <span className={cn('block text-accent-2', cheapest && 'font-semibold')}>289 {fmtUsdPerM(b.input)} <span className="text-ink-3">/</span> {fmtUsdPerM(b.output)}290 </span>291 <span className="mt-0.5 hidden items-center gap-1 text-[11px] text-ink-3 md:flex">292 <TierBadge tier={b.tier} />293 {b.url ? (294 <a href={b.url} target="_blank" rel="noopener noreferrer" className="max-w-[10rem] truncate hover:text-accent">295 {host(b.url)}296 </a>297 ) : null}298 <span>· {fmtAgo(b.observed)}</span>299 </span>300 </Td>301 );302 })}303 </tr>304 ))}305 </tbody>306 </ScrollTable>307 <Note className="mt-3">308 Cheapest current input / output price of each provider for each model, as published on the provider's pricing page (USD per 1M tokens). Bold = the model's cheapest provider. Full tables and history on each model page, or the <a href={routes.prices()} className="link">price index</a>.309 </Note>310 </>311 );312}313