import type { Metadata } from 'next'; import Link from 'next/link'; import { Bars } from '@/components/charts/charts'; import { TerminalLayout } from '@/components/layout/terminal'; import { RailFilters } from '@/components/changes/rail-filters'; import { type ChipItem } from '@/components/timeline/chip-row'; import { occurredAt, TIMELINE_LANES } from '@/components/timeline/lanes'; import { TimelineWorkbench } from '@/components/timeline/timeline-workbench'; import { EntityBadge } from '@/components/ui/badges'; import { withParams } from '@/components/ui/pagination'; import { Container, Note, PageHeader } from '@/components/ui/section'; import { EmptyState, Unavailable } from '@/components/ui/unavailable'; import { api, apiD1, apiD3, safe } from '@/lib/api'; import { fmtInt, fmtMonth, fmtYear, num } from '@/lib/format'; import { CATEGORY_LABELS, categoryLabel, eventLabel, IMPORTANCE_LABELS, routes, SITE_NAME } from '@/lib/site'; import type { ChangeEvent } from '@/lib/types'; export const revalidate = 600; type SP = Record; const LIMIT = 1000; const KEYS = ['entity', 'year', 'category', 'type', 'importance_min', 'include_backfill', 'org', 'family'] as const; function current(sp: SP): Record { const out: Record = {}; for (const k of KEYS) if (sp[k]) out[k] = sp[k]; if (out.year && !/^\d{4}$/.test(out.year)) delete out.year; if (out.include_backfill !== '1') delete out.include_backfill; return out; } /** `org` / `family` are shortcuts for the entity-scoped timeline (an organization's timeline includes its models' events). */ function scopeEntity(cur: Record): string | undefined { return cur.entity ?? cur.org ?? cur.family; } export async function generateMetadata({ searchParams }: { searchParams: Promise }): Promise { const cur = current(await searchParams); const bits: string[] = []; const scope = scopeEntity(cur); if (scope) bits.push(scope); if (cur.category) bits.push(categoryLabel(cur.category).toLowerCase()); if (cur.type) bits.push(eventLabel(cur.type).toLowerCase()); if (cur.year) bits.push(cur.year); const title = bits.length ? `Timeline — ${bits.join(' · ')}` : 'Timeline — the AI ecosystem, lane by lane'; return { title, description: 'Eight lanes — models, research, prices, benchmarks, providers, hardware, frameworks, companies — of source-attributed events keyed on when they occurred, brushable by range, with historical backfill on demand.', alternates: { canonical: withParams('/timeline', cur, {}) }, openGraph: { title: `${title} | ${SITE_NAME}` }, robots: scope ? { index: false, follow: true } : undefined, }; } export default async function TimelinePage({ searchParams }: { searchParams: Promise }) { const sp = await searchParams; const cur = current(sp); const scope = scopeEntity(cur); const backfill = cur.include_backfill === '1'; const [res, cats, stats, entity, orgs, fams, meth] = await Promise.all([ safe(apiD3.timeline({ entity: scope, year: cur.year, category: cur.category, importance_min: cur.importance_min, include_backfill: backfill ? 1 : undefined, limit: LIMIT })), safe(api.changesCategories(365)), safe(api.stats()), scope ? safe(api.entity(scope)) : Promise.resolve(null), safe(api.companies({ limit: 40, sort: 'models' })), safe(apiD1.families({ limit: 40, sort: 'models' })), safe(apiD3.methodology()), ]); const months = res?.items ?? []; const all: ChangeEvent[] = months.flatMap((m) => m.events); const events = cur.type ? all.filter((e) => e.event_type === cur.type) : all; const loaded = all.length; const capped = loaded >= LIMIT; const href = (patch: Record) => withParams('/timeline', cur, patch); // Years: current UTC year back to the earliest year the atlas knows. const thisYear = new Date().getUTCFullYear(); const earliestCandidates = [stats?.first_entity_at, ...months.map((m) => m.month), cur.year].filter((v): v is string => !!v).map((v) => Number(fmtYear(v))).filter((n) => Number.isFinite(n) && n > 1990); const earliest = earliestCandidates.length ? Math.min(...earliestCandidates) : thisYear; const years: string[] = []; for (let y = thisYear; y >= Math.max(earliest, thisYear - 12); y--) years.push(String(y)); const catCounts = new Map(); for (const it of cats?.items ?? []) catCounts.set(it.category, (catCounts.get(it.category) ?? 0) + (num(it.count) ?? 0)); const catKeys = catCounts.size ? [...catCounts.entries()].sort((a, b) => b[1] - a[1]).map(([k]) => k) : Object.keys(CATEGORY_LABELS); if (cur.category && !catKeys.includes(cur.category)) catKeys.push(cur.category); const typeCounts = new Map(); for (const e of all) typeCounts.set(e.event_type, (typeCounts.get(e.event_type) ?? 0) + 1); const typeKeys = [...typeCounts.entries()].sort((a, b) => b[1] - a[1]).map(([k]) => k); const yearChips: ChipItem[] = [{ href: href({ year: undefined }), label: 'All years', active: !cur.year }, ...years.map((y) => ({ href: href({ year: y }), label: y, active: cur.year === y }))]; const density = [...months].reverse().map((m) => ({ label: fmtMonth(m.month), value: m.count ?? m.events.length })); const activeCount = Object.keys(cur).length; const filters = ( ({ href: c.href, label: c.label, active: c.active })) }, { kind: 'select', name: 'category', label: 'Category (lane)', value: cur.category, any: 'All lanes', options: catKeys.map((c) => ({ value: c, label: `${categoryLabel(c)}${catCounts.size ? ` (${fmtInt(catCounts.get(c) ?? 0)})` : ''}` })) }, { kind: 'select', name: 'type', label: 'Event type', value: cur.type, any: 'All types', options: [...typeKeys.map((t) => ({ value: t, label: `${eventLabel(t)} (${fmtInt(typeCounts.get(t) ?? 0)})` })), ...(cur.type && !typeKeys.includes(cur.type) ? [{ value: cur.type, label: `${eventLabel(cur.type)} (0 loaded)` }] : [])], note: 'Filters the loaded events (the API has no type filter on /timeline).' }, { kind: 'select', name: 'org', label: 'Organization', value: cur.org, any: 'Whole atlas', remote: 'companies', options: (orgs?.items ?? []).filter((o) => o.slug === cur.org).map((o) => ({ value: o.slug, label: `${o.name} (${fmtInt(o.model_count)})` })) }, { kind: 'select', name: 'family', label: 'Family', value: cur.family, any: 'Any family', remote: 'families', options: (fams?.items ?? []).filter((f) => f.slug === cur.family).map((f) => ({ value: f.slug, label: `${f.name} (${fmtInt(f.model_count)})` })), note: 'Organization and family scope the timeline to that entity (one at a time).' }, { kind: 'select', name: 'importance_min', label: 'Min importance', value: cur.importance_min, options: [3, 2, 1].map((n) => ({ value: String(n), label: `${IMPORTANCE_LABELS[n]} (≥ ${n})` })) }, { kind: 'checkbox', name: 'include_backfill', label: 'Include historical backfill', checked: backfill, hint: 'Backfill = history imported when a source is first crawled (release dates, past prices). Off by default so the timeline shows what AI Atlas observed happening; on, it shows the reconstructed history.' }, ...(cur.year ? [{ kind: 'hidden' as const, name: 'year', value: cur.year }] : []), ...(cur.entity ? [{ kind: 'hidden' as const, name: 'entity', value: cur.entity }] : []), ]} /> ); const inspector = (

Lanes

    {TIMELINE_LANES.filter((l) => l.key !== 'other' || all.some((e) => l.categories.length === 0 && !TIMELINE_LANES.some((x) => x.categories.includes(e.category)))).map((l) => { const n = all.filter((e) => (TIMELINE_LANES.find((x) => x.categories.includes(e.category))?.key ?? 'other') === l.key).length; return (
  • {l.label} {fmtInt(n)}
  • ); })}

Semantics

{Object.entries(meth?.event_semantics ?? {}).slice(0, 5).map(([k, v]) => (
{k}
{v}
))} {!meth?.event_semantics &&
Definitions unavailable.
}

Times are UTC. Per-entity history: open any entity's History tab. Compare two dates in Diff; see the atlas as of a date in the Time machine.

); return ( <> Timeline : 'Timeline 2.0'} title={entity ? <>Timeline of {entity.name} : scope ? <>Timeline of {scope} : 'The ecosystem, lane by lane'} lede={entity ? <>Every recorded change for this {entity.entity_type.replace(/_/g, ' ')}{entity.organization ? ` by ${entity.organization.name}` : ''}{['company', 'organization', 'lab', 'university'].includes(entity.entity_type) ? ' and for the models it develops' : ''}, keyed on when it occurred. Back to the entity → : 'Models · Research · Prices · Benchmarks · Providers · Hardware · Frameworks · Companies. Dots are events sized by importance; drag on the lanes to zoom the list to a range. Everything links to its entity and to the source that stated it.'} aside={res ?

{fmtInt(events.length)}{cur.type ? ` of ${fmtInt(loaded)} loaded` : ''} events{capped ? ` · first ${fmtInt(LIMIT)}` : ''}

: undefined} className="pb-3" />
{!res ? ( ) : events.length === 0 ? ( {backfill ? 'Try another year, lane or scope, or open ' : 'Nothing was observed live for this selection — turn on “include historical backfill” for reconstructed history, or open '} the live feed. ) : ( <> {density.length > 1 && (

Events per month · {fmtInt(density.length)} months loaded

)} Showing up to {fmtInt(LIMIT)} events{capped ? ' — the API cap; narrow by year, lane or scope to see everything' : ''}. Dates are occurred_at (effective date when a source states one, else observation) — hover a date for the observation time.{!backfill && ' Historical backfill is excluded; the checkbox in the rail includes it.'} Cursor-paged history: changes feed. {events.length > 0 &&

Earliest loaded event: {occurredAt(events[events.length - 1]!)}

} )}
); }