Merge branch 'master' into feat/plugin-refine

This commit is contained in:
Yifffan authored and GitHub committed 2026-09-18 15:16:13 +08:00
commit 0c4f554d6e
6 files changed
+20 -8

No files matched your search

@@ -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: 6feb95d987f11bcf81b9bfa2c67068567244dcec
README.zh.md: 9316d698bdf11f630f5815b9fffd0bbdc4546031
README.md: c1567814b5de39f9b8819fcc8848e36c0a6ff842
README.zh.md: baa0f02e8274a7146f9eade36d3ad464ac944b27
+1 -1
View File
@@ -43,7 +43,7 @@ Expand **Customized settings → Model options** to edit each model. Both provid
### Adding and deleting providers
The add flow is a card carrying the dormant-directory provider select — a bare-mounted `llm-pi-ai` offers its whole installed catalog before any route exists. **Add a custom provider** declares a route pi-ai does not ship; the create card asks for a unique **Provider ID**, an endpoint, a protocol, and at least one uniquely-identified model, because nothing can default those. 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.
The add flow is a card carrying the dormant-directory provider select — a bare-mounted `llm-pi-ai` offers its whole installed catalog before any route exists. **Add a custom provider** declares a route pi-ai does not ship; the create card asks for a unique **Provider ID**, an endpoint, a protocol, and at least one uniquely-identified model, because nothing can default those. 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.
### First-run dialogs
@@ -43,7 +43,7 @@ kind: "package-reference"
### 新增与删除提供方
「新增」流程是一张承载休眠目录提供方选择框的卡片——裸挂载的 `llm-pi-ai` 在任何路由存在之前就能提供其完整的已安装 catalog。**添加自定义提供方**声明一条 pi-ai 不提供的路由;创建卡片会索要唯一的 **Provider ID**、端点、协议与至少一个可唯一识别的模型,因为没有东西能为它们兜底。端点必须是可解析的 HTTP 或 HTTPS URL;localhost、IPv4 与 IPv6 字面地址以及自定义端口仍然有效。语法错误会在字段处阻止询问与创建,请求失败则继续作为独立的提供方错误显示。**获取可用模型**通过 `llm/discoverModels` Remote 查询表单显示的端点,因此新增提供方一次即可完成,而非先保存再返回;回复打开的是可搜索选择器而非直接写入,只有点击**添加所选**才会写入。每个选中候选会在提供方公布相应信息时,把 id、显示名、上下文窗口、最大输出 token 数和已公布的输入类型复制进可编辑行;已经存在的行保留用户调整过的值。搜索会匹配模型 id 与可选显示名称,且不会清除隐藏项的勾选状态。**全选**会加入可见结果,而**取消全选**会清空全部勾选,以免意外采用隐藏结果。只有用户层单独携带某行时,该行才可删除(删除会恢复组合基线),其确认对话框会指名该提供方。
「新增」流程是一张承载休眠目录提供方选择框的卡片——裸挂载的 `llm-pi-ai` 在任何路由存在之前就能提供其完整的已安装 catalog。**添加自定义提供方**声明一条 pi-ai 不提供的路由;创建卡片会索要唯一的 **Provider ID**、端点、协议与至少一个可唯一识别的模型,因为没有东西能为它们兜底。端点占位示例随所选协议变化: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 与可选显示名称,且不会清除隐藏项的勾选状态。**全选**会加入可见结果,而**取消全选**会清空全部勾选,以免意外采用隐藏结果。只有用户层单独携带某行时,该行才可删除(删除会恢复组合基线),其确认对话框会指名该提供方。
### 首次运行弹窗
@@ -236,7 +236,9 @@ export function CustomProviderCard(props: CustomProviderCardProps): ReactNode {
className={styles['input']}
type="text"
value={baseURL}
placeholder={t('customBaseUrlPlaceholder')}
placeholder={t(protocol === 'anthropic-messages'
? 'customAnthropicBaseUrlPlaceholder'
: 'customBaseUrlPlaceholder')}
aria-label={t('baseUrl')}
aria-invalid={baseUrlInvalid}
disabled={profileDisabled}
@@ -93,6 +93,7 @@ export const en = {
customBaseUrlInvalid: 'Enter a valid HTTP or HTTPS URL.',
customNeedsModels: 'A custom provider needs at least one model.',
customBaseUrlPlaceholder: 'https://gateway.example/v1',
customAnthropicBaseUrlPlaceholder: 'https://gateway.example',
settingsPathUnresolvable: 'unresolvable settings path',
create: 'Create provider',
creating: 'Creating\u2026',
@@ -204,6 +205,7 @@ export const zh: { [Key in keyof typeof en]: string } = {
customBaseUrlInvalid: '请输入有效的 HTTP 或 HTTPS 地址。',
customNeedsModels: '自定义提供方至少需要一个模型。',
customBaseUrlPlaceholder: 'https://gateway.example/v1',
customAnthropicBaseUrlPlaceholder: 'https://gateway.example',
settingsPathUnresolvable: '无法解析设置路径',
create: '创建提供方',
creating: '创建中\u2026',
@@ -1372,10 +1372,18 @@ describe('hand-declared providers', () => {
it('creates with the chosen protocol and no display name', async () => {
const { mutate, onClose } = mountCard()
const baseUrl = screen.getByLabelText<HTMLInputElement>(en.baseUrl)
const protocol = screen.getByLabelText(en.customApi)
expect(baseUrl.placeholder).toBe('https://gateway.example/v1')
fireEvent.change(protocol, { target: { value: 'openai-responses' } })
expect(baseUrl.placeholder).toBe('https://gateway.example/v1')
fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
fireEvent.change(screen.getByLabelText(en.baseUrl), { target: { value: 'https://acme.test/v1' } })
fireEvent.change(screen.getByLabelText(en.customApi), { target: { value: 'anthropic-messages' } })
fireEvent.change(baseUrl, { target: { value: 'https://acme.test/anthropic' } })
fireEvent.change(protocol, { target: { value: 'anthropic-messages' } })
expect(baseUrl.placeholder).toBe('https://gateway.example')
expect(baseUrl.value).toBe('https://acme.test/anthropic')
fireEvent.click(screen.getByRole('button', { name: en.addModel }))
fireEvent.change(screen.getByLabelText(`${en.modelId} 1`), { target: { value: 'm' } })
fireEvent.click(screen.getByText(en.create))
@@ -1387,7 +1395,7 @@ describe('hand-declared providers', () => {
// nothing ever sets. The with-key case is covered above.
expect(firstMutate(mutate).ops[0]?.value).toEqual({
api: 'anthropic-messages',
baseURL: 'https://acme.test/v1',
baseURL: 'https://acme.test/anthropic',
models: [{ id: 'm' }],
})
})