LLM Cost Calculator — programmatic SEO

The model directory at /llm-cost-calculator/* is a fully Supabase-backed pSEO surface. Every page renders statically from build-time queries. UGC (edit suggestions, add-a-model requests + votes) writes through Postgres RPCs from the browser anon key.

Routes

URLSourceGenerated by
/llm-cost-calculatorindex.astrohard-coded
/llm-cost-calculator/{provider}[provider]/index.astrogetAllProviders() ∩ providers with ≥1 model
/llm-cost-calculator/{provider}/{model}[provider]/[model].astrogetAllModels() (~2,500 pages)
/llm-cost-calculator/comparecompare/index.astrogetTopComparisonPairs(120)
/llm-cost-calculator/compare/{a-vs-b}compare/[pair].astrogetTopComparisonPairs(1500)

Total: ~4,200 static pages once seeded.

Where the data comes from

agentcc-gateway/internal/modeldb/litellm.json   ─┐
                                                  ├──→  scripts/seed-models.mjs  ──→  Supabase ──→  build-time read ──→  static HTML
OpenRouter /api/v1/models (gap-fill pricing)    ─┘                                  models_public · model_perf_latest · model_benchmarks_primary

Run order:

  1. supabase/models-schema.sql (paste once into Supabase SQL editor)
  2. node scripts/seed-models.mjs (re-runs weekly; idempotent upsert)
  3. npm run build (Astro reads Supabase, renders 4k+ pages)

What sets this apart from Maxim’s calculator

DimensionMaximFuture AGI
Models covered2,0222,714+
Modalitieschat onlychat, embedding, image, audio, video, rerank, OCR, moderation
Pricing freshness signalnonelast_verified_at on every page, weekly cron
Edit suggestionsnoone-click form per field, queued in DB
Add-a-model requestsnoUGC table with public vote counts
Where-it’s-good guidancenoderived from caps + benchmarks
Public benchmark scoresnoMMLU / GPQA / HumanEval / SWE-bench / Arena ELO with citations
First-party perf datanogateway latency aggregates when available
Routing recommendationnocopy-paste Agent Command Center config per page
Schema.orgSoftwareApplication onlyProduct + Offer + FAQPage + BreadcrumbList
Sibling comparisonsnocheaper / larger context / higher max output
llms.txtnoevery provider hub listed

File layout

src/
├── lib/
│   ├── models-data.ts          # build-time fetcher (anon key, cached)
│   └── models-ugc.ts           # browser RPC client
├── components/
│   └── models/
│       ├── CostCalculator.tsx        # React island (sliders + presets)
│       ├── EditField.tsx             # React island (suggest-edit form)
│       ├── RequestModelButton.tsx    # React island (add-a-model dialog)
│       ├── PriceRow.astro            # pricing-table row + edit-button
│       ├── Spec.astro                # spec dl row + edit-button
│       ├── PerfStat.astro            # latency stat card
│       ├── SiblingColumn.astro       # cheaper/bigger/faster column
│       ├── CompareRow.astro          # head-to-head row with auto-highlight
│       └── HeroStat.astro            # directory hub stat tile
├── pages/
│   └── llm-cost-calculator/
│       ├── index.astro                       # master hub
│       ├── [provider]/
│       │   ├── index.astro                   # provider hub
│       │   └── [model].astro                 # ★ the workhorse
│       ├── compare/
│       │   ├── index.astro                   # comparison hub
│       │   └── [pair].astro                  # one comparison
│       └── README.md                         # this file
public/
└── llms.txt                                  # extended with all provider hubs
supabase/
└── models-schema.sql                         # 8 tables + 3 views + 3 RPCs
scripts/
├── lib/
│   ├── slugs.mjs                             # provider + model slug normalizer
│   └── benchmarks.mjs                        # 24 reference benchmarks
├── seed-models.mjs                           # litellm + OpenRouter loader
└── MODEL-DIRECTORY-SEED.md                   # runbook

Adding a new mode (e.g. realtime)

  1. Add to the mode CHECK constraint in models-schema.sql (already includes most modes).
  2. Update MODE_MAP in scripts/lib/slugs.mjs.
  3. Add an entry to MODE_ORDER in index.astro for hub display.
  4. Re-seed.

Adding a new provider

  1. Append to PROVIDERS and LITELLM_PROVIDER_MAP in scripts/lib/slugs.mjs.
  2. Re-run the seed loader — it upserts model_providers and brings in the models.
  3. The provider hub page generates automatically because getStaticPaths filters by providers-with-models.

Adding benchmark scores

For now, populate model_benchmarks from the Supabase dashboard (one row per model × benchmark × source). A future enhancement is a scrape pipeline driven by the benchmarks.upstream_url column.

Adding gateway-derived perf

model_perf rows get inserted by a separate aggregation pipeline (out of scope for this code). The view model_perf_latest picks the freshest gateway row per model automatically.