HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import Link from 'next/link';2import { ScrollX } from '@/components/models/scroll-x';3import { DataTable, Td, Th } from '@/components/ui/data-table';4import { KeyValue, type KVRow } from '@/components/ui/key-value';5import { SourceCell } from '@/components/ui/provenance';6import { Note } from '@/components/ui/section';7import { EmptyState, Unavailable } from '@/components/ui/unavailable';8import { cn } from '@/lib/cn';9import { fmtDate, fmtDateTime, fmtInt, fmtValue } from '@/lib/format';10import { PROSE_KEYS, propertyLabel, routes } from '@/lib/site';11import type { AsOfPayload, Claim, EntityDetail } from '@/lib/types';12import { AsOfPicker } from './asof-picker';1314/* Canonical order for model properties (mirrors SpecTable in blocks.tsx; kept local to avoid touching that file). */15const MODEL_ORDER = ['family', 'version', 'release_date', 'status', 'openness', 'license', 'architecture', 'parameter_count', 'active_parameter_count', 'is_moe', 'context_length', 'max_output_tokens', 'knowledge_cutoff', 'training_data_cutoff', 'modalities', 'modalities_input', 'modalities_output', 'languages', 'tokenizer', 'api_model_id', 'api_alias', 'base_model', 'quantization', 'quant_format', 'file_size_gb', 'deprecation_date', 'retirement_date', 'retirement_tentative', 'hf_repo', 'pipeline_tag', 'official_url', 'model_card_url', 'paper_url', 'repository_url', 'metric.downloads', 'metric.likes'];16const HIDDEN = new Set(['name', 'slug', 'id', 'entity_type']);17const ORDER = new Map(MODEL_ORDER.map((k, i) => [k, i]));18const byCanonical = (a: string, b: string) => (ORDER.get(a) ?? 999) - (ORDER.get(b) ?? 999) || a.localeCompare(b);1920const STATUS_CLS: Record<string, string> = {21 current: 'text-positive bg-positive-soft',22 superseded: 'text-ink-3 bg-surface-2',23 conflicting: 'text-danger bg-danger-soft',24 retracted: 'text-warning bg-warning-soft',25};26function ClaimStatus({ status }: { status: string }) {27 return <span className={cn('inline-flex items-center whitespace-nowrap rounded-[3px] px-1.5 py-[1px] text-[11px] font-medium leading-4 tracking-wide', STATUS_CLS[status] ?? 'text-ink-2 bg-surface-2')}>{status}</span>;28}2930/* ------------------------------------------------------------------------------------------------------ as of */3132export function AsOfBlock({ d, asof, payload }: { d: EntityDetail; asof: string; payload: AsOfPayload | null }) {33 const back = `${routes.entity(d)}?tab=history`;34 if (!payload) {35 return (36 <div className="hairline pt-4">37 <Unavailable what={`State as of ${asof}`} reason="Could not load the state for that date — check the date (YYYY-MM-DD, not in the future)." />38 <p className="mt-2 text-sm"><Link href={back} className="link">Back to today →</Link></p>39 </div>40 );41 }42 const attrs = payload.attributes ?? {};43 const keys = Object.keys(attrs).filter((k) => !HIDDEN.has(k) && !PROSE_KEYS.has(k) && attrs[k] !== null && attrs[k] !== undefined && attrs[k] !== '' && !(Array.isArray(attrs[k]) && (attrs[k] as unknown[]).length === 0));44 keys.sort(byCanonical);45 const rows: KVRow[] = keys.map((k) => ({ key: k, raw: attrs[k] }));46 return (47 <div className="hairline pt-4">48 <div className="flex flex-wrap items-center justify-between gap-2 bg-accent-soft px-3 py-2.5 text-sm">49 <p className="text-ink">50 <span className="font-medium">Viewing AI Atlas as of {fmtDate(payload.date)}</span>51 <span className="text-ink-2"> — attributes exactly as the atlas knew them on that day; later corrections are not shown.</span>52 </p>53 <Link href={back} className="link shrink-0 py-2">Back to today →</Link>54 </div>55 {!payload.existed ? (56 <EmptyState title={`${d.name} was not yet in AI Atlas on ${fmtDate(payload.date)}`} className="mt-4">57 First seen {fmtDate(payload.first_seen_at)}. Nothing is inferred backwards: no attribute is shown for dates before the first observation.58 </EmptyState>59 ) : (60 <div className="mt-4">61 <p className="eyebrow mb-2">62 Attributes as of {fmtDate(payload.date)} <span className="tnum text-ink-3">{fmtInt(payload.claims?.length ?? 0)} claims in force</span>63 </p>64 <KeyValue rows={rows} />65 </div>66 )}67 </div>68 );69}7071/* ------------------------------------------------------------------------------------------------------ claim history */7273export function ClaimHistory({ d, claims, property }: { d: EntityDetail; claims: Claim[] | null; property?: string }) {74 const base = `${routes.entity(d)}?tab=history`;75 if (!claims) return <Unavailable what="Claim history" />;76 if (!claims.length)77 return (78 <EmptyState title={property ? `No claims recorded for ${propertyLabel(property)}` : 'No claims recorded yet'}>79 {property ? <Link href={base} className="link">Show all properties →</Link> : 'Claims appear when a source states a fact; every later change is kept as a new claim.'}80 </EmptyState>81 );82 const groups = new Map<string, Claim[]>();83 for (const c of claims) {84 const arr = groups.get(c.property);85 if (arr) arr.push(c);86 else groups.set(c.property, [c]);87 }88 const keys = [...groups.keys()].sort(byCanonical);89 const conflicting = claims.filter((c) => c.status === 'conflicting').length;90 return (91 <div className="space-y-8">92 <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-ink-3">93 <span className="tnum">{fmtInt(claims.length)} claims · {fmtInt(keys.length)} properties</span>94 {conflicting > 0 && <span className="font-medium text-danger">{fmtInt(conflicting)} conflicting</span>}95 {property && (96 <Link href={base} className="link">97 Show all properties98 </Link>99 )}100 </div>101 {keys.map((k) => {102 const rows = groups.get(k) ?? [];103 return (104 <section key={k} id={`h-${k}`} className="scroll-mt-20">105 <h3 className="mb-2 flex flex-wrap items-baseline gap-x-2">106 <Link href={`${base}&property=${encodeURIComponent(k)}`} className="text-sm font-semibold text-ink hover:text-accent" aria-current={property === k ? 'true' : undefined}>107 {propertyLabel(k)}108 </Link>109 <span className="mono text-[11px] text-ink-3">{k}</span>110 <span className="tnum text-xs text-ink-3">{fmtInt(rows.length)}</span>111 {rows.some((c) => c.status === 'conflicting') && <span className="text-xs font-medium text-danger">conflicting claims</span>}112 </h3>113 <ScrollX><DataTable compact caption={`Claim history for ${propertyLabel(k)}`}>114 <thead>115 <tr>116 <Th>Value</Th>117 <Th>Valid from → to</Th>118 <Th>Status</Th>119 <Th>Source</Th>120 <Th>Confidence</Th>121 <Th>Extractor</Th>122 </tr>123 </thead>124 <tbody>125 {rows.map((c) => (126 <tr key={c.id} className={c.status === 'conflicting' ? 'border-l-2 border-danger' : undefined}>127 <Td primary className={c.status === 'conflicting' ? 'pl-2 md:pl-2' : undefined}>128 <span className="tnum">{fmtValue(c.value, c.property)}</span>129 {c.unit && !/parameter_count|context_length|max_output_tokens|memory_gb|file_size_gb/.test(c.property) && <span className="text-xs text-ink-3"> {c.unit}</span>}130 </Td>131 <Td label="Valid" className="tnum text-xs text-ink-2">132 <time dateTime={c.valid_from}>{fmtDateTime(c.valid_from)}</time>133 <span className="text-ink-3"> → </span>134 {c.valid_to ? <time dateTime={c.valid_to}>{fmtDateTime(c.valid_to)}</time> : <span className="font-medium text-positive">current</span>}135 </Td>136 <Td label="Status"><ClaimStatus status={c.status} /></Td>137 <Td label="Source"><SourceCell url={c.source_url} tier={c.tier} name={c.source_name} /></Td>138 <Td label="Confidence" className={cn('text-xs', c.confidence === 'conflicted' && 'text-danger', c.confidence === 'low' && 'text-warning', !['conflicted', 'low'].includes(c.confidence) && 'text-ink-2')}>{c.confidence}</Td>139 <Td label="Extractor" className="mono text-xs text-ink-3">{c.extractor}</Td>140 </tr>141 ))}142 </tbody>143 </DataTable></ScrollX>144 </section>145 );146 })}147 <Note>148 Claims are temporal and append-only: a new observation closes the previous claim (<span className="mono">valid_to</span>) instead of overwriting it. Conflicting claims from different sources are kept side by side and flagged — never averaged. <Link href="/methodology" className="link">Methodology →</Link>149 </Note>150 </div>151 );152}153154/* ------------------------------------------------------------------------------------------------------ panel */155156export function HistoryPanel({ d, asof, asofPayload, claims, property }: { d: EntityDetail; asof?: string; asofPayload: AsOfPayload | null; claims: Claim[] | null; property?: string }) {157 return (158 <div className="space-y-10">159 <section>160 <p className="eyebrow mb-2">As of</p>161 <p className="mb-3 max-w-2xl text-sm text-ink-2">Rewind the record: see this entity's attributes exactly as AI Atlas knew them on a given day.</p>162 <AsOfPicker value={asof} />163 {asof && <div className="mt-4"><AsOfBlock d={d} asof={asof} payload={asofPayload} /></div>}164 </section>165 <section>166 <p className="eyebrow mb-2">Claim history{property ? <> · {propertyLabel(property)}</> : null}</p>167 <ClaimHistory d={d} claims={claims} property={property} />168 </section>169 </div>170 );171}172