118 lines
5.0 KiB
Markdown
118 lines
5.0 KiB
Markdown
# 实现方案 — 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 URL(type="url")
|
||
- API Key(type="password")
|
||
- Model Name(type="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 配置
|
||
|
||
### 步骤 5:CSS / 其他清理
|
||
- `index.css` 无需修改(本次不涉及新样式)
|
||
- `tsconfig.json` 无需修改
|
||
|
||
## 3. 依赖关系
|
||
- 步骤 1(types)必须先完成
|
||
- 步骤 2(SystemSettings)和步骤 3(ReportEditor)可并行
|
||
- 步骤 4(Login)在步骤 1 之后即可进行
|
||
- 步骤 5 在最后
|
||
|
||
## 4. 风险预案
|
||
- **数据迁移失败**:若 `localStorage` 中旧 settings 读取异常,使用 `try/catch` 包裹迁移逻辑,失败时回退到 `DEFAULT_AI_PROVIDERS`
|
||
- **类型不匹配**:旧代码中可能还有未清理的 `settings.apiKey` 引用,build/lint 阶段会暴露,逐一修复
|
||
- **Provider 配置为空**:在 ReportEditor 调用时增加 `provider?.apiKey` 的判空保护,避免 undefined 报错
|