Files
Mdeical_Sur_Report/工程分析/实现方案-2026-04-19-03-03-55.md

4.4 KiB
Raw Blame History

实现方案 — 2026-04-19-03-03-55

1. 方案概述

三处修补:① endpoint 尾部斜杠净化防止 404② testApi 捕获模型列表并动态切换 Model Name 输入框为下拉栏;③ 将 chatMessages 纳入现有草稿持久化生命周期。

2. 详细步骤

步骤 1src/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(/\/+$/, '');

步骤 2src/pages/ReportEditor.tsx — 聊天记录持久化

目标文件src/pages/ReportEditor.tsx 修改内容

  1. stateRef 增加 chatMessages

    const stateRef = useRef({ reportData, videos, capturedFrames, activeTab, loadedTemplateId, chatMessages });
    
  2. 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
    });
    
  3. setChatMessages 的调用处同步更新 stateRef.current.chatMessages

    • handleAIGenerate 中发送 user 消息时
    • handleAIGenerate 中收到 model 消息时
    • 也可在 setChatInput('') 之后统一用 useEffect 监听 chatMessages 变化来同步 ref

    更简单的方案:增加一个 useEffect 监听 chatMessages

    useEffect(() => {
      stateRef.current.chatMessages = chatMessages;
    }, [chatMessages]);
    
  4. 初始化 useEffectdraft 恢复分支)中恢复 chatMessages

    if (draft.chatMessages) {
      setChatMessages(draft.chatMessages);
    }
    

步骤 3src/pages/SystemSettings.tsx — 模型名称下拉栏

目标文件src/pages/SystemSettings.tsx 修改内容

  1. 新增 state
    const [availableModels, setAvailableModels] = useState<string[]>([]);
    
  2. 修改 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([]);
      }
    };
    
  3. 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(旧 draftsetChatMessages 不执行,保持空数组