119 lines
4.4 KiB
Markdown
119 lines
4.4 KiB
Markdown
# 实现方案 — 2026-04-19-03-03-55
|
||
|
||
## 1. 方案概述
|
||
三处修补:① endpoint 尾部斜杠净化防止 404;② testApi 捕获模型列表并动态切换 Model Name 输入框为下拉栏;③ 将 `chatMessages` 纳入现有草稿持久化生命周期。
|
||
|
||
## 2. 详细步骤
|
||
|
||
### 步骤 1:`src/pages/ReportEditor.tsx` — endpoint 净化
|
||
**目标文件**:`src/pages/ReportEditor.tsx`
|
||
**修改内容**:
|
||
在 `handleAIGenerate` 中,将:
|
||
```ts
|
||
const apiEndpoint = provider?.endpoint || 'https://api.moonshot.cn/v1';
|
||
```
|
||
改为:
|
||
```ts
|
||
const apiEndpoint = (provider?.endpoint || 'https://api.moonshot.cn/v1').replace(/\/+$/, '');
|
||
```
|
||
|
||
### 步骤 2:`src/pages/ReportEditor.tsx` — 聊天记录持久化
|
||
**目标文件**:`src/pages/ReportEditor.tsx`
|
||
**修改内容**:
|
||
1. `stateRef` 增加 `chatMessages`:
|
||
```ts
|
||
const stateRef = useRef({ reportData, videos, capturedFrames, activeTab, loadedTemplateId, chatMessages });
|
||
```
|
||
2. `saveDraftToStorage` 增加 `chatMessages`:
|
||
```ts
|
||
storage.set(key, {
|
||
content: currentContent,
|
||
draftReportId: reportId || null,
|
||
reportData: stateRef.current.reportData,
|
||
videos: stateRef.current.videos,
|
||
capturedFrames: stateRef.current.capturedFrames,
|
||
activeTab: stateRef.current.activeTab,
|
||
loadedTemplateId: stateRef.current.loadedTemplateId,
|
||
chatMessages: stateRef.current.chatMessages
|
||
});
|
||
```
|
||
3. 在 `setChatMessages` 的调用处同步更新 `stateRef.current.chatMessages`:
|
||
- `handleAIGenerate` 中发送 user 消息时
|
||
- `handleAIGenerate` 中收到 model 消息时
|
||
- 也可在 `setChatInput('')` 之后统一用 `useEffect` 监听 `chatMessages` 变化来同步 ref
|
||
|
||
更简单的方案:增加一个 `useEffect` 监听 `chatMessages`:
|
||
```ts
|
||
useEffect(() => {
|
||
stateRef.current.chatMessages = chatMessages;
|
||
}, [chatMessages]);
|
||
```
|
||
4. 初始化 `useEffect`(draft 恢复分支)中恢复 `chatMessages`:
|
||
```ts
|
||
if (draft.chatMessages) {
|
||
setChatMessages(draft.chatMessages);
|
||
}
|
||
```
|
||
|
||
### 步骤 3:`src/pages/SystemSettings.tsx` — 模型名称下拉栏
|
||
**目标文件**:`src/pages/SystemSettings.tsx`
|
||
**修改内容**:
|
||
1. 新增 state:
|
||
```ts
|
||
const [availableModels, setAvailableModels] = useState<string[]>([]);
|
||
```
|
||
2. 修改 `testApi`:
|
||
```ts
|
||
const testApi = async () => {
|
||
const provider = settings.aiProviders[settings.activeAiProvider];
|
||
if (!provider?.apiKey) { alert('请先输入 API 密钥'); return; }
|
||
try {
|
||
const res = await fetch(`${provider.endpoint.replace(/\/+$/, '')}/models`, {
|
||
method: 'GET',
|
||
headers: { 'Authorization': `Bearer ${provider.apiKey}`, 'Content-Type': 'application/json' }
|
||
});
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const models = data.data?.map((m: any) => m.id).filter((id: string) => id) || [];
|
||
setAvailableModels(models);
|
||
if (models.length > 0 && !provider.modelName) {
|
||
const next = { ...settings.aiProviders };
|
||
next[settings.activeAiProvider] = { ...next[settings.activeAiProvider], modelName: models[0] };
|
||
setSettings({ ...settings, aiProviders: next });
|
||
}
|
||
alert(`连接成功!可用模型数: ${models.length}`);
|
||
} else {
|
||
alert(`连接失败: ${res.status} ${res.statusText}`);
|
||
setAvailableModels([]);
|
||
}
|
||
} catch (e: any) {
|
||
alert(`连接失败: ${e.message}`);
|
||
setAvailableModels([]);
|
||
}
|
||
};
|
||
```
|
||
3. Model Name UI 改为条件渲染:
|
||
```tsx
|
||
{availableModels.length > 0 ? (
|
||
<select value={settings.aiProviders[settings.activeAiProvider]?.modelName || ''}
|
||
onChange={(e) => {
|
||
const next = { ...settings.aiProviders };
|
||
next[settings.activeAiProvider] = { ...next[settings.activeAiProvider], modelName: e.target.value };
|
||
setSettings({ ...settings, aiProviders: next });
|
||
}}
|
||
className="input-minimal bg-white">
|
||
{availableModels.map(m => <option key={m} value={m}>{m}</option>)}
|
||
</select>
|
||
) : (
|
||
<input type="text" ... />
|
||
)}
|
||
```
|
||
|
||
## 3. 依赖关系
|
||
- 步骤 1 和步骤 2 可并行(都在 ReportEditor.tsx)
|
||
- 步骤 3 独立(SystemSettings.tsx)
|
||
|
||
## 4. 风险预案
|
||
- 若 `/models` 接口返回格式非标准 OpenAI 格式(无 `data` 数组),`models` 列表为空,自动回退到 input 输入框
|
||
- 若 draft 中没有 `chatMessages`(旧 draft),`setChatMessages` 不执行,保持空数组
|