Merge pull request #4961 from deepseek-harness/worktree/model-discovery-display-name
fix(web): show discovered model names in picker
This commit is contained in:
commit
37e4dc2dee
7 files changed
+22
-14
No files matched your search
@@ -397,7 +397,7 @@ describe('web e2e: Models settings page configures a dormant provider', () => {
|
||||
const picker = page.getByRole('dialog', { name: '选择要添加的模型' })
|
||||
await picker.getByRole('button', { name: '取消全选' }).click()
|
||||
await picker.getByRole('searchbox', { name: '搜索模型' }).fill('gpt-6-astra')
|
||||
await picker.getByRole('checkbox', { name: 'gpt-6-astra', exact: true }).check()
|
||||
await picker.getByRole('checkbox', { name: 'GPT-6 Astra', exact: true }).check()
|
||||
await picker.getByRole('button', { name: '添加所选' }).click()
|
||||
await dialog.getByRole('button', { name: '模型选项 1' }).click()
|
||||
expect(await image.isChecked()).toBe(true)
|
||||
|
||||
@@ -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-settings-models/README.md
|
||||
README.md: 8bced338559ae1609ce3816f3ea7e760dcce2da7
|
||||
README.zh.md: ebb321842aa5a68b9cd3aa7355afdb249c71ee6d
|
||||
README.md: e0accf787aba9d2857887773b4f1a3d703fc1fc1
|
||||
README.zh.md: 39b443c2293a9da0f7a82bc70d10539a2d153aa8
|
||||
@@ -45,7 +45,7 @@ Expand **Customized settings → Model options** to edit each model. Both provid
|
||||
|
||||
### Adding and deleting providers
|
||||
|
||||
**Add model provider** is the one entry to the add card, offered while any editor-backed settings namespace is mounted and enabled while either mode below can proceed. The card opens on a segmented mode switch with a one-line purpose beneath it: **Third-party model provider** carries the dormant-directory provider select over the provider editor — a bare-mounted `llm-pi-ai` offers its whole installed catalog before any route exists — and **Custom model API** carries the create form for a route pi-ai does not ship: a relay, a self-hosted server, or any other OpenAI- or Anthropic-compatible endpoint. A mode is offered only while its namespace is mounted; with one mode the card shows that form alone under the mode's name, and a mode whose namespace has nothing left to adopt or no protocol to declare is disabled with the reason as its hover text. A panel mounts the first time its mode is shown and stays mounted, hidden, while the card is open and its mode stays offered, so switching modes discards neither draft; the switch locks while either panel has a write or an endpoint interrogation in flight, since a switch underneath one would orphan its answer. Each tab controls its panel through ids, and closing either panel forgets the catalog draft's target. The custom form asks for a unique **Provider ID**, an endpoint, a protocol, and at least one uniquely-identified model, because nothing can default those. The protocol picker names each protocol by its product name — OpenAI Chat Completions, OpenAI Responses, Anthropic Messages — while the stored value stays the schema identifier; a protocol the adapter adds before this page names it shows that identifier. The endpoint placeholder follows the selected protocol: OpenAI Chat Completions and Responses show `https://gateway.example/v1`, while Anthropic Messages shows `https://gateway.example` because its SDK appends `/v1/messages`. Changing the protocol preserves any address already entered. The endpoint must be a parseable HTTP or HTTPS URL; localhost, IPv4 and IPv6 literals, and custom ports remain valid. A syntax error blocks both discovery and creation at the field, while a request failure remains a separate provider error. **Fetch available models** asks the `llm/discoverModels` Remote about the endpoint the form shows, so adding a provider is one pass instead of save-then-return; the reply opens a searchable picker rather than being written, and nothing is written until **Add selected**. Each selected candidate copies its id, display name, context window, output-token cap, and disclosed input types into the editable row when disclosed, while an existing row retains its user-tuned values. Search matches model ids and optional display names without clearing hidden selections. **Select all** adds the visible results, while **Deselect all** clears the entire selection so hidden results cannot be adopted accidentally. A row is deletable only when the user layer alone carries it (removal restores the composition base), and its confirmation dialog names the provider.
|
||||
**Add model provider** is the one entry to the add card, offered while any editor-backed settings namespace is mounted and enabled while either mode below can proceed. The card opens on a segmented mode switch with a one-line purpose beneath it: **Third-party model provider** carries the dormant-directory provider select over the provider editor — a bare-mounted `llm-pi-ai` offers its whole installed catalog before any route exists — and **Custom model API** carries the create form for a route pi-ai does not ship: a relay, a self-hosted server, or any other OpenAI- or Anthropic-compatible endpoint. A mode is offered only while its namespace is mounted; with one mode the card shows that form alone under the mode's name, and a mode whose namespace has nothing left to adopt or no protocol to declare is disabled with the reason as its hover text. A panel mounts the first time its mode is shown and stays mounted, hidden, while the card is open and its mode stays offered, so switching modes discards neither draft; the switch locks while either panel has a write or an endpoint interrogation in flight, since a switch underneath one would orphan its answer. Each tab controls its panel through ids, and closing either panel forgets the catalog draft's target. The custom form asks for a unique **Provider ID**, an endpoint, a protocol, and at least one uniquely-identified model, because nothing can default those. The protocol picker names each protocol by its product name — OpenAI Chat Completions, OpenAI Responses, Anthropic Messages — while the stored value stays the schema identifier; a protocol the adapter adds before this page names it shows that identifier. The endpoint placeholder follows the selected protocol: OpenAI Chat Completions and Responses show `https://gateway.example/v1`, while Anthropic Messages shows `https://gateway.example` because its SDK appends `/v1/messages`. Changing the protocol preserves any address already entered. The endpoint must be a parseable HTTP or HTTPS URL; localhost, IPv4 and IPv6 literals, and custom ports remain valid. A syntax error blocks both discovery and creation at the field, while a request failure remains a separate provider error. **Fetch available models** asks the `llm/discoverModels` Remote about the endpoint the form shows, so adding a provider is one pass instead of save-then-return; the reply opens a searchable picker rather than being written, and nothing is written until **Add selected**. Each candidate displays its name, falling back to its id when no name is supplied. Rows stay on one line; hovering reveals the full text when it is truncated. Selection and configuration retain the original model id. Each selected candidate copies its id, display name, context window, output-token cap, and disclosed input types into the editable row when disclosed, while an existing row retains its user-tuned values. Search matches model ids and optional display names without clearing hidden selections. **Select all** adds the visible results, while **Deselect all** clears the entire selection so hidden results cannot be adopted accidentally. A row is deletable only when the user layer alone carries it (removal restores the composition base), and its confirmation dialog names the provider.
|
||||
|
||||
### First-run dialogs
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ Host 配置 `credentialOnboarding` 默认为 `true`。Electron preload 标记会
|
||||
|
||||
### 新增与删除提供商
|
||||
|
||||
**添加模型提供商**是新增卡片的唯一入口:只要任一带编辑器的设置 namespace 已挂载就会出现,下述两种方式任一可继续时才可点击。卡片打开后顶部是分段式的方式切换,下方一行小字说明所选方式的用途:**第三方模型提供商**承载休眠目录提供商选择框与提供商编辑器——裸挂载的 `llm-pi-ai` 在任何路由存在之前就能提供其完整的已安装 catalog;**自定义模型 API**承载为 pi-ai 不提供的路由(中转站、自部署服务或其他兼容 OpenAI / Anthropic 协议的接口)准备的创建表单。某种方式只在其 namespace 已挂载时提供;只剩一种方式时卡片以该方式为标题直接显示表单;namespace 已无可采用的提供商或没有可声明的协议时,对应方式会被禁用并把原因作为悬停提示。面板在其方式首次显示时挂载,之后在卡片打开且该方式仍被提供期间保持挂载但隐藏,因此切换方式不会丢掉任何一边的草稿;任一面板有写入或端点探测进行中时滑块锁定,因为此时切换会让结果落到看不见的面板上。每个 tab 通过 id 控制其面板,关闭任一面板都会忘记目录草稿的目标。自定义表单会索要唯一的 **Provider ID**、端点、协议与至少一个可唯一识别的模型,因为没有东西能为它们兜底。协议选择框以产品名称显示各协议——OpenAI Chat Completions、OpenAI Responses、Anthropic Messages——存储的值仍是 schema 标识符;适配器新增而本页尚未命名的协议直接显示其标识符。端点占位示例随所选协议变化:OpenAI Chat Completions 与 Responses 显示 `https://gateway.example/v1`,Anthropic Messages 显示 `https://gateway.example`,因为其 SDK 会追加 `/v1/messages`。切换协议会保留已输入的地址。端点必须是可解析的 HTTP 或 HTTPS URL;localhost、IPv4 与 IPv6 字面地址以及自定义端口仍然有效。语法错误会在字段处阻止询问与创建,请求失败则继续作为独立的提供商错误显示。**获取可用模型**通过 `llm/discoverModels` Remote 查询表单显示的端点,因此新增提供商一次即可完成,而非先保存再返回;回复打开的是可搜索选择器而非直接写入,只有点击**添加所选**才会写入。每个选中候选会在提供商公布相应信息时,把 id、显示名、上下文窗口、最大输出 token 数和已公布的输入类型复制进可编辑行;已经存在的行保留用户调整过的值。搜索会匹配模型 id 与可选显示名称,且不会清除隐藏项的勾选状态。**全选**会加入可见结果,而**取消全选**会清空全部勾选,以免意外采用隐藏结果。只有用户层单独携带某行时,该行才可删除(删除会恢复组合基线),其确认对话框会指名该提供商。
|
||||
**添加模型提供商**是新增卡片的唯一入口:只要任一带编辑器的设置 namespace 已挂载就会出现,下述两种方式任一可继续时才可点击。卡片打开后顶部是分段式的方式切换,下方一行小字说明所选方式的用途:**第三方模型提供商**承载休眠目录提供商选择框与提供商编辑器——裸挂载的 `llm-pi-ai` 在任何路由存在之前就能提供其完整的已安装 catalog;**自定义模型 API**承载为 pi-ai 不提供的路由(中转站、自部署服务或其他兼容 OpenAI / Anthropic 协议的接口)准备的创建表单。某种方式只在其 namespace 已挂载时提供;只剩一种方式时卡片以该方式为标题直接显示表单;namespace 已无可采用的提供商或没有可声明的协议时,对应方式会被禁用并把原因作为悬停提示。面板在其方式首次显示时挂载,之后在卡片打开且该方式仍被提供期间保持挂载但隐藏,因此切换方式不会丢掉任何一边的草稿;任一面板有写入或端点探测进行中时滑块锁定,因为此时切换会让结果落到看不见的面板上。每个 tab 通过 id 控制其面板,关闭任一面板都会忘记目录草稿的目标。自定义表单会索要唯一的 **Provider ID**、端点、协议与至少一个可唯一识别的模型,因为没有东西能为它们兜底。协议选择框以产品名称显示各协议——OpenAI Chat Completions、OpenAI Responses、Anthropic Messages——存储的值仍是 schema 标识符;适配器新增而本页尚未命名的协议直接显示其标识符。端点占位示例随所选协议变化:OpenAI Chat Completions 与 Responses 显示 `https://gateway.example/v1`,Anthropic Messages 显示 `https://gateway.example`,因为其 SDK 会追加 `/v1/messages`。切换协议会保留已输入的地址。端点必须是可解析的 HTTP 或 HTTPS URL;localhost、IPv4 与 IPv6 字面地址以及自定义端口仍然有效。语法错误会在字段处阻止询问与创建,请求失败则继续作为独立的提供商错误显示。**获取可用模型**通过 `llm/discoverModels` Remote 查询表单显示的端点,因此新增提供商一次即可完成,而非先保存再返回;回复打开的是可搜索选择器而非直接写入,只有点击**添加所选**才会写入。候选项显示模型名称,未提供名称时回退到 id。每行保持单行,文字截断时可悬停查看完整内容。勾选与配置仍使用原始模型 id。每个选中候选会在提供商公布相应信息时,把 id、显示名、上下文窗口、最大输出 token 数和已公布的输入类型复制进可编辑行;已经存在的行保留用户调整过的值。搜索会匹配模型 id 与可选显示名称,且不会清除隐藏项的勾选状态。**全选**会加入可见结果,而**取消全选**会清空全部勾选,以免意外采用隐藏结果。只有用户层单独携带某行时,该行才可删除(删除会恢复组合基线),其确认对话框会指名该提供商。
|
||||
|
||||
### 首次运行弹窗
|
||||
|
||||
|
||||
@@ -447,10 +447,9 @@ export function ModelListEditor(props: ModelListEditorProps): ReactNode {
|
||||
checked={picked.has(candidate.id)}
|
||||
onChange={() => { toggle(candidate.id) }}
|
||||
/>
|
||||
{/* The id alone: it is the string adoption writes, and the
|
||||
capacities the endpoint reported are adopted with it and
|
||||
editable in the row that appears. */}
|
||||
<span className={styles['candidateId']}>{candidate.id}</span>
|
||||
<span className={styles['candidateName']} title={candidate.name ?? candidate.id}>
|
||||
{candidate.name ?? candidate.id}
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -705,11 +705,13 @@ select.input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.candidateId {
|
||||
.candidateName {
|
||||
flex: 1 1 auto;
|
||||
font-family: var(--ds-font-family-code);
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
overflow-wrap: anywhere;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.candidateEmpty {
|
||||
|
||||
@@ -609,6 +609,8 @@ describe('endpoint interrogation', () => {
|
||||
|
||||
fireEvent.click(screen.getByText(en.fetchModels))
|
||||
await screen.findByText(en.fetchTitle)
|
||||
expect(screen.getByRole<HTMLInputElement>('checkbox', { name: 'Fresh' }).checked).toBe(true)
|
||||
expect(screen.queryByRole('checkbox', { name: 'fresh' })).toBeNull()
|
||||
// The already-configured row starts unchecked; the new one starts checked.
|
||||
const boxes = [...document.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')]
|
||||
expect(boxes.map(box => box.checked)).toEqual([false, true])
|
||||
@@ -748,6 +750,8 @@ describe('endpoint interrogation', () => {
|
||||
fireEvent.click(screen.getByText(en.fetchModels))
|
||||
const dialog = await screen.findByRole('dialog')
|
||||
const search = screen.getByLabelText<HTMLInputElement>(en.fetchSearch)
|
||||
expect(dialog.textContent).toContain('Beta Display')
|
||||
expect(dialog.textContent).not.toContain('opaque-id')
|
||||
expect([...dialog.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')]
|
||||
.map(box => box.checked)).toEqual([true, true, true])
|
||||
|
||||
@@ -755,9 +759,12 @@ describe('endpoint interrogation', () => {
|
||||
expect(dialog.textContent).toContain('alpha')
|
||||
expect(dialog.textContent).not.toContain('opaque-id')
|
||||
|
||||
// The display name is searchable even though adoption and the row use id.
|
||||
fireEvent.change(search, { target: { value: 'beta' } })
|
||||
expect(dialog.textContent).toContain('opaque-id')
|
||||
expect(dialog.textContent).toContain('Beta Display')
|
||||
expect(dialog.textContent).not.toContain('alpha')
|
||||
|
||||
fireEvent.change(search, { target: { value: 'opaque' } })
|
||||
expect(dialog.textContent).toContain('Beta Display')
|
||||
expect(dialog.textContent).not.toContain('alpha')
|
||||
|
||||
fireEvent.click(within_(dialog, en.fetchDeselectAll))
|
||||
|
||||
Reference in new issue
Block a user