diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index 2cb0c86..3bcb59b 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -892,7 +892,7 @@ export default function ReportEditor() { if (aiModifyEnabled && targetRegionEl) { promptText += `【你需要进行修改的目标区域 HTML 源码】:\n${currentHtml || '(当前区域为空)'}\n\n`; } - promptText += `【医生指令】: ${text}`; + promptText += `【医生指令】: ${text}\n\n【格式要求】:\n1. 生成完整、结构化的多段落 HTML 内容,不要只改写现有段落\n2. 段落使用
标签,段落之间不要使用
标签或换行符\n3. 输出紧凑 HTML,标签间不要有空格或换行`;
if (allImages.length > 0) {
messageContent = [];
allImages.forEach(url => {
@@ -903,7 +903,7 @@ export default function ReportEditor() {
messageContent = promptText;
}
const systemPrompt = aiModifyEnabled
- ? '你是一名专业的外科医生助理。当前处于【修改模式】。\n我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写并输出目标区域的 HTML。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"(简短回复话术)和 "updatedHtml"(修改后的完整内部 HTML 代码)两个字段\n3. 绝对不要包含任何 Markdown 标记(如 ```json)'
+ ? '你是一名专业的外科医生助理。当前处于【修改模式】。\n我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写并输出目标区域的 HTML。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"(简短回复话术)和 "updatedHtml"(修改后的完整内部 HTML 代码)两个字段\n3. updatedHtml 必须生成完整、结构化的多段落内容,不要只改写现有段落,要基于全局信息补充完善\n4. 段落必须使用
标签包裹,段落之间绝对不要使用
标签,也不要使用任何换行符 (\\n)\n5. 输出的 HTML 必须紧凑,标签之间不要有空格或换行\n6. 绝对不要包含任何 Markdown 标记(如 ```json)'
: '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记';
const response = await fetch(`${apiEndpoint}/chat/completions`, {
method: 'POST',
@@ -936,15 +936,19 @@ export default function ReportEditor() {
setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', content: responseJson.reply }]);
}
if (responseJson.updatedHtml && aiModifyEnabled) {
+ let cleanHtml = responseJson.updatedHtml;
+ cleanHtml = cleanHtml.replace(/
/gi, '');
+ cleanHtml = cleanHtml.replace(/<\/p>\s*
/gi, '
'); + cleanHtml = cleanHtml.trim(); if (targetRegionEl) { setDiffModal({ isOpen: true, originalHtml: currentHtml, - newHtml: responseJson.updatedHtml, + newHtml: cleanHtml, targetId: actualTargetId }); } else { - execCmd('insertHTML', responseJson.updatedHtml); + execCmd('insertHTML', cleanHtml); } } setAiUploadedImages([]); diff --git a/工程分析/20260419_0413/实现方案.md b/工程分析/20260419_0413/实现方案.md new file mode 100644 index 0000000..310b5d3 --- /dev/null +++ b/工程分析/20260419_0413/实现方案.md @@ -0,0 +1,101 @@ +# 实现方案 + +## 修改文件 +- `src/pages/ReportEditor.tsx` + +## 修改位置 1:System Prompt 强化(约 line 905-907) + +在 systemPrompt 中增加对「生成完整性」和「HTML 紧凑性」的明确要求。 + +**原代码**: +```tsx + const systemPrompt = aiModifyEnabled + ? '你是一名专业的外科医生助理。当前处于【修改模式】。\n我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写并输出目标区域的 HTML。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"(简短回复话术)和 "updatedHtml"(修改后的完整内部 HTML 代码)两个字段\n3. 绝对不要包含任何 Markdown 标记(如 ```json)' + : '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记'; +``` + +**新代码**: +```tsx + const systemPrompt = aiModifyEnabled + ? '你是一名专业的外科医生助理。当前处于【修改模式】。\n我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写并输出目标区域的 HTML。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"(简短回复话术)和 "updatedHtml"(修改后的完整内部 HTML 代码)两个字段\n3. updatedHtml 必须生成完整、结构化的多段落内容,不要只改写现有段落,要基于全局信息补充完善\n4. 段落必须使用
标签包裹,段落之间绝对不要使用
标签,也不要使用任何换行符 (\\n)\n5. 输出的 HTML 必须紧凑,标签之间不要有空格或换行\n6. 绝对不要包含任何 Markdown 标记(如 ```json)'
+ : '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记';
+```
+
+**变更点**:
+1. 修改模式 systemPrompt 增加第 3 点:明确要求生成完整、结构化的多段落内容,基于全局信息补充完善
+2. 修改模式 systemPrompt 增加第 4 点:段落必须用 `
` 包裹,禁止 `
` 和 `\n`
+3. 修改模式 systemPrompt 增加第 5 点:HTML 必须紧凑,标签间不要有空格或换行
+
+## 修改位置 2:Prompt 文本强化(约 line 891-895)
+
+在 `promptText` 中增加对「生成完整性」的强调。
+
+**原代码**:
+```tsx
+ let promptText = `【全局手术报告参考内容】:\n${globalContextText}\n\n`;
+ if (aiModifyEnabled && targetRegionEl) {
+ promptText += `【你需要进行修改的目标区域 HTML 源码】:\n${currentHtml || '(当前区域为空)'}\n\n`;
+ }
+ promptText += `【医生指令】: ${text}`;
+```
+
+**新代码**:
+```tsx
+ let promptText = `【全局手术报告参考内容】:\n${globalContextText}\n\n`;
+ if (aiModifyEnabled && targetRegionEl) {
+ promptText += `【你需要进行修改的目标区域 HTML 源码】:\n${currentHtml || '(当前区域为空)'}\n\n`;
+ }
+ promptText += `【医生指令】: ${text}\n\n【格式要求】:\n1. 生成完整、结构化的多段落 HTML 内容,不要只改写现有段落\n2. 段落使用
标签,段落之间不要使用
标签或换行符\n3. 输出紧凑 HTML,标签间不要有空格或换行`;
+```
+
+**变更点**:
+1. 在医生指令后追加「格式要求」段落
+2. 明确要求生成完整多段落内容,不要只改写现有段落
+3. 强调 `
` 标签、禁止 `
`、禁止换行符、紧凑 HTML
+
+## 修改位置 3:HTML 清洗后处理(约 line 938-945)
+
+在 `setDiffModal` 之前对 `updatedHtml` 进行正则清洗。
+
+**原代码**:
+```tsx
+ if (responseJson.updatedHtml && aiModifyEnabled) {
+ if (targetRegionEl) {
+ setDiffModal({
+ isOpen: true,
+ originalHtml: currentHtml,
+ newHtml: responseJson.updatedHtml,
+ targetId: actualTargetId
+ });
+ } else {
+ execCmd('insertHTML', responseJson.updatedHtml);
+ }
+ }
+```
+
+**新代码**:
+```tsx
+ if (responseJson.updatedHtml && aiModifyEnabled) {
+ let cleanHtml = responseJson.updatedHtml;
+ cleanHtml = cleanHtml.replace(/
/gi, '');
+ cleanHtml = cleanHtml.replace(/<\/p>\s*
/gi, '
');
+ cleanHtml = cleanHtml.trim();
+ if (targetRegionEl) {
+ setDiffModal({
+ isOpen: true,
+ originalHtml: currentHtml,
+ newHtml: cleanHtml,
+ targetId: actualTargetId
+ });
+ } else {
+ execCmd('insertHTML', cleanHtml);
+ }
+ }
+```
+
+**变更点**:
+1. 新增 `cleanHtml` 变量,初始值为 `responseJson.updatedHtml`
+2. 移除 `
` 标签(不区分大小写)
+3. 移除 `
` 之间的空白字符(空格、换行、回车) +4. 移除首尾空白 +5. `setDiffModal` 和 `execCmd` 均使用 `cleanHtml` diff --git a/工程分析/20260419_0413/测试方案.md b/工程分析/20260419_0413/测试方案.md new file mode 100644 index 0000000..a068da5 --- /dev/null +++ b/工程分析/20260419_0413/测试方案.md @@ -0,0 +1,52 @@ +# 测试方案 + +## 测试环境 +- 浏览器访问 `http://localhost:4173/` +- 进入「图文报告生成」→ 新建报告 + +## 测试用例 1:AI 生成完整多段落内容 + +**步骤**: +1. 编辑器中插入一个 AI 可编辑区域 +2. 在区域中只写一句话(如「建立气腹」) +3. 在编辑器其他位置写入完整的手术报告信息(患者信息、其他步骤等) +4. 勾选「允许修改正文」→ 发送「请完善手术步骤描述」 + +**预期结果**: +- diff 弹窗左侧显示原有的一句话 +- diff 弹窗右侧显示 AI 生成的完整多段落内容(包含多个 `
` 标签) +- 内容应基于全局报告信息补充完善,不只是改写原有的一句话 + +## 测试用例 2:右侧无多余空行 + +**步骤**: +1. 编辑器中插入 AI 可编辑区域,写入多段内容(如 3 个 `
` 段落)
+2. 勾选「允许修改正文」→ 发送「请润色这段内容」
+3. 观察 diff 弹窗左右两侧的段落间距
+
+**预期结果**:
+- 左右两侧段落间距一致
+- 右侧 AI 版本不应出现额外的空行或 `
`
+- 段落间仅由 `
` 标签的自然 margin 分隔
+
+## 测试用例 3:HTML 清洗兜底
+
+**步骤**:
+1. 触发 AI 修改,在浏览器 DevTools 中查看 `responseJson.updatedHtml` 原始值
+2. 确认原始值中可能包含 `
` 或 `\n`
+3. 观察 diff 弹窗右侧最终渲染结果
+
+**预期结果**:
+- 即使原始返回值包含 `
` 或 `\n`,diff 弹窗右侧也不应显示多余空行
+- 清洗后的 HTML 结构紧凑
+
+## 测试用例 4:编译与部署
+
+**步骤**:
+1. 执行 `npm run build`
+2. 确认无 TypeScript 编译错误
+3. 预览服务正常启动并返回 200
+
+**预期结果**:
+- `vite build` 成功完成
+- 预览页面可正常访问
diff --git a/工程分析/20260419_0413/需求分析.md b/工程分析/20260419_0413/需求分析.md
new file mode 100644
index 0000000..8140dbb
--- /dev/null
+++ b/工程分析/20260419_0413/需求分析.md
@@ -0,0 +1,39 @@
+# 需求分析
+
+## 时间戳
+2026-04-19 04:13
+
+## 需求来源
+用户在使用 AI 修改确认弹窗时遇到两个问题:
+1. diff 弹窗左侧原始版本只显示一段内容,希望 AI 能一次性把全部内容都修改好
+2. diff 弹窗右侧 AI 提议版本的段落间有多余空行,希望结构和左侧保持一致
+
+## 问题 1:左侧只显示一段
+
+**现象**:diff 弹窗左侧「原始版本」只展示了一段文本。
+
+**根因分析**:
+- `currentHtml` 取自 `.ai-content` 的 `innerHTML`,如果 `.ai-content` 内确实只有一段内容,左侧自然只显示一段
+- 这不是代码 Bug,而是当前 AI 区域的内容组织问题
+- 但用户期望 AI 能基于全局上下文生成更丰富、更完整的内容,而不是仅改写当前已有的段落
+
+**解决方向**:
+- 在 systemPrompt 和 prompt 中明确要求 AI 生成完整的、结构化的多段落内容
+- 强调 AI 应基于全局参考内容补充和完善目标区域,而不是仅做局部改写
+
+## 问题 2:右侧多余空行
+
+**现象**:diff 弹窗右侧「AI 提议版本」的段落间出现额外空行,与左侧结构不一致。
+
+**根因分析**:
+1. 大模型返回的 HTML 中可能包含 `
` 标签或 `\n` 换行符
+2. `
` 中的换行符会被浏览器解析为文本节点,产生额外空白
+3. 不同 LLM 的输出格式随机性导致 HTML 结构不统一
+
+**解决方向**:
+1. **输入端控制**:在 systemPrompt 中明确要求紧凑 HTML(禁止 `
`、禁止换行符)
+2. **输出端兜底**:在 `setDiffModal` 之前对 `updatedHtml` 进行正则清洗,移除多余空行和 `
`
+
+## 约束条件
+- 保持现有 diff 弹窗的左右分栏结构不变
+- 清洗逻辑不应破坏合法的 HTML 结构(如 `
` 标签内的内容)
diff --git a/工程分析/经验记录.md b/工程分析/经验记录.md
index 3d1db37..63f5427 100644
--- a/工程分析/经验记录.md
+++ b/工程分析/经验记录.md
@@ -718,3 +718,34 @@ ReportEditor 采用 `useRef` 作为自动保存的数据快照机制(避免 Re
**D. 后续如何避免问题**
- 当功能存在「全局开关 + 局部选择器」两层控制时,**全局开关开启后应自动兜底局部选择器**,避免因用户遗漏局部配置而导致功能降级。
- System Prompt 中应显式标注当前模式名称(如「修改模式」「对话模式」),大模型对显式标签的遵循度远高于隐式条件推断。
+
+
+---
+
+## 记录 32:AI diff 弹窗内容不完整 + 右侧多余空行
+
+**A. 具体问题**
+1. AI 修改确认弹窗左侧原始版本只显示一段内容,用户希望 AI 能一次性生成完整的多段落内容。
+2. diff 弹窗右侧 AI 提议版本的段落间出现额外空行,与左侧结构不一致。
+
+**B. 产生问题原因**
+1. **内容不完整**:大模型被给予的目标区域源码(`currentHtml`)可能只有一段,且 systemPrompt 没有明确要求「生成完整、结构化的多段落内容」,导致 AI 只做局部改写。
+2. **多余空行**:大模型返回的 HTML 中常包含 `
` 标签或 `\n` 换行符。`
` 中的换行符会被浏览器解析为文本节点,产生额外空白。 + +**C. 解决问题方案** +1. **输入端控制(System Prompt + Prompt)**: + - systemPrompt 增加明确要求:`updatedHtml 必须生成完整、结构化的多段落内容,不要只改写现有段落` + - systemPrompt 增加 HTML 格式约束:`段落必须使用
标签包裹,段落之间绝对不要使用
标签,也不要使用任何换行符`
+ - promptText 末尾追加「格式要求」段落,再次强调完整多段落、`
` 标签、禁止 `
`、紧凑 HTML
+2. **输出端兜底(正则清洗)**:
+ ```ts
+ let cleanHtml = responseJson.updatedHtml;
+ cleanHtml = cleanHtml.replace(/
/gi, '');
+ cleanHtml = cleanHtml.replace(/<\/p>\s*
/gi, '
'); + cleanHtml = cleanHtml.trim(); + ``` + 在 `setDiffModal` 和 `execCmd` 之前统一清洗,确保右侧渲染结构与左侧一致。 + +**D. 后续如何避免问题** +- 当大模型返回的 HTML 需要在前端渲染时,**必须同时在输入端(prompt)和输出端(后处理)进行格式约束**,单靠一端无法完全控制不同 LLM 的输出随机性。 +- 对于「生成完整性」类需求,必须在 prompt 中明确使用「必须生成完整...」「不要只改写...」等强制性措辞,否则大模型倾向于做最小化修改。