SPB Git

spb/ai-atlas

Public
9.6 KB · 171 lines tsx
1import Link from 'next/link';2import { DataTable, Td, Th } from '@/components/ui/data-table';3import { KeyValue, type KVRow } from '@/components/ui/key-value';4import { SourceCell } from '@/components/ui/provenance';5import { Note } from '@/components/ui/section';6import { EmptyState, Unavailable } from '@/components/ui/unavailable';7import { cn } from '@/lib/cn';8import { fmtDate, fmtDateTime, fmtInt, fmtValue } from '@/lib/format';9import { PROSE_KEYS, propertyLabel, routes } from '@/lib/site';10import type { AsOfPayload, Claim, EntityDetail } from '@/lib/types';11import { AsOfPicker } from './asof-picker';1213/* Canonical order for model properties (mirrors SpecTable in blocks.tsx; kept local to avoid touching that file). */14const 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'];15const HIDDEN = new Set(['name', 'slug', 'id', 'entity_type']);16const ORDER = new Map(MODEL_ORDER.map((k, i) => [k, i]));17const byCanonical = (a: string, b: string) => (ORDER.get(a) ?? 999) - (ORDER.get(b) ?? 999) || a.localeCompare(b);1819const STATUS_CLS: Record<string, string> = {20  current: 'text-positive bg-positive-soft',21  superseded: 'text-ink-3 bg-surface-2',22  conflicting: 'text-danger bg-danger-soft',23  retracted: 'text-warning bg-warning-soft',24};25function ClaimStatus({ status }: { status: string }) {26  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>;27}2829/* ------------------------------------------------------------------------------------------------------ as of */3031export function AsOfBlock({ d, asof, payload }: { d: EntityDetail; asof: string; payload: AsOfPayload | null }) {32  const back = `${routes.entity(d)}?tab=history`;33  if (!payload) {34    return (35      <div className="hairline pt-4">36        <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)." />37        <p className="mt-2 text-sm"><Link href={back} className="link">Back to today →</Link></p>38      </div>39    );40  }41  const attrs = payload.attributes ?? {};42  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));43  keys.sort(byCanonical);44  const rows: KVRow[] = keys.map((k) => ({ key: k, raw: attrs[k] }));45  return (46    <div className="hairline pt-4">47      <div className="flex flex-wrap items-center justify-between gap-2 bg-accent-soft px-3 py-2.5 text-sm">48        <p className="text-ink">49          <span className="font-medium">Viewing AI Atlas as of {fmtDate(payload.date)}</span>50          <span className="text-ink-2"> — attributes exactly as the atlas knew them on that day; later corrections are not shown.</span>51        </p>52        <Link href={back} className="link shrink-0 py-2">Back to today →</Link>53      </div>54      {!payload.existed ? (55        <EmptyState title={`${d.name} was not yet in AI Atlas on ${fmtDate(payload.date)}`} className="mt-4">56          First seen {fmtDate(payload.first_seen_at)}. Nothing is inferred backwards: no attribute is shown for dates before the first observation.57        </EmptyState>58      ) : (59        <div className="mt-4">60          <p className="eyebrow mb-2">61            Attributes as of {fmtDate(payload.date)} <span className="tnum text-ink-3">{fmtInt(payload.claims?.length ?? 0)} claims in force</span>62          </p>63          <KeyValue rows={rows} />64        </div>65      )}66    </div>67  );68}6970/* ------------------------------------------------------------------------------------------------------ claim history */7172export function ClaimHistory({ d, claims, property }: { d: EntityDetail; claims: Claim[] | null; property?: string }) {73  const base = `${routes.entity(d)}?tab=history`;74  if (!claims) return <Unavailable what="Claim history" />;75  if (!claims.length)76    return (77      <EmptyState title={property ? `No claims recorded for ${propertyLabel(property)}` : 'No claims recorded yet'}>78        {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.'}79      </EmptyState>80    );81  const groups = new Map<string, Claim[]>();82  for (const c of claims) {83    const arr = groups.get(c.property);84    if (arr) arr.push(c);85    else groups.set(c.property, [c]);86  }87  const keys = [...groups.keys()].sort(byCanonical);88  const conflicting = claims.filter((c) => c.status === 'conflicting').length;89  return (90    <div className="space-y-8">91      <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-ink-3">92        <span className="tnum">{fmtInt(claims.length)} claims · {fmtInt(keys.length)} properties</span>93        {conflicting > 0 && <span className="font-medium text-danger">{fmtInt(conflicting)} conflicting</span>}94        {property && (95          <Link href={base} className="link">96            Show all properties97          </Link>98        )}99      </div>100      {keys.map((k) => {101        const rows = groups.get(k) ?? [];102        return (103          <section key={k} id={`h-${k}`} className="scroll-mt-20">104            <h3 className="mb-2 flex flex-wrap items-baseline gap-x-2">105              <Link href={`${base}&property=${encodeURIComponent(k)}`} className="text-sm font-semibold text-ink hover:text-accent" aria-current={property === k ? 'true' : undefined}>106                {propertyLabel(k)}107              </Link>108              <span className="mono text-[11px] text-ink-3">{k}</span>109              <span className="tnum text-xs text-ink-3">{fmtInt(rows.length)}</span>110              {rows.some((c) => c.status === 'conflicting') && <span className="text-xs font-medium text-danger">conflicting claims</span>}111            </h3>112            <DataTable compact caption={`Claim history for ${propertyLabel(k)}`}>113              <thead>114                <tr>115                  <Th>Value</Th>116                  <Th>Valid from → to</Th>117                  <Th>Status</Th>118                  <Th>Source</Th>119                  <Th>Confidence</Th>120                  <Th>Extractor</Th>121                </tr>122              </thead>123              <tbody>124                {rows.map((c) => (125                  <tr key={c.id} className={c.status === 'conflicting' ? 'border-l-2 border-danger' : undefined}>126                    <Td primary className={c.status === 'conflicting' ? 'pl-2 md:pl-2' : undefined}>127                      <span className="tnum">{fmtValue(c.value, c.property)}</span>128                      {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>}129                    </Td>130                    <Td label="Valid" className="tnum text-xs text-ink-2">131                      <time dateTime={c.valid_from}>{fmtDateTime(c.valid_from)}</time>132                      <span className="text-ink-3"> → </span>133                      {c.valid_to ? <time dateTime={c.valid_to}>{fmtDateTime(c.valid_to)}</time> : <span className="font-medium text-positive">current</span>}134                    </Td>135                    <Td label="Status"><ClaimStatus status={c.status} /></Td>136                    <Td label="Source"><SourceCell url={c.source_url} tier={c.tier} name={c.source_name} /></Td>137                    <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>138                    <Td label="Extractor" className="mono text-xs text-ink-3">{c.extractor}</Td>139                  </tr>140                ))}141              </tbody>142            </DataTable>143          </section>144        );145      })}146      <Note>147        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>148      </Note>149    </div>150  );151}152153/* ------------------------------------------------------------------------------------------------------ panel */154155export function HistoryPanel({ d, asof, asofPayload, claims, property }: { d: EntityDetail; asof?: string; asofPayload: AsOfPayload | null; claims: Claim[] | null; property?: string }) {156  return (157    <div className="space-y-10">158      <section>159        <p className="eyebrow mb-2">As of</p>160        <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>161        <AsOfPicker value={asof} />162        {asof && <div className="mt-4"><AsOfBlock d={d} asof={asof} payload={asofPayload} /></div>}163      </section>164      <section>165        <p className="eyebrow mb-2">Claim history{property ? <> · {propertyLabel(property)}</> : null}</p>166        <ClaimHistory d={d} claims={claims} property={property} />167      </section>168    </div>169  );170}171
Files · 9f1db171c786cf4a3b99436329f29b145c02aeec
Menu

Theme