import type { Metadata } from 'next'; import Link from 'next/link'; import { DataStrip, SectionNav } from '@/components/layout/terminal'; import { BreadcrumbLd } from '@/components/meta/breadcrumb-ld'; import { daysBefore, firstOfMonth, firstOfQuarter, ISO_DAY, monthsBefore, todayUtc } from '@/components/temporal/dates'; import { ContextChangesSection, EntitiesSection, EventsSection, LeadersSection, NewEntitiesSection, PriceChangesSection } from '@/components/temporal/diff-sections'; import { Container, Note, PageHeader } from '@/components/ui/section'; import { Unavailable } from '@/components/ui/unavailable'; import { api, ApiError, apiD1, apiD3, safe } from '@/lib/api'; import { fmtDate, fmtInt, num } from '@/lib/format'; import { routes, SITE_NAME, SITE_URL } from '@/lib/site'; import type { ChangeEvent, DiffPayload11, EntitySummary } from '@/lib/types'; type SP = { a?: string; b?: string; scope?: string; scope_custom?: string; include_backfill?: string }; const LIMIT = 200; function resolve(sp: SP): { a: string; b: string; scope: string; valid: boolean; backfill: boolean } { const b = sp.b && ISO_DAY.test(sp.b) ? sp.b : todayUtc(); const a = sp.a && ISO_DAY.test(sp.a) ? sp.a : daysBefore(b, 7); const scope = (sp.scope_custom?.trim() || sp.scope || 'all').trim() || 'all'; const valid = (!sp.a || ISO_DAY.test(sp.a)) && (!sp.b || ISO_DAY.test(sp.b)) && a < b; return { a, b, scope, valid, backfill: sp.include_backfill === '1' }; } function href(a: string, b: string, scope: string, backfill = false): string { const p = new URLSearchParams({ a, b }); if (scope !== 'all') p.set('scope', scope); if (backfill) p.set('include_backfill', '1'); return `/diff?${p.toString()}`; } function describeScope(s: DiffPayload11['scope'], fallback: string): string { if (!s) return fallback === 'all' ? 'the whole atlas' : fallback; if (typeof s === 'string') return s; const kind = typeof s.kind === 'string' ? s.kind : fallback; if (kind === 'all') return 'the whole atlas'; const org = s.organization as { name?: string } | undefined; if (kind === 'org' && org?.name) return `organization · ${org.name}`; if (kind === 'family' && typeof s.family === 'string') return `family · ${s.family}`; if (kind === 'models') return 'models only'; return kind; } export async function generateMetadata({ searchParams }: { searchParams: Promise }): Promise { const { a, b, scope } = resolve(await searchParams); const title = `Diff the AI world — ${fmtDate(a)} → ${fmtDate(b)}${scope !== 'all' ? ` (${scope})` : ''}`; 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.`; const og = `${SITE_URL}/diff/og?a=${a}&b=${b}&scope=${encodeURIComponent(scope)}`; 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] } }; } export default async function DiffPage({ searchParams }: { searchParams: Promise }) { const sp = await searchParams; const { a, b, scope, valid, backfill } = resolve(sp); const today = todayUtc(); const [orgs, fams] = await Promise.all([safe(api.companies({ limit: 40, sort: 'models' })), safe(apiD1.families({ limit: 40, sort: 'models' }))]); let payload: DiffPayload11 | null = null; let error: string | null = null; if (!valid) error = 'Dates must be YYYY-MM-DD and the first date must come before the second.'; else { try { payload = await apiD3.diff(a, b, scope, LIMIT, backfill); } catch (e) { error = e instanceof ApiError && e.status === 400 ? e.detail ?? 'The API rejected these parameters.' : null; } } const cls = 'h-11 w-full border border-rule bg-surface px-2.5 text-sm text-ink focus:border-accent focus:outline-none'; const orgOptions = (orgs?.items ?? []).map((o) => ({ value: `org:${o.slug}`, label: `${o.name} (${fmtInt(o.model_count)} models)` })); const famOptions = (fams?.items ?? []).filter((f) => f.canonical).map((f) => ({ value: `family:${f.slug}`, label: `${f.name} (${fmtInt(f.model_count)})` })); const knownScopes = new Set(['all', 'models', ...orgOptions.map((o) => o.value), ...famOptions.map((o) => o.value)]); const custom = knownScopes.has(scope) ? '' : scope; const c = payload?.counts ?? {}; const cnt = (k: string): number | null => num(c[k]); // Split new entities by type (the API's new_entities is every type for scope=all). const newAll: EntitySummary[] = payload?.new_entities ?? []; const newModels = newAll.filter((e) => e.entity_type === 'model'); const newPapers = newAll.filter((e) => e.entity_type === 'paper'); const newOther = newAll.filter((e) => e.entity_type !== 'model' && e.entity_type !== 'paper'); const newTotal = cnt('new_entities'); const listCapped = newTotal !== null && newTotal > newAll.length; 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); const presets: { label: string; a: string; b: string }[] = [ { label: 'Last 7 days', a: daysBefore(today, 7), b: today }, { label: 'Since the 1st', a: firstOfMonth(today), b: today }, { label: 'Last 30 days', a: monthsBefore(today, 1), b: today }, { label: 'This quarter', a: firstOfQuarter(today), b: today }, { label: 'Last 90 days', a: daysBefore(today, 90), b: today }, ]; const sections = payload ? [ { id: 'new-models', label: 'New models' }, { id: 'retired', label: 'Retired' }, { id: 'prices', label: 'Prices' }, { id: 'context', label: 'Context' }, { id: 'leaders', label: 'Leaders' }, { id: 'papers', label: 'Papers' }, { id: 'providers', label: 'Providers' }, { id: 'hardware', label: 'Hardware' }, { id: 'properties', label: 'Properties' }, { id: 'gone', label: 'Gone' }, ] : []; return ( What changed between {fmtDate(a)} and {fmtDate(b)} } 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." aside={payload ?

Scope: {describeScope(payload.scope, scope)}

: undefined} >
Reset
    {presets.map((p) => { const on = p.a === a && p.b === b; return (
  • {p.label}
  • ); })}
{error ? ( ) : !payload ? ( ) : ( <> {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'))}. {listCapped && <> The entity lists are capped at {fmtInt(LIMIT)} by the API — per-type counts below are “of the first {fmtInt(LIMIT)}”.} {payload.note && <> {payload.note}} {newOther.length > 0 && } “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 occurred_at{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. Atlas as of {fmtDate(a)} · Methodology → )}
); }