SPB Git

spb/ai-atlas

Public
27.6 KB · 504 lines tsx
1import { ExternalLink, GitFork, MemoryStick } from 'lucide-react';2import Link from 'next/link';3import { CompareButton } from '@/components/compare/compare-button';4import { ViewBeacon } from '@/components/layout/view-beacon';5import { EntityBadge, OpennessBadge, StatusBadge } from '@/components/ui/badges';6import { EntityLink, QualityMark } from '@/components/ui/entity';7import { Container, Note } from '@/components/ui/section';8import { TabPanel, Tabs, type TabDef } from '@/components/ui/tabs';9import { api, safe } from '@/lib/api';10import { fmtAgo, fmtDate, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format';11import { routes, SITE_NAME, SITE_URL, typeLabel } from '@/lib/site';12import type { EntityDetail, EntitySummary } from '@/lib/types';13import { Capabilities, EntityList, HardwareFitTable, Identity, LineageBlock, ModelsTable, PriceHistory, PricesTable, ProvenanceSummary, RelationsBlock, ResultsTable, SourcesTable, SpecTable, TimelineList } from './blocks';14import { HistoryPanel } from './history';1516/** Search params an entity page understands (History tab). */17export type EntityPageParams = { asof?: string; property?: string };18const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;1920/* ---------------------------------------------------------------------------------------------------------- header chips */2122function headerChips(d: EntityDetail): { label: string; value: string; key: string }[] {23  const a = d.attributes ?? {};24  const out: { label: string; value: string; key: string }[] = [];25  const add = (key: string, label: string, value: string | null) => value && out.push({ key, label, value });26  switch (d.entity_type) {27    case 'model':28    case 'quantization': {29      const p = num(a.parameter_count);30      const ap = num(a.active_parameter_count);31      add('parameter_count', 'Parameters', p === null ? null : ap !== null && ap !== p ? `${fmtParams(p)} · ${fmtParams(ap)} active` : fmtParams(p));32      add('context_length', 'Context', num(a.context_length) === null ? null : `${fmtTokens(a.context_length)} tokens`);33      add('release_date', 'Released', typeof a.release_date === 'string' ? fmtDate(a.release_date) : null);34      add('license', 'License', typeof a.license === 'string' ? a.license : null);35      add('knowledge_cutoff', 'Knowledge cutoff', typeof a.knowledge_cutoff === 'string' ? fmtDate(a.knowledge_cutoff) : null);36      break;37    }38    case 'company':39    case 'organization':40    case 'lab':41    case 'university':42      add('country', 'Country', typeof a.country === 'string' ? a.country : null);43      add('founded', 'Founded', a.founded ? String(a.founded).slice(0, 4) : null);44      add('headquarters', 'HQ', typeof a.headquarters === 'string' ? a.headquarters : null);45      add('org_kind', 'Kind', typeof a.org_kind === 'string' ? a.org_kind : null);46      add('employee_count', 'Employees', num(a.employee_count) === null ? null : fmtInt(a.employee_count));47      break;48    case 'paper':49      add('published_at', 'Published', typeof a.published_at === 'string' ? fmtDate(a.published_at) : null);50      add('venue', 'Venue', typeof a.venue === 'string' ? a.venue : null);51      add('arxiv_id', 'arXiv', typeof a.arxiv_id === 'string' ? a.arxiv_id : null);52      add('primary_category', 'Category', typeof a.primary_category === 'string' ? a.primary_category : null);53      break;54    case 'hardware':55      add('kind', 'Kind', typeof a.kind === 'string' ? a.kind : null);56      add('memory_gb', 'Memory', num(a.memory_gb) === null ? null : `${fmtInt(a.memory_gb)} GB ${typeof a.memory_type === 'string' ? a.memory_type : ''}`.trim());57      add('memory_bandwidth_gbs', 'Bandwidth', num(a.memory_bandwidth_gbs) === null ? null : `${fmtInt(a.memory_bandwidth_gbs)} GB/s`);58      add('tdp_watts', 'TDP', num(a.tdp_watts) === null ? null : `${fmtInt(a.tdp_watts)} W`);59      add('release_date', 'Released', typeof a.release_date === 'string' ? fmtDate(a.release_date) : null);60      break;61    case 'benchmark':62      add('category', 'Category', typeof a.category === 'string' ? a.category : null);63      add('task', 'Task', typeof a.task === 'string' ? a.task : null);64      add('metric', 'Metric', typeof a.metric === 'string' ? a.metric : null);65      add('creator', 'Creator', typeof a.creator === 'string' ? a.creator : null);66      break;67    case 'framework':68    case 'library':69    case 'runtime':70    case 'repository':71      add('latest_version', 'Version', typeof a.latest_version === 'string' ? a.latest_version : null);72      add('latest_release_at', 'Released', typeof a.latest_release_at === 'string' ? fmtDate(a.latest_release_at) : null);73      add('language', 'Language', typeof a.language === 'string' ? a.language : null);74      add('license', 'License', typeof a.license === 'string' ? a.license : null);75      add('metric.stars', 'Stars', num(a['metric.stars']) === null ? null : fmtInt(a['metric.stars']));76      break;77    case 'dataset':78      add('modality', 'Modality', typeof a.modality === 'string' ? a.modality : null);79      add('size', 'Size', a.size ? String(a.size) : null);80      add('license', 'License', typeof a.license === 'string' ? a.license : null);81      add('publisher', 'Publisher', typeof a.publisher === 'string' ? a.publisher : null);82      break;83    case 'provider':84      add('regions', 'Regions', Array.isArray(a.regions) && a.regions.length ? `${a.regions.length}` : null);85      break;86    default:87      break;88  }89  return out.slice(0, 5);90}9192function primaryUrl(d: EntityDetail): string | null {93  const a = d.attributes ?? {};94  for (const k of ['official_url', 'website', 'model_card_url', 'pdf_url', 'repository_url', 'spec_url', 'docs_url']) {95    const v = a[k];96    if (typeof v === 'string' && /^https?:\/\//.test(v)) return v;97  }98  return null;99}100101/* ---------------------------------------------------------------------------------------------------------- tabs per type */102103type TabKey = 'overview' | 'capabilities' | 'benchmarks' | 'providers' | 'hardware' | 'lineage' | 'research' | 'models' | 'repositories' | 'leaderboard' | 'runnable' | 'relations' | 'history' | 'timeline' | 'sources';104105function tabsFor(d: EntityDetail, claimCount?: number | null): TabDef[] {106  const t = d.entity_type;107  const c = (n: number | undefined | null) => (n ? n : undefined);108  const rel = d.relations?.reduce((n, g) => n + g.items.length, 0) ?? 0;109  const sources = d.sources?.length ?? 0;110  const timeline = d.timeline?.length ?? 0;111  const tail: TabDef[] = [112    { id: 'history', label: 'History', count: c(claimCount ?? d.counts?.claims) },113    { id: 'timeline', label: 'Timeline', count: c(timeline) },114    { id: 'sources', label: 'Sources', count: c(sources) },115  ];116  if (t === 'model' || t === 'quantization')117    return [118      { id: 'overview', label: 'Overview' },119      { id: 'capabilities', label: 'Capabilities' },120      { id: 'benchmarks', label: 'Benchmarks', count: c(d.results?.length) },121      { id: 'providers', label: 'Providers & Pricing', count: c(d.prices?.length) },122      { id: 'hardware', label: 'Hardware', count: c(d.hardware_fit?.length) },123      { id: 'lineage', label: 'Lineage', count: c((d.lineage?.ancestors.length ?? 0) + (d.lineage?.descendants.length ?? 0) + (d.lineage?.quantizations.length ?? 0)) },124      { id: 'research', label: 'Research', count: c((d.papers?.length ?? 0) + (d.repositories?.length ?? 0)) },125      ...tail,126    ];127  if (['company', 'organization', 'lab', 'university'].includes(t))128    return [129      { id: 'overview', label: 'Overview' },130      { id: 'models', label: 'Models', count: c(d.models?.total ?? d.models?.items.length) },131      { id: 'research', label: 'Research', count: c(d.papers?.length) },132      { id: 'providers', label: 'Providers', count: c(d.relations?.filter((g) => g.predicate === 'available_through' || g.predicate === 'operates').reduce((n, g) => n + g.items.length, 0)) },133      { id: 'repositories', label: 'Repositories', count: c(d.repositories?.length) },134      ...tail,135    ];136  if (t === 'provider')137    return [138      { id: 'overview', label: 'Overview' },139      { id: 'providers', label: 'Models & Pricing', count: c(d.prices?.length ?? d.models?.total) },140      { id: 'relations', label: 'Relations', count: c(rel) },141      ...tail,142    ];143  if (t === 'benchmark')144    return [145      { id: 'overview', label: 'Overview' },146      { id: 'leaderboard', label: 'Leaderboard', count: c(d.results?.length) },147      { id: 'relations', label: 'Relations', count: c(rel) },148      ...tail,149    ];150  if (t === 'hardware')151    return [152      { id: 'overview', label: 'Overview' },153      { id: 'runnable', label: 'Runnable models', count: c(d.models?.total ?? d.models?.items.length) },154      { id: 'relations', label: 'Relations', count: c(rel) },155      ...tail,156    ];157  if (t === 'paper')158    return [159      { id: 'overview', label: 'Overview' },160      { id: 'models', label: 'Related models', count: c(d.relations?.filter((g) => g.items.some((i) => i.entity_type === 'model')).reduce((n, g) => n + g.items.filter((i) => i.entity_type === 'model').length, 0)) },161      { id: 'relations', label: 'Relations', count: c(rel) },162      ...tail,163    ];164  return [{ id: 'overview', label: 'Overview' }, { id: 'relations', label: 'Relations', count: c(rel) }, ...tail];165}166167/* ---------------------------------------------------------------------------------------------------------- JSON-LD */168169function jsonLd(d: EntityDetail, canonical: string) {170  const a = d.attributes ?? {};171  const url = `${SITE_URL}${canonical}`;172  const org = d.organization ? { '@type': 'Organization', name: d.organization.name, url: `${SITE_URL}${routes.entity({ entity_type: 'company', slug: d.organization.slug })}` } : undefined;173  const base: Record<string, unknown> = { '@context': 'https://schema.org', name: d.name, url, description: d.description ?? undefined, alternateName: d.aliases?.length ? d.aliases : undefined, identifier: d.identifiers?.map((i) => ({ '@type': 'PropertyValue', propertyID: i.scheme, value: i.value })) };174  switch (d.entity_type) {175    case 'model':176    case 'quantization':177      return { ...base, '@type': ['SoftwareApplication', 'Product'], applicationCategory: 'AI model', creator: org, manufacturer: org, datePublished: typeof a.release_date === 'string' ? a.release_date : undefined, license: typeof a.license === 'string' ? a.license : undefined, offers: d.prices?.length ? d.prices.slice(0, 8).map((p) => ({ '@type': 'Offer', seller: { '@type': 'Organization', name: p.provider.name }, price: num(p.input_per_mtok) ?? undefined, priceCurrency: p.currency || 'USD', description: 'Input price per 1M tokens' })) : undefined };178    case 'company':179    case 'organization':180    case 'lab':181    case 'university':182      return { ...base, '@type': 'Organization', foundingDate: a.founded ? String(a.founded) : undefined, sameAs: typeof a.website === 'string' ? [a.website] : undefined, location: typeof a.headquarters === 'string' ? a.headquarters : undefined };183    case 'paper':184      return { ...base, '@type': 'ScholarlyArticle', headline: d.name, datePublished: typeof a.published_at === 'string' ? a.published_at : undefined, author: Array.isArray(a.authors) ? (a.authors as unknown[]).slice(0, 30).map((n) => ({ '@type': 'Person', name: String(n) })) : undefined, sameAs: [a.pdf_url, a.arxiv_id ? `https://arxiv.org/abs/${a.arxiv_id}` : null].filter(Boolean), abstract: typeof a.abstract === 'string' ? a.abstract : undefined };185    case 'hardware':186      return { ...base, '@type': 'Product', manufacturer: typeof a.manufacturer === 'string' ? { '@type': 'Organization', name: a.manufacturer } : org, category: typeof a.kind === 'string' ? a.kind : undefined };187    case 'framework':188    case 'library':189    case 'runtime':190    case 'repository':191    case 'tool':192      return { ...base, '@type': 'SoftwareSourceCode', codeRepository: typeof a.repository_url === 'string' ? a.repository_url : undefined, programmingLanguage: typeof a.language === 'string' ? a.language : undefined, license: typeof a.license === 'string' ? a.license : undefined, author: org };193    case 'dataset':194      return { ...base, '@type': 'Dataset', license: typeof a.license === 'string' ? a.license : undefined, creator: org };195    default:196      return { ...base, '@type': 'Thing' };197  }198}199200/* ---------------------------------------------------------------------------------------------------------- page */201202/**203 * Shared entity page for every type. Header (badges, name, org, description, key chips) + URL-driven tabs whose set204 * depends on the type. Panels are all server-rendered (SEO); the client Tabs only toggles visibility.205 */206export async function EntityPage({ d, canonical, related, asof: asofRaw, historyProperty }: { d: EntityDetail; canonical: string; related?: EntitySummary[] | null; asof?: string; historyProperty?: string }) {207  const a = d.attributes ?? {};208  const chips = headerChips(d);209  const openness = typeof a.openness === 'string' ? a.openness : null;210  const link = primaryUrl(d);211  // History tab data: full claim history always (it is the tab's content); the "as of" state only when requested.212  const asof = asofRaw && ISO_DAY.test(asofRaw) ? asofRaw : asofRaw ? 'invalid' : undefined;213  const property = historyProperty?.trim() || undefined;214  const [history, asofPayload] = await Promise.all([safe(api.entityHistory(d.slug, property)), asof && asof !== 'invalid' ? safe(api.entityAsOf(d.slug, asof)) : Promise.resolve(null)]);215  const claims = history?.items ?? null;216  const tabs = tabsFor(d, property ? null : claims?.length);217  const memoryGb = d.entity_type === 'hardware' ? num(a.memory_gb) : null;218  const isModel = d.entity_type === 'model' || d.entity_type === 'quantization';219  const isCompany = ['company', 'organization', 'lab', 'university'].includes(d.entity_type);220  const ld = jsonLd(d, canonical);221  const prose = typeof a.abstract === 'string' ? a.abstract : null;222  const modelRelations = d.relations?.filter((g) => g.items.some((i) => i.entity_type === 'model')).flatMap((g) => g.items.filter((i) => i.entity_type === 'model')) ?? [];223224  return (225    <Container wide>226      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />227      <ViewBeacon path={canonical} />228229      <nav aria-label="Breadcrumb" className="pt-5 text-xs text-ink-3">230        <ol className="flex flex-wrap items-center gap-1.5">231          <li><Link href="/" className="hover:text-ink">AI Atlas</Link></li>232          <li aria-hidden>/</li>233          <li><Link href={routes.listing(d.entity_type)} className="hover:text-ink">{typeLabel(d.entity_type, true)}</Link></li>234          {d.organization && isModel && (235            <>236              <li aria-hidden>/</li>237              <li><Link href={routes.entity({ entity_type: 'company', slug: d.organization.slug })} className="hover:text-ink">{d.organization.name}</Link></li>238            </>239          )}240          <li aria-hidden>/</li>241          <li className="text-ink-2">{d.name}</li>242        </ol>243      </nav>244245      <header className="pb-6 pt-4 md:pb-8 md:pt-5">246        <div className="flex flex-wrap items-center gap-2">247          <EntityBadge type={d.entity_type} />248          <StatusBadge status={d.status} />249          {openness && <OpennessBadge openness={openness} />}250          {typeof a.family === 'string' && <span className="text-xs text-ink-3">family · {a.family}</span>}251        </div>252        <div className="mt-3 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">253          <div className="min-w-0">254            <h1 className="display text-[30px] md:text-[44px]">{d.name}</h1>255            <p className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[15px] text-ink-2">256              {d.organization && (257                <Link href={routes.entity({ entity_type: 'company', slug: d.organization.slug })} className="font-medium text-ink hover:text-accent">258                  {d.organization.name}259                </Link>260              )}261              {link && (262                <a href={link} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-ink-3 hover:text-accent">263                  {link.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '').slice(0, 48)} <ExternalLink className="size-3.5" aria-hidden />264                </a>265              )}266            </p>267            {d.description && <p className="mt-3 max-w-3xl text-[15px] leading-relaxed text-ink-2">{d.description}</p>}268            <div className="mt-4 flex flex-wrap items-center gap-2" aria-label="Actions">269              <CompareButton e={d} />270              <Link href={routes.graph(d.slug)} className="inline-flex h-9 items-center gap-1.5 border border-rule px-2.5 text-sm text-ink-2 hover:border-rule-strong hover:text-ink">271                <GitFork className="size-3.5" aria-hidden /> Explore graph272              </Link>273              {memoryGb !== null && (274                <Link href={routes.hardwareFit({ memory_gb: memoryGb, quant: '4bit', context: 8192 })} className="inline-flex h-9 items-center gap-1.5 border border-rule px-2.5 text-sm text-ink-2 hover:border-rule-strong hover:text-ink">275                  <MemoryStick className="size-3.5" aria-hidden /> What can this run?276                </Link>277              )}278            </div>279          </div>280          <div className="shrink-0 text-xs text-ink-3 lg:text-right">281            <QualityMark q={d.quality?.score} label />282            <p className="mt-1" title={d.updated_at}>Updated {fmtAgo(d.updated_at)} · first seen {fmtDate(d.first_seen_at)}</p>283            <p className="mono mt-0.5 text-[11px]">{d.id}</p>284          </div>285        </div>286        {chips.length > 0 && (287          <dl className="mt-5 grid grid-cols-2 gap-x-6 gap-y-3 border-y border-rule py-3 sm:grid-cols-3 lg:grid-cols-5">288            {chips.map((c) => {289              const p = d.provenance?.[c.key];290              return (291                <div key={c.key} className="min-w-0">292                  <dt className="eyebrow">{c.label}</dt>293                  <dd className="tnum mt-0.5 truncate text-[15px] font-medium text-ink" title={c.value}>{c.value}</dd>294                  {p && <dd className="truncate text-[11px] text-ink-3" title={`${p.source_name ?? p.url ?? ''} · tier ${p.tier} · observed ${fmtAgo(p.observed_at)}`}>T{p.tier} · {fmtAgo(p.observed_at)}</dd>}295                </div>296              );297            })}298          </dl>299        )}300      </header>301302      <Tabs tabs={tabs} className="pb-10">303        {/* ------------------------------------------------------------------------------------------ Overview */}304        <TabPanel id="overview">305          <div className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_22rem]">306            <div className="min-w-0 space-y-8">307              {prose && (308                <section>309                  <p className="eyebrow mb-2">Abstract</p>310                  <p className="prose-atlas max-w-3xl text-[15px] leading-relaxed text-ink-2">{prose}</p>311                </section>312              )}313              {d.entity_type === 'paper' && Array.isArray(a.authors) && (a.authors as unknown[]).length > 0 && (314                <section>315                  <p className="eyebrow mb-2">Authors <span className="tnum text-ink-3">{(a.authors as unknown[]).length}</span></p>316                  <p className="text-sm text-ink-2">{(a.authors as unknown[]).map(String).join(', ')}</p>317                </section>318              )}319              <section>320                <p className="eyebrow mb-2">Specification</p>321                <SpecTable d={d} exclude={isModel ? [...['tool_calling', 'structured_output', 'reasoning', 'vision', 'audio', 'fine_tuning_available'], 'authors'] : ['authors']} />322                <Note className="mt-3">323                  Each value shows its source, tier and observation time. Conflicting claims are kept side by side and flagged — never averaged. <Link href="/methodology" className="link">How AI Atlas records facts →</Link>324                </Note>325              </section>326              <section>327                <p className="eyebrow mb-2">Provenance</p>328                <ProvenanceSummary provenance={d.provenance} quality={d.quality} />329              </section>330            </div>331            <aside className="min-w-0 space-y-8">332              <section>333                <p className="eyebrow mb-2">Relations</p>334                <RelationsBlock relations={d.relations ?? []} />335              </section>336              <section>337                <p className="eyebrow mb-2">Identity</p>338                <Identity d={d} />339                <p className="mono mt-2 break-all text-[11px] text-ink-3">slug {d.slug}</p>340              </section>341              {related && related.length > 0 && (342                <section>343                  <p className="eyebrow mb-2">Related</p>344                  <ul className="divide-y divide-rule border-y border-rule">345                    {related.slice(0, 8).map((r) => (346                      <li key={r.id} className="flex items-center gap-2 py-2 text-sm">347                        <EntityBadge type={r.entity_type} small />348                        <EntityLink e={r} className="truncate" />349                        {r.organization && <span className="ml-auto shrink-0 text-xs text-ink-3">{r.organization.name}</span>}350                      </li>351                    ))}352                  </ul>353                </section>354              )}355              <section>356                <p className="eyebrow mb-2">Compare</p>357                <div className="flex flex-wrap items-center gap-3 text-sm">358                  <CompareButton e={d} />359                  <Link href={routes.compare([d.slug])} className="link py-2">Open compare →</Link>360                </div>361              </section>362            </aside>363          </div>364        </TabPanel>365366        {/* ------------------------------------------------------------------------------------------ Model tabs */}367        {isModel && (368          <TabPanel id="capabilities">369            <Capabilities d={d} />370          </TabPanel>371        )}372        {isModel && (373          <TabPanel id="benchmarks">374            <ResultsTable results={d.results ?? []} perspective="model" />375          </TabPanel>376        )}377        {(isModel || d.entity_type === 'provider') && (378          <TabPanel id="providers">379            <div className="space-y-8">380              {d.entity_type === 'provider' && d.models && d.models.items.length > 0 && !(d.prices?.length) && (381                <section>382                  <p className="eyebrow mb-2">Models served</p>383                  <ModelsTable items={d.models.items} total={d.models.total} moreHref={`/models?provider=${encodeURIComponent(d.slug)}`} />384                </section>385              )}386              <section>387                <p className="eyebrow mb-2">Current prices</p>388                <PricesTable prices={d.prices ?? []} perspective={isModel ? 'model' : 'provider'} />389              </section>390              {isModel && d.providers && d.providers.length > 0 && (391                <section>392                  <p className="eyebrow mb-2">Available through <span className="tnum text-ink-3">{d.providers.length}</span></p>393                  <p className="flex flex-wrap gap-x-3 gap-y-1 text-sm">394                    {d.providers.map((p) => (395                      <EntityLink key={p.id} e={p} />396                    ))}397                  </p>398                </section>399              )}400              <section>401                <p className="eyebrow mb-2">Price history</p>402                <PriceHistory history={d.price_history ?? d.prices ?? []} perspective={isModel ? 'model' : 'provider'} />403              </section>404            </div>405          </TabPanel>406        )}407        {isModel && (408          <TabPanel id="hardware">409            <HardwareFitTable rows={d.hardware_fit ?? []} />410          </TabPanel>411        )}412        {isModel && (413          <TabPanel id="lineage">414            <LineageBlock d={d} />415          </TabPanel>416        )}417        {(isModel || isCompany) && (418          <TabPanel id="research">419            <div className="grid gap-10 lg:grid-cols-2">420              <section>421                <p className="eyebrow mb-2">Papers <span className="tnum text-ink-3">{d.papers?.length ?? 0}</span></p>422                <EntityList items={d.papers ?? []} empty="No papers linked yet." />423              </section>424              <section>425                <p className="eyebrow mb-2">Repositories <span className="tnum text-ink-3">{d.repositories?.length ?? 0}</span></p>426                <EntityList items={d.repositories ?? []} empty="No repositories linked yet." />427              </section>428            </div>429          </TabPanel>430        )}431432        {/* ------------------------------------------------------------------------------------------ Company tabs */}433        {isCompany && (434          <TabPanel id="models">435            <ModelsTable items={d.models?.items ?? []} total={d.models?.total} moreHref={`/models?org=${encodeURIComponent(d.slug)}`} />436          </TabPanel>437        )}438        {isCompany && (439          <TabPanel id="providers">440            <RelationsBlock relations={(d.relations ?? []).filter((g) => ['available_through', 'operates', 'owns'].includes(g.predicate))} />441          </TabPanel>442        )}443        {isCompany && (444          <TabPanel id="repositories">445            <EntityList items={d.repositories ?? []} empty="No repositories linked yet." />446          </TabPanel>447        )}448449        {/* ------------------------------------------------------------------------------------------ Other types */}450        {d.entity_type === 'benchmark' && (451          <TabPanel id="leaderboard">452            <ResultsTable results={d.results ?? []} perspective="benchmark" />453          </TabPanel>454        )}455        {d.entity_type === 'hardware' && (456          <TabPanel id="runnable">457            <div className="mb-3 flex flex-wrap items-center gap-2">458              <span className="inline-flex items-center border border-dashed border-warning/60 px-1.5 text-[11px] font-medium uppercase tracking-wide text-warning">Estimated</span>459              <Note>Models whose estimated memory footprint fits this device — see <Link href="/methodology#estimates" className="link">estimate method</Link>.</Note>460            </div>461            <ModelsTable items={d.models?.items ?? []} total={d.models?.total} />462          </TabPanel>463        )}464        {d.entity_type === 'paper' && (465          <TabPanel id="models">466            <EntityList items={modelRelations} empty="No models linked to this paper yet." />467          </TabPanel>468        )}469        {!isModel && !isCompany && (470          <TabPanel id="relations">471            <RelationsBlock relations={d.relations ?? []} />472          </TabPanel>473        )}474475        <TabPanel id="history">476          <HistoryPanel d={d} asof={asof === 'invalid' ? asofRaw : asof} asofPayload={asof === 'invalid' ? null : asofPayload} claims={claims} property={property} />477        </TabPanel>478        <TabPanel id="timeline">479          <TimelineList events={d.timeline ?? []} slug={d.slug} />480        </TabPanel>481        <TabPanel id="sources">482          <SourcesTable sources={d.sources ?? []} />483        </TabPanel>484      </Tabs>485    </Container>486  );487}488489export function describeEntity(d: EntityDetail): string {490  const a = d.attributes ?? {};491  const bits: string[] = [];492  const t = typeLabel(d.entity_type).toLowerCase();493  bits.push(`${d.name} is a${/^[aeiou]/.test(t) ? 'n' : ''} ${t}${d.organization ? ` by ${d.organization.name}` : ''}`);494  if (d.entity_type === 'model') {495    if (num(a.parameter_count) !== null) bits.push(`with ${fmtParams(a.parameter_count)} parameters`);496    if (num(a.context_length) !== null) bits.push(`and a ${fmtTokens(a.context_length)}-token context window`);497    if (typeof a.release_date === 'string') bits.push(`released ${fmtDate(a.release_date)}`);498  }499  let s = bits.join(' ') + '.';500  if (d.description) s = `${d.description.slice(0, 160)}${d.description.length > 160 ? '…' : ''} ${s}`;501  s += ` Specifications, prices, benchmarks, lineage, timeline and sources on ${SITE_NAME}.`;502  return s.slice(0, 300);503}504
Files · 9f1db171c786cf4a3b99436329f29b145c02aeec
Menu

Theme