HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { DataStrip, SectionNav } from '@/components/layout/terminal';4import { BreadcrumbLd } from '@/components/meta/breadcrumb-ld';5import { daysBefore, firstOfMonth, firstOfQuarter, ISO_DAY, monthsBefore, todayUtc } from '@/components/temporal/dates';6import { ContextChangesSection, EntitiesSection, EventsSection, LeadersSection, NewEntitiesSection, PriceChangesSection } from '@/components/temporal/diff-sections';7import { Container, Note, PageHeader } from '@/components/ui/section';8import { Unavailable } from '@/components/ui/unavailable';9import { api, ApiError, apiD1, apiD3, safe } from '@/lib/api';10import { fmtDate, fmtInt, num } from '@/lib/format';11import { routes, SITE_NAME, SITE_URL } from '@/lib/site';12import type { ChangeEvent, DiffPayload11, EntitySummary } from '@/lib/types';1314type SP = { a?: string; b?: string; scope?: string; scope_custom?: string; include_backfill?: string };15const LIMIT = 200;1617function resolve(sp: SP): { a: string; b: string; scope: string; valid: boolean; backfill: boolean } {18 const b = sp.b && ISO_DAY.test(sp.b) ? sp.b : todayUtc();19 const a = sp.a && ISO_DAY.test(sp.a) ? sp.a : daysBefore(b, 7);20 const scope = (sp.scope_custom?.trim() || sp.scope || 'all').trim() || 'all';21 const valid = (!sp.a || ISO_DAY.test(sp.a)) && (!sp.b || ISO_DAY.test(sp.b)) && a < b;22 return { a, b, scope, valid, backfill: sp.include_backfill === '1' };23}24function href(a: string, b: string, scope: string, backfill = false): string {25 const p = new URLSearchParams({ a, b });26 if (scope !== 'all') p.set('scope', scope);27 if (backfill) p.set('include_backfill', '1');28 return `/diff?${p.toString()}`;29}30function describeScope(s: DiffPayload11['scope'], fallback: string): string {31 if (!s) return fallback === 'all' ? 'the whole atlas' : fallback;32 if (typeof s === 'string') return s;33 const kind = typeof s.kind === 'string' ? s.kind : fallback;34 if (kind === 'all') return 'the whole atlas';35 const org = s.organization as { name?: string } | undefined;36 if (kind === 'org' && org?.name) return `organization · ${org.name}`;37 if (kind === 'family' && typeof s.family === 'string') return `family · ${s.family}`;38 if (kind === 'models') return 'models only';39 return kind;40}4142export async function generateMetadata({ searchParams }: { searchParams: Promise<SP> }): Promise<Metadata> {43 const { a, b, scope } = resolve(await searchParams);44 const title = `Diff the AI world — ${fmtDate(a)} → ${fmtDate(b)}${scope !== 'all' ? ` (${scope})` : ''}`;45 const description = `Every recorded change in the AI ecosystem between ${a} and ${b}: new and retired models, price and context changes, new benchmark leaders, papers, provider and hardware changes — from the change log, nothing inferred.`;46 const og = `${SITE_URL}/diff/og?a=${a}&b=${b}&scope=${encodeURIComponent(scope)}`;47 return { title, description, alternates: { canonical: href(a, b, scope) }, openGraph: { title: `${title} | ${SITE_NAME}`, description, url: `${SITE_URL}${href(a, b, scope)}`, images: [{ url: og, width: 1200, height: 630 }] }, twitter: { card: 'summary_large_image', images: [og] } };48}4950export default async function DiffPage({ searchParams }: { searchParams: Promise<SP> }) {51 const sp = await searchParams;52 const { a, b, scope, valid, backfill } = resolve(sp);53 const today = todayUtc();54 const [orgs, fams] = await Promise.all([safe(api.companies({ limit: 40, sort: 'models' })), safe(apiD1.families({ limit: 40, sort: 'models' }))]);55 let payload: DiffPayload11 | null = null;56 let error: string | null = null;57 if (!valid) error = 'Dates must be YYYY-MM-DD and the first date must come before the second.';58 else {59 try {60 payload = await apiD3.diff(a, b, scope, LIMIT, backfill);61 } catch (e) {62 error = e instanceof ApiError && e.status === 400 ? e.detail ?? 'The API rejected these parameters.' : null;63 }64 }65 const cls = 'h-11 w-full border border-rule bg-surface px-2.5 text-sm text-ink focus:border-accent focus:outline-none';66 const orgOptions = (orgs?.items ?? []).map((o) => ({ value: `org:${o.slug}`, label: `${o.name} (${fmtInt(o.model_count)} models)` }));67 const famOptions = (fams?.items ?? []).filter((f) => f.canonical).map((f) => ({ value: `family:${f.slug}`, label: `${f.name} (${fmtInt(f.model_count)})` }));68 const knownScopes = new Set(['all', 'models', ...orgOptions.map((o) => o.value), ...famOptions.map((o) => o.value)]);69 const custom = knownScopes.has(scope) ? '' : scope;70 const c = payload?.counts ?? {};71 const cnt = (k: string): number | null => num(c[k]);7273 // Split new entities by type (the API's new_entities is every type for scope=all).74 const newAll: EntitySummary[] = payload?.new_entities ?? [];75 const newModels = newAll.filter((e) => e.entity_type === 'model');76 const newPapers = newAll.filter((e) => e.entity_type === 'paper');77 const newOther = newAll.filter((e) => e.entity_type !== 'model' && e.entity_type !== 'paper');78 const newTotal = cnt('new_entities');79 const listCapped = newTotal !== null && newTotal > newAll.length;80 const retired: EntitySummary[] = (payload?.retired_models ?? []).map((r) => ('entity' in (r as ChangeEvent) && (r as ChangeEvent).entity ? ((r as ChangeEvent).entity as EntitySummary) : (r as EntitySummary))).filter((e) => e && e.slug);8182 const presets: { label: string; a: string; b: string }[] = [83 { label: 'Last 7 days', a: daysBefore(today, 7), b: today },84 { label: 'Since the 1st', a: firstOfMonth(today), b: today },85 { label: 'Last 30 days', a: monthsBefore(today, 1), b: today },86 { label: 'This quarter', a: firstOfQuarter(today), b: today },87 { label: 'Last 90 days', a: daysBefore(today, 90), b: today },88 ];89 const sections = payload90 ? [91 { id: 'new-models', label: 'New models' },92 { id: 'retired', label: 'Retired' },93 { id: 'prices', label: 'Prices' },94 { id: 'context', label: 'Context' },95 { id: 'leaders', label: 'Leaders' },96 { id: 'papers', label: 'Papers' },97 { id: 'providers', label: 'Providers' },98 { id: 'hardware', label: 'Hardware' },99 { id: 'properties', label: 'Properties' },100 { id: 'gone', label: 'Gone' },101 ]102 : [];103104 return (105 <Container wide>106 <BreadcrumbLd items={[{ name: SITE_NAME, href: '/' }, { name: 'Diff', href: '/diff' }, { name: `${a} → ${b}`, href: href(a, b, scope) }]} />107 <PageHeader108 eyebrow="Diff the AI world"109 title={110 <>111 What changed between <span className="tnum">{fmtDate(a)}</span> and <span className="tnum">{fmtDate(b)}</span>112 </>113 }114 lede="Two dates, one scope: new and retired models, price and context changes, new benchmark leaders, new papers, provider and hardware changes. Built from the change log keyed on when things occurred — nothing is inferred, and the URL is the report."115 aside={payload ? <p className="text-sm text-ink-3">Scope: <span className="text-ink-2">{describeScope(payload.scope, scope)}</span></p> : undefined}116 >117 <form action="/diff" method="get" className="mt-6 grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6" data-diff-form>118 <label className="block min-w-0">119 <span className="eyebrow block pb-1">From (UTC)</span>120 <input type="date" name="a" defaultValue={a} max={today} className={cls} required />121 </label>122 <label className="block min-w-0">123 <span className="eyebrow block pb-1">To (UTC)</span>124 <input type="date" name="b" defaultValue={b} max={today} className={cls} required />125 </label>126 <label className="block min-w-0">127 <span className="eyebrow block pb-1">Scope</span>128 <select name="scope" defaultValue={custom ? '' : scope} className={cls}>129 <option value="all">Everything</option>130 <option value="models">Models only</option>131 {orgOptions.length > 0 && (132 <optgroup label="Organization">133 {orgOptions.map((o) => (134 <option key={o.value} value={o.value}>135 {o.label}136 </option>137 ))}138 </optgroup>139 )}140 {famOptions.length > 0 && (141 <optgroup label="Family">142 {famOptions.map((o) => (143 <option key={o.value} value={o.value}>144 {o.label}145 </option>146 ))}147 </optgroup>148 )}149 {custom && <option value="">Custom (below)</option>}150 </select>151 </label>152 <label className="block min-w-0">153 <span className="eyebrow block pb-1">Custom scope</span>154 <input name="scope_custom" defaultValue={custom} placeholder="org:<slug> · family:<slug>" className={cls} />155 </label>156 <label className="flex min-w-0 items-end">157 <span className="flex h-11 items-center gap-2 text-sm text-ink-2">158 <input type="checkbox" name="include_backfill" value="1" defaultChecked={backfill} className="size-4 accent-[var(--accent)]" /> include backfill159 </span>160 </label>161 <div className="flex items-end gap-2">162 <button type="submit" className="inline-flex h-11 flex-1 items-center justify-center bg-ink px-3 text-sm font-medium text-canvas hover:opacity-90">163 Compare dates164 </button>165 <Link href={routes.diff()} className="inline-flex h-11 items-center border border-rule px-3 text-sm text-ink-2 hover:text-ink">166 Reset167 </Link>168 </div>169 </form>170 <ul className="no-scrollbar -mx-4 mt-3 flex gap-1 overflow-x-auto px-4 md:mx-0 md:px-0" aria-label="Presets">171 {presets.map((p) => {172 const on = p.a === a && p.b === b;173 return (174 <li key={p.label} className="shrink-0">175 <Link href={href(p.a, p.b, scope, backfill)} className={`inline-flex h-9 items-center border px-2.5 text-sm ${on ? 'border-ink bg-ink text-canvas' : 'border-rule text-ink-2 hover:border-rule-strong hover:text-ink'}`} aria-current={on ? 'true' : undefined}>176 {p.label}177 </Link>178 </li>179 );180 })}181 </ul>182 </PageHeader>183184 <div className="pb-16">185 {error ? (186 <Unavailable what="Diff" reason={error} />187 ) : !payload ? (188 <Unavailable what="Diff" reason="The change log could not be read right now." />189 ) : (190 <>191 <DataStrip192 dense193 items={[194 { label: 'New models', value: fmtInt(listCapped ? null : newModels.length), hint: listCapped ? `${fmtInt(newTotal)} new entities in total` : undefined, definition: 'Canonical models first seen between the two dates (artifacts excluded unless include=artifacts).', href: '#new-models' },195 { label: 'Retired models', value: fmtInt(cnt('retired_models') ?? retired.length), definition: 'Models whose status became retired/deprecated in the window.', href: '#retired' },196 { label: 'Price changes', value: fmtInt(cnt('price_changes') ?? payload.price_changes.length), definition: 'PRICE_CHANGED events that occurred in the window.', href: '#prices' },197 { label: 'Context changes', value: fmtInt(cnt('context_changes') ?? payload.context_changes?.length), definition: 'CONTEXT_CHANGED events in the window.', href: '#context' },198 { label: 'New leaders', value: fmtInt(cnt('new_benchmark_leaders') ?? payload.new_benchmark_leaders?.length), definition: 'Benchmarks whose primary-group leader differs between the two dates.', href: '#leaders' },199 { label: 'New papers', value: fmtInt(listCapped ? null : newPapers.length), definition: 'Papers first seen in the window (from the new-entities list).', href: '#papers' },200 { label: 'Provider changes', value: fmtInt(cnt('provider_changes') ?? payload.provider_changes?.length), definition: 'Listings and delistings by providers.', href: '#providers' },201 { label: 'Hardware changes', value: fmtInt(cnt('hardware_changes') ?? payload.hardware_changes?.length), definition: 'Hardware entities and spec changes.', href: '#hardware' },202 ]}203 />204 <Note className="mt-2">205 {fmtInt(cnt('events'))} events in the window · {fmtInt(cnt('claims_superseded'))} claims superseded · price rows opened {fmtInt(cnt('price_rows_opened'))} / closed {fmtInt(cnt('price_rows_closed'))} · entities at {fmtDate(a)}: {fmtInt(cnt('entities_at_a'))} → at {fmtDate(b)}: {fmtInt(cnt('entities_at_b'))}.206 {listCapped && <> The entity lists are capped at {fmtInt(LIMIT)} by the API — per-type counts below are “of the first {fmtInt(LIMIT)}”.</>}207 {payload.note && <> {payload.note}</>}208 </Note>209 <SectionNav items={sections} className="mt-4" />210211 <NewEntitiesSection id="new-models" title="New models" items={newModels} total={listCapped ? null : newModels.length} empty="No new canonical model between these dates" type="model" />212 <EntitiesSection id="retired" title="Retired models" items={retired} total={cnt('retired_models')} empty="No model was retired between these dates" />213 <PriceChangesSection id="prices" title="Price changes" items={payload.price_changes} total={cnt('price_changes')} empty="No price change between these dates" />214 <ContextChangesSection id="context" title="Context changes" items={payload.context_changes ?? []} total={cnt('context_changes')} empty="No context-window change between these dates" />215 <LeadersSection id="leaders" items={payload.new_benchmark_leaders ?? []} a={a} b={b} />216 <NewEntitiesSection id="papers" title="New papers" items={newPapers} total={listCapped ? null : newPapers.length} empty="No new paper between these dates" type="paper" />217 <PriceChangesSection id="providers" title="Provider changes" items={payload.provider_changes ?? []} total={cnt('provider_changes')} empty="No provider listing or delisting between these dates" />218 <EventsSection id="hardware" title="Hardware changes" items={payload.hardware_changes ?? []} total={cnt('hardware_changes')} empty="No hardware change between these dates" />219 <EventsSection id="properties" title="Other property changes" items={payload.property_changes} total={cnt('property_changes')} empty="No other property change between these dates" lede="Openness, licence, status, parameters, release-date and other material property changes (context changes are listed above)." />220 {newOther.length > 0 && <NewEntitiesSection id="new-other" title="Other new entities" items={newOther} total={listCapped ? null : newOther.length} empty="" type="other" />}221 <EntitiesSection id="gone" title="Gone entities" items={payload.gone_entities} total={cnt('gone_entities')} empty="No entity disappeared between these dates" />222 <Note className="mt-6">223 “Gone” means an entity present at the first date is no longer current at the second (merged or retired) — its record and history are kept. Events are keyed on <span className="mono">occurred_at</span>{backfill ? ' and include back-filled history' : '; back-filled history is excluded (tick “include backfill” to add it)'}. Per-entity history: any entity's History tab. <Link href={routes.timeMachine(a)} className="link">Atlas as of {fmtDate(a)}</Link> · <Link href={routes.methodology()} className="link">Methodology →</Link>224 </Note>225 </>226 )}227 </div>228 </Container>229 );230}231