4.4 KiB
4.4 KiB
实现方案 — 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 中,将:
const apiEndpoint = provider?.endpoint || 'https://api.moonshot.cn/v1';
改为:
const apiEndpoint = (provider?.endpoint || 'https://api.moonshot.cn/v1').replace(/\/+$/, '');
步骤 2:src/pages/ReportEditor.tsx — 聊天记录持久化
目标文件:src/pages/ReportEditor.tsx
修改内容:
-
stateRef增加chatMessages:const stateRef = useRef({ reportData, videos, capturedFrames, activeTab, loadedTemplateId, chatMessages }); -
saveDraftToStorage增加chatMessages: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 }); -
在
setChatMessages的调用处同步更新stateRef.current.chatMessages:handleAIGenerate中发送 user 消息时handleAIGenerate中收到 model 消息时- 也可在
setChatInput('')之后统一用useEffect监听chatMessages变化来同步 ref
更简单的方案:增加一个
useEffect监听chatMessages:useEffect(() => { stateRef.current.chatMessages = chatMessages; }, [chatMessages]); -
初始化
useEffect(draft 恢复分支)中恢复chatMessages:if (draft.chatMessages) { setChatMessages(draft.chatMessages); }
步骤 3:src/pages/SystemSettings.tsx — 模型名称下拉栏
目标文件:src/pages/SystemSettings.tsx
修改内容:
- 新增 state:
const [availableModels, setAvailableModels] = useState<string[]>([]); - 修改
testApi: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([]); } }; - Model Name UI 改为条件渲染:
{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不执行,保持空数组