# 实现方案 — 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([]); ``` 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 ? ( ) : ( )} ``` ## 3. 依赖关系 - 步骤 1 和步骤 2 可并行(都在 ReportEditor.tsx) - 步骤 3 独立(SystemSettings.tsx) ## 4. 风险预案 - 若 `/models` 接口返回格式非标准 OpenAI 格式(无 `data` 数组),`models` 列表为空,自动回退到 input 输入框 - 若 draft 中没有 `chatMessages`(旧 draft),`setChatMessages` 不执行,保持空数组