HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import { Check, Minus, X } from 'lucide-react';2import Link from 'next/link';3import type { ReactNode } from 'react';4import { Estimated } from '@/components/ui/badges';5import { Hint } from '@/components/ui/hint';6import { Note } from '@/components/ui/section';7import { cn } from '@/lib/cn';8import { fmtGb, fmtInt, fmtUsdPerM, num } from '@/lib/format';9import type { Fit, ModelLicence, Num, PriceDistribution, PriceDistributionStats } from '@/lib/types';1011/*12 Small server-safe building blocks shared by the intelligence surfaces (frontier · prices · providers · open · run-locally …).13 Every number is passed in from the API; nothing here computes a score.14*/1516/** Methodology footnote under a section: the API's own `methodology` / `note` string, never paraphrased into a claim. */17export function Methodology({ text, children, className }: { text?: string | null; children?: ReactNode; className?: string }) {18 if (!text && !children) return null;19 return (20 <Note className={cn('mt-3 max-w-4xl', className)}>21 <span className="eyebrow mr-1.5 text-[10px]">Method</span>22 {text}23 {children}{' '}24 <Link href="/methodology" className="text-ink-3 underline decoration-dotted hover:text-ink">25 /methodology26 </Link>27 </Note>28 );29}3031/** Tiny min · p25 · median · p75 · max range bar on a log₁₀ axis shared by the whole column (`domain`). Amber = money. */32export function RangeBar({ d, domain, format = (v) => fmtUsdPerM(v), className, label }: { d: PriceDistributionStats | null | undefined; domain: [number, number]; format?: (v: number) => string; className?: string; label?: string }) {33 const min = num(d?.min);34 const p25 = num(d?.p25);35 const med = num(d?.median);36 const p75 = num(d?.p75);37 const max = num(d?.max);38 if (min === null || max === null || med === null) return <span className="text-xs text-ink-3">—</span>;39 const [lo, hi] = domain;40 const L = (v: number) => Math.log10(Math.max(v, lo));41 const pct = (v: number) => (hi === lo ? 50 : ((L(v) - L(lo)) / (L(hi) - L(lo))) * 100);42 const title = `${label ?? 'Distribution'}: min ${format(min)} · p25 ${p25 === null ? '—' : format(p25)} · median ${format(med)} · p75 ${p75 === null ? '—' : format(p75)} · max ${format(max)}${num(d?.n) !== null ? ` · n=${fmtInt(d?.n)}` : ''}`;43 return (44 <span className={cn('inline-flex items-center gap-2', className)} title={title} aria-label={title}>45 <span className="relative block h-3 w-24 shrink-0" aria-hidden>46 <span className="absolute inset-y-[5px] left-0 right-0 rounded-sm bg-surface-3" />47 <span className="absolute inset-y-[5px] rounded-sm bg-accent-2/60" style={{ left: `${pct(min)}%`, width: `${Math.max(1, pct(max) - pct(min))}%` }} />48 {p25 !== null && p75 !== null && <span className="absolute inset-y-[3px] rounded-sm bg-accent-2" style={{ left: `${pct(p25)}%`, width: `${Math.max(1.5, pct(p75) - pct(p25))}%` }} />}49 <span className="absolute inset-y-0 w-[2px] bg-ink" style={{ left: `calc(${pct(med)}% - 1px)` }} />50 </span>51 <span className="tnum text-xs text-ink-2">52 {format(med)} <span className="text-ink-3">med</span>53 </span>54 </span>55 );56}5758/** Log-domain helper for a column of distributions. */59export function distDomain(items: (PriceDistributionStats | null | undefined)[]): [number, number] {60 const mins = items.map((d) => num(d?.min)).filter((v): v is number => v !== null && v > 0);61 const maxs = items.map((d) => num(d?.max)).filter((v): v is number => v !== null && v > 0);62 if (!mins.length || !maxs.length) return [0.01, 100];63 return [Math.min(...mins), Math.max(...maxs)];64}6566/** Histogram of current offers by price bucket (from `/prices/index.distribution`). */67export function DistBars({ d, className }: { d: PriceDistribution | null | undefined; className?: string }) {68 const buckets = d?.buckets ?? [];69 const vals = buckets.map((b) => num(b.offers) ?? 0);70 const max = Math.max(1, ...vals);71 if (!buckets.length) return <p className={cn('text-xs text-ink-3', className)}>No distribution returned.</p>;72 return (73 <div className={className}>74 <ol className="flex h-28 items-end gap-1" role="img" aria-label={`Current offers by ${d?.metric ?? 'price'} bucket`}>75 {buckets.map((b, i) => {76 const v = vals[i] ?? 0;77 return (78 <li key={b.label} className="group relative flex min-w-0 flex-1 flex-col items-center justify-end" title={`${b.label}: ${fmtInt(v)} offers`}>79 <span className="tnum mb-0.5 text-[10px] text-ink-3">{v ? fmtInt(v) : ''}</span>80 <span className="block w-full rounded-t-[2px] bg-accent-2/80 group-hover:bg-accent-2" style={{ height: `${Math.max(2, (v / max) * 80)}px` }} />81 </li>82 );83 })}84 </ol>85 <ol className="mt-1 flex gap-1 border-t border-rule pt-1" aria-hidden>86 {buckets.map((b) => (87 <li key={b.label} className="tnum min-w-0 flex-1 truncate text-center text-[9px] text-ink-3 sm:text-[10px]">88 {b.label}89 </li>90 ))}91 </ol>92 <p className="tnum mt-1 text-xs text-ink-3">93 {fmtInt(d?.offers)} current offers · {d?.unit ?? 'USD per 1M tokens'} · {d?.metric?.replace(/_per_mtok$/, '').replace(/_/g, ' ')}94 </p>95 </div>96 );97}9899/** Estimated fit result: ✓/✗ + estimated memory + headroom, always next to an `Estimated` label. `compact` for table cells. */100export function FitCell({ fit, compact = false, className }: { fit: Partial<Fit> | null | undefined; compact?: boolean; className?: string }) {101 if (!fit || fit.fits === null || fit.fits === undefined) return <span className={cn('text-xs text-ink-3', className)} aria-label="no estimate">—</span>;102 const mem = num(fit.estimated_memory_gb);103 const head = num(fit.headroom_gb);104 return (105 <span className={cn('tnum inline-flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-xs', className)}>106 <span className={fit.fits ? 'font-medium text-positive' : 'text-danger'}>{fit.fits ? '✓ fits' : '✗ too large'}</span>107 {mem !== null && <span className="text-ink-2">{fmtGb(mem, 1)}</span>}108 {!compact && head !== null && <span className="text-ink-3">{head >= 0 ? '+' : '−'}{fmtGb(Math.abs(head), 1)} headroom</span>}109 {fit.quantization && <span className="mono text-[10px] text-ink-3">{fit.quantization}</span>}110 </span>111 );112}113114/** Fit breakdown (weights est./observed · KV cache + method · overhead · reserve) as a compact inline list. */115export function FitBreakdownList({ fit, className }: { fit: Fit; className?: string }) {116 const b = fit.breakdown;117 if (!b) return fit.note ? <p className={cn('text-xs text-ink-3', className)}>{fit.note}</p> : null;118 return (119 <ul className={cn('tnum flex flex-wrap gap-x-3 gap-y-0.5 text-[11px] text-ink-3', className)}>120 <li>121 weights <span className="text-ink-2">{fmtGb(b.weights_gb, 1)}</span> <SourceTag source={b.weights_source} />122 </li>123 <li>124 KV cache <span className="text-ink-2">{fmtGb(b.kv_cache_gb, 2)}</span> <span className="mono">({b.kv_cache_method})</span>125 </li>126 <li>127 overhead <span className="text-ink-2">{fmtGb(b.overhead_gb, 1)}</span>128 </li>129 <li>130 reserved <span className="text-ink-2">{fmtGb(b.reserved_gb, 0)}</span>131 </li>132 <li>133 context <span className="text-ink-2">{fmtInt(b.context)}</span> × batch {fmtInt(b.batch)}134 </li>135 </ul>136 );137}138139/** "observed" (green, file size read from the artifact) vs "estimated" (dashed warning). */140export function SourceTag({ source }: { source: string | null | undefined }) {141 if (source === 'observed') return <span className="rounded-[3px] bg-positive-soft px-1 text-[10px] font-medium uppercase tracking-wide text-positive">observed</span>;142 return <span className="rounded-[3px] border border-dashed border-warning/60 px-1 text-[10px] font-medium uppercase tracking-wide text-warning">estimated</span>;143}144145/** Banner above any table of estimates: label + the API assumptions (never our own wording). */146export function EstimateBanner({ assumptions, note, counts, className }: { assumptions?: string[]; note?: string | null; counts?: { fits?: Num; evaluated?: Num } | null; className?: string }) {147 return (148 <div className={cn('border-y border-rule py-3', className)} data-estimate-banner>149 <p className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm">150 <Estimated />151 <span className="font-semibold text-ink">All fit figures are estimates, not measurements.</span>152 {counts && num(counts.fits) !== null && num(counts.evaluated) !== null && (153 <span className="tnum text-ink-3">154 <span className="font-medium text-positive">{fmtInt(counts.fits)}</span> of {fmtInt(counts.evaluated)} evaluated models fit155 </span>156 )}157 </p>158 {note && <p className="mt-1.5 text-xs leading-relaxed text-ink-2">{note}</p>}159 {assumptions && assumptions.length > 0 && (160 <details className="mt-1.5 text-xs text-ink-3">161 <summary className="cursor-pointer select-none hover:text-ink">Assumptions ({assumptions.length})</summary>162 <ul className="mt-1 list-disc space-y-0.5 pl-5 leading-relaxed">163 {assumptions.map((a) => (164 <li key={a}>{a}</li>165 ))}166 </ul>167 </details>168 )}169 </div>170 );171}172173/** Licence permission glyphs: commercial · redistribution · derivatives · hosting (✓ allowed · ✗ restricted · – unknown). */174export function LicencePerms({ l, className, withLabel = false }: { l: ModelLicence | null | undefined; className?: string; withLabel?: boolean }) {175 if (!l || l.key === null) {176 return (177 <span className={cn('inline-flex flex-wrap items-center gap-1 text-xs text-ink-3', className)} title={l && 'note' in l && l.note ? l.note : 'Licence not classified'}>178 {l?.raw ? <span className="max-w-[9rem] truncate text-ink-2">{l.raw}</span> : <span>—</span>}179 <span className="rounded-[3px] bg-surface-2 px-1 text-[10px] uppercase tracking-wide">unclassified</span>180 </span>181 );182 }183 const items: [string, boolean | null | undefined, boolean][] = [184 ['Commercial use', l.commercial_use, false],185 ['Redistribution', l.redistribution, false],186 ['Derivatives', l.derivatives, false],187 ['Hosting', l.hosting_restrictions === null || l.hosting_restrictions === undefined ? null : !l.hosting_restrictions, false],188 ];189 return (190 <span className={cn('inline-flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-xs', className)}>191 {withLabel && <span className="mr-1 truncate text-ink">{l.label ?? l.key}</span>}192 {!withLabel && <span className="mono mr-0.5 text-[11px] text-ink-2">{l.key}</span>}193 {items.map(([label, v]) => (194 <span key={label} className={cn('inline-flex size-4 items-center justify-center rounded-[3px]', v === true ? 'bg-positive-soft text-positive' : v === false ? 'bg-danger-soft text-danger' : 'bg-surface-2 text-ink-3')} title={`${label}: ${v === true ? 'allowed' : v === false ? 'restricted' : 'unknown'}`} aria-label={`${label}: ${v === true ? 'allowed' : v === false ? 'restricted' : 'unknown'}`}>195 {v === true ? <Check className="size-3" aria-hidden /> : v === false ? <X className="size-3" aria-hidden /> : <Minus className="size-3" aria-hidden />}196 </span>197 ))}198 </span>199 );200}201export function LicenceLegend({ className }: { className?: string }) {202 return (203 <span className={cn('flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-ink-3', className)}>204 <span>Permissions, in order:</span>205 <span>commercial use</span>206 <span>· redistribution</span>207 <span>· derivatives</span>208 <span>· hosting</span>209 <span className="inline-flex items-center gap-1">210 <span className="inline-flex size-3.5 items-center justify-center rounded-[2px] bg-positive-soft text-positive"><Check className="size-2.5" aria-hidden /></span> allowed211 </span>212 <span className="inline-flex items-center gap-1">213 <span className="inline-flex size-3.5 items-center justify-center rounded-[2px] bg-danger-soft text-danger"><X className="size-2.5" aria-hidden /></span> restricted214 </span>215 <span className="inline-flex items-center gap-1">216 <span className="inline-flex size-3.5 items-center justify-center rounded-[2px] bg-surface-2 text-ink-3"><Minus className="size-2.5" aria-hidden /></span> unknown217 </span>218 </span>219 );220}221222/** Trust level chip (leaderboard rows). */223const TRUST_TONE: Record<string, string> = {224 'official-benchmark': 'bg-positive-soft text-positive',225 'peer-reviewed': 'bg-positive-soft text-positive',226 'independent-evaluator': 'bg-accent-soft text-accent',227 'official-model-card': 'bg-warning-soft text-warning',228 community: 'bg-surface-2 text-ink-2',229 unverified: 'bg-danger-soft text-danger',230};231export function TrustChip({ level, label, className }: { level: string | null | undefined; label?: string | null; className?: string }) {232 if (!level) return <span className={cn('text-xs text-ink-3', className)}>—</span>;233 return (234 <span className={cn('inline-flex items-center whitespace-nowrap rounded-[3px] px-1.5 py-[1px] text-[11px] font-medium leading-4', TRUST_TONE[level] ?? 'bg-surface-2 text-ink-2', className)} title={label ?? level}>235 {level.replace(/-/g, ' ')}236 </span>237 );238}239240/** Rank chips "benchmark #n" (observed dimensions, never summed). */241export function RankChips({ ranks, max = 4, className }: { ranks: Record<string, number> | { benchmark: string; rank: number }[] | undefined | null; max?: number; className?: string }) {242 const arr = !ranks ? [] : Array.isArray(ranks) ? ranks : Object.entries(ranks).map(([benchmark, rank]) => ({ benchmark, rank }));243 const sorted = arr.slice().sort((a, b) => a.rank - b.rank);244 if (!sorted.length) return <span className={cn('text-xs text-ink-3', className)}>—</span>;245 return (246 <span className={cn('inline-flex flex-wrap gap-1', className)}>247 {sorted.slice(0, max).map((r) => (248 <Link key={r.benchmark} href={`/benchmarks/${encodeURIComponent(r.benchmark)}`} className="tnum inline-flex items-center gap-1 rounded-[3px] bg-surface-2 px-1.5 py-[1px] text-[11px] text-ink-2 hover:text-accent" title={`Rank ${r.rank} on ${r.benchmark} (primary comparability group)`}>249 <span className="truncate max-w-[7rem]">{r.benchmark}</span>250 <span className="font-medium text-ink">#{r.rank}</span>251 </Link>252 ))}253 {sorted.length > max && <span className="text-[11px] text-ink-3">+{sorted.length - max}</span>}254 </span>255 );256}257258/** Column header with a sort link (keeps the rest of the URL). */259export function SortTh({ active, href, children, num: n, dir = 'asc' }: { active: boolean; href: string; children: ReactNode; num?: boolean; dir?: 'asc' | 'desc' }) {260 return (261 <th scope="col" className={cn(n && 'num')} aria-sort={active ? (dir === 'asc' ? 'ascending' : 'descending') : undefined}>262 <Link href={href} className={active ? 'text-ink' : 'hover:text-ink'}>263 {children}264 {active && <span aria-hidden> {dir === 'asc' ? '↑' : '↓'}</span>}265 </Link>266 </th>267 );268}269270/** Label + definition tooltip pair used in rails and strips. */271export function Defined({ label, definition }: { label: ReactNode; definition?: string | null }) {272 return (273 <span className="inline-flex items-center gap-0.5">274 {label}275 {definition && <Hint text={definition} />}276 </span>277 );278}279280/** Shared form control classes for the GET forms in rails. */281export const CTRL = 'h-11 lg:h-10 w-full border border-rule bg-surface px-2.5 text-sm text-ink focus:border-accent focus:outline-none';282export const CTRL_LG = 'h-11 w-full border border-rule bg-surface px-2.5 text-[15px] text-ink focus:border-accent focus:outline-none';283export const BTN_PRIMARY = 'inline-flex h-11 items-center justify-center gap-1.5 bg-ink px-4 text-sm font-medium text-canvas hover:opacity-90';284export const BTN_GHOST = 'inline-flex h-11 lg:h-10 items-center justify-center border border-rule px-3 text-sm text-ink-2 hover:border-rule-strong hover:text-ink';285286/** Field wrapper for rail forms. */287export function Field({ label, children, hint, className }: { label: string; children: ReactNode; hint?: ReactNode; className?: string }) {288 return (289 <label className={cn('block min-w-0', className)}>290 <span className="eyebrow block pb-1">{label}</span>291 {children}292 {hint && <span className="mt-0.5 block text-[11px] text-ink-3">{hint}</span>}293 </label>294 );295}296