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%
24.6 KB · 592 lines typescript
Raw Blame History
1/** Site constants, routes per entity type, labels and colours shared by pages and components. */23export const SITE_NAME = 'AI Atlas';4export const SITE_URL = (process.env.NEXT_PUBLIC_SITE_URL ?? 'https://www.ai-atlas.co').replace(/\/$/, '');5export const TAGLINE = 'The temporal knowledge graph of the AI ecosystem';6export const DESCRIPTION =7  'AI Atlas is a continuously reconstructed, source-attributed, historical map of the global AI ecosystem: models, organizations, research, providers and pricing, benchmarks, hardware, frameworks, datasets and tools — every number answers who said it, when, where, and how it was extracted.';8export const CONTACT_EMAIL = 'contact@spboucher.ai';9export const AUTHOR_NAME = 'Simon-Pierre Boucher';10/** Theme tokens mirrored from globals.css (`--canvas`) for the manifest and `<meta theme-color>` — they cannot read CSS variables. */11export const THEME_LIGHT = '#f6f6f3';12export const THEME_DARK = '#0b0d11';13export const HOST_NAME = 'MacLustr';14export const HOST_URL = 'https://www.maclustr.io';15export const PUBLIC_API_BASE = 'https://www.ai-atlas.co/api/v1';16export const BOT_UA = 'AIAtlasBot';1718/** Entity type → URL path segment (plural). Types not listed fall back to /explore/<type>/<slug> → generic entity page. */19export const TYPE_PATH: Record<string, string> = {20  model: 'models',21  company: 'companies',22  organization: 'companies',23  lab: 'companies',24  university: 'companies',25  paper: 'papers',26  provider: 'providers',27  benchmark: 'benchmarks',28  hardware: 'hardware',29  framework: 'frameworks',30  library: 'frameworks',31  runtime: 'frameworks',32  dataset: 'datasets',33  tool: 'tools',34  agent: 'tools',35  mcp_server: 'tools',36  product: 'tools',37  repository: 'repositories',38  model_family: 'families',39  artifact: 'artifacts',40  license: 'licenses',41};42/** URL path segment → API mount + canonical entity types accepted there. */43export const PATH_TYPES: Record<string, { label: string; singular: string; types: string[]; api: string }> = {44  models: { label: 'Models', singular: 'Model', types: ['model', 'quantization'], api: 'models' },45  companies: { label: 'Companies', singular: 'Company', types: ['company', 'organization', 'lab', 'university'], api: 'companies' },46  papers: { label: 'Research', singular: 'Paper', types: ['paper'], api: 'papers' },47  providers: { label: 'Providers', singular: 'Provider', types: ['provider'], api: 'providers' },48  benchmarks: { label: 'Benchmarks', singular: 'Benchmark', types: ['benchmark'], api: 'benchmarks' },49  hardware: { label: 'Hardware', singular: 'Hardware', types: ['hardware'], api: 'hardware' },50  frameworks: { label: 'Frameworks', singular: 'Framework', types: ['framework', 'library', 'runtime'], api: 'frameworks' },51  datasets: { label: 'Datasets', singular: 'Dataset', types: ['dataset'], api: 'datasets' },52  tools: { label: 'Tools', singular: 'Tool', types: ['tool', 'agent', 'mcp_server', 'product'], api: 'tools' },53  repositories: { label: 'Repositories', singular: 'Repository', types: ['repository'], api: 'entities' },54  families: { label: 'Families', singular: 'Family', types: ['model_family'], api: 'families' },55  artifacts: { label: 'Artifacts', singular: 'Artifact', types: ['artifact'], api: 'models' },56  licenses: { label: 'Licenses', singular: 'License', types: ['license'], api: 'entities' },57};5859export const TYPE_LABELS: Record<string, string> = {60  model: 'Model',61  company: 'Company',62  organization: 'Organization',63  lab: 'Lab',64  university: 'University',65  researcher: 'Researcher',66  paper: 'Paper',67  dataset: 'Dataset',68  benchmark: 'Benchmark',69  provider: 'Provider',70  framework: 'Framework',71  library: 'Library',72  runtime: 'Runtime',73  repository: 'Repository',74  tool: 'Tool',75  agent: 'Agent',76  mcp_server: 'MCP server',77  product: 'Product',78  hardware: 'Hardware',79  quantization: 'Quantization',80  license: 'License',81  regulation: 'Regulation',82  incident: 'Incident',83  release: 'Release',84  conference: 'Conference',85  country: 'Country',86  robot: 'Robot',87  data_center: 'Data center',88  standard: 'Standard',89  funding_round: 'Funding round',90  acquisition: 'Acquisition',91  course: 'Course',92  job: 'Job',93  model_family: 'Family',94  artifact: 'Artifact',95};96export const TYPE_LABELS_PLURAL: Record<string, string> = {97  model: 'Models',98  company: 'Companies',99  organization: 'Organizations',100  lab: 'Labs',101  university: 'Universities',102  researcher: 'Researchers',103  paper: 'Papers',104  dataset: 'Datasets',105  benchmark: 'Benchmarks',106  provider: 'Providers',107  framework: 'Frameworks',108  library: 'Libraries',109  runtime: 'Runtimes',110  repository: 'Repositories',111  tool: 'Tools',112  agent: 'Agents',113  mcp_server: 'MCP servers',114  product: 'Products',115  hardware: 'Hardware',116  quantization: 'Quantizations',117  license: 'Licenses',118  regulation: 'Regulations',119  incident: 'Incidents',120  release: 'Releases',121  conference: 'Conferences',122  country: 'Countries',123  robot: 'Robots',124  data_center: 'Data centers',125  standard: 'Standards',126  funding_round: 'Funding rounds',127  acquisition: 'Acquisitions',128  course: 'Courses',129  job: 'Jobs',130  model_family: 'Families',131  artifact: 'Artifacts',132};133export function typeLabel(t: string, pluralForm = false): string {134  return (pluralForm ? TYPE_LABELS_PLURAL[t] : TYPE_LABELS[t]) ?? t.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());135}136137/** Badge colour family per entity type (CSS variables `--type-<key>` in globals.css). */138export const TYPE_COLOR_KEY: Record<string, string> = {139  model: 'model',140  quantization: 'model',141  model_family: 'model',142  artifact: 'model',143  license: 'tool',144  company: 'company',145  organization: 'company',146  lab: 'company',147  university: 'company',148  researcher: 'company',149  paper: 'paper',150  provider: 'provider',151  benchmark: 'benchmark',152  hardware: 'hardware',153  robot: 'hardware',154  data_center: 'hardware',155  framework: 'framework',156  library: 'framework',157  runtime: 'framework',158  repository: 'framework',159  dataset: 'dataset',160  tool: 'tool',161  agent: 'tool',162  mcp_server: 'tool',163  product: 'tool',164};165166export const routes = {167  home: () => '/',168  search: (q: string, type?: string) => `/search?q=${encodeURIComponent(q)}${type ? `&type=${encodeURIComponent(type)}` : ''}`,169  models: () => '/models',170  companies: () => '/companies',171  papers: () => '/papers',172  providers: () => '/providers',173  benchmarks: () => '/benchmarks',174  hardware: () => '/hardware',175  frameworks: () => '/frameworks',176  datasets: () => '/datasets',177  tools: () => '/tools',178  changes: () => '/changes',179  changesDay: (date: string) => `/changes/${date}`,180  timeline: (q?: { entity?: string; year?: string; category?: string }) => {181    const p = new URLSearchParams();182    if (q?.entity) p.set('entity', q.entity);183    if (q?.year) p.set('year', q.year);184    if (q?.category) p.set('category', q.category);185    const s = p.toString();186    return s ? `/timeline?${s}` : '/timeline';187  },188  compare: (ids?: string[]) => (ids?.length ? `/compare?ids=${ids.map(encodeURIComponent).join(',')}` : '/compare'),189  prices: () => '/prices',190  hardwareFit: (q?: { memory_gb?: number | string; quant?: string; context?: number | string }) => {191    const p = new URLSearchParams();192    if (q?.memory_gb !== undefined && q.memory_gb !== '') p.set('memory_gb', String(q.memory_gb));193    if (q?.quant) p.set('quant', q.quant);194    if (q?.context !== undefined && q.context !== '') p.set('context', String(q.context));195    const s = p.toString();196    return s ? `/hardware/fit?${s}` : '/hardware/fit';197  },198  benchmark: (slug: string, q?: { config?: string; model?: string }) => {199    const p = new URLSearchParams();200    if (q?.config) p.set('config', q.config);201    if (q?.model) p.set('model', q.model);202    const s = p.toString();203    return `/benchmarks/${encodeURIComponent(slug)}${s ? `?${s}` : ''}`;204  },205  diff: (q?: { a?: string; b?: string; scope?: string }) => {206    const p = new URLSearchParams();207    if (q?.a) p.set('a', q.a);208    if (q?.b) p.set('b', q.b);209    if (q?.scope && q.scope !== 'all') p.set('scope', q.scope);210    const s = p.toString();211    return s ? `/diff?${s}` : '/diff';212  },213  graph: (slug?: string, depth?: 1 | 2) => (slug ? `/graph/${encodeURIComponent(slug)}${depth === 2 ? '?depth=2' : ''}` : '/graph'),214  /** Entity page "as of" view (History tab). */215  entityAsOf: (e: { entity_type: string; slug: string }, date: string) => `${routes.entity(e)}?tab=history&asof=${encodeURIComponent(date)}`,216  /** Entity page History tab, optionally filtered to one property (deep link from the evidence drawer). */217  entityHistory: (e: { entity_type: string; slug: string }, property?: string) => `${routes.entity(e)}?tab=history${property ? `&property=${encodeURIComponent(property)}` : ''}`,218  // ---- 1.1 routes (pages built by the D1–D3 streams; the shell links to them from here)219  frontier: () => '/frontier',220  pulse: () => '/pulse',221  open: () => '/open',222  families: () => '/families',223  family: (slug: string) => `/families/${encodeURIComponent(slug)}`,224  timeMachine: (date?: string) => (date ? `/time-machine?date=${encodeURIComponent(date)}` : '/time-machine'),225  calculator: () => '/calculator',226  runLocally: () => '/run-locally',227  findAModel: () => '/find-a-model',228  artifact: (slug: string) => `/artifacts/${encodeURIComponent(slug)}`,229  agents: () => '/agents',230  licenses: () => '/licenses',231  claim: (id: string) => `/claims/${encodeURIComponent(id)}`,232  watchlist: () => '/watchlist',233  benchmarkMatrix: () => '/benchmarks?view=matrix',234  admin: (section?: string) => (section ? `/admin/${section}` : '/admin'),235  explore: () => '/explore',236  exploreType: (t: string) => `/explore/${encodeURIComponent(t)}`,237  methodology: () => '/methodology',238  sources: () => '/sources',239  about: () => '/about',240  developers: () => '/developers',241  bot: () => '/bot',242  listing: (type: string) => {243    const p = TYPE_PATH[type];244    return p ? `/${p}` : `/explore/${encodeURIComponent(type)}`;245  },246  /** Canonical URL of any entity: /models/<slug>, /companies/<slug>, … or /explore/<type>/<slug>. */247  entity: (e: { entity_type: string; slug: string }) => {248    const p = TYPE_PATH[e.entity_type];249    return p ? `/${p}/${encodeURIComponent(e.slug)}` : `/explore/${encodeURIComponent(e.entity_type)}/${encodeURIComponent(e.slug)}`;250  },251};252253export const exploreNav = [254  { href: '/models', label: 'Models', type: 'model' },255  { href: '/companies', label: 'Companies', type: 'company' },256  { href: '/papers', label: 'Research', type: 'paper' },257  { href: '/providers', label: 'Providers & Pricing', type: 'provider' },258  { href: '/benchmarks', label: 'Benchmarks', type: 'benchmark' },259  { href: '/hardware', label: 'Hardware', type: 'hardware' },260  { href: '/frameworks', label: 'Frameworks', type: 'framework' },261  { href: '/datasets', label: 'Datasets', type: 'dataset' },262  { href: '/tools', label: 'Tools', type: 'tool' },263];264/** Desktop header: `AI Atlas | Models · Frontier · Benchmarks · Prices · Research · Graph · Changes · More ▾`. */265export const primaryNav = [266  { href: '/models', label: 'Models' },267  { href: '/frontier', label: 'Frontier' },268  { href: '/benchmarks', label: 'Benchmarks' },269  { href: '/prices', label: 'Prices' },270  { href: '/papers', label: 'Research' },271  { href: '/graph', label: 'Graph' },272  { href: '/changes', label: 'Changes' },273];274export type NavItem = { href: string; label: string; hint?: string; type?: string };275export type NavGroup = { label: string; items: NavItem[] };276/** "More ▾" panel (desktop) and the mobile "More" sheet: the full site, grouped. */277export const moreGroups: NavGroup[] = [278  {279    label: 'Explore',280    items: [281      { href: '/companies', label: 'Organizations', type: 'company' },282      { href: '/providers', label: 'Providers', type: 'provider' },283      { href: '/hardware', label: 'Hardware', type: 'hardware' },284      { href: '/datasets', label: 'Datasets', type: 'dataset' },285      { href: '/frameworks', label: 'Frameworks', type: 'framework' },286      { href: '/agents', label: 'Agents & Tools', type: 'tool' },287      { href: '/open', label: 'Open Models', type: 'model' },288      { href: '/families', label: 'Families', type: 'model' },289    ],290  },291  {292    label: 'Intelligence',293    items: [294      { href: '/compare', label: 'Compare' },295      { href: '/calculator', label: 'Calculator', hint: 'Cost of a workload across providers' },296      { href: '/run-locally', label: 'Run locally', hint: 'Which models fit your hardware' },297      { href: '/find-a-model', label: 'Find a model' },298      { href: '/pulse', label: 'Pulse' },299      { href: '/watchlist', label: 'Watchlist' },300    ],301  },302  {303    label: 'Temporal',304    items: [305      { href: '/timeline', label: 'Timeline' },306      { href: '/time-machine', label: 'Time Machine', hint: 'The atlas as of any date' },307      { href: '/diff', label: 'Diff', hint: 'What changed between two dates' },308    ],309  },310  {311    label: 'About',312    items: [313      { href: '/sources', label: 'Sources' },314      { href: '/methodology', label: 'Methodology' },315      { href: '/developers', label: 'API' },316      { href: '/about', label: 'About' },317    ],318  },319];320/** Flat "More" list in the order required by the spec (desktop panel columns are built from `moreGroups`). */321export const moreNav: NavItem[] = moreGroups.flatMap((g) => g.items);322/** Mobile bottom tab bar: Home · Models · Changes · Search · More. */323export const mobileTabs = [324  { href: '/', label: 'Home' },325  { href: '/models', label: 'Models' },326  { href: '/changes', label: 'Changes' },327];328/** Footer columns. */329export const footerGroups: NavGroup[] = [330  { label: 'Explore', items: [...exploreNav.map((n) => ({ href: n.href, label: n.label })), { href: '/open', label: 'Open models' }, { href: '/families', label: 'Families' }] },331  { label: 'Intelligence', items: [{ href: '/frontier', label: 'Frontier' }, { href: '/compare', label: 'Compare' }, { href: '/prices', label: 'Prices' }, { href: '/calculator', label: 'Calculator' }, { href: '/run-locally', label: 'Run locally' }, { href: '/find-a-model', label: 'Find a model' }, { href: '/graph', label: 'Knowledge graph' }, { href: '/pulse', label: 'Pulse' }] },332  { label: 'Temporal', items: [{ href: '/changes', label: 'Changes' }, { href: '/timeline', label: 'Timeline' }, { href: '/time-machine', label: 'Time machine' }, { href: '/diff', label: 'Diff two dates' }, { href: '/watchlist', label: 'Watchlist' }] },333  { label: 'About', items: [{ href: '/about', label: 'About' }, { href: '/methodology', label: 'Methodology' }, { href: '/sources', label: 'Sources' }, { href: '/developers', label: 'API / Developers' }, { href: '/bot', label: 'AIAtlasBot' }] },334];335/** "Signature products" strip on the homepage. */336export const signatureProducts: NavItem[] = [337  { href: '/frontier', label: 'Frontier', hint: 'Who leads, on what, since when' },338  { href: '/graph', label: 'Knowledge graph', hint: 'Every entity, every relation' },339  { href: '/graph', label: 'Lineage', hint: 'Base → fine-tune → quantization' },340  { href: '/time-machine', label: 'Time machine', hint: 'The atlas as of any date' },341  { href: '/diff', label: 'Diff the AI world', hint: 'Two dates, every change' },342  { href: '/prices', label: 'Price intelligence', hint: 'USD per 1M tokens, over time' },343  { href: '/calculator', label: 'Cost vs performance', hint: 'Pareto view of models' },344  { href: '/benchmarks?view=matrix', label: 'Benchmark matrix', hint: 'Models × benchmarks' },345  { href: '/open', label: 'Open model frontier', hint: 'Best open weights, tracked' },346  { href: '/run-locally', label: 'Local model finder', hint: 'What fits your machine' },347  { href: '/methodology', label: 'Field-level provenance', hint: 'Who said it, when, where' },348  { href: '/changes', label: 'Change history', hint: 'Nothing is overwritten' },349];350/** Type prefixes understood by the command palette (`m:claude`). */351export const PALETTE_PREFIXES: Record<string, { types: string[]; label: string }> = {352  m: { types: ['model', 'quantization', 'artifact'], label: 'Models' },353  b: { types: ['benchmark'], label: 'Benchmarks' },354  o: { types: ['company', 'organization', 'lab', 'university'], label: 'Organizations' },355  p: { types: ['provider'], label: 'Providers' },356  h: { types: ['hardware'], label: 'Hardware' },357};358359/** Change-event vocabulary (sdk/facts.py MATERIAL_PROPERTIES + writer): label, colour family. */360export const EVENT_TYPE_LABELS: Record<string, string> = {361  NEW_MODEL: 'New model',362  NEW_COMPANY: 'New company',363  NEW_PROVIDER: 'New provider',364  NEW_PAPER: 'New paper',365  NEW_DATASET: 'New dataset',366  NEW_BENCHMARK: 'New benchmark',367  NEW_FRAMEWORK: 'New framework',368  NEW_HARDWARE: 'New hardware',369  NEW_TOOL: 'New tool',370  NEW_REPOSITORY: 'New repository',371  NEW_ORGANIZATION: 'New organization',372  NEW_ENTITY: 'New entity',373  MODEL_UPDATED: 'Model updated',374  PRICE_CHANGED: 'Price changed',375  PROVIDER_LISTED: 'Listed by provider',376  CONTEXT_CHANGED: 'Context window changed',377  MAX_OUTPUT_CHANGED: 'Max output changed',378  STATUS_CHANGED: 'Status changed',379  LICENSE_CHANGED: 'License changed',380  OPENNESS_CHANGED: 'Openness changed',381  PARAMETERS_CHANGED: 'Parameters changed',382  RELEASE_DATE_CHANGED: 'Release date changed',383  KNOWLEDGE_CUTOFF_CHANGED: 'Knowledge cutoff changed',384  CAPABILITIES_CHANGED: 'Capabilities changed',385  RATE_LIMIT_CHANGED: 'Rate limit changed',386  SPEC_CHANGED: 'Spec changed',387  DEPRECATION_ANNOUNCED: 'Deprecation announced',388  RETIREMENT_ANNOUNCED: 'Retirement announced',389  BENCHMARK_RESULT: 'Benchmark result',390  BENCHMARK_UPDATED: 'Benchmark updated',391  ANNOUNCEMENT: 'Announcement',392  RELEASE: 'Release',393  VERSION_RELEASED: 'Version released',394  PROPERTY_CHANGED: 'Property changed',395  DOCUMENT_CHANGED: 'Document changed',396};397export function eventLabel(t: string): string {398  return EVENT_TYPE_LABELS[t] ?? t.toLowerCase().replace(/_/g, ' ').replace(/^\w/, (c) => c.toUpperCase());399}400/** Colour family for an event type: accent-2 (amber) for money, positive for new, warning/danger for deprecations. */401export function eventTone(t: string): 'new' | 'price' | 'warn' | 'danger' | 'bench' | 'neutral' {402  if (t.startsWith('NEW_') || t === 'RELEASE' || t === 'VERSION_RELEASED') return 'new';403  if (t === 'PRICE_CHANGED' || t === 'PROVIDER_LISTED') return 'price';404  if (t === 'DEPRECATION_ANNOUNCED') return 'warn';405  if (t === 'RETIREMENT_ANNOUNCED') return 'danger';406  if (t.startsWith('BENCHMARK')) return 'bench';407  return 'neutral';408}409410export const CATEGORY_LABELS: Record<string, string> = {411  model: 'Models',412  company: 'Companies',413  paper: 'Research',414  dataset: 'Datasets',415  benchmark: 'Benchmarks',416  provider: 'Providers & pricing',417  price: 'Pricing',418  framework: 'Frameworks',419  repository: 'Repositories',420  tool: 'Tools',421  hardware: 'Hardware',422  regulation: 'Regulation',423  incident: 'Incidents',424  release: 'Releases',425  document: 'Documents',426  announcement: 'Announcements',427};428export function categoryLabel(c: string): string {429  return CATEGORY_LABELS[c] ?? c.replace(/_/g, ' ').replace(/^\w/, (x) => x.toUpperCase());430}431432export const IMPORTANCE_LABELS: Record<number, string> = { 0: 'Minor', 1: 'Notable', 2: 'Important', 3: 'Major' };433434/** Human labels for attribute keys (docs/CONNECTORS.md property vocabulary). */435export const PROPERTY_LABELS: Record<string, string> = {436  family: 'Family',437  version: 'Version',438  release_date: 'Release date',439  status: 'Status',440  openness: 'Openness',441  license: 'License',442  architecture: 'Architecture',443  parameter_count: 'Parameters',444  active_parameter_count: 'Active parameters',445  is_moe: 'Mixture of experts',446  modalities: 'Modalities',447  modalities_input: 'Input modalities',448  modalities_output: 'Output modalities',449  context_length: 'Context window',450  max_output_tokens: 'Max output',451  knowledge_cutoff: 'Knowledge cutoff',452  training_data_cutoff: 'Training data cutoff',453  languages: 'Languages',454  tool_calling: 'Tool calling',455  structured_output: 'Structured output',456  reasoning: 'Reasoning',457  vision: 'Vision',458  audio: 'Audio',459  fine_tuning_available: 'Fine-tuning available',460  tokenizer: 'Tokenizer',461  api_model_id: 'API model id',462  api_alias: 'API alias',463  official_url: 'Official page',464  model_card_url: 'Model card',465  paper_url: 'Paper',466  repository_url: 'Repository',467  hf_repo: 'Hugging Face repo',468  pipeline_tag: 'Pipeline tag',469  base_model: 'Base model',470  quantization: 'Quantization',471  quant_format: 'Quantization format',472  file_size_gb: 'File size',473  deprecation_date: 'Deprecation date',474  retirement_date: 'Retirement date',475  retirement_tentative: 'Retirement tentative',476  'metric.downloads': 'Downloads',477  'metric.likes': 'Likes',478  'metric.stars': 'Stars',479  'metric.forks': 'Forks',480  country: 'Country',481  headquarters: 'Headquarters',482  founded: 'Founded',483  website: 'Website',484  domains: 'Domains',485  hf_org: 'Hugging Face org',486  github_org: 'GitHub org',487  org_kind: 'Kind',488  legal_name: 'Legal name',489  founders: 'Founders',490  leadership: 'Leadership',491  employee_count: 'Employees',492  pricing_url: 'Pricing page',493  docs_url: 'Documentation',494  regions: 'Regions',495  features: 'Features',496  authors: 'Authors',497  published_at: 'Published',498  updated_at: 'Updated',499  abstract: 'Abstract',500  arxiv_id: 'arXiv id',501  doi: 'DOI',502  categories: 'Categories',503  primary_category: 'Primary category',504  pdf_url: 'PDF',505  code_url: 'Code',506  venue: 'Venue',507  category: 'Category',508  task: 'Task',509  metric: 'Metric',510  unit: 'Unit',511  creator: 'Creator',512  paper: 'Paper',513  known_limitations: 'Known limitations',514  methodology: 'Methodology',515  kind: 'Kind',516  memory_gb: 'Memory',517  memory_type: 'Memory type',518  memory_bandwidth_gbs: 'Memory bandwidth',519  tdp_watts: 'TDP',520  runtimes: 'Runtimes',521  manufacturer: 'Manufacturer',522  spec_url: 'Spec sheet',523  price_usd: 'Price (USD)',524  compute_fp16_tflops: 'FP16 compute',525  latest_version: 'Latest version',526  latest_release_at: 'Latest release',527  language: 'Language',528  topics: 'Topics',529  pypi: 'PyPI',530  modality: 'Modality',531  size: 'Size',532  publisher: 'Publisher',533  description: 'Description',534};535export function propertyLabel(k: string): string {536  return PROPERTY_LABELS[k] ?? k.replace(/^metric\./, '').replace(/_/g, ' ').replace(/^\w/, (c) => c.toUpperCase());537}538/** Attribute keys that are URLs (rendered as links). */539export const URL_KEYS = new Set(['official_url', 'model_card_url', 'paper_url', 'repository_url', 'website', 'pricing_url', 'docs_url', 'pdf_url', 'code_url', 'spec_url']);540/** Soft / long text properties rendered as prose, not in the spec table. */541export const PROSE_KEYS = new Set(['description', 'abstract', 'summary', 'tagline', 'availability_note', 'notes', 'training_data_notes', 'safety_notes', 'hardware_requirements', 'known_limitations', 'methodology']);542543export const PREDICATE_LABELS: Record<string, { out: string; in: string }> = {544  develops: { out: 'Develops', in: 'Developed by' },545  owns: { out: 'Owns', in: 'Owned by' },546  operates: { out: 'Operates', in: 'Operated by' },547  available_through: { out: 'Available through', in: 'Serves' },548  evaluated_on: { out: 'Evaluated on', in: 'Evaluated models' },549  described_by: { out: 'Described by', in: 'Describes' },550  derived_from: { out: 'Derived from', in: 'Derivatives' },551  fine_tuned_from: { out: 'Fine-tuned from', in: 'Fine-tunes' },552  quantized_from: { out: 'Quantized from', in: 'Quantizations' },553  distilled_from: { out: 'Distilled from', in: 'Distillations' },554  merged_from: { out: 'Merged from', in: 'Merged into' },555  superseded_by: { out: 'Superseded by', in: 'Supersedes' },556  runs_on: { out: 'Runs on', in: 'Runs' },557  uses: { out: 'Uses', in: 'Used by' },558  manufactures: { out: 'Manufactures', in: 'Manufactured by' },559  funded_by: { out: 'Funded by', in: 'Funds' },560  acquired: { out: 'Acquired', in: 'Acquired by' },561  authored: { out: 'Authored', in: 'Authors' },562  works_at: { out: 'Works at', in: 'People' },563  uses_dataset: { out: 'Trained on', in: 'Used to train' },564  evaluates_on: { out: 'Evaluates on', in: 'Evaluated by' },565  integrates: { out: 'Integrates', in: 'Integrated by' },566  published_by: { out: 'Published by', in: 'Publications' },567};568export function predicateLabel(p: string, direction: 'out' | 'in'): string {569  return PREDICATE_LABELS[p]?.[direction] ?? p.replace(/_/g, ' ').replace(/^\w/, (c) => c.toUpperCase());570}571572export const OPENNESS_LABELS: Record<string, string> = { 'open-weights': 'Open weights', 'open-source': 'Open source', proprietary: 'Proprietary', restricted: 'Restricted' };573export const STATUS_LABELS: Record<string, string> = {574  active: 'Active',575  preview: 'Preview',576  deprecated: 'Deprecated',577  retired: 'Retired',578  announced: 'Announced',579  'limited-availability': 'Limited availability',580  unknown: 'Unknown',581};582export const TIER_LABELS: Record<number, string> = { 1: 'Official', 2: 'Quality secondary', 3: 'Community', 4: 'Unverified' };583584export const EXAMPLE_QUERIES = [585  'open models with more than 100B parameters',586  'cheapest model with 1M context',587  'models released in 2026',588  'Anthropic',589  'GPQA leaderboard',590  'MoE models with 128k context',591];592