2026-04-19-02-48-25 重构AI接口配置:多服务商底座架构、OpenAI兼容协议、动态模型切换、旧数据自动迁移

This commit is contained in:
2026-04-19 02:53:26 +08:00
parent 221daf61a5
commit d5cbbf9137
8 changed files with 416 additions and 59 deletions

View File

@@ -0,0 +1,117 @@
# 实现方案 — 2026-04-19-02-48-25
## 1. 方案概述
基于 OpenAI 兼容协议进行标准化抽象。将 4 个散装字段收敛为 `activeAiProvider` + `aiProviders` 字典结构。UI 改为"选择器 + 动态表单",调用端完全配置驱动。通过初始化时的数据迁移保证旧用户无损升级。
## 2. 详细步骤
### 步骤 1`src/types.ts` — 数据结构重构
**目标文件**`src/types.ts`
**修改内容**
1. 新增 `AiProviderConfig` 接口:
```ts
export interface AiProviderConfig {
endpoint: string;
apiKey: string;
modelName: string;
}
```
2. 重构 `SystemSettings`
- 删除:`apiEndpoint`, `apiKey`, `kimiApiKey`, `kimiApiEndpoint`
- 新增:`activeAiProvider: string`
- 新增:`aiProviders: Record<string, AiProviderConfig>`
3. 新增默认预设常量:
```ts
export const DEFAULT_AI_PROVIDERS: Record<string, AiProviderConfig> = {
kimi: { endpoint: 'https://api.moonshot.cn/v1', apiKey: '', modelName: 'kimi-k2-5' },
deepseek: { endpoint: 'https://api.deepseek.com/v1', apiKey: '', modelName: 'deepseek-chat' },
openai: { endpoint: 'https://api.openai.com/v1', apiKey: '', modelName: 'gpt-4o' },
custom: { endpoint: '', apiKey: '', modelName: '' }
};
```
### 步骤 2`src/pages/SystemSettings.tsx` — UI 重构 + 数据迁移
**目标文件**`src/pages/SystemSettings.tsx`
**修改内容**
1. **初始化数据迁移**(在 `useEffect` 读取 settings 后):
```ts
const migrateOldAiSettings = (saved: any) => {
if (!saved.aiProviders) {
const providers = { ...DEFAULT_AI_PROVIDERS };
if (saved.kimiApiKey || saved.kimiApiEndpoint) {
providers.kimi = {
endpoint: saved.kimiApiEndpoint || providers.kimi.endpoint,
apiKey: saved.kimiApiKey || '',
modelName: 'kimi-k2-5'
};
}
saved.aiProviders = providers;
saved.activeAiProvider = 'kimi';
// 清理旧字段(可选,保留 localStorage 中不删除,但代码不再读取)
storage.set('systemSettings', saved);
}
return saved;
};
```
2. **状态初始化**
```ts
const [settings, setSettings] = useState<SystemSettings>({
frameCount: 12,
framePositions: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60],
defaultTemplate: '',
frameMode: 'uniform',
activeAiProvider: 'kimi',
aiProviders: { ...DEFAULT_AI_PROVIDERS }
});
```
3. **UI 改造**
- 用一个 `<select>` 选择 `activeAiProvider`kimi / deepseek / openai / custom
- 下方动态展示 3 个输入框:
- Base URLtype="url"
- API Keytype="password"
- Model Nametype="text"
- 输入框的 `onChange` 更新 `aiProviders[activeAiProvider]` 的对应字段
4. **`testApi` 重构**
- 读取当前 `activeAiProvider` 对应的 `endpoint` 和 `apiKey`
- 发起 `GET {endpoint}/models` 测试连通性
- 若失败fallback 到发一个极简的 chat completion`{"messages":[{"role":"user","content":"hi"}]}`
5. **`resetToDefault`**:使用新的数据结构,包含 `activeAiProvider: 'kimi'` 和 `DEFAULT_AI_PROVIDERS`
### 步骤 3`src/pages/ReportEditor.tsx` — 调用逻辑解耦
**目标文件**`src/pages/ReportEditor.tsx`
**修改内容**
1. `handleAIGenerate` 中:
```ts
const settings = storage.get<SystemSettings>('systemSettings', {} as SystemSettings);
const provider = settings.aiProviders?.[settings.activeAiProvider || 'kimi'];
const apiKey = provider?.apiKey || '';
const apiEndpoint = provider?.endpoint || 'https://api.moonshot.cn/v1';
const modelName = provider?.modelName || 'kimi-k2-5';
```
2. Fetch body 中:
```ts
model: modelName,
```
3. 错误提示文案从"尚未配置 Kimi API Key"改为"尚未配置 AI API Key"
### 步骤 4`src/pages/Login.tsx` — 默认初始化更新(检查)
**目标文件**`src/pages/Login.tsx`
**修改内容**
- 检查 `initData()` 或 `defaultSettings` 中是否硬编码了旧的 `apiKey`/`apiEndpoint`
- 若有,替换为新的 `activeAiProvider` + `aiProviders` 结构
- 由于 Login.tsx 的初始化是全局唯一入口,必须确保新用户首次使用时 `systemSettings` 包含正确的默认 AI 配置
### 步骤 5CSS / 其他清理
- `index.css` 无需修改(本次不涉及新样式)
- `tsconfig.json` 无需修改
## 3. 依赖关系
- 步骤 1types必须先完成
- 步骤 2SystemSettings和步骤 3ReportEditor可并行
- 步骤 4Login在步骤 1 之后即可进行
- 步骤 5 在最后
## 4. 风险预案
- **数据迁移失败**:若 `localStorage` 中旧 settings 读取异常,使用 `try/catch` 包裹迁移逻辑,失败时回退到 `DEFAULT_AI_PROVIDERS`
- **类型不匹配**:旧代码中可能还有未清理的 `settings.apiKey` 引用build/lint 阶段会暴露,逐一修复
- **Provider 配置为空**:在 ReportEditor 调用时增加 `provider?.apiKey` 的判空保护,避免 undefined 报错

View File

@@ -0,0 +1,56 @@
# 测试方案 — 2026-04-19-02-48-25
## 1. 测试范围
- SystemSettings 数据迁移(旧用户无损升级)
- SystemSettings 新 UI服务商切换、动态表单、保存
- ReportEditor AI 调用解耦(模型名随配置变化)
- 类型检查与构建
## 2. 测试步骤与预期结果
### 场景 1旧用户数据迁移
1. 在浏览器 DevTools → Application → LocalStorage 中手动保留旧的 `systemSettings`(包含 `kimiApiKey: 'sk-test'``kimiApiEndpoint: 'https://api.moonshot.cn/v1'`
2. 刷新页面,进入 `/system-settings`
预期:
- AI 接口集成卡片显示"Kimi"为当前选中服务商
- Base URL 自动填充 `https://api.moonshot.cn/v1`
- API Key 自动填充 `sk-test`
- Model Name 自动填充 `kimi-k2-5`
### 场景 2新用户默认初始化
1. 清空 localStorage重新登录
预期:进入 `/system-settings`AI 配置默认展示 Kimi且 Model Name 为 `kimi-k2-5`
### 场景 3服务商切换
1.`/system-settings` 中将服务商从 Kimi 切换为 DeepSeek
预期:下方三个输入框的值自动切换为 DeepSeek 的默认值
2. 修改 DeepSeek 的 API Key 为 `sk-ds-test`
3. 切换回 Kimi
预期:显示 Kimi 的配置(之前填的值保留)
4. 切换回 DeepSeek
预期:显示 `sk-ds-test`(证明各服务商配置独立存储)
### 场景 4自定义服务商
1. 选择"自定义"
2. 填写 Base URL `http://localhost:11434/v1`、Key `ollama`、Model `llama3`
3. 保存
预期:保存成功,刷新后值保留
### 场景 5ReportEditor 调用解耦
1. 在 SystemSettings 中选择 DeepSeekModel 填 `deepseek-chat`
2. 进入 `/report-editor`,插入 AI 区域,切换到 AI撰写 Tab
3. 发送一条消息(不需要真实 API Key只看 Network 请求或 console
预期:
- 若 Key 为空,提示"尚未配置 AI API Key"
- 若有 Key请求的 `model` 字段应为 `deepseek-chat`(而非硬编码 `kimi-k2-5`
### 场景 6类型检查与构建
1. `npm run lint`
预期0 errors
2. `npm run build`
预期:成功
3. 访问 `http://localhost:4173/`
预期200 OK
## 3. 回滚检查
- 若测试失败,执行 `git checkout main` 或从 Gitea 拉取上一个 commit

View File

@@ -441,3 +441,106 @@
- 在无法使用 Docker 的环境中,可将 `npm run build && npm run preview -- --host` 作为标准部署脚本。
- 重新部署前务必先清理旧的同类型进程,避免端口冲突或多版本服务同时运行导致访问混乱。
- **切勿使用 Shell 后台任务(`run_in_background=true`)长时间运行 `npm run preview`**,因为任务超时机制(默认 60s会强制终止 preview 进程,导致服务中断。
---
## 记录 22参考信息文件夹导致 tsc 编译失败
**A. 具体问题**
执行 `npm run lint``tsc --noEmit`)时,报错 `参考信息/参考-ReportEditor.tsx` 中找不到模块 `'../components/Sidebar'` 等。该文件只是用户提供的参考代码,不应参与编译。
**B. 产生问题原因**
`tsconfig.json` 中没有配置 `exclude` 字段TypeScript 默认会递归编译项目根目录下所有 `.ts`/`.tsx` 文件,包括非源码的参考文件。
**C. 解决问题方案**
`tsconfig.json` 中增加 `exclude` 字段:
```json
"exclude": ["参考信息", "dist", "node_modules"]
```
**D. 后续如何避免问题**
- 任何非源码的参考文件、文档、备份代码必须放在被 `tsconfig.exclude``.gitignore` 排除的目录中。
- 修改 `tsconfig.json` 后应立即运行 `npm run lint` 验证排除是否生效。
---
## 记录 23大文件2200+行)增量修改的定位策略
**A. 具体问题**
`ReportEditor.tsx` 有 2224 行需要在多处插入代码imports、state、函数、工具栏 JSX、tab 切换 JSX、AI 面板 JSX、Diff 弹窗 JSX。直接全文搜索效率低且容易定位错误。
**B. 产生问题原因**
单文件组件承载了过多功能编辑器、视频分析、表单、AI 面板),导致任何新增功能都需要在文件的多个离散位置插入代码。
**C. 解决问题方案**
采用 **"Grep 定位 + 精确读取 + StrReplaceFile"** 的三段式策略:
1. 先用 `Grep` 找到目标代码的精确行号
2.`ReadFile` 读取该行号前后 10-20 行,获取精确文本
3.`StrReplaceFile` 进行最小化字符串替换,确保只改目标区域
**D. 后续如何避免问题**
- 对于超过 1500 行的单文件组件,新增功能时应优先使用 `Grep` 定位关键锚点(如 `const [activeTab``<div className="flex items-center gap-1 p-3` 等),避免盲目滚动阅读。
- 若需在同一文件的 5 个以上位置插入代码,建议先用 Agent 生成修改草案,再人工审核关键锚点。
- 考虑在未来重构中将超大组件按功能拆分为子组件(如 `ReportEditorToolbar``ReportEditorAiPanel`),降低后续修改成本。
---
## 记录 24数据结构重构时的旧数据迁移策略
**A. 具体问题**
重构 `SystemSettings` 时,将 `apiEndpoint`/`apiKey`/`kimiApiKey`/`kimiApiEndpoint` 四个散装字段替换为 `activeAiProvider` + `aiProviders` 字典结构。如果直接删除旧字段,已配置 API Key 的老用户会丢失配置。
**B. 产生问题原因**
TypeScript 接口变更后,从 `localStorage` 读出的旧数据对象缺少新字段,直接赋值给新类型的 state 会导致类型错误(缺少 `activeAiProvider``aiProviders`)或运行时逻辑断裂。
**C. 解决问题方案**
1.`SystemSettings.tsx` 的初始化 `useEffect` 中增加数据迁移逻辑:
```ts
if (!savedSettings.aiProviders) {
const providers = { ...DEFAULT_AI_PROVIDERS };
if ((savedSettings as any).kimiApiKey || (savedSettings as any).kimiApiEndpoint) {
providers.kimi = {
endpoint: (savedSettings as any).kimiApiApiEndpoint || providers.kimi.endpoint,
apiKey: (savedSettings as any).kimiApiKey || '',
modelName: 'kimi-k2-5'
};
}
savedSettings.aiProviders = providers;
savedSettings.activeAiProvider = 'kimi';
storage.set('systemSettings', savedSettings);
}
```
2. 使用 `(savedSettings as any)` 临时绕过旧字段的类型检查,避免在 `types.ts` 中保留废弃字段。
3. 迁移后旧字段仍保留在 `localStorage` 中(不主动删除),但代码不再读取。
**D. 后续如何避免问题**
- 任何涉及 `localStorage` 数据结构变更的重构,都必须在初始化入口提供**自动迁移逻辑**,否则用户数据会静默丢失。
- 迁移逻辑应使用 `try/catch` 包裹,防止脏数据导致页面白屏。
- 旧字段可通过 `(obj as any).oldField` 安全访问,无需在类型定义中长期保留废弃字段。
---
## 记录 25多服务商配置字典的 UI 绑定模式
**A. 具体问题**
SystemSettings 需要支持 4 个服务商Kimi/DeepSeek/OpenAI/自定义),每个服务商有 3 个配置项endpoint/apiKey/modelName。若用 12 个独立 state 或输入框,代码会极其臃肿。
**B. 产生问题原因**
早期设计采用平铺字段(`kimiApiKey`、`deepseekApiKey`...),导致每新增一个服务商就要改 types + UI + 调用逻辑三处。
**C. 解决问题方案**
采用 **"字典 + 动态下标"** 模式:
1. `types.ts` 中统一定义 `Record<string, AiProviderConfig>`
2. UI 中只有一个 `activeAiProvider` select下方 3 个输入框统一绑定到 `aiProviders[activeAiProvider].xxx`
3. `onChange` 时创建浅拷贝更新:
```ts
const next = { ...settings.aiProviders };
next[settings.activeAiProvider] = { ...next[settings.activeAiProvider], endpoint: e.target.value };
setSettings({ ...settings, aiProviders: next });
```
**D. 后续如何避免问题**
- 当一组配置具有"同构多实例"特征时(多个服务商、多个环境、多个账号),优先使用 `Record<string, Config>` 而非平铺字段。
- 动态表单的 `onChange` 必须注意不可变更新:先浅拷贝外层字典,再浅拷贝当前项,最后修改目标字段。直接 `settings.aiProviders[k].endpoint = x` 会触发 React 引用比较优化导致不刷新。

View File

@@ -0,0 +1,48 @@
# 需求分析 — 2026-04-19-02-48-25
## 1. 需求背景
当前 system-settings 中的 AI 接口配置存在冗余和僵化问题:
- 平铺展示 4 个输入框(`apiEndpoint`/`apiKey`/`kimiApiEndpoint`/`kimiApiKey`),用户困惑该填哪个
- ReportEditor 中 AI 调用逻辑硬编码 `kimi-k2-5` 模型和 Kimi 接口,无法切换其他模型
- 未来需要支持 DeepSeek、OpenAI、本地模型等多种服务商且模型版本会频繁迭代
## 2. 需求拆解
- [ ] **Task 1**:重构 `types.ts``SystemSettings` 的数据结构
- 废除 `apiEndpoint``apiKey``kimiApiKey``kimiApiEndpoint` 四个散装字段
- 新增 `activeAiProvider: string`(当前激活的服务商)
- 新增 `aiProviders: Record<string, AiProviderConfig>`(多服务商配置字典)
- `AiProviderConfig` 包含:`endpoint``apiKey``modelName`
- [ ] **Task 2**:重构 `SystemSettings.tsx` UI
- 改为"服务商选择器 + 动态配置表单"模式
- 预设服务商Kimi、DeepSeek、OpenAI、自定义
- 每个服务商可配置Base URL、API Key、Model Name
- `testApi` 改为通用测试,使用当前选中的服务商配置
- [ ] **Task 3**:重构 `ReportEditor.tsx` 调用逻辑
- `handleAIGenerate``aiProviders[activeAiProvider]` 动态读取 endpoint/key/model
- 消除 `model: 'kimi-k2-5'` 硬编码
- 保持 OpenAI 兼容的 messages 组装逻辑不变
- [ ] **Task 4**:数据迁移
- 首次加载时,若检测到旧的 `kimiApiKey`/`kimiApiEndpoint`,自动迁移到 `aiProviders.kimi`
- 默认模型名设为 `kimi-k2-5`
## 3. 影响范围
| 文件 | 修改类型 | 风险等级 |
|------|----------|----------|
| `src/types.ts` | 修改(重构 SystemSettings | **高**(数据结构变更) |
| `src/pages/SystemSettings.tsx` | 修改UI 重构 + 数据迁移) | **高** |
| `src/pages/ReportEditor.tsx` | 修改(解耦 AI 调用) | 中 |
| `src/pages/Login.tsx` | 可能修改(默认设置初始化) | 低 |
## 4. 优先级
- P0数据结构重构 + 迁移逻辑(不做好,旧数据会丢失或报错)
- P0SystemSettings UI 改造
- P0ReportEditor 调用解耦
- P1Login.tsx 默认初始化更新
## 5. 验收标准
- [ ] SystemSettings 中只看到 1 个服务商选择下拉框 + 3 个动态输入框Base URL / API Key / Model
- [ ] 切换服务商时,输入框的值自动切换为该服务商的配置
- [ ] 旧用户(已有 `kimiApiKey`)首次进入 settings数据自动迁移配置不丢失
- [ ] ReportEditor AI 调用使用当前选中的服务商配置,模型名随配置变化
- [ ] `npm run lint` 无类型错误
- [ ] `npm run build` 成功,页面可正常访问