fix(ui-models): restore the add-provider row and hint an empty capacity

The two ways to gain a provider had picked up the shared button base's pill
shape and shrunk to their labels, so they read as two stray buttons of
different lengths under the list instead of its last slot. They split the row
evenly again, on the row cards' own corner and the dashed outline this page
already uses for "nothing here yet"; the rule that overrides the base now
says so in one place rather than layering a second `.addButton` block.

An empty capacity shows the adapter's route-level fallback as its placeholder,
so a blank field reads as "sized by the route" rather than as a model with no
capacity. It is a hint, not a mirror: the field counts K as 1000 while the
fallback is 262144, and a deployment may override it.

The picker's description says what the list is without promising an edit the
rows themselves already offer.
This commit is contained in:
Yichen Jiang
2026-08-05 20:46:00 +08:00
committed by imccyu
parent dc7510a902
commit 3a3abc2bc4
6 changed files with 34 additions and 13 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-models/README.md
README.md: 9d9833cf269d6b2225605e5a4b095d291d97a6bd
README.zh.md: c19499a1e49a8d537a1722ab4dc4c4b6f0ee2026
README.md: b55914197e472edec8a8b6d4d3e02036d1697728
README.zh.md: ca93c3d5a2a85fffb22707f8389f1e979468e2ec

View File

@@ -12,7 +12,7 @@ Every edit lands as `settings.mutate` path ops against the stored section — a
## Model list and endpoint interrogation
A pi-ai profile's `models` list is edited on the card: one row per model showing its id and display name, with the context window and output cap behind a per-row disclosure and two label-free actions — expand and delete — on the right. An empty list means "serve this route's built-in catalog", so a row is only ever added deliberately; clearing a capacity drops it rather than storing a value the schema would reject, and the adapter's route-level fallbacks size whatever configuration leaves out. A capacity that is not a positive integer is simply not stored.
A pi-ai profile's `models` list is edited on the card: one row per model showing its id and display name, with the context window and output cap behind a per-row disclosure and two label-free actions — expand and delete — on the right. An empty list means "serve this route's built-in catalog", so a row is only ever added deliberately; clearing a capacity drops it rather than storing a value the schema would reject, and the adapter's route-level fallbacks size whatever configuration leaves out — an empty capacity shows those fallbacks' magnitude as its placeholder, a hint rather than a mirror, since the field counts `K` as 1000 and a deployment may override them. A capacity that is not a positive integer is simply not stored.
**Fetch available models** asks `llm.discoverModels` about the endpoint the form **currently shows**, including a base URL edited but not yet saved and a key typed but not yet stored, so adding a provider is one pass instead of save-then-return. The reply opens a picker rather than being written: candidates already configured start unchecked, so adopting a selection never overwrites a capacity the user corrected. A provider that cannot be interrogated is a detour, not a dead end — the adapter's own message appears beside the rows, which stay editable by hand.

View File

@@ -12,7 +12,7 @@
## 模型列表与端点询问
pi-ai profile 的 `models` 列表就在卡片上编辑:一行一个模型,行上显示 id 与显示名称,上下文窗口与输出上限收在该行的展开区内,右侧是两个无文字的操作——展开与删除。空列表意味着「使用该路由的内置 catalog」因此每一行都只会被刻意添加清空容量会丢弃它而不是存入一个 schema 会拒绝的值,配置留空的部分由适配器的路由级回退值定尺寸。不是正整数的容量根本不会被存下。
pi-ai profile 的 `models` 列表就在卡片上编辑:一行一个模型,行上显示 id 与显示名称,上下文窗口与输出上限收在该行的展开区内,右侧是两个无文字的操作——展开与删除。空列表意味着「使用该路由的内置 catalog」因此每一行都只会被刻意添加清空容量会丢弃它而不是存入一个 schema 会拒绝的值,配置留空的部分由适配器的路由级回退值定尺寸——留空的容量以这些回退值的量级作为占位符,那只是提示而非镜像:该字段按 1000 计 `K`,且部署可以覆盖这些回退值。不是正整数的容量根本不会被存下。
**获取可用模型**会针对表单**当前显示**的端点调用 `llm.discoverModels`,包括已修改但尚未保存的 API 地址和已键入但尚未存储的密钥,因此新增一个提供方是一趟走完,而不是「先保存再回来」。回复会打开一个选择框而不是直接写入:已配置过的候选默认不勾选,因此采纳一次选择绝不会覆盖用户已更正的容量。无法被询问的提供方只是绕路而非死路——适配器自己的消息会显示在各行旁边,而这些行仍可手工编辑。

View File

@@ -109,6 +109,22 @@ function IconTrash(): ReactNode {
/** The two token counts edited as K/M-suffixed text behind a row's disclosure. */
type CapacityField = 'contextWindow' | 'maxTokens'
/**
* What an empty capacity field is worth, shown as its placeholder so a row left
* blank does not read as a model with no capacity at all.
*
* The magnitudes are the adapter's own route-level fallbacks (`llm-pi-ai`'s
* `defaultContextWindow` and `defaultMaxTokens`), spelled the way a person
* would say them. They are a hint, not a mirror: this page counts `K` as 1000,
* so typing `256K` stores 256000 while leaving the field blank keeps the
* adapter's 262144. A deployment that overrides those defaults is not
* reflected here — nothing on this page can read them.
*/
const CAPACITY_HINT: Readonly<Record<CapacityField, string>> = {
contextWindow: '256K',
maxTokens: '32K',
}
/**
* Spell a stored count for a field that may be unset. The spelling itself is
* {@link formatCapacity}, shared with the DeepSeek catalog editor so both
@@ -373,6 +389,7 @@ export function ModelListEditor(props: ModelListEditorProps): ReactNode {
type="text"
inputMode="numeric"
value={capacityText(index, 'contextWindow')}
placeholder={CAPACITY_HINT.contextWindow}
aria-label={`${t('modelContextWindow')} ${index + 1}`}
disabled={disabled}
onChange={(event) => { editCapacity(index, 'contextWindow', event.target.value) }}
@@ -385,6 +402,7 @@ export function ModelListEditor(props: ModelListEditorProps): ReactNode {
type="text"
inputMode="numeric"
value={capacityText(index, 'maxTokens')}
placeholder={CAPACITY_HINT.maxTokens}
aria-label={`${t('modelMaxTokens')} ${index + 1}`}
disabled={disabled}
onChange={(event) => { editCapacity(index, 'maxTokens', event.target.value) }}

View File

@@ -273,14 +273,17 @@
}
.addButton {
/* Master's pill shape and glyph gap, sized to share the row equally so the
two ways to gain a provider read as siblings and line up with the rows
above rather than as two pills of different lengths. */
display: inline-flex;
align-items: center;
justify-content: center;
/* Overrides the shared button base above: these two are not pills sitting in
a footer but the last slot of the provider list, so they split the row
evenly and repeat the row cards' corner. Dashed, like every other "nothing
here yet" affordance on this page, to read as a place rather than a
command. */
flex: 1 1 0;
min-width: 180px;
gap: 6px;
align-self: flex-start;
height: 44px;
border: 1px dashed var(--dsw-alias-border-l3);
border-radius: 12px;
}
.addCard,

View File

@@ -61,7 +61,7 @@ export const en = {
fetchNeedsBaseUrl: 'Enter the base URL first, then fetch.',
fetchEmpty: 'The provider listed no models. Add them by hand.',
fetchTitle: 'Choose models to add',
fetchDescription: 'These are the models the provider reports. Choose the ones to add; you can still edit their capacities afterwards.',
fetchDescription: 'These are the models this provider has available. Choose the ones to add.',
fetchAdopt: 'Add selected',
customAdd: 'Add a custom provider',
customTitle: 'Custom provider',
@@ -145,7 +145,7 @@ export const zh: typeof en = {
fetchNeedsBaseUrl: '请先填写 API 地址,再获取。',
fetchEmpty: '该提供方没有列出任何模型,请手动添加。',
fetchTitle: '选择要添加的模型',
fetchDescription: '以下是提供方报告的模型勾选要添加的项,添加后仍可修改其容量。',
fetchDescription: '以下是模型提供方的可用模型勾选要添加的模型。',
fetchAdopt: '添加所选',
customAdd: '添加自定义提供方',
customTitle: '自定义提供方',