fix(editor): AI只聊天不修改——解绑SystemPrompt目标区域依赖 + 增加光标插入降级
- systemPrompt条件从'aiModifyEnabled && targetRegionEl'改为'aiModifyEnabled',确保开启修改模式后大模型始终返回updatedHtml
- 接收updatedHtml逻辑增加if/else分支:targetRegionEl存在时走diff弹窗,不存在时调用execCmd('insertHTML')降级插入光标位置
- 参考参考-ReportEditor.tsx中injectAIText的降级机制
This commit is contained in:
@@ -894,8 +894,8 @@ export default function ReportEditor() {
|
|||||||
} else {
|
} else {
|
||||||
messageContent = promptText;
|
messageContent = promptText;
|
||||||
}
|
}
|
||||||
const systemPrompt = aiModifyEnabled && targetRegionEl
|
const systemPrompt = aiModifyEnabled
|
||||||
? '你是一名专业的外科医生助理。我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写并输出目标区域的 HTML。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }'
|
? '你是一名专业的外科医生助理。我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写或生成目标区域的 HTML。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }'
|
||||||
: '你是一名专业的外科医生助理。请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:严格返回合法的 JSON 对象。\nJSON 格式如下:\n{ "reply": "你的分析和回答" }';
|
: '你是一名专业的外科医生助理。请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:严格返回合法的 JSON 对象。\nJSON 格式如下:\n{ "reply": "你的分析和回答" }';
|
||||||
const response = await fetch(`${apiEndpoint}/chat/completions`, {
|
const response = await fetch(`${apiEndpoint}/chat/completions`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -927,13 +927,17 @@ export default function ReportEditor() {
|
|||||||
if (responseJson.reply) {
|
if (responseJson.reply) {
|
||||||
setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', content: responseJson.reply }]);
|
setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', content: responseJson.reply }]);
|
||||||
}
|
}
|
||||||
if (responseJson.updatedHtml && aiModifyEnabled && targetRegionEl) {
|
if (responseJson.updatedHtml && aiModifyEnabled) {
|
||||||
setDiffModal({
|
if (targetRegionEl) {
|
||||||
isOpen: true,
|
setDiffModal({
|
||||||
originalHtml: currentHtml,
|
isOpen: true,
|
||||||
newHtml: responseJson.updatedHtml,
|
originalHtml: currentHtml,
|
||||||
targetId: aiTargetRegion
|
newHtml: responseJson.updatedHtml,
|
||||||
});
|
targetId: aiTargetRegion
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
execCmd('insertHTML', responseJson.updatedHtml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setAiUploadedImages([]);
|
setAiUploadedImages([]);
|
||||||
setAiSelectedFrames([]);
|
setAiSelectedFrames([]);
|
||||||
|
|||||||
66
工程分析/20260419_0344/实现方案.md
Normal file
66
工程分析/20260419_0344/实现方案.md
Normal file
@@ -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` 保持不变
|
||||||
54
工程分析/20260419_0344/测试方案.md
Normal file
54
工程分析/20260419_0344/测试方案.md
Normal file
@@ -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` 成功完成
|
||||||
|
- 预览页面可正常访问
|
||||||
53
工程分析/20260419_0344/需求分析.md
Normal file
53
工程分析/20260419_0344/需求分析.md
Normal file
@@ -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'` 可保留,但需通过降级逻辑补偿
|
||||||
31
工程分析/经验记录.md
31
工程分析/经验记录.md
@@ -650,3 +650,34 @@ ReportEditor 采用 `useRef` 作为自动保存的数据快照机制(避免 Re
|
|||||||
**D. 后续如何避免问题**
|
**D. 后续如何避免问题**
|
||||||
- 在复杂 React 组件(尤其是与 contentEditable 共存)中使用 Checkbox 时,**优先使用 `<label>` 直接包裹 `<input>`** 的写法,避免依赖 `id`/`htmlFor`。
|
- 在复杂 React 组件(尤其是与 contentEditable 共存)中使用 Checkbox 时,**优先使用 `<label>` 直接包裹 `<input>`** 的写法,避免依赖 `id`/`htmlFor`。
|
||||||
- 向大模型发送局部修改请求时,**必须同时提供全局上下文**,否则 AI 无法基于文档其他部分的信息进行推理和修改。
|
- 向大模型发送局部修改请求时,**必须同时提供全局上下文**,否则 AI 无法基于文档其他部分的信息进行推理和修改。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 记录 30:AI「只聊天不干活」——System Prompt 过度依赖目标区域 + 缺少降级插入
|
||||||
|
|
||||||
|
**A. 具体问题**
|
||||||
|
用户在 report-editor 中输入「请随机填充文本内容」,AI 聊天面板有输出,但编辑器中的 AI 可编辑区域没有任何更新。
|
||||||
|
|
||||||
|
**B. 产生问题原因**
|
||||||
|
1. **System Prompt 条件过于严苛**:`systemPrompt` 的构建条件是 `aiModifyEnabled && targetRegionEl`。由于 `aiTargetRegion` 默认值为 `'none'`,如果用户未在下拉框中明确选中区域,`targetRegionEl` 为 `null`,systemPrompt 降级为纯聊天模式,大模型根本不会返回 `updatedHtml` 字段。
|
||||||
|
2. **接收逻辑缺少降级**:`responseJson.updatedHtml` 的接收条件是 `aiModifyEnabled && targetRegionEl`,同样因为 `targetRegionEl` 为 `null` 而被跳过。即使大模型返回了 HTML,也会被丢弃。
|
||||||
|
3. **缺少光标插入降级**:参考代码 `injectAIText` 中,当找不到目标区域时,会调用 `execCmd('insertHTML', htmlContent)` 将内容直接插入当前光标位置。当前代码完全没有这种降级机制。
|
||||||
|
|
||||||
|
**C. 解决问题方案**
|
||||||
|
1. **解绑 System Prompt**:将条件从 `aiModifyEnabled && targetRegionEl` 改为 `aiModifyEnabled`,让大模型在「允许修改正文」开启时始终返回 `updatedHtml`。
|
||||||
|
2. **增加降级插入逻辑**:
|
||||||
|
```ts
|
||||||
|
if (responseJson.updatedHtml && aiModifyEnabled) {
|
||||||
|
if (targetRegionEl) {
|
||||||
|
setDiffModal({...}); // 原有流程:目标区域存在时走 diff 弹窗
|
||||||
|
} else {
|
||||||
|
execCmd('insertHTML', responseJson.updatedHtml); // 降级:插入光标位置
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. 复用当前代码已存在的 `execCmd` 辅助函数,自动处理 focus、contentRef 更新和草稿保存。
|
||||||
|
|
||||||
|
**D. 后续如何避免问题**
|
||||||
|
- 设计「修改/生成」类 AI 功能时,**systemPrompt 的条件应尽量只依赖用户意图开关**(如 `aiModifyEnabled`),而非依赖具体 UI 状态(如某个下拉框是否选中)。UI 状态应只影响「如何注入结果」,不应影响「是否要求模型生成结果」。
|
||||||
|
- 任何「目标区域注入」逻辑都必须配备**降级方案**(如光标处插入),防止因用户未选中区域而导致功能完全失效。
|
||||||
|
|||||||
Reference in New Issue
Block a user