feat(ai): diff弹窗内容完整性优化 + HTML空行清洗

- systemPrompt和promptText中明确要求AI生成完整多段落内容,不要只改写现有段落
- systemPrompt增加HTML格式约束:<p>标签包裹、禁止<br>和换行符、紧凑HTML
- setDiffModal和execCmd之前增加正则清洗:移除<br>、移除</p>与<p>间空白、trim首尾
This commit is contained in:
2026-04-19 04:15:36 +08:00
parent 9f73d8595c
commit c7e7033e7d
5 changed files with 231 additions and 4 deletions

View File

@@ -0,0 +1,101 @@
# 实现方案
## 修改文件
- `src/pages/ReportEditor.tsx`
## 修改位置 1System 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. 段落必须使用 <p> 标签包裹,段落之间绝对不要使用 <br> 标签,也不要使用任何换行符 (\\n)\n5. 输出的 HTML 必须紧凑,标签之间不要有空格或换行\n6. 绝对不要包含任何 Markdown 标记(如 ```json'
: '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】并根据【医生指令】进行专业解答。\n重要指令\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记';
```
**变更点**
1. 修改模式 systemPrompt 增加第 3 点:明确要求生成完整、结构化的多段落内容,基于全局信息补充完善
2. 修改模式 systemPrompt 增加第 4 点:段落必须用 `<p>` 包裹,禁止 `<br>``\n`
3. 修改模式 systemPrompt 增加第 5 点HTML 必须紧凑,标签间不要有空格或换行
## 修改位置 2Prompt 文本强化(约 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. 段落使用 <p> 标签,段落之间不要使用 <br> 标签或换行符\n3. 输出紧凑 HTML标签间不要有空格或换行`;
```
**变更点**
1. 在医生指令后追加「格式要求」段落
2. 明确要求生成完整多段落内容,不要只改写现有段落
3. 强调 `<p>` 标签、禁止 `<br>`、禁止换行符、紧凑 HTML
## 修改位置 3HTML 清洗后处理(约 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(/<br\s*\/?>/gi, '');
cleanHtml = cleanHtml.replace(/<\/p>\s*<p>/gi, '</p><p>');
cleanHtml = cleanHtml.trim();
if (targetRegionEl) {
setDiffModal({
isOpen: true,
originalHtml: currentHtml,
newHtml: cleanHtml,
targetId: actualTargetId
});
} else {
execCmd('insertHTML', cleanHtml);
}
}
```
**变更点**
1. 新增 `cleanHtml` 变量,初始值为 `responseJson.updatedHtml`
2. 移除 `<br>` 标签(不区分大小写)
3. 移除 `</p>``<p>` 之间的空白字符(空格、换行、回车)
4. 移除首尾空白
5. `setDiffModal``execCmd` 均使用 `cleanHtml`

View File

@@ -0,0 +1,52 @@
# 测试方案
## 测试环境
- 浏览器访问 `http://localhost:4173/`
- 进入「图文报告生成」→ 新建报告
## 测试用例 1AI 生成完整多段落内容
**步骤**
1. 编辑器中插入一个 AI 可编辑区域
2. 在区域中只写一句话(如「建立气腹」)
3. 在编辑器其他位置写入完整的手术报告信息(患者信息、其他步骤等)
4. 勾选「允许修改正文」→ 发送「请完善手术步骤描述」
**预期结果**
- diff 弹窗左侧显示原有的一句话
- diff 弹窗右侧显示 AI 生成的完整多段落内容(包含多个 `<p>` 标签)
- 内容应基于全局报告信息补充完善,不只是改写原有的一句话
## 测试用例 2右侧无多余空行
**步骤**
1. 编辑器中插入 AI 可编辑区域,写入多段内容(如 3 个 `<p>` 段落)
2. 勾选「允许修改正文」→ 发送「请润色这段内容」
3. 观察 diff 弹窗左右两侧的段落间距
**预期结果**
- 左右两侧段落间距一致
- 右侧 AI 版本不应出现额外的空行或 `<br>`
- 段落间仅由 `<p>` 标签的自然 margin 分隔
## 测试用例 3HTML 清洗兜底
**步骤**
1. 触发 AI 修改,在浏览器 DevTools 中查看 `responseJson.updatedHtml` 原始值
2. 确认原始值中可能包含 `<br>``\n`
3. 观察 diff 弹窗右侧最终渲染结果
**预期结果**
- 即使原始返回值包含 `<br>``\n`diff 弹窗右侧也不应显示多余空行
- 清洗后的 HTML 结构紧凑
## 测试用例 4编译与部署
**步骤**
1. 执行 `npm run build`
2. 确认无 TypeScript 编译错误
3. 预览服务正常启动并返回 200
**预期结果**
- `vite build` 成功完成
- 预览页面可正常访问

View File

@@ -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 中可能包含 `<br>` 标签或 `\n` 换行符
2. `</p>\n<p>` 中的换行符会被浏览器解析为文本节点,产生额外空白
3. 不同 LLM 的输出格式随机性导致 HTML 结构不统一
**解决方向**
1. **输入端控制**:在 systemPrompt 中明确要求紧凑 HTML禁止 `<br>`、禁止换行符)
2. **输出端兜底**:在 `setDiffModal` 之前对 `updatedHtml` 进行正则清洗,移除多余空行和 `<br>`
## 约束条件
- 保持现有 diff 弹窗的左右分栏结构不变
- 清洗逻辑不应破坏合法的 HTML 结构(如 `<p>` 标签内的内容)

View File

@@ -718,3 +718,34 @@ ReportEditor 采用 `useRef` 作为自动保存的数据快照机制(避免 Re
**D. 后续如何避免问题**
- 当功能存在「全局开关 + 局部选择器」两层控制时,**全局开关开启后应自动兜底局部选择器**,避免因用户遗漏局部配置而导致功能降级。
- System Prompt 中应显式标注当前模式名称(如「修改模式」「对话模式」),大模型对显式标签的遵循度远高于隐式条件推断。
---
## 记录 32AI diff 弹窗内容不完整 + 右侧多余空行
**A. 具体问题**
1. AI 修改确认弹窗左侧原始版本只显示一段内容,用户希望 AI 能一次性生成完整的多段落内容。
2. diff 弹窗右侧 AI 提议版本的段落间出现额外空行,与左侧结构不一致。
**B. 产生问题原因**
1. **内容不完整**:大模型被给予的目标区域源码(`currentHtml`)可能只有一段,且 systemPrompt 没有明确要求「生成完整、结构化的多段落内容」,导致 AI 只做局部改写。
2. **多余空行**:大模型返回的 HTML 中常包含 `<br>` 标签或 `\n` 换行符。`</p>\n<p>` 中的换行符会被浏览器解析为文本节点,产生额外空白。
**C. 解决问题方案**
1. **输入端控制System Prompt + Prompt**
- systemPrompt 增加明确要求:`updatedHtml 必须生成完整、结构化的多段落内容,不要只改写现有段落`
- systemPrompt 增加 HTML 格式约束:`段落必须使用 <p> 标签包裹,段落之间绝对不要使用 <br> 标签,也不要使用任何换行符`
- promptText 末尾追加「格式要求」段落,再次强调完整多段落、`<p>` 标签、禁止 `<br>`、紧凑 HTML
2. **输出端兜底(正则清洗)**
```ts
let cleanHtml = responseJson.updatedHtml;
cleanHtml = cleanHtml.replace(/<br\s*\/?>/gi, '');
cleanHtml = cleanHtml.replace(/<\/p>\s*<p>/gi, '</p><p>');
cleanHtml = cleanHtml.trim();
```
在 `setDiffModal` 和 `execCmd` 之前统一清洗,确保右侧渲染结构与左侧一致。
**D. 后续如何避免问题**
- 当大模型返回的 HTML 需要在前端渲染时,**必须同时在输入端prompt和输出端后处理进行格式约束**,单靠一端无法完全控制不同 LLM 的输出随机性。
- 对于「生成完整性」类需求,必须在 prompt 中明确使用「必须生成完整...」「不要只改写...」等强制性措辞,否则大模型倾向于做最小化修改。