SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
9.7 KB · 172 lines tsx
Raw Blame History
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