Skip to content

Commit fa8e073

Browse files
authored
feat(deeplink): add provider import (#1392)
* feat(deeplink): add provider import * fix(deeplink): harden startup and settings * fix(deeplink): address review feedback * fix(deeplink): harden parsing and queueing * fix(deeplink): reset review regressions
1 parent db2fcee commit fa8e073

42 files changed

Lines changed: 4539 additions & 339 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Provider Deeplink Import 实施计划
2+
3+
## 1. 当前实现基线
4+
5+
### 1.1 Deeplink 现状
6+
7+
1. `src/main/presenter/deeplinkPresenter/index.ts` 已支持 `deepchat://start``deepchat://mcp/install`
8+
2. 设置窗口已经支持通过 `SETTINGS_EVENTS.NAVIGATE` 进行页面跳转。
9+
3. 设置 App 已有 MCP deeplink 的初始化处理,可复用设置窗口 ready 后接收事件的模式。
10+
11+
### 1.2 Provider 设置页现状
12+
13+
1. Provider 列表与配置由 `providerStore` 驱动。
14+
2. Provider 详情页可基于路由参数 `providerId` 切换目标 provider。
15+
3. 自定义 provider 已有手动新增流程,可复用新增后的选中逻辑。
16+
17+
## 2. 设计决策
18+
19+
### 2.1 Payload 与共享类型
20+
21+
新增共享模块 `src/shared/providerDeeplink.ts`
22+
23+
1. 常量:
24+
- `PROVIDER_INSTALL_ROUTE`
25+
- `PROVIDER_INSTALL_VERSION`
26+
2. 类型:
27+
- `ProviderInstallDeeplinkPayload`
28+
- `ProviderInstallPreview`
29+
3. 工具函数:
30+
- `maskApiKey`
31+
- custom type 校验
32+
33+
### 2.2 主进程事件流
34+
35+
入口:`deepchat://provider/install?v=1&data=...`
36+
37+
处理顺序:
38+
39+
1. `DeeplinkPresenter.handleDeepLink` 识别 `provider/install`
40+
2. Base64 解码 + JSON 解析 + 字段校验
41+
3. built-in:
42+
- 校验 `id`
43+
- 拒绝 `acp`
44+
4. custom:
45+
- 校验 `name/type`
46+
- 校验 `type` 在允许列表中
47+
- 拒绝 `acp`
48+
5. 创建/聚焦设置窗口
49+
6. 发送:
50+
- `SETTINGS_EVENTS.NAVIGATE -> settings-provider`
51+
- `SETTINGS_EVENTS.PROVIDER_INSTALL -> preview`
52+
53+
错误策略:
54+
55+
1. 解析失败或 payload 不合法时,发 `NOTIFICATION_EVENTS.SHOW_ERROR`
56+
2. 失败时不写任何 provider 配置
57+
58+
### 2.3 渲染进程事件流
59+
60+
`src/renderer/settings/App.vue`
61+
62+
1. 监听 `SETTINGS_EVENTS.PROVIDER_INSTALL`
63+
2. 确保 provider store 已初始化
64+
3. built-in 导入时切到 `settings-provider/:providerId`
65+
4. custom 导入时切到 `settings-provider`
66+
5. 把 preview 放入新的 pending import store
67+
68+
`src/renderer/src/stores/providerDeeplinkImport.ts`
69+
70+
1. 只维护当前 pending preview
71+
2. 对话框开关由 preview 是否存在推导
72+
73+
### 2.4 对话框与落库行为
74+
75+
`ProviderDeeplinkImportDialog` 只负责展示解析结果,不自行写配置。
76+
77+
展示规则:
78+
79+
1. built-in:`icon + id`
80+
2. custom:`icon + name`,并额外显示 `type`
81+
3. 两类都展示 `baseUrl`
82+
4. 两类都展示脱敏 `apiKey`
83+
5. built-in 额外显示覆盖 warning
84+
85+
确认逻辑放在 `ModelProviderSettings.vue`
86+
87+
1. built-in:
88+
- 更新 `baseUrl/apiKey`
89+
- 若未启用则自动启用
90+
- 刷新该 provider 模型
91+
- 切换到对应 provider 页面
92+
2. custom:
93+
- 生成新 `id`
94+
- 创建 `custom: true` provider
95+
- 默认 `enable: true`
96+
- 刷新新 provider 模型
97+
- 切换到新 provider 页面
98+
3. cancel:
99+
- 清空 pending preview
100+
- 不写配置
101+
102+
### 2.5 Provider 兼容策略
103+
104+
1. built-in provider 以 `id` 作为唯一匹配键,因此导入是覆盖语义。
105+
2. custom provider 以 `type/apiType` 校验,但确认后总是新增实例,因此是追加语义。
106+
3. `vertex``aws-bedrock``github-copilot` 等允许部分导入,即使后续仍需补专属字段,也不阻塞 `baseUrl/apiKey` 导入。
107+
4. `acp` 独立于本流程,不进入 `settings-provider` 导入链路。
108+
109+
## 3. Manual Playground
110+
111+
新增:
112+
113+
- `test/manual/deeplink-playground.html`
114+
115+
页面结构:
116+
117+
1. `start`
118+
2. `mcp/install`
119+
3. `provider/install`
120+
4. `provider/install builder`
121+
122+
规则:
123+
124+
1. built-in 列出当前所有默认 provider `id`,排除 `acp`
125+
2. custom 列出当前所有允许导入的 `apiType`,排除 `acp`
126+
3. 每项展示:
127+
- label
128+
- raw JSON
129+
- deeplink
130+
- `Open`
131+
- `Copy`
132+
4. 示例数据全部使用假地址和假 key
133+
134+
## 4. 测试策略
135+
136+
### 4.1 Main
137+
138+
1. built-in payload 成功时:
139+
- 打开设置窗
140+
- 发送 `NAVIGATE`
141+
- 发送 `PROVIDER_INSTALL`
142+
2. custom payload 成功时:
143+
- 发送 custom preview
144+
3. 非法 payload:
145+
- 不发送导入事件
146+
- 发送错误通知
147+
148+
### 4.2 Renderer
149+
150+
1. `App.vue` 收到 `PROVIDER_INSTALL` 后正确导航并写入 preview store
151+
2. `ModelProviderSettings.vue`
152+
- built-in confirm 覆盖并启用 provider
153+
- custom confirm 新增并选中新 provider
154+
- cancel 不写配置
155+
3. `ProviderDeeplinkImportDialog.vue` 正确展示 built-in/custom 解析结果
156+
157+
### 4.3 Manual
158+
159+
1. playground 中三类 deeplink 都能生成合法协议链接
160+
2. built-in/custom 列表覆盖范围正确
161+
3. builder 输出格式与应用解析格式一致
162+
163+
## 5. 风险与缓解
164+
165+
1. 风险:设置窗口创建后事件发送早于页面监听注册。
166+
缓解:复用现有 settings 事件通道,并在 App 侧做独立导航兜底。
167+
168+
2. 风险:部分 provider 启用后仍缺专属字段,模型刷新可能失败。
169+
缓解:允许部分导入;模型刷新失败只记录日志,不回滚导入。
170+
171+
3. 风险:手工验证页 provider 列表与真实支持集合漂移。
172+
缓解:built-in 与 custom 列表以当前代码中的 provider 集合为准,变更时同步更新此页。
173+
174+
## 6. 质量门槛
175+
176+
1. `pnpm run format`
177+
2. `pnpm run i18n`
178+
3. `pnpm run lint`
179+
4. `pnpm run typecheck`
180+
5. 关键 main/renderer 测试通过
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Provider Deeplink Import 规格
2+
3+
## 概述
4+
5+
新增 provider 导入 deeplink:
6+
7+
- `deepchat://provider/install?v=1&data=<base64(JSON)>`
8+
9+
其中 `data` 只接受两种结构,且 `id``type` 必须二选一:
10+
11+
1. `{ id, baseUrl, apiKey }`
12+
2. `{ name, type, baseUrl, apiKey }`
13+
14+
导入后统一进入 Provider Settings,先展示确认对话框;用户确认后才写入配置,取消则直接丢弃。
15+
16+
## 背景与动机
17+
18+
1. 用户经常需要在多个 built-in provider 与 custom provider 之间切换配置。
19+
2. 当前 provider 配置主要依赖手动录入,分享和一键导入成本高。
20+
3. DeepLink 已经用于 `start``mcp/install`,provider 导入应沿用同一套唤起能力。
21+
4. 需要一个独立的手工验证页,降低联调和回归验证成本。
22+
23+
## 用户故事
24+
25+
### US-1:一键导入内置 Provider
26+
27+
作为用户,我希望点击一个 deeplink 后直接进入对应 provider 设置,并在确认后覆盖它的 `baseUrl``apiKey`
28+
29+
### US-2:一键新增 Custom Provider
30+
31+
作为用户,我希望通过 deeplink 快速新增一个 custom provider,而不是手动新建并逐项填写。
32+
33+
### US-3:导入前确认
34+
35+
作为用户,我希望在真正写入前看到解析结果,避免误覆盖现有配置。
36+
37+
### US-4:手工验证入口
38+
39+
作为开发者或测试者,我希望仓库里有一个静态网页,能集中打开所有支持的 deeplink。
40+
41+
## 功能需求
42+
43+
### A. Provider Deeplink 协议
44+
45+
- [ ] 新增 `deepchat://provider/install?v=1&data=<base64(JSON)>`
46+
- [ ] `v=1` 是当前唯一支持版本
47+
- [ ] `data` Base64 解码后必须是 JSON object
48+
- [ ] payload 只允许两种结构:
49+
- [ ] `{ id, baseUrl, apiKey }`
50+
- [ ] `{ name, type, baseUrl, apiKey }`
51+
- [ ] `id``type` 同时存在或同时缺失时,必须拒绝
52+
53+
### B. 内置 Provider 导入
54+
55+
- [ ] 当 payload 包含 `id` 时,按内置 provider id 匹配
56+
- [ ] `id='acp'` 必须拒绝
57+
- [ ] unknown `id` 必须拒绝
58+
- [ ] 确认后覆盖目标 provider 的 `baseUrl``apiKey`
59+
- [ ] 若目标 provider 当前未启用,确认后自动启用
60+
- [ ] 完成后停留在对应 provider 设置页
61+
- [ ] 若是 `vertex``aws-bedrock``github-copilot` 等仍需额外字段的 provider,允许部分导入,不阻塞确认
62+
63+
### C. Custom Provider 导入
64+
65+
- [ ] 当 payload 包含 `type` 时,按 provider `apiType` 匹配
66+
- [ ] `type='acp'` 必须拒绝
67+
- [ ] unknown `type` 必须拒绝
68+
- [ ] custom payload 必须包含 `name`
69+
- [ ] 确认后总是新增一条 custom provider,不复用旧条目
70+
- [ ] 新 provider 默认 `enable=true`
71+
- [ ] 完成后停留在新 provider 设置页
72+
73+
### D. 设置页行为
74+
75+
- [ ] deeplink 唤起后自动进入 `settings-provider`
76+
- [ ] 在真正写入前弹出 `Import Provider` 对话框
77+
- [ ] built-in 对话框只展示:
78+
- [ ] `id + icon`
79+
- [ ] `baseUrl`
80+
- [ ] 脱敏 `apiKey`
81+
- [ ] custom 对话框只展示:
82+
- [ ] `name + icon`
83+
- [ ] `type`
84+
- [ ] `baseUrl`
85+
- [ ] 脱敏 `apiKey`
86+
- [ ] built-in 导入需要展示“将覆盖当前配置”的提示
87+
- [ ] 取消后不写入任何 provider 配置
88+
89+
### E. 错误处理
90+
91+
- [ ] 非法 Base64、非法 JSON、非法版本、缺字段、unknown `id/type` 均必须拒绝
92+
- [ ] 非法 deeplink 需要有可见错误提示
93+
- [ ] 拒绝场景不得写入 provider 配置
94+
95+
### F. Manual Playground
96+
97+
- [ ] 新增 `test/manual/deeplink-playground.html`
98+
- [ ] 页面覆盖三类 deeplink:
99+
- [ ] `start`
100+
- [ ] `mcp/install`
101+
- [ ] `provider/install`
102+
- [ ] `provider/install` 区块必须列出:
103+
- [ ] 所有 built-in provider `id`,排除 `acp`
104+
- [ ] 所有允许的 custom `apiType`,排除 `acp`
105+
- [ ] 每项都提供 `Open``Copy`
106+
- [ ] 每项都展示原始 JSON 与最终 deeplink
107+
- [ ] 页面提供一个可编辑 builder,用于临时生成 deeplink
108+
- [ ] 所有示例数据必须为 fake data
109+
110+
## 验收标准
111+
112+
- [ ] 打开 built-in provider deeplink 时,设置窗进入对应 provider,并弹出确认对话框
113+
- [ ] 确认 built-in provider 导入后,`baseUrl/apiKey` 被覆盖,provider 被自动启用
114+
- [ ] 打开 custom provider deeplink 时,设置窗进入 provider 设置页,并弹出确认对话框
115+
- [ ] 确认 custom provider 导入后,会新增一条启用中的 custom provider
116+
- [ ] 取消导入时,不产生任何配置写入
117+
- [ ] 非法 payload 只显示错误,不进入确认流程
118+
- [ ] 手工验证页可直接生成并打开三类 deeplink
119+
120+
## 非目标
121+
122+
1. 不扩展 provider deeplink 的版本协商机制,本次仅支持 `v=1`
123+
2. 不新增 provider 专属迁移脚本或持久化 schema。
124+
3. 不为 `acp` provider 引入导入能力。
125+
4. 不修改现有 `start``mcp/install` 的协议格式。
126+
127+
## 约束
128+
129+
1. 保持现有 Presenter + EventBus 架构。
130+
2. 所有用户可见文案必须走 i18n。
131+
3. 不破坏现有 provider 配置存储结构。
132+
4. Manual playground 不打包进应用,仅作为仓库内测试辅助页。
133+
134+
## 开放问题
135+
136+
无。
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Provider Deeplink Import Tasks
2+
3+
## T0 规格与设计
4+
5+
- [x] 完成 `spec.md`
6+
- [x] 完成 `plan.md`
7+
- [x] 完成 `tasks.md`
8+
9+
## T1 共享协议与事件
10+
11+
- [x] 新增 provider deeplink 共享类型与协议常量
12+
- [x] 新增 `SETTINGS_EVENTS.PROVIDER_INSTALL`
13+
- [x] 补共享类型导出
14+
15+
## T2 主进程解析与分发
16+
17+
- [x]`deeplinkPresenter` 新增 `provider/install` 入口
18+
- [x] 校验 `v=1`
19+
- [x] 校验 Base64 / JSON / 字段结构
20+
- [x] built-in 导入按 `id` 匹配
21+
- [x] custom 导入按 `type` 校验
22+
- [x] 拒绝 `acp`
23+
- [x] 发送设置页导航与 preview 事件
24+
- [x] 非法 payload 显示错误通知
25+
26+
## T3 设置页预览与确认
27+
28+
- [x] 新增 pending import store
29+
- [x] `App.vue` 监听 `PROVIDER_INSTALL`
30+
- [x] built-in 导航到目标 provider
31+
- [x] 新增 `ProviderDeeplinkImportDialog`
32+
- [x] built-in confirm 覆盖 `baseUrl/apiKey` 并自动启用
33+
- [x] custom confirm 新增启用中的 custom provider
34+
- [x] cancel 只清空 pending preview
35+
36+
## T4 i18n 与测试
37+
38+
- [x] 补齐 provider import 对话框文案
39+
- [x] 新增 main deeplink 测试
40+
- [x] 新增 settings app 事件处理测试
41+
- [x] 新增 provider settings confirm 测试
42+
43+
## T5 Manual Playground
44+
45+
- [x] 新增 `test/manual/deeplink-playground.html`
46+
- [x] 覆盖 `start`
47+
- [x] 覆盖 `mcp/install`
48+
- [x] 覆盖 built-in provider import,排除 `acp`
49+
- [x] 覆盖 custom provider import,排除 `acp`
50+
- [x] 提供 builder、Open、Copy、raw JSON、deeplink 展示
51+
- [x] 更新 `test/README.md`
52+
53+
## T6 质量检查
54+
55+
- [ ] `pnpm run format`
56+
- [ ] `pnpm run i18n`
57+
- [ ] `pnpm run lint`
58+
- [ ] `pnpm run typecheck`
59+
- [ ] 运行相关测试并记录结果

src/main/events.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ export const WINDOW_EVENTS = {
123123
export const SETTINGS_EVENTS = {
124124
READY: 'settings:ready',
125125
NAVIGATE: 'settings:navigate',
126-
CHECK_FOR_UPDATES: 'settings:check-for-updates'
126+
CHECK_FOR_UPDATES: 'settings:check-for-updates',
127+
PROVIDER_INSTALL: 'settings:provider-install'
127128
}
128129

129130
// ollama 相关事件

0 commit comments

Comments
 (0)