Skip to content

Commit 25da27a

Browse files
committed
docs(webui): document the provider dialog interaction, bilingual
1 parent 506c0a9 commit 25da27a

2 files changed

Lines changed: 93 additions & 15 deletions

File tree

‎docs/webui.md‎

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,7 +1238,7 @@ clients:
12381238
| Archived tasks page | the tab renders its empty state; the list, its restore and its delete need the archived-session contract |
12391239
| Usage & models three-source switching | the segmented tabs now match the desktop form (ticket 53), but they are a **view switcher** — they do not switch the model source in use; real Token Plan / MiniMax API / custom-model routing plus source badges still need a model-routing contract |
12401240
| MiniMax API key panel | input + connectivity test + save-and-use |
1241-
| Custom model drag-reorder, per-model toggles, preset picker | provider contract work; adding is dialog-based (ticket 54), editing stays on the rail + editor surfaces |
1241+
| Custom model drag-reorder, per-model toggles, preset picker | provider contract work; add, edit and delete all converge on one dialog (this batch), leaving the list to display and delete |
12421242
| Search keyword highlighting | the reference itself never wired it (component + keyframes defined, no call site) |
12431243
| General-page dataDir footer | see the section table above |
12441244

@@ -1263,10 +1263,10 @@ The Token Plan view is the desktop's five blocks (the tabs plus four cards):
12631263
| Invoice row | The one fully live affordance: 申请 ↗ opens the MiniMax open platform in a new tab |
12641264

12651265
The custom-models view is the existing provider panel (API keys, protocols,
1266-
model lists, connection tests, preset one-click enable) with every
1267-
`data-testid` unchanged; ticket 54 rebuilt the **add** flow into the
1268-
desktop's dialog form (next section) while the rail + editor surfaces stay
1269-
as the editing path.
1266+
model lists, connection tests, preset one-click enable); ticket 54 rebuilt
1267+
the **add** flow into the desktop's dialog form (next section) and this batch
1268+
folded **editing** into that same dialog (see "One dialog also edits" below) —
1269+
the panel is now a list plus a delete affordance, with no second editor.
12701270

12711271
**Add-model dialog (ticket 54, 53b)**
12721272

@@ -1276,7 +1276,7 @@ deep-link — opens the desktop's modal instead of appending a rail draft:
12761276

12771277
| Dialog region | Contract |
12781278
| --- | --- |
1279-
| Provider select (「请选择提供商」) | Options are `GET /api/providers/presets` plus a 「+ 其他(自定义)」 sentinel; choosing a preset fills id / label / auth-type / baseURL and seeds 「API 格式」, choosing the sentinel expands the custom fields (id, display name, auth type, baseURL). DeepSeek / Zhipu AI(智谱)/ Moonshot AI (China) carry the reference's spellings; other local presets keep their catalogue labels. A 404 catalogue degrades to the custom-only dropdown |
1279+
| Provider select (「请选择提供商」) | Options are `GET /api/providers/presets` plus a 「+ 其他(自定义)」 sentinel; choosing a preset fills id / label / auth-type / baseURL and seeds 「API 格式」, choosing the sentinel expands the custom fields (id, display name, auth type); 接口地址 is a top-level field for both branches — see "One dialog also edits" below. DeepSeek / Zhipu AI(智谱)/ Moonshot AI (China) carry the reference's spellings; other local presets keep their catalogue labels. A 404 catalogue degrades to the custom-only dropdown |
12801280
| API 格式 | The desktop's **second** field, rendered for every provider rather than only for 「其他(自定义)」. It is the existing wire `protocol` under the desktop's labels — `OpenAI Completions` / `Anthropic Messages` / `Gemini` — so no new format reaches the backend. Choosing a preset seeds it from that preset's own protocol and it stays editable afterwards. The protocol select that used to sit inside the custom branch was removed rather than kept alongside: two controls bound to one value is how the preset and custom branches end up disagreeing about what gets saved |
12811281
| 自定义 Headers | Rows of (name, value) with 「+ 添加 Header」 and a per-row remove, held as a **list** rather than an object so a half-typed row survives editing. A blank name is dropped, a name is trimmed but a value is not, and a later duplicate wins — all three decided in one place (`headerPairsToRecord`), so the dialog, the PUT body and the server cannot disagree. Zero rows render an explicit placeholder rather than collapsing. The collapse result lands in `auth.headers` on the PUT body and comes back in `auth.headers` on `GET /api/providers` |
12821282
| API key (`AntInput.Password`) | The eye toggle is safe here and only here: the field's value is what the user just typed, not a masked placeholder — the editor's no-reveal rule (keep-existing-key convention) is untouched |
@@ -1288,8 +1288,11 @@ deep-link — opens the desktop's modal instead of appending a rail draft:
12881288
Ticket 54 invariants — no server-contract change (the `/api/providers` PUT
12891289
body, `/api/set-model`, and every endpoint are untouched; the whole delta is
12901290
client-side plus tests and docs); every pre-existing `data-testid` on the
1291-
panel/editor surfaces survives in source (`webapp/test/add-model-dialog.test.ts`
1292-
pins the list); the preset catalogue, thinkingLevels editing semantics,
1291+
panel/editor surfaces survived in source at the time
1292+
(`webapp/test/add-model-dialog.test.ts` pinned 36 + 2) — **that assertion is
1293+
now void**: after the flat editor's retirement its testids are pinned as
1294+
must-not-return instead, while the list chrome's testids are still pinned
1295+
one by one; the preset catalogue, thinkingLevels editing semantics,
12931296
provider grouping and the thinking-display exceptions are unchanged; the
12941297
auto-add deep-link still lands on the custom-models view, now opening the
12951298
dialog. The legacy rail-draft `addProvider` path and the editor's dead
@@ -1338,9 +1341,10 @@ reference `design-ref/screenshots/byok-custom-model-official.png`):
13381341
`POST /api/providers/test` contract verbatim — protocol whitelist,
13391342
local key-format check, then a real fetch against the configured
13401343
baseURL — with no new route. The dialog shell assembles the probe
1341-
from the current form values (preset branch takes the preset's
1342-
protocol/baseURL, custom branch takes the custom fields) with a 4s
1343-
timeout, and renders 「可达 · Nms」 (success token) / 「不可达:
1344+
from the current form values (the protocol comes from 「API 格式」,
1345+
the endpoint from the top-level 接口地址 field — which this batch
1346+
lifted out of the custom branch, so a preset is no longer pinned
1347+
to its catalogue endpoint) with a 4s timeout, and renders 「可达 · Nms」 (success token) / 「不可达:
13441348
error」 (error token). **Granularity, stated honestly**: the probe
13451349
is endpoint-level (baseURL + key) and does not exercise the
13461350
entry's model id — the tooltip and this paragraph say so rather
@@ -1415,6 +1419,55 @@ reference and there is no second source to check it against. Rebuilding
14151419
the model-entry structure is a larger change than this batch and is left
14161420
alone.
14171421

1422+
**This batch — one dialog also edits, the flat editor is retired, and
1423+
「API 格式」 drives the endpoint surface.**
1424+
1425+
| Change | Contract |
1426+
| --- | --- |
1427+
| Editing runs through the same dialog | A click on a list row opens the **same** modal (`editTarget`), seeded by `editSeedFromDraft`: a preset provider lands on its own catalogue branch, a custom one on 「+ 其他(自定义)」. Id, display name, auth type, 接口地址, the custom headers and the model entries all arrive pre-filled. The title switches from 添加模型 to 编辑模型 (`providers.dialog.editTitle`). Two entry points share one form rather than two forms free to drift |
1428+
| The commit starts from the stored record | The committed draft begins at the record being edited (`const base = editTarget ?? newDraftProvider()`); only the fields the form can reach are taken from form state. `enabled`, `preset` and `draftId` are properties of the **record**, not the form — rebuilding one would re-enable a provider the operator had disabled, or detach it from its preset. The panel locates and replaces the row by `draftId`, not by wire id: matching on the id would write a second record instead of renaming the first whenever a custom id is retyped |
1429+
| The key still never lands on disk in edit mode | The edit seed's API Key is always `""`, the server's keep-the-existing-key sentinel; the masked value reaches the placeholder only and is never written back as a value. An untouched field preserves the stored credential; a typed one replaces it |
1430+
| The flat editor is retired | `ProviderEditor` and its `ApiKeyInput` / `DraftModelList` / `DraftModelRow`, plus the panel-level 「保存供应商」 button, the whole-list validation and the selected-row probe, are deleted. Delete did not disappear with them: it moved onto the list row as 🗑 (`provider-delete-{draftId}`, `Popconfirm` confirmation). Preset rows still carry no delete, the retired editor's own rule — a preset's lifecycle belongs to the preset controls |
1431+
| The row element changed | The row is now a `<div>` wrapper rather than a `<button>`: it holds a second clickable control, and a button inside a button is invalid HTML with ambiguous keyboard semantics. The row's edit control is `provider-row-edit-{draftId}`, its delete is `provider-delete-{draftId}` |
1432+
| 接口地址 lifted to a top-level field | The field used to sit inside the 「+ 其他(自定义)」 branch, which meant **11 of the 12 preset providers could neither see nor change their endpoint**. It now renders for every provider: a preset choice seeds it with the catalogue endpoint, the operator may override it, and an empty field falls back to the same protocol default the server uses |
1433+
1434+
What the 「API 格式」 selection drives (`API_FORMAT_SPECS`, every value
1435+
transcribed verbatim from `server/lib/providers-config.js` —
1436+
`DEFAULT_BASE_URL` and `probe()`):
1437+
1438+
| Format | Default endpoint | What 连通检测 actually sends | How the key travels |
1439+
| --- | --- | --- | --- |
1440+
| `OpenAI Completions` | `https://api.openai.com` | `GET {baseURL}/v1/models` | `Authorization: Bearer` header |
1441+
| `Anthropic Messages` | `https://api.anthropic.com` | `POST {baseURL}/v1/messages` | `x-api-key` header |
1442+
| `Gemini` | `https://generativelanguage.googleapis.com` | `GET {baseURL}/v1beta/models?key=…` | **`?key=` query parameter**, not a header |
1443+
1444+
The hint under the field shows the **resolved** probe target: the typed
1445+
endpoint when there is one, the format's default when the field is blank —
1446+
i.e. the request the test button will send. The credential line switches with
1447+
it, and Gemini's says outright that the key rides in the query string;
1448+
copying the other two formats' "key in a header" habit yields a 401.
1449+
1450+
**KNOWN DEBT — per-format field show/hide was not built, and here is
1451+
why.** The three protocols carry the same field set in the
1452+
`/api/providers` PUT contract; the backend has no field to hide per format.
1453+
The linkage therefore lands on **dynamic content** (the endpoint default, the
1454+
probe request, the credential's transport) rather than on a field's presence:
1455+
hiding a field this backend cannot store would produce a form that lies about
1456+
what it saves, which is worse than not hiding it. Real per-format show/hide
1457+
requires extending the PUT contract and is its own batch.
1458+
1459+
**This batch's invariants.** No server-contract change (all five
1460+
`/api/providers` endpoints and `/api/providers/test` are reused as they are;
1461+
the whole delta is client-side plus tests and docs). The PUT body is
1462+
byte-identical — still `draftToWire` + `api.putProviders({version: 2})`, with
1463+
an edit substituting one record in the list. `enabled`, `preset`, the masking
1464+
convention, the preset catalogue and the thinkingLevels semantics are all
1465+
unchanged, and the engine layer, `sessions.js` and markdown were not touched.
1466+
On the test side the list chrome's testids stay pinned positively, the
1467+
retired editor's eleven are pinned negatively (a return turns the suite red),
1468+
two named suites cover the format linkage and the edit reuse, and ten revert
1469+
mutations were run against them — all ten were caught.
1470+
14181471
**Ticket 53 invariants** — no server-contract change (the delta:
14191472
`panels.tsx` / `usage-models-cards.tsx` / `icons.tsx` / `i18n.ts` plus two
14201473
test files); the h2 header, the per-tab fade-in and the 760px page width are

0 commit comments

Comments
 (0)