import { ExternalLink, GitFork } from 'lucide-react'; import Link from 'next/link'; import { CompareButton } from '@/components/compare/compare-button'; import { CompareTrayBar } from '@/components/compare/compare-tray-bar'; import { Evidence } from '@/components/evidence/evidence'; import { SectionNav } from '@/components/layout/terminal'; import { ViewBeacon } from '@/components/layout/view-beacon'; import { IdentityBadge, OpennessChip } from '@/components/models/badges'; import { LineageTree } from '@/components/models/lineage-tree'; import { OpennessBlock } from '@/components/models/openness-block'; import { PriceHistoryChart } from '@/components/models/price-history'; import { ScrollToSection } from '@/components/models/scroll-to-section'; import { identityStrip } from '@/components/models/shared'; import { EntityBadge, StatusBadge } from '@/components/ui/badges'; import { EntityLink, QualityMark } from '@/components/ui/entity'; import { KeyValue, type KVRow } from '@/components/ui/key-value'; import { Container, Note } from '@/components/ui/section'; import { WatchButton } from '@/components/watchlist/watch-button'; import { api, apiD1, safe } from '@/lib/api'; import { fmtAgo, fmtDate, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format'; import { routes, SITE_NAME, SITE_URL } from '@/lib/site'; import type { EntitySummary, ModelDetail } from '@/lib/types'; import { AsOfPicker } from './asof-picker'; import { Capabilities, EntityList, PricesTable, ProvenanceSummary, RelationsBlock, SourcesTable, TimelineList } from './blocks'; import { AsOfBlock, ClaimHistory } from './history'; import { ArtifactsBlock, ComparabilityLegend, DeploymentsTable, HardwareFitBlock, IdentityPanel, ModelBenchmarksBlock, VersionHistoryBlock } from './model-blocks'; /* Model page 3.0: sticky header with identity strip → SectionNav → sections in a fixed order (only those with data render): Overview · Architecture · Capabilities · Benchmarks · Providers & Pricing · Price history · Hardware fit · Lineage · Versions & Artifacts · Repositories · Papers · Datasets · Timeline · Change history · Provenance. Every value opens the evidence drawer. */ export type ModelPageParams = { asof?: string; property?: string; tab?: string }; const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/; const 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']; const 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']; function sectionTitle(id: string): string { return SECTIONS.find((s) => s.id === id)?.label ?? id; } const SECTIONS = [ { id: 'overview', label: 'Overview' }, { id: 'architecture', label: 'Architecture' }, { id: 'capabilities', label: 'Capabilities' }, { id: 'benchmarks', label: 'Benchmarks' }, { id: 'providers-pricing', label: 'Providers & Pricing' }, { id: 'price-history', label: 'Price history' }, { id: 'hardware-fit', label: 'Hardware fit' }, { id: 'lineage', label: 'Lineage' }, { id: 'versions-artifacts', label: 'Versions & Artifacts' }, { id: 'repositories', label: 'Repositories' }, { id: 'papers', label: 'Papers' }, { id: 'datasets', label: 'Datasets' }, { id: 'timeline', label: 'Timeline' }, { id: 'change-history', label: 'Change history' }, { id: 'provenance', label: 'Provenance' }, ]; function Sec({ id, children, count, lede, action }: { id: string; children: React.ReactNode; count?: number | null; lede?: React.ReactNode; action?: { href: string; label: string } }) { return (

{sectionTitle(id)} {count !== undefined && count !== null && {fmtInt(count)}}

{action && ( {action.label} → )}
{lede &&
{lede}
} {children}
); } function jsonLd(d: ModelDetail, canonical: string) { const a = d.attributes ?? {}; const org = d.organization ? { '@type': 'Organization', name: d.organization.name, url: `${SITE_URL}${routes.entity({ entity_type: 'company', slug: d.organization.slug })}` } : undefined; const deployments = d.deployments ?? []; return { '@context': 'https://schema.org', '@type': ['SoftwareApplication', 'Product'], name: d.name, url: `${SITE_URL}${canonical}`, description: d.description ?? undefined, applicationCategory: 'AI model', alternateName: d.aliases?.length ? d.aliases : undefined, identifier: d.identifiers?.map((i) => ({ '@type': 'PropertyValue', propertyID: i.scheme, value: i.value })), creator: org, manufacturer: org, datePublished: typeof a.release_date === 'string' ? a.release_date : undefined, license: d.licence && 'key' in d.licence && d.licence.key ? (d.licence.url ?? d.licence.key) : typeof a.license === 'string' ? a.license : undefined, 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, additionalProperty: [ num(a.parameter_count) !== null ? { '@type': 'PropertyValue', name: 'parameter_count', value: num(a.parameter_count) } : null, num(a.context_length) !== null ? { '@type': 'PropertyValue', name: 'context_length', value: num(a.context_length), unitText: 'tokens' } : null, typeof a.openness === 'string' ? { '@type': 'PropertyValue', name: 'openness', value: a.openness } : null, ].filter(Boolean), offers: deployments.length ? 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' })) : undefined, }; } /** SEO description: "Qwen3.6 35B A3B by Qwen: 35B parameters (3B active), 262K context, open weights (Apache-2.0), released 14 May 2026. …" */ export function describeModel(d: ModelDetail): string { const a = d.attributes ?? {}; const bits: string[] = []; const p = num(a.parameter_count); const ap = num(a.active_parameter_count); if (p !== null) bits.push(`${fmtParams(p)} parameters${ap !== null && ap !== p ? ` (${fmtParams(ap)} active)` : ''}`); if (num(a.context_length) !== null) bits.push(`${fmtTokens(a.context_length)} context`); if (d.openness?.label) bits.push(`${d.openness.label.toLowerCase()}${d.licence && 'key' in d.licence && d.licence.key ? ` (${d.licence.key})` : ''}`); if (typeof a.release_date === 'string') bits.push(`released ${fmtDate(a.release_date)}`); const n = d.deployments?.length ?? 0; const b = d.benchmarks?.items.length ?? 0; const tail = [n ? `${n} provider deployment${n === 1 ? '' : 's'}` : null, b ? `${b} benchmark${b === 1 ? '' : 's'}` : null].filter(Boolean).join(', '); let s = `${d.name}${d.organization ? ` by ${d.organization.name}` : ''}${bits.length ? `: ${bits.join(', ')}` : ''}.`; if (tail) s += ` ${tail} with sourced prices and scores.`; s += ` Every value carries its source, tier and observation time on ${SITE_NAME}.`; return s.slice(0, 300); } export async function ModelPage({ d, canonical, related, params }: { d: ModelDetail; canonical: string; related?: EntitySummary[] | null; params: ModelPageParams }) { const a = d.attributes ?? {}; const asofRaw = params.asof?.trim() || undefined; const asof = asofRaw && ISO_DAY.test(asofRaw) ? asofRaw : undefined; const property = params.property?.trim() || undefined; 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)]); const claims = history?.items ?? null; const entity = { name: d.name, entity_type: d.entity_type }; const licenceKey = d.licence && 'key' in d.licence && d.licence.key ? d.licence.key : typeof a.license === 'string' ? a.license : null; const strip = identityStrip(a, { opennessLabel: d.openness?.label ?? null, licence: licenceKey }); const family = d.family && 'slug' in d.family && d.family.slug ? d.family : null; const familyLabel = d.family && !('slug' in d.family && d.family.slug) ? d.family.name : typeof a.family === 'string' ? a.family : null; const link = ['official_url', 'model_card_url', 'website'].map((k) => a[k]).find((v): v is string => typeof v === 'string' && /^https?:\/\//.test(v)) ?? null; const deployments = d.deployments ?? []; const priceHistory = d.price_history ?? d.prices ?? []; const datasets = (d.relations ?? []).flatMap((g) => g.items.filter((i) => i.entity_type === 'dataset')); const lineage = d.lineage ?? { ancestors: [], descendants: [], quantizations: [] }; const artifactKinds = (d.artifacts?.items ?? []).map((g) => ({ kind: g.kind, count: g.count })); const hasLineage = lineage.ancestors.length + lineage.descendants.length + lineage.quantizations.length + artifactKinds.length > 0; 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] })); const overviewRows: KVRow[] = OVERVIEW_KEYS.filter((k) => a[k] !== undefined && a[k] !== null && a[k] !== '').map((k) => ({ key: k, raw: a[k] })); 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); const present = new Set(['overview', 'change-history', 'provenance']); if (archRows.length) present.add('architecture'); if (hasCaps) present.add('capabilities'); if (d.benchmarks?.items.length || d.results?.length) present.add('benchmarks'); if (deployments.length || d.prices?.length) present.add('providers-pricing'); if (priceHistory.length) present.add('price-history'); if (d.hardware_fit?.length) present.add('hardware-fit'); if (hasLineage) present.add('lineage'); if (d.version_history?.length || d.artifacts?.total || d.identity) present.add('versions-artifacts'); if (d.repositories?.length) present.add('repositories'); if (d.papers?.length) present.add('papers'); if (datasets.length) present.add('datasets'); if (d.timeline?.length) present.add('timeline'); const nav = SECTIONS.filter((s) => present.has(s.id)); const ld = jsonLd(d, canonical); return (