Repository navigation
Merged
Conversation
…s cost is a footnote (#304) The owner asked for this: "这个费用不是重要信息,不要写这么大,小字备注即可.重要的反而是上次探测时间和下次探测预计". ## What changed - **The status card's two facts.** Under the band, the card on `/status/` and `/zh/status/` shows two facts. Each is a small caps label over a 15px semibold value: - **Last probe / 上次探测:** the local time with the relative time beside it, e.g. `Oct 25, 17:18 · 5 hours ago` or `10月25日 17:18 · 5小时前`. The value is still the link to the run, and its tooltip shows the full date. - **Next probe (expected) / 下次探测(预计):** the time from the `cron` of `status.yml`, as before. It is labeled expected because GitHub may start a scheduled run late. - At 375px the two facts stack. - **The month's probe cost.** It was a text-2xl figure on the right. It is now one 12px gray line under **Run a check now**, e.g. `Probe cost this month $0.031 · 3 runs` or `本月探测费用 $0.031 · 3 次`. The earlier months moved into its tooltip. - **Layout.** The owner followed up with "把立即检测和探测费用放到右边,这样高度可以优化一下". The times sit on the left. The button, with the cost line under it, sits on the right, so on a desktop the card body is one row instead of three: the pager row moved up from y=331 to 265 at 1280px. - From about 760px (en) or 720px (zh) down to 640px, the button and cost wrap to a second line, still right-aligned. - On a phone they sit under the times, the cost under the button, so the cost never wraps in after the script fills it. - **No layout shift.** The facts start as `—` and are never hidden. The footnote is visible from the build when the build has the month's file. The band's title gets `min-h-7`, because it is filled by the script and its empty `<p>` used to collapse by 8px. - **Channel lines.** The owner also asked: "每个渠道那行的费用去掉吧,模型监测页面状态条上面的". The head line above each channel's strip drops the channel's cost for this month and keeps TTFT, TPS and uptime. A day's card, opened from a bar, still shows that day's cost. - **Strings.** `STRINGS.status` drops `checked`, `next` and `month` and gains `lastProbe`, `nextProbe` and `cost`, in both languages. ## Verification (vps4) - `npm ci && npm run build` in `site/`, with `MMSP_STATUS_DIR` set to today's `status-data` files. vps4 cannot reach raw.githubusercontent.com, so the files were copied there. `site/` has no check or lint script. - **Puppeteer:** `/status/` and `/zh/status/` at 1280 and 375, light and dark. - The values, the run link (`…/actions/runs/40180079133`), both full-date tooltips and the footnote text are as above. - No horizontal overflow and no page errors. - **The pager row's y**, with JS disabled vs. after the script ran, is the same at every width checked: - 265 / 265 at 1280, in en and zh. - 426.5 / 426.5 at 375, in en and zh. - Before the band's `min-h-7` fix it moved by 8px. A first try that put the button and cost in one row on phones moved by 24px (en) and 70px (zh), so that version was dropped. - At 640, 720 and 820px, en and zh, there is no horizontal overflow. ## Left as is - The zh button and progress strings say 检测 (立即检测一次), while the card now says 探测, which is the owner's wording. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…ndex assembles each kit in the browser (#305) The owner asked for a Model Index JSON without `kit`, since the page can assemble it, and without `vendor`, keeping only the provider. This PR does that and drops the per-locale `kits.json` files too: the page now builds each dialog in the browser from one shared template. On the open question of this PR, the owner decided that every dialog uses the full constructor (see below). ## What changed - **`/models.json`** has one row per line. Each row holds `model`, `base_url`, `client`, `provider` (the name), `input_modalities`, `output_modalities`, `context_window`, `pricing` and `api_key_env`. `pricing` holds only the token buckets `prompt_tokens`, `response_tokens` and `cached_tokens`. `api_key_format` appears only where the provider's key is not a plain token (Vertex AI, Bedrock, vLLM), with the English hint as before. Missing values are left out, not written as `null`. The file is still one JSON value, and `JSON.parse` reads it. - **New top-level `call`.** One recipe works for every row. I checked it against the constructors in `src_py/mmsp/auto_client.py` (`model, api_key, base_url, client_type`) and `src_ts/src/autoClient.ts` (`model, apiKey, baseUrl, clientType`): ```json "call": { "python": "AutoLLMClient(model=model, client_type=client, base_url=base_url, api_key=os.environ[api_key_env])", "typescript": "new AutoLLMClient({ model, clientType: client, baseUrl: base_url, apiKey: process.env[api_key_env] })", "docs": "https://mmsp.penguin.ooo/docs/quickstart/" } ``` The top-level `pricing` sentence is now the only place that says USD. It also says thoughts are billed at `response_tokens`. The endpoint reads `listSupportedModels("USD")` and `providerOf` directly, so it no longer builds 345 kits. The build fails if a registry row ever bills thoughts at a different price from the response. - **`site/src/shared/kit.ts`** is new and safe to load in the browser: no `import.meta.env`, no `?raw`, and only `localePath` from `i18n.ts`. These moved out of `catalog.ts`: `literal`, `ASK`, `PLAYBACK`, `TS_BODY`, `PY_BODY`, `prompt`, `indent`, `typescriptProgram`, `pythonProgram`, `PROMPT`, `formatContext` and `formatPrice`. It also holds `kitFor(entry, provider)`, `agentPromptFor(entry, provider, locale, site, modality)` and `catalogKit(...)`, which is the old per-row body of `catalogKits`. `kitFor` always writes the full constructor, so `KitEntry` has no `short` and `pythonProgram` always imports `os`. - **`catalog.ts`** now runs only at build time. It keeps `statusKey` and `catalogRows`, and re-exports `literal`, `formatContext` and `formatPrice`. Two functions are new: `kitEntry()` works out each entry's kind, and `kitProvider()` gives the provider fields a kit needs. `CatalogRow.kit` and `catalogKits()` are gone, and so is the `MODEL_FAMILIES` parsing (`credentials.ts?raw`, `FAMILIES`, `familyClient`), which only the short rule used. - **`providers.ts`** lost the `official` field (client and base URL of the vendor's own endpoint) of its seven vendor providers. Only the short rule read it, and the status page's script, which also imports `providers.ts`, shrinks by 93 B gz. - **`ModelCatalog.astro`** - Each `<tr>` gains `data-client`, `data-p` (an index into one `<script type="application/json" data-kit-providers>` table), `data-kind` (left out for text) and `data-in`/`data-out` (modality letters TIVAE, in registry order). The section gains `data-site`. - The model id and base URL come from attributes the row already has: the model cell's text and the provider cell's `title`. - `showKit` builds the kit synchronously. The fetch, the prefetch, the stale-reply guard, the loading and failure lines, the `.kit-loading` shimmer CSS and the `kit.loading`/`kit.loadFailed` strings are all removed. - The modality labels travel in `data-words`. Importing `STRINGS` would have added about 4.0 KB gz to the client bundle (`i18n.ts` alone is 4,027 B at gzip -9), so I passed the labels instead. - Deleted `site/src/pages/models/kits.json.ts` and `site/src/pages/zh/models/kits.json.ts`. - Updated the `/models.json` line in `llms.txt`. In `site/README.md`, the file table and the "Models and status" paragraph no longer mention kits.json, the README no longer says `/models.json` carries the programs, and the sentence about the short form now says every dialog passes the model, client type, base URL and key variable. - Added the changelog entry `changelog/unreleased/2026-10-25-models-json-slim.md` and its `.zh.md`, plus an index line in each unreleased README. ## Owner's decision: the full constructor in every dialog > 弹窗可以统一成完整写法 Every program and server row in the dialog now has the full form, the same one `call` describes: - **TypeScript:** the multi-line constructor with `model`, `clientType`, `baseUrl` and `apiKey: process.env.<ENV>`. - **Python:** `import os` and the multi-line constructor with `model`, `client_type`, `base_url` and `api_key=os.environ["<ENV>"]`. - **Server row:** `model_id`, `base_url`, `api_key`, `server_model_id` and `client_type`, in that order. - **Agent prompt:** it embeds both programs, so it follows. **55 rows per locale changed**, not the ~88 estimated. They are exactly the 55 rows at the seven vendors' own endpoints, all of which had the short form, and the previous build had 55 `data-short` attributes per locale. By endpoint: Gemini API 15, OpenAI 14, Anthropic 10, Z.ai 5, Moonshot 5, DeepSeek 4, MiniMax 2. By kind: 48 text, 2 embedding, 2 image, 3 speech. The other 290 rows already used the full form and are byte-identical. The run lines did not change for any row. The full form now matches the registry test, which constructs every entry with `model`, `apiKey`, `baseUrl` and `clientType` (`src_ts/tests/client.test.ts`). ## Removed from `/models.json`, field by field | Field | How to get it now | | --- | --- | | `kit` (TypeScript and Python programs, server row) | Fill the top-level `call` with the row's fields. The full programs are still on the Model Index, in each row's dialog | | `vendor` | Dropped. A gateway's model id names the maker (`anthropic/claude-opus-5.5`), and the page still shows the maker's mark | | `status_key` | `` `${client}+${base_url}+${model}` ``, as `status.key` says | | `pricing.currency` | Always USD, as the top-level `pricing` says | | `pricing.thoughts_tokens` | Always equal to `response_tokens`: every row goes through `usd()` in `src_ts/src/registry.ts`, and the build now checks this | | `null` values | Left out: a missing `context_window` or `pricing` means the registry has none | | `/models/kits.json`, `/zh/models/kits.json` | Removed. The page builds each dialog from the row | ## Sizes (bytes, gzip -9, all builds made on vps4) | | origin/dev 5a0550a | this branch (1b575d7) | | --- | --- | --- | | `/models.json` | 837,545 raw / 23,592 gz | **115,078 raw / 6,798 gz** | | `/models/` HTML | 1,154,025 / 53,974 | 1,179,159 / 55,255 | | `/zh/models/` HTML | 1,166,447 / 54,788 | 1,191,590 / 56,124 | | catalog script (`ModelCatalog…js`) | 7,251 / 2,927 | 15,767 / 6,593 | | status script (`StatusBoard…js`) | 18,359 / 6,869 | 17,830 / 6,776 | | `/models/kits.json` (prefetched after load) | 1,838,349 / 35,934 | gone | | `/zh/models/kits.json` | 1,865,338 / 36,630 | gone | Against the previous head (dafdbd0), the full constructor made `/models/` 605 B raw and 23 B gz smaller, because the `data-short` attributes are gone, and the catalog script 199 B raw and 45 B gz smaller. Apart from those attributes and the two renamed chunks, both models pages are byte-identical to the dafdbd0 build. The page HTML stays under the 60 KB gz budget. Per visit, a reader now downloads about 31 KB gz less: the kits.json is gone (−35.9 KB), while the HTML grows by 1.3 KB and the script by 3.7 KB. ## Load (`perf.mjs`: Slow 4G, 4× CPU, 390×844, median of 5 runs, ms) "Before" is origin/dev. "After" was re-run on 1b575d7. | path | mode | DCL before → after | FCP before → after | kits.json fetch before | click → code before → after | | --- | --- | --- | --- | --- | --- | | `/models/` | cold | 951 → 966 | 600 → 608 | 954 → 1297 | 361 → 85 | | `/models/` | warm | 941 → 954 | 596 → 592 | 943 → 1290 | 146 → 101 | | `/zh/models/` | cold | 943 → 948 | 604 → 584 | 945 → 1297 | 371 → 94 | | `/zh/models/` | warm | 944 → 952 | 596 → 592 | 947 → 1298 | 148 → 95 | DCL is 5 to 15 ms later, which is about what the extra 5 KB gz costs on Slow 4G. FCP moved by no more than 20 ms either way. The roughly 340 ms kits.json download after load is gone. A dialog now fills in 85 to 101 ms after the click, against 361/371 ms cold and 146/148 ms warm before. ## Dialog check against origin/dev's kits.json: 580 identical, 110 expanded as expected Method: I built origin/dev and kept its `dist/models/kits.json` and `dist/zh/models/kits.json` as golden files. A puppeteer-core script ran headless Chrome against a static server on this branch's `dist`. For every `tr[data-row]` on `/models/` and `/zh/models/`, it clicked the row's code button and read each tab's code and run text (`textContent`; nothing is highlighted). It clicked Copy for agent with `navigator.clipboard.writeText` stubbed. A second script compared each row with `golden[row.dataset.key]`: - **A golden row with the full form** must be byte-identical in all three tabs, the three run texts and the agent prompt. - **A golden row with the short form** must equal golden after three edits. The one-line TypeScript and Python constructors expand to the full ones, filled from the row's `client` and `base_url` and the `api_key_env` of `/models.json`. `import os` goes after `import asyncio`, and the server row is rebuilt with `base_url` and `client_type`. The agent prompt gets the same constructor edits. The run texts must be unchanged. - **Every row** must contain, in TypeScript, `clientType:`, `baseUrl:` and `apiKey: process.env.`; in Python, `import os`, `client_type=`, `base_url=` and `api_key=os.environ[`; and in the server JSON, `base_url` and `client_type` equal to the row's, keys in the order `model_id, base_url, api_key, server_model_id, client_type`. The agent prompt must contain both programs verbatim, and no one-line constructor may remain. ``` slim-diff.mjs (collection): en: 345 rows, 290 identical, 0 golden keys without a row, kits.json requests 0, errors 0 zh: 345 rows, 290 identical, 0 golden keys without a row, kits.json requests 0, errors 0 580/690 identical full-check.mjs: en: {"rows":345,"goldenShort":55,"goldenFull":290,"unchanged":290,"changed":55,"changedAsExpected":55,"fullChecks":345} en: changed rows by kind {"text":48,"image":2,"audio":3,"embed":2} en: line diffs of the changed rows (field -removed +added: rows) {"typescript -1 +6":55,"python -1 +7":55,"server -1 +3":55,"agent -2 +13":55} zh: {"rows":345,"goldenShort":55,"goldenFull":290,"unchanged":290,"changed":55,"changedAsExpected":55,"fullChecks":345} zh: changed rows by kind {"text":48,"image":2,"audio":3,"embed":2} zh: line diffs of the changed rows (field -removed +added: rows) {"typescript -1 +6":55,"python -1 +7":55,"server -1 +3":55,"agent -2 +13":55} all checks passed ``` The server row's `-1` is `"server_model_id": "…"`, which gains a trailing comma. <details> <summary>Before/after, one row per kind, each tab (en; zh programs are the same)</summary> All four sample rows are at the Gemini API, so the added lines differ only by model id. Text, `gemini-4-argon`: ```diff async function main() { - const client = new AutoLLMClient({ model: "gemini-4-argon" }); + const client = new AutoLLMClient({ + model: "gemini-4-argon", + clientType: "google-official", + baseUrl: "https://generativelanguage.googleapis.com", + apiKey: process.env.GEMINI_API_KEY, + }); for await (const event of client.streamingResponseStateful({ ``` ```diff import asyncio +import os ... async def main(): - client = AutoLLMClient(model="gemini-4-argon") + client = AutoLLMClient( + model="gemini-4-argon", + client_type="google-official", + base_url="https://generativelanguage.googleapis.com", + api_key=os.environ["GEMINI_API_KEY"], + ) async for event in client.streaming_response_stateful( ``` ```diff "model_id": "gemini-4-argon", + "base_url": "https://generativelanguage.googleapis.com", "api_key": "$GEMINI_API_KEY", - "server_model_id": "gemini-4-argon" + "server_model_id": "gemini-4-argon", + "client_type": "google-official" } ``` The embedding row (`gemini-embedding-2`), the image row (`gemini-3.1-flash-lite-image`) and the speech row (`gemini-3.8-flash-tts`) have the same hunks with their own model id. In the image and speech programs, `import os` lands between `import asyncio` and `from pathlib import Path`. </details> `/models.json` check, unchanged by this commit (the file is byte-identical to dafdbd0's): - It parses, has 345 rows and 345 row lines, and contains no `null`. - No row has `kit`, `vendor` or `status_key`, and no `pricing` has `currency` or `thoughts_tokens`. - Every (client, base_url, model) maps to a baseline row with the same `status_key`, modalities, `context_window`, `prompt_tokens`/`response_tokens`/`cached_tokens`, `api_key_env`, `api_key_format` and `provider`. - In the baseline, `thoughts_tokens == response_tokens` on every priced row. ## Screenshots I re-took the 17 dialog shots on 1b575d7 and compared them with origin/dev's. They cover a text row (`gpt-6.1-sol`), a gateway row (`amazon/nova-premier-v1` on OpenRouter), embedding (`gemini-embedding-2`), image (`gemini-3.1-flash-lite-image`), speech (`gemini-3.8-flash-tts`), Vertex (`gemini-4-argon`, service-account hint) and Bedrock (`global.anthropic.claude-fable-5-1`). Each was taken in en and zh light at 1440, plus Vertex zh dark, speech en dark and Bedrock zh at 390. - **8 PNGs are byte-identical:** the gateway, Vertex and Bedrock shots, whose rows already had the full form. - **9 differ, as expected:** text, embedding, image and speech in en and zh, plus speech en dark. These are former short rows. I looked at two new shots, `gemini-4-argon` at the Gemini API (TypeScript, zh, light, 1440) and `claude-opus-5-5` (Python, zh, light, 1440). Each lays out like the OpenRouter row did before: the multi-line constructor sits in the code pane, and the Run block starts at the bottom edge of a 900-px window, where the panel scrolls. ## Build `npm ci && npm run build` succeeds on vps4. `dist/models/kits.json` and `dist/zh/models/kits.json` are absent. site/package.json has no `check` script, so none ran. A strict `tsc` (typescript 5.9.3 via npx, temporary config, nothing added to the project) passes on `kit.ts`, `catalog.ts` and the dialog's `<script>` body at 1b575d7. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Contributor
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Syncs
devintomain, so the site deploys with:/status/and/zh/status/./models.json. It droppedkit,vendor,status_key,pricing.currencyandpricing.thoughts_tokens. It is one row per line, has no nulls, and gains a top-levelcallthat works for every row. Size: 837,545 → 115,078 bytes.kits.jsonremoved./models/kits.jsonand/zh/models/kits.jsonare gone. The Model Index builds each dialog in the browser.🤖 Generated with Claude Code