HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import { ExternalLink, GitFork } from 'lucide-react';2import Link from 'next/link';3import { CompareButton } from '@/components/compare/compare-button';4import { CompareTrayBar } from '@/components/compare/compare-tray-bar';5import { Evidence } from '@/components/evidence/evidence';6import { SectionNav } from '@/components/layout/terminal';7import { ViewBeacon } from '@/components/layout/view-beacon';8import { IdentityBadge, OpennessChip } from '@/components/models/badges';9import { LineageTree } from '@/components/models/lineage-tree';10import { OpennessBlock } from '@/components/models/openness-block';11import { PriceHistoryChart } from '@/components/models/price-history';12import { ScrollToSection } from '@/components/models/scroll-to-section';13import { identityStrip } from '@/components/models/shared';14import { EntityBadge, StatusBadge } from '@/components/ui/badges';15import { EntityLink, QualityMark } from '@/components/ui/entity';16import { KeyValue, type KVRow } from '@/components/ui/key-value';17import { Container, Note } from '@/components/ui/section';18import { WatchButton } from '@/components/watchlist/watch-button';19import { api, apiD1, safe } from '@/lib/api';20import { fmtAgo, fmtDate, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format';21import { routes, SITE_NAME, SITE_URL } from '@/lib/site';22import type { EntitySummary, ModelDetail } from '@/lib/types';23import { AsOfPicker } from './asof-picker';24import { Capabilities, EntityList, PricesTable, ProvenanceSummary, RelationsBlock, SourcesTable, TimelineList } from './blocks';25import { AsOfBlock, ClaimHistory } from './history';26import { ArtifactsBlock, ComparabilityLegend, DeploymentsTable, HardwareFitBlock, IdentityPanel, ModelBenchmarksBlock, VersionHistoryBlock } from './model-blocks';2728/*29 Model page 3.0: sticky header with identity strip → SectionNav → sections in a fixed order (only those with data render):30 Overview · Architecture · Capabilities · Benchmarks · Providers & Pricing · Price history · Hardware fit · Lineage ·31 Versions & Artifacts · Repositories · Papers · Datasets · Timeline · Change history · Provenance. Every value opens the evidence drawer.32*/3334export type ModelPageParams = { asof?: string; property?: string; tab?: string };35const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;36const ARCH_KEYS = ['architecture', 'model_type', 'parameter_count', 'active_parameter_count', 'is_moe', 'num_experts', 'num_layers', 'hidden_size', 'tokenizer', 'vocab_size', 'weights_dtype', 'file_size_gb', 'library_name', 'pipeline_tag', 'hf_repo', 'training_tokens', 'training_data_cutoff'];37const OVERVIEW_KEYS = ['release_date', 'status', 'version', 'knowledge_cutoff', 'deprecation_date', 'retirement_date', 'official_url', 'model_card_url', 'paper_url', 'repository_url', 'api_model_id', 'openrouter_id'];3839function sectionTitle(id: string): string {40 return SECTIONS.find((s) => s.id === id)?.label ?? id;41}42const SECTIONS = [43 { id: 'overview', label: 'Overview' },44 { id: 'architecture', label: 'Architecture' },45 { id: 'capabilities', label: 'Capabilities' },46 { id: 'benchmarks', label: 'Benchmarks' },47 { id: 'providers-pricing', label: 'Providers & Pricing' },48 { id: 'price-history', label: 'Price history' },49 { id: 'hardware-fit', label: 'Hardware fit' },50 { id: 'lineage', label: 'Lineage' },51 { id: 'versions-artifacts', label: 'Versions & Artifacts' },52 { id: 'repositories', label: 'Repositories' },53 { id: 'papers', label: 'Papers' },54 { id: 'datasets', label: 'Datasets' },55 { id: 'timeline', label: 'Timeline' },56 { id: 'change-history', label: 'Change history' },57 { id: 'provenance', label: 'Provenance' },58];5960function Sec({ id, children, count, lede, action }: { id: string; children: React.ReactNode; count?: number | null; lede?: React.ReactNode; action?: { href: string; label: string } }) {61 return (62 <section id={id} className="scroll-mt-[calc(var(--header-h)+3rem)] border-t border-rule py-6 md:py-8" data-section={id}>63 <div className="mb-3 flex flex-wrap items-end justify-between gap-2">64 <h2 className="text-base font-semibold tracking-tight text-ink md:text-lg">65 {sectionTitle(id)}66 {count !== undefined && count !== null && <span className="tnum ml-2 text-sm font-normal text-ink-3">{fmtInt(count)}</span>}67 </h2>68 {action && (69 <Link href={action.href} className="link text-sm">70 {action.label} →71 </Link>72 )}73 </div>74 {lede && <div className="mb-3 max-w-3xl text-sm text-ink-2">{lede}</div>}75 {children}76 </section>77 );78}7980function jsonLd(d: ModelDetail, canonical: string) {81 const a = d.attributes ?? {};82 const org = d.organization ? { '@type': 'Organization', name: d.organization.name, url: `${SITE_URL}${routes.entity({ entity_type: 'company', slug: d.organization.slug })}` } : undefined;83 const deployments = d.deployments ?? [];84 return {85 '@context': 'https://schema.org',86 '@type': ['SoftwareApplication', 'Product'],87 name: d.name,88 url: `${SITE_URL}${canonical}`,89 description: d.description ?? undefined,90 applicationCategory: 'AI model',91 alternateName: d.aliases?.length ? d.aliases : undefined,92 identifier: d.identifiers?.map((i) => ({ '@type': 'PropertyValue', propertyID: i.scheme, value: i.value })),93 creator: org,94 manufacturer: org,95 datePublished: typeof a.release_date === 'string' ? a.release_date : undefined,96 license: d.licence && 'key' in d.licence && d.licence.key ? (d.licence.url ?? d.licence.key) : typeof a.license === 'string' ? a.license : undefined,97 isPartOf: d.family && 'slug' in d.family && d.family.slug ? { '@type': 'CreativeWorkSeries', name: d.family.name, url: `${SITE_URL}${routes.family(d.family.slug)}` } : undefined,98 additionalProperty: [99 num(a.parameter_count) !== null ? { '@type': 'PropertyValue', name: 'parameter_count', value: num(a.parameter_count) } : null,100 num(a.context_length) !== null ? { '@type': 'PropertyValue', name: 'context_length', value: num(a.context_length), unitText: 'tokens' } : null,101 typeof a.openness === 'string' ? { '@type': 'PropertyValue', name: 'openness', value: a.openness } : null,102 ].filter(Boolean),103 offers: deployments.length104 ? deployments.slice(0, 8).map((p) => ({ '@type': 'Offer', seller: { '@type': 'Organization', name: p.provider.name }, price: num(p.prices.output) ?? undefined, priceCurrency: p.prices.currency || 'USD', description: 'Output price per 1M tokens', availability: p.status === 'active' ? 'https://schema.org/InStock' : 'https://schema.org/Discontinued' }))105 : undefined,106 };107}108109/** SEO description: "Qwen3.6 35B A3B by Qwen: 35B parameters (3B active), 262K context, open weights (Apache-2.0), released 14 May 2026. …" */110export function describeModel(d: ModelDetail): string {111 const a = d.attributes ?? {};112 const bits: string[] = [];113 const p = num(a.parameter_count);114 const ap = num(a.active_parameter_count);115 if (p !== null) bits.push(`${fmtParams(p)} parameters${ap !== null && ap !== p ? ` (${fmtParams(ap)} active)` : ''}`);116 if (num(a.context_length) !== null) bits.push(`${fmtTokens(a.context_length)} context`);117 if (d.openness?.label) bits.push(`${d.openness.label.toLowerCase()}${d.licence && 'key' in d.licence && d.licence.key ? ` (${d.licence.key})` : ''}`);118 if (typeof a.release_date === 'string') bits.push(`released ${fmtDate(a.release_date)}`);119 const n = d.deployments?.length ?? 0;120 const b = d.benchmarks?.items.length ?? 0;121 const tail = [n ? `${n} provider deployment${n === 1 ? '' : 's'}` : null, b ? `${b} benchmark${b === 1 ? '' : 's'}` : null].filter(Boolean).join(', ');122 let s = `${d.name}${d.organization ? ` by ${d.organization.name}` : ''}${bits.length ? `: ${bits.join(', ')}` : ''}.`;123 if (tail) s += ` ${tail} with sourced prices and scores.`;124 s += ` Every value carries its source, tier and observation time on ${SITE_NAME}.`;125 return s.slice(0, 300);126}127128export async function ModelPage({ d, canonical, related, params }: { d: ModelDetail; canonical: string; related?: EntitySummary[] | null; params: ModelPageParams }) {129 const a = d.attributes ?? {};130 const asofRaw = params.asof?.trim() || undefined;131 const asof = asofRaw && ISO_DAY.test(asofRaw) ? asofRaw : undefined;132 const property = params.property?.trim() || undefined;133 const [history, asofPayload, benchList] = await Promise.all([safe(api.entityHistory(d.slug, property)), asof ? safe(api.entityAsOf(d.slug, asof)) : Promise.resolve(null), d.benchmarks?.items.length ? safe(apiD1.benchmarks()) : Promise.resolve(null)]);134 const claims = history?.items ?? null;135 const entity = { name: d.name, entity_type: d.entity_type };136 const licenceKey = d.licence && 'key' in d.licence && d.licence.key ? d.licence.key : typeof a.license === 'string' ? a.license : null;137 const strip = identityStrip(a, { opennessLabel: d.openness?.label ?? null, licence: licenceKey });138 const family = d.family && 'slug' in d.family && d.family.slug ? d.family : null;139 const familyLabel = d.family && !('slug' in d.family && d.family.slug) ? d.family.name : typeof a.family === 'string' ? a.family : null;140 const link = ['official_url', 'model_card_url', 'website'].map((k) => a[k]).find((v): v is string => typeof v === 'string' && /^https?:\/\//.test(v)) ?? null;141 const deployments = d.deployments ?? [];142 const priceHistory = d.price_history ?? d.prices ?? [];143 const datasets = (d.relations ?? []).flatMap((g) => g.items.filter((i) => i.entity_type === 'dataset'));144 const lineage = d.lineage ?? { ancestors: [], descendants: [], quantizations: [] };145 const artifactKinds = (d.artifacts?.items ?? []).map((g) => ({ kind: g.kind, count: g.count }));146 const hasLineage = lineage.ancestors.length + lineage.descendants.length + lineage.quantizations.length + artifactKinds.length > 0;147 const archRows: KVRow[] = ARCH_KEYS.filter((k) => a[k] !== undefined && a[k] !== null && a[k] !== '' && !(Array.isArray(a[k]) && (a[k] as unknown[]).length === 0)).map((k) => ({ key: k, raw: a[k] }));148 const overviewRows: KVRow[] = OVERVIEW_KEYS.filter((k) => a[k] !== undefined && a[k] !== null && a[k] !== '').map((k) => ({ key: k, raw: a[k] }));149 const hasCaps = ['tool_calling', 'structured_output', 'reasoning', 'vision', 'audio', 'fine_tuning_available', 'modalities', 'modalities_input', 'modalities_output', 'languages'].some((k) => a[k] !== undefined && a[k] !== null);150151 const present = new Set<string>(['overview', 'change-history', 'provenance']);152 if (archRows.length) present.add('architecture');153 if (hasCaps) present.add('capabilities');154 if (d.benchmarks?.items.length || d.results?.length) present.add('benchmarks');155 if (deployments.length || d.prices?.length) present.add('providers-pricing');156 if (priceHistory.length) present.add('price-history');157 if (d.hardware_fit?.length) present.add('hardware-fit');158 if (hasLineage) present.add('lineage');159 if (d.version_history?.length || d.artifacts?.total || d.identity) present.add('versions-artifacts');160 if (d.repositories?.length) present.add('repositories');161 if (d.papers?.length) present.add('papers');162 if (datasets.length) present.add('datasets');163 if (d.timeline?.length) present.add('timeline');164 const nav = SECTIONS.filter((s) => present.has(s.id));165 const ld = jsonLd(d, canonical);166167 return (168 <Container wide>169 <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />170 <ViewBeacon path={canonical} />171 <ScrollToSection />172173 <nav aria-label="Breadcrumb" className="pt-5 text-xs text-ink-3">174 <ol className="flex flex-wrap items-center gap-1.5">175 <li>176 <Link href="/" className="hover:text-ink">177 AI Atlas178 </Link>179 </li>180 <li aria-hidden>/</li>181 <li>182 <Link href={routes.models()} className="hover:text-ink">183 Models184 </Link>185 </li>186 {d.organization && (187 <>188 <li aria-hidden>/</li>189 <li>190 <Link href={routes.entity({ entity_type: 'company', slug: d.organization.slug })} className="hover:text-ink">191 {d.organization.name}192 </Link>193 </li>194 </>195 )}196 {family && (197 <>198 <li aria-hidden>/</li>199 <li>200 <Link href={routes.family(family.slug)} className="hover:text-ink">201 {family.name}202 </Link>203 </li>204 </>205 )}206 <li aria-hidden>/</li>207 <li className="text-ink-2">{d.name}</li>208 </ol>209 </nav>210211 {/* ------------------------------------------------------------------------------------------------ header */}212 <header className="pb-4 pt-4 md:pt-5" data-model-header>213 <div className="flex flex-wrap items-center gap-2">214 <EntityBadge type="model" />215 <StatusBadge status={d.status} />216 {d.openness && <OpennessChip openness={d.openness.category} label={d.openness.label} />}217 <IdentityBadge level={d.identity_confidence} />218 {d.redirected_from && <span className="text-xs text-ink-3">redirected from {d.redirected_from.slug}</span>}219 </div>220 <div className="mt-3 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">221 <div className="min-w-0">222 <h1 className="display text-[30px] md:text-[44px]">{d.name}</h1>223 <p className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[15px] text-ink-2">224 {d.organization && (225 <Link href={routes.entity({ entity_type: 'company', slug: d.organization.slug })} className="font-medium text-ink hover:text-accent">226 {d.organization.name}227 </Link>228 )}229 {family ? (230 <Link href={routes.family(family.slug)} className="hover:text-accent">231 family · {family.name}232 </Link>233 ) : familyLabel ? (234 <span className="text-ink-3" title="Family label from the source, not yet a canonical family">235 family · {familyLabel}236 </span>237 ) : null}238 {typeof a.release_date === 'string' && (239 <Evidence slug={d.slug} property="release_date" value={a.release_date} display={fmtDate(a.release_date)} fallback={d.provenance?.release_date} entity={entity}>240 released {fmtDate(a.release_date)}241 </Evidence>242 )}243 {link && (244 <a href={link} target="_blank" rel="noopener noreferrer" className="inline-flex max-w-full min-w-0 items-center gap-1 text-ink-3 hover:text-accent">245 <span className="truncate">{link.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '').slice(0, 48)}</span> <ExternalLink className="size-3.5 shrink-0" aria-hidden />246 </a>247 )}248 </p>249 {d.description && <p className="mt-3 max-w-3xl text-[15px] leading-relaxed text-ink-2">{d.description}</p>}250 <div className="mt-4 flex flex-wrap items-center gap-2" aria-label="Actions">251 <CompareButton e={d} />252 <WatchButton e={d} />253 <Link href={`${routes.graph(d.slug)}?mode=lineage`} 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">254 <GitFork className="size-3.5" aria-hidden /> Open in Graph255 </Link>256 </div>257 </div>258 <div className="shrink-0 text-xs text-ink-3 lg:text-right">259 <QualityMark q={d.quality?.score} label />260 <p className="mt-1" title={d.updated_at}>261 Updated {fmtAgo(d.updated_at)} · first seen {fmtDate(d.first_seen_at)}262 </p>263 <p className="mono mt-0.5 text-[11px]">{d.id}</p>264 </div>265 </div>266 {strip.length > 0 && (267 <p className="tnum mt-4 flex flex-wrap items-center gap-x-2 gap-y-1 border-y border-rule py-2.5 text-[15px] font-medium text-ink" data-identity-strip>268 {strip.map((s, i) => (269 <span key={s.key} className="inline-flex items-center gap-2">270 {i > 0 && (271 <span aria-hidden className="text-ink-3">272 ·273 </span>274 )}275 <Evidence slug={d.slug} property={s.key} value={a[s.key]} display={s.text} fallback={d.provenance?.[s.key]} entity={entity}>276 {s.text}277 </Evidence>278 </span>279 ))}280 </p>281 )}282 </header>283284 <SectionNav items={nav} />285286 <div className="pb-16">287 {/* -------------------------------------------------------------------------------------------- Overview */}288 <Sec id="overview">289 <div className="grid gap-8 lg:grid-cols-[minmax(0,1fr)_24rem]">290 <div className="min-w-0 space-y-6">291 <div>292 <p className="eyebrow mb-2">Identity</p>293 <IdentityPanel d={d} />294 </div>295 <div>296 <p className="eyebrow mb-2">Openness</p>297 <OpennessBlock openness={d.openness} licence={d.licence} />298 </div>299 {overviewRows.length > 0 && (300 <div>301 <p className="eyebrow mb-2">Key facts</p>302 <KeyValue rows={overviewRows} provenance={d.provenance} slug={d.slug} entity={entity} />303 </div>304 )}305 </div>306 <aside className="min-w-0 space-y-6">307 <div>308 <p className="eyebrow mb-2">Relations</p>309 <RelationsBlock relations={d.relations ?? []} exclude={['evaluated_on', 'artifact_of', 'quantized_from']} />310 </div>311 {(d.aliases?.length > 0 || d.identifiers?.length > 0) && (312 <div>313 <p className="eyebrow mb-2">Identifiers</p>314 {d.identifiers?.length > 0 && (315 <dl className="kv [&>div]:grid-cols-[8rem_minmax(0,1fr)]">316 {d.identifiers.slice(0, 12).map((i) => (317 <div key={`${i.scheme}:${i.value}`}>318 <dt className="mono text-[11px]">{i.scheme}</dt>319 <dd className="mono break-all text-[12px] text-ink">{i.value}</dd>320 </div>321 ))}322 </dl>323 )}324 {d.aliases?.length > 0 && (325 <p className="mt-2 text-xs text-ink-3">326 Also known as: <span className="text-ink-2">{d.aliases.join(', ')}</span>327 </p>328 )}329 <p className="mono mt-2 break-all text-[11px] text-ink-3">slug {d.slug}</p>330 </div>331 )}332 {related && related.length > 0 && (333 <div>334 <p className="eyebrow mb-2">Related</p>335 <ul className="divide-y divide-rule border-y border-rule">336 {related.slice(0, 8).map((r) => (337 <li key={r.id} className="flex items-center gap-2 py-1.5 text-sm">338 <EntityBadge type={r.entity_type} small />339 <EntityLink e={r} className="truncate" />340 {r.organization && <span className="ml-auto shrink-0 text-xs text-ink-3">{r.organization.name}</span>}341 </li>342 ))}343 </ul>344 </div>345 )}346 </aside>347 </div>348 </Sec>349350 {present.has('architecture') && (351 <Sec id="architecture">352 <KeyValue rows={archRows} provenance={d.provenance} slug={d.slug} entity={entity} />353 </Sec>354 )}355 {present.has('capabilities') && (356 <Sec id="capabilities">357 <Capabilities d={d} />358 </Sec>359 )}360 {present.has('benchmarks') && (361 <Sec id="benchmarks" count={d.benchmarks?.total_rows ?? d.results?.length} lede={<ComparabilityLegend />} action={{ href: `${routes.compare([d.slug])}`, label: 'Compare with another model' }}>362 <ModelBenchmarksBlock d={d} leaders={benchList?.items ?? null} />363 </Sec>364 )}365 {present.has('providers-pricing') && (366 <Sec id="providers-pricing" count={deployments.length || d.prices?.length} action={{ href: `${routes.prices()}?model=${encodeURIComponent(d.slug)}`, label: 'All offers in the price terminal' }}>367 {deployments.length ? <DeploymentsTable deployments={deployments} modelSlug={d.slug} /> : <PricesTable prices={d.prices ?? []} perspective="model" />}368 </Sec>369 )}370 {present.has('price-history') && (371 <Sec id="price-history" lede="Step lines per provider; amber markers are recorded changes. Click a marker or a row for the evidence behind that price.">372 <div className="grid gap-8 lg:grid-cols-2">373 <PriceHistoryChart history={priceHistory} field="output_per_mtok" modelSlug={d.slug} modelName={d.name} />374 <PriceHistoryChart history={priceHistory} field="input_per_mtok" modelSlug={d.slug} modelName={d.name} />375 </div>376 </Sec>377 )}378 {present.has('hardware-fit') && (379 <Sec id="hardware-fit" count={d.hardware_fit?.length}>380 <HardwareFitBlock rows={d.hardware_fit ?? []} assumptions={d.hardware_fit_assumptions} modelSlug={d.slug} />381 </Sec>382 )}383 {present.has('lineage') && (384 <Sec id="lineage" action={{ href: `${routes.graph(d.slug)}?mode=lineage`, label: 'Open in Graph' }} lede="Explicit derived_from / fine_tuned_from / distilled_from relations stated by sources; artifacts collapsed by kind.">385 <LineageTree self={d} ancestors={lineage.ancestors} descendants={lineage.descendants} artifactKinds={artifactKinds.length ? artifactKinds : lineage.quantizations.length ? [{ kind: 'quantization', count: lineage.quantizations.length }] : []} />386 </Sec>387 )}388 {present.has('versions-artifacts') && (389 <Sec id="versions-artifacts" count={d.artifacts?.total}>390 <div className="grid gap-8 lg:grid-cols-2">391 <div>392 <p className="eyebrow mb-2">Version history</p>393 <VersionHistoryBlock items={d.version_history ?? []} d={d} />394 </div>395 <div>396 <p className="eyebrow mb-2">397 Artifacts <span className="tnum text-ink-3">{fmtInt(d.artifacts?.total ?? 0)}</span>398 </p>399 <ArtifactsBlock d={d} />400 </div>401 </div>402 </Sec>403 )}404 {present.has('repositories') && (405 <Sec id="repositories" count={d.repositories?.length}>406 <EntityList items={d.repositories ?? []} />407 </Sec>408 )}409 {present.has('papers') && (410 <Sec id="papers" count={d.papers?.length}>411 <EntityList items={d.papers ?? []} />412 </Sec>413 )}414 {present.has('datasets') && (415 <Sec id="datasets" count={datasets.length}>416 <EntityList items={datasets} />417 </Sec>418 )}419 {present.has('timeline') && (420 <Sec id="timeline" count={d.timeline?.length} action={{ href: routes.timeline({ entity: d.slug }), label: 'Full timeline' }}>421 <TimelineList events={d.timeline ?? []} />422 </Sec>423 )}424 <Sec id="change-history" count={property ? undefined : claims?.length} lede="Temporal, append-only claims: a new observation closes the previous claim instead of overwriting it. Rewind the record with the as-of picker.">425 <div className="space-y-6">426 <AsOfPicker value={asofRaw} />427 {asofRaw && <AsOfBlock d={d} asof={asofRaw} payload={asof ? asofPayload : null} />}428 <ClaimHistory d={d} claims={claims} property={property} />429 </div>430 </Sec>431 <Sec id="provenance">432 <div className="space-y-6">433 <ProvenanceSummary provenance={d.provenance} quality={d.quality} />434 <div>435 <p className="eyebrow mb-2">436 Source documents <span className="tnum text-ink-3">{fmtInt(d.sources?.length ?? 0)}</span>437 </p>438 <SourcesTable sources={d.sources ?? []} />439 </div>440 <Note>441 Data quality ({num(d.quality?.score) === null ? 'not computed' : `${Math.round(num(d.quality?.score) as number)}/100`}) measures how well AI Atlas knows this entity — completeness, primary-source ratio, freshness, conflicts — never how good the model is. <Link href="/methodology" className="link">Methodology →</Link>442 </Note>443 </div>444 </Sec>445 </div>446 <CompareTrayBar />447 </Container>448 );449}450