From c1d2438d2bfbf9850b43179914470983cd85a0d0 Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Sun, 19 Apr 2026 03:47:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(editor):=20AI=E5=8F=AA=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E4=B8=8D=E4=BF=AE=E6=94=B9=E2=80=94=E2=80=94=E8=A7=A3=E7=BB=91?= =?UTF-8?q?SystemPrompt=E7=9B=AE=E6=A0=87=E5=8C=BA=E5=9F=9F=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=20+=20=E5=A2=9E=E5=8A=A0=E5=85=89=E6=A0=87=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E9=99=8D=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - systemPrompt条件从'aiModifyEnabled && targetRegionEl'改为'aiModifyEnabled',确保开启修改模式后大模型始终返回updatedHtml - 接收updatedHtml逻辑增加if/else分支:targetRegionEl存在时走diff弹窗,不存在时调用execCmd('insertHTML')降级插入光标位置 - 参考参考-ReportEditor.tsx中injectAIText的降级机制 --- src/pages/ReportEditor.tsx | 22 ++++++---- 工程分析/20260419_0344/实现方案.md | 66 ++++++++++++++++++++++++++++++ 工程分析/20260419_0344/测试方案.md | 54 ++++++++++++++++++++++++ 工程分析/20260419_0344/需求分析.md | 53 ++++++++++++++++++++++++ 工程分析/经验记录.md | 31 ++++++++++++++ 5 files changed, 217 insertions(+), 9 deletions(-) create mode 100644 工程分析/20260419_0344/实现方案.md create mode 100644 工程分析/20260419_0344/测试方案.md create mode 100644 工程分析/20260419_0344/需求分析.md diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index e9205e4..1327bef 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -894,8 +894,8 @@ export default function ReportEditor() { } else { messageContent = promptText; } - const systemPrompt = aiModifyEnabled && targetRegionEl - ? '你是一名专业的外科医生助理。我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写并输出目标区域的 HTML。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }' + const systemPrompt = aiModifyEnabled + ? '你是一名专业的外科医生助理。我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写或生成目标区域的 HTML。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }' : '你是一名专业的外科医生助理。请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:严格返回合法的 JSON 对象。\nJSON 格式如下:\n{ "reply": "你的分析和回答" }'; const response = await fetch(`${apiEndpoint}/chat/completions`, { method: 'POST', @@ -927,13 +927,17 @@ export default function ReportEditor() { if (responseJson.reply) { setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', content: responseJson.reply }]); } - if (responseJson.updatedHtml && aiModifyEnabled && targetRegionEl) { - setDiffModal({ - isOpen: true, - originalHtml: currentHtml, - newHtml: responseJson.updatedHtml, - targetId: aiTargetRegion - }); + if (responseJson.updatedHtml && aiModifyEnabled) { + if (targetRegionEl) { + setDiffModal({ + isOpen: true, + originalHtml: currentHtml, + newHtml: responseJson.updatedHtml, + targetId: aiTargetRegion + }); + } else { + execCmd('insertHTML', responseJson.updatedHtml); + } } setAiUploadedImages([]); setAiSelectedFrames([]); diff --git a/工程分析/20260419_0344/实现方案.md b/工程分析/20260419_0344/实现方案.md new file mode 100644 index 0000000..cc7a553 --- /dev/null +++ b/工程分析/20260419_0344/实现方案.md @@ -0,0 +1,66 @@ +# 实现方案 + +## 修改文件 +- `src/pages/ReportEditor.tsx` + +## 修改位置:`handleAIGenerate` 函数(约 line 897-937) + +### 修改 1:System Prompt 条件解绑 + +**原代码**(line 897-899): +```tsx +const systemPrompt = aiModifyEnabled && targetRegionEl + ? '你是一名专业的外科医生助理。我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写并输出目标区域的 HTML。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }' + : '你是一名专业的外科医生助理。请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:严格返回合法的 JSON 对象。\nJSON 格式如下:\n{ "reply": "你的分析和回答" }'; +``` + +**新代码**: +```tsx +const systemPrompt = aiModifyEnabled + ? '你是一名专业的外科医生助理。我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写或生成目标区域的 HTML。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }' + : '你是一名专业的外科医生助理。请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:严格返回合法的 JSON 对象。\nJSON 格式如下:\n{ "reply": "你的分析和回答" }'; +``` + +**变更点**:条件从 `aiModifyEnabled && targetRegionEl` 改为 `aiModifyEnabled`,解绑对目标区域存在的强依赖。 + +### 修改 2:接收逻辑增加降级插入 + +**原代码**(line 930-937): +```tsx +if (responseJson.updatedHtml && aiModifyEnabled && targetRegionEl) { + setDiffModal({ + isOpen: true, + originalHtml: currentHtml, + newHtml: responseJson.updatedHtml, + targetId: aiTargetRegion + }); +} +``` + +**新代码**: +```tsx +if (responseJson.updatedHtml && aiModifyEnabled) { + if (targetRegionEl) { + setDiffModal({ + isOpen: true, + originalHtml: currentHtml, + newHtml: responseJson.updatedHtml, + targetId: aiTargetRegion + }); + } else { + execCmd('insertHTML', responseJson.updatedHtml); + } +} +``` + +**变更点**: +1. 接收条件从 `responseJson.updatedHtml && aiModifyEnabled && targetRegionEl` 放宽为 `responseJson.updatedHtml && aiModifyEnabled` +2. 增加 `if/else` 分支: + - `targetRegionEl` 存在 → 走 diff 弹窗(原有流程) + - `targetRegionEl` 不存在 → 调用 `execCmd('insertHTML', ...)` 直接插入光标位置(参考代码降级逻辑) +3. `execCmd` 为当前代码已存在的辅助函数,会自动处理 focus、execCommand、contentRef 更新和草稿保存 + +## 不修改的内容 +- `aiTargetRegion` 默认值 `'none'` 保持不变 +- `promptText` 构建逻辑保持不变(全局上下文 + 目标区域源码 + 医生指令) +- Diff 弹窗和 `confirmAiInjection` 保持不变 diff --git a/工程分析/20260419_0344/测试方案.md b/工程分析/20260419_0344/测试方案.md new file mode 100644 index 0000000..b19a6b5 --- /dev/null +++ b/工程分析/20260419_0344/测试方案.md @@ -0,0 +1,54 @@ +# 测试方案 + +## 测试环境 +- 浏览器访问 `http://localhost:4173/` +- 进入「图文报告生成」→ 新建报告 + +## 测试用例 1:开启修改模式 + 选中目标区域 → Diff 弹窗 + +**步骤**: +1. 编辑器中插入一个 AI 可编辑区域(如「手术步骤」) +2. AI 面板底部勾选「允许修改正文」 +3. 下拉框选中「手术步骤」区域 +4. 输入「请随机填充文本内容」并发送 + +**预期结果**: +- AI 聊天面板有回复 +- 弹出 diff 确认弹窗,展示原文 vs AI 修改后的 HTML +- 点击确认后,目标区域内容更新 + +## 测试用例 2:开启修改模式 + 未选中目标区域 → 光标插入 + +**步骤**: +1. 编辑器中不插入任何 AI 可编辑区域(或保持下拉框为「无可用 AI 区域」/不选) +2. AI 面板底部勾选「允许修改正文」 +3. 在编辑器正文处点击放置光标 +4. 输入「请随机填充文本内容」并发送 + +**预期结果**: +- AI 聊天面板有回复 +- **不弹出 diff 弹窗** +- AI 生成的内容直接插入到编辑器当前光标位置 +- 编辑器内容自动保存到草稿 + +## 测试用例 3:关闭修改模式 → 纯聊天 + +**步骤**: +1. AI 面板底部取消勾选「允许修改正文」 +2. 输入「请随机填充文本内容」并发送 + +**预期结果**: +- AI 聊天面板有回复 +- 编辑器内容不发生任何变化 +- 不弹出 diff 弹窗 + +## 测试用例 4:编译与部署 + +**步骤**: +1. 执行 `npm run build` +2. 确认无 TypeScript 编译错误 +3. 预览服务正常启动并返回 200 + +**预期结果**: +- `vite build` 成功完成 +- 预览页面可正常访问 diff --git a/工程分析/20260419_0344/需求分析.md b/工程分析/20260419_0344/需求分析.md new file mode 100644 index 0000000..4524c79 --- /dev/null +++ b/工程分析/20260419_0344/需求分析.md @@ -0,0 +1,53 @@ +# 需求分析 + +## 时间戳 +2026-04-19 03:44 + +## 需求来源 +用户在 report-editor 中输入「请随机填充文本内容」,AI 在聊天气泡中正常回复,但编辑器中的「手术步骤-AI可编辑部分」没有修改。 + +## 现象 +1. AI 聊天面板有输出(reply) +2. 编辑器中的 AI 可编辑区域内容未更新 +3. 没有触发 diff 确认弹窗 + +## 根因分析 + +对比当前代码与 `参考-ReportEditor.tsx`,问题有两个层面: + +### 层面 1:System Prompt 条件过于严苛 +当前代码: +```tsx +const systemPrompt = aiModifyEnabled && targetRegionEl ? '...修改HTML...' : '...纯聊天...' +``` +`targetRegionEl` 的获取依赖 `aiTargetRegion`(默认 `'none'`)。如果用户未在下拉框中选中具体区域,`targetRegionEl` 为 `null`,systemPrompt 降级为纯聊天模式,大模型根本不会返回 `updatedHtml` 字段。 + +参考代码: +```tsx +const systemPrompt = aiModifyEnabled ? '...修改HTML...' : '...纯聊天...' +``` +只依赖 `aiModifyEnabled`,只要用户开启「允许修改正文」,大模型就会返回 `updatedHtml`。 + +### 层面 2:接收逻辑缺少降级插入 +当前代码: +```tsx +if (responseJson.updatedHtml && aiModifyEnabled && targetRegionEl) { + setDiffModal({...}) +} +``` +同样因为 `targetRegionEl` 为 `null`,即使大模型返回了 `updatedHtml`,也会被丢弃。 + +参考代码 `injectAIText`: +```tsx +if (targetRegion) { + targetRegion.innerHTML = ... // 注入目标区域 +} else { + execCmd('insertHTML', htmlContent); // 降级:插入光标位置 +} +``` +找不到目标区域时,直接将内容插入编辑器当前光标位置。 + +## 约束条件 +- 当前代码已有 `execCmd` 辅助函数,可直接复用 +- 需要保留 diff 弹窗流程(目标区域存在时) +- `aiTargetRegion` 默认值 `'none'` 可保留,但需通过降级逻辑补偿 diff --git a/工程分析/经验记录.md b/工程分析/经验记录.md index 84bfe81..2a64c21 100644 --- a/工程分析/经验记录.md +++ b/工程分析/经验记录.md @@ -650,3 +650,34 @@ ReportEditor 采用 `useRef` 作为自动保存的数据快照机制(避免 Re **D. 后续如何避免问题** - 在复杂 React 组件(尤其是与 contentEditable 共存)中使用 Checkbox 时,**优先使用 `