Skip to content

docs: add filterable AI models catalog snapshot#43

Merged
charlesrhoward merged 1 commit into
mainfrom
docs/ai-models-table
May 18, 2026
Merged

docs: add filterable AI models catalog snapshot#43
charlesrhoward merged 1 commit into
mainfrom
docs/ai-models-table

Conversation

@charlesrhoward
Copy link
Copy Markdown
Contributor

@charlesrhoward charlesrhoward commented May 18, 2026

Summary

  • Adds a client-side filterable AI models table to content/docs/web/models.mdx (search by id/provider/name, chip filters for provider and capability, columns for context length and per-Mtok pricing).
  • New pnpm gen:models script (scripts/gen-models-table.ts) writes a snapshot of the Supabase ai_models catalog to src/data/ai-models.json — 84 available, non-hidden models across 16 providers in the current run.
  • Reads NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY from env. The existing Vercel-managed values were already set on Preview/Production; I added the same pair to the Development environment so vercel env pull populates .env.local.

Why

The previous models.mdx argued against a static table because the live catalog can drift. Keeping it as a regenerated snapshot — owned by a single script and rendered by one client component — gives docs readers a browseable catalog without claiming it is real-time. The intro now positions the table as "a recent snapshot" with the web app as source of truth.

Test plan

  • pnpm types:check clean (verified locally)
  • pnpm lint clean (verified locally)
  • pnpm gen:models regenerates the snapshot deterministically
  • Visit /web/models in pnpm dev and confirm: table renders, search filters rows, provider/capability chips toggle, "Clear" resets, dark mode contrast is acceptable
  • Confirm Vercel preview build picks up the existing NEXT_PUBLIC_SUPABASE_* env vars (no runtime use; the JSON is committed)

Notes for reviewers

  • src/data/ai-models.json is committed and intended to be refreshed by hand-running pnpm gen:models. No build-time fetch.
  • The anon key + URL are public, but moved to Vercel env per request rather than hardcoded in the script.

🤖 Generated with Claude Code


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Surface the live ai_models catalog (84 models, 16 providers) on the
Available Models page as a client-side filterable table. Snapshot is
regenerated from Supabase via `pnpm gen:models`; credentials come from
the existing NEXT_PUBLIC_SUPABASE_* Vercel env vars.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mogplex-docs Ready Ready Preview, Comment May 18, 2026 9:29pm

Request Review

Copy link
Copy Markdown

@mogplex mogplex Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mogplex PR Review

Status: Attention needed

This PR adds a well-structured, client-side filterable AI models catalog to the docs. The component logic is solid and the script is clean. There is one security concern worth discussing: the Supabase project URL (enxvgkxsrpbtqlaeotjz.supabase.co) is hardcoded inside the committed ai-models.json as the source field. While the PR notes and description correctly acknowledge that the anon key is public-scope, permanently embedding an identifiable project reference in the git history is worth a deliberate choice rather than an accident. Additionally, there's a minor correctness issue with the formatPrice helper for sub-$0.001 values, and a missing no-pagination guard in the generator script. Overall the PR is close to merge-ready with one intentional decision to confirm and two small fixes.

4 findings were added inline.

View check run

Comment thread src/data/ai-models.json
@@ -0,0 +1,1710 @@
{
"source": "https://enxvgkxsrpbtqlaeotjz.supabase.co/rest/v1/ai_models",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning: Supabase project URL permanently embedded in committed JSON

The source field is "https://enxvgkxsrpbtqlaeotjz.supabase.co/rest/v1/ai_models" — the full project reference URL is now in git history forever. The PR description acknowledges the anon key is public, but the project ID (enxvgkxsrpbtqlaeotjz) is also now permanently disclosed in the repo, regardless of whether the key is ever rotated or the table ACLs change.

This may be completely intentional (the docs site is public, the anon key is intended to be public, and the URL is derivable anyway from NEXT_PUBLIC_SUPABASE_URL). If so, a brief comment in AGENTS.md or the PR description confirming this is acceptable would silence the concern for future readers.

If you'd prefer not to commit it, you can strip the source field from the JSON snapshot in the generator:

const snapshot = {
  fetched_at: new Date().toISOString(),
  count: rows.length,
  models: rows,
};

The URL is not consumed by the component (ai-models-table.tsx never reads snapshot.source), so removing it is purely cosmetic.

async function main() {
const url = new URL(`${SUPABASE_URL}/rest/v1/ai_models`);
url.searchParams.set('select', SELECT);
url.searchParams.set('is_hidden', 'eq.false');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning: Generator has no pagination — silently truncates at Supabase's default row limit

The Supabase REST API returns a maximum of 1000 rows by default (configurable per project, but often set lower). The script makes a single fetch with no limit override and no check that rows.length equals the expected total. If the catalog ever grows past the project's page size, the snapshot will silently contain a partial list with count reflecting only what was returned.

Fix: either set an explicit high limit, or assert that the response isn't suspiciously truncated:

url.searchParams.set('limit', '10000'); // Supabase max is configurable; set high
// After fetch:
if (rows.length === 0) throw new Error('No rows returned — check RLS / filters');
// Optionally: warn if near a round boundary that might indicate truncation
if (rows.length % 100 === 0) {
  console.warn(`Received exactly ${rows.length} rows — may be hitting a page limit.`);
}

function formatPrice(value: number | null): string {
if (value == null) return '—';
const perMtok = value * 1_000_000;
if (perMtok >= 1) return `$${perMtok.toFixed(2)}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: formatPrice produces $0.00 for very small values instead of a more useful string

For a pricing_input like 7e-8 (e.g. zai/glm-4.7-flash), perMtok = 7e-8 * 1_000_000 = 0.07. Since 0.07 < 1, the code falls into the toFixed(3) branch and produces "$0.07" — correct.

However, for a value of exactly 1e-9, perMtok = 0.001 and toFixed(3) gives "$0.001", then the regex strips trailing zeros leaving "$0.001" — fine. For 1e-10, perMtok = 0.0001 and toFixed(3) rounds to "$0.000", then both regexes strip it entirely to "$" — a broken output string.

No entry in the current snapshot triggers this, but it's a latent bug if a free/very-cheap model is added. Consider clamping to a minimum precision:

function formatPrice(value: number | null): string {
  if (value == null) return '—';
  const perMtok = value * 1_000_000;
  if (perMtok === 0) return '$0';
  if (perMtok >= 1) return `$${perMtok.toFixed(2)}`;
  if (perMtok >= 0.001) return `$${perMtok.toFixed(3).replace(/0+$/, '').replace(/\.$/, '')}`;
  return `$${perMtok.toExponential(1)}`; // e.g. "$1.0e-4"
}

label="Provider"
options={allProviders}
selected={providers}
onToggle={(v) => setProviders((s) => toggle(s, v))}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: DATA.count may not equal filtered.length — the footer copy could confuse readers

DATA.count is the value written by the generator at snapshot time (84). The footer reads "Showing X of 84 models". If someone reruns gen:models and the catalog shrinks (say, a provider is disabled), count and models.length could diverge, so DATA.count might not equal DATA.models.length. It would be safer to use DATA.models.length as the denominator:

Showing {filtered.length} of {DATA.models.length} models.

@charlesrhoward charlesrhoward merged commit 3149502 into main May 18, 2026
6 checks passed
@charlesrhoward charlesrhoward deleted the docs/ai-models-table branch May 18, 2026 21:35
charlesrhoward added a commit that referenced this pull request May 18, 2026
* docs: address PR #43 review findings on ai models table

- Generator now sets an explicit limit and errors on suspected truncation
  instead of silently returning a partial snapshot.
- formatPrice falls back to exponential notation below $0.001 so very
  cheap models no longer render as the broken string "$".
- Footer denominator uses live models.length, not the snapshot-time count.
- Drops the Supabase project URL from the snapshot — it is not consumed
  by the component and there is no reason to embed it in committed JSON.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: drop redundant count field from ai-models snapshot

The component reads DATA.models.length now, so count was dead data that
could silently drift out of sync on manual JSON edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant