feat(ai): 修改模式自动锁定目标区域 + SystemPrompt模式语义强化
- handleAIGenerate开头增加自动修正目标区域逻辑:修改模式开启且未选区域时,自动选择文档中第一个AI区域 - systemPrompt明确标注'当前处于【修改模式】/【对话模式】',并细化字段要求 - diffModal的targetId改为使用actualTargetId,确保确认注入时使用实际修正后的区域ID
This commit is contained in:
@@ -874,7 +874,15 @@ export default function ReportEditor() {
|
|||||||
setIsGenerating(false);
|
setIsGenerating(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const targetRegionEl = editorRef.current?.querySelector(`.ai-region[data-ai-id="${aiTargetRegion}"] .ai-content`) as HTMLElement | null;
|
let actualTargetId = aiTargetRegion;
|
||||||
|
if (aiModifyEnabled && actualTargetId === 'none') {
|
||||||
|
const availableRegions = checkAiRegions();
|
||||||
|
if (availableRegions.length > 0) {
|
||||||
|
actualTargetId = availableRegions[0].id;
|
||||||
|
setAiTargetRegion(actualTargetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const targetRegionEl = editorRef.current?.querySelector(`.ai-region[data-ai-id="${actualTargetId}"] .ai-content`) as HTMLElement | null;
|
||||||
const currentHtml = targetRegionEl ? targetRegionEl.innerHTML.replace(/​/g, '').trim() : '';
|
const currentHtml = targetRegionEl ? targetRegionEl.innerHTML.replace(/​/g, '').trim() : '';
|
||||||
const globalContextText = editorRef.current?.innerText || '';
|
const globalContextText = editorRef.current?.innerText || '';
|
||||||
let messageContent: any;
|
let messageContent: any;
|
||||||
@@ -895,8 +903,8 @@ export default function ReportEditor() {
|
|||||||
messageContent = promptText;
|
messageContent = promptText;
|
||||||
}
|
}
|
||||||
const systemPrompt = aiModifyEnabled
|
const systemPrompt = aiModifyEnabled
|
||||||
? '你是一名专业的外科医生助理。我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写或生成目标区域的 HTML。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }'
|
? '你是一名专业的外科医生助理。当前处于【修改模式】。\n我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写并输出目标区域的 HTML。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"(简短回复话术)和 "updatedHtml"(修改后的完整内部 HTML 代码)两个字段\n3. 绝对不要包含任何 Markdown 标记(如 ```json)'
|
||||||
: '你是一名专业的外科医生助理。请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:严格返回合法的 JSON 对象。\nJSON 格式如下:\n{ "reply": "你的分析和回答" }';
|
: '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记';
|
||||||
const response = await fetch(`${apiEndpoint}/chat/completions`, {
|
const response = await fetch(`${apiEndpoint}/chat/completions`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -933,7 +941,7 @@ export default function ReportEditor() {
|
|||||||
isOpen: true,
|
isOpen: true,
|
||||||
originalHtml: currentHtml,
|
originalHtml: currentHtml,
|
||||||
newHtml: responseJson.updatedHtml,
|
newHtml: responseJson.updatedHtml,
|
||||||
targetId: aiTargetRegion
|
targetId: actualTargetId
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
execCmd('insertHTML', responseJson.updatedHtml);
|
execCmd('insertHTML', responseJson.updatedHtml);
|
||||||
|
|||||||
70
工程分析/20260419_0359/实现方案.md
Normal file
70
工程分析/20260419_0359/实现方案.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# 实现方案
|
||||||
|
|
||||||
|
## 修改文件
|
||||||
|
- `src/pages/ReportEditor.tsx`
|
||||||
|
|
||||||
|
## 修改位置:`handleAIGenerate` 函数(约 line 860-950)
|
||||||
|
|
||||||
|
### 修改 1:自动修正目标区域
|
||||||
|
|
||||||
|
在获取 `targetRegionEl` 之前,增加自动修正逻辑:如果修改模式开启且未选定区域,自动选择文档中的第一个 AI 区域。
|
||||||
|
|
||||||
|
**原代码**(line 877):
|
||||||
|
```tsx
|
||||||
|
const targetRegionEl = editorRef.current?.querySelector(`.ai-region[data-ai-id="${aiTargetRegion}"] .ai-content`) as HTMLElement | null;
|
||||||
|
```
|
||||||
|
|
||||||
|
**新代码**:
|
||||||
|
```tsx
|
||||||
|
let actualTargetId = aiTargetRegion;
|
||||||
|
if (aiModifyEnabled && actualTargetId === 'none') {
|
||||||
|
const availableRegions = checkAiRegions();
|
||||||
|
if (availableRegions.length > 0) {
|
||||||
|
actualTargetId = availableRegions[0].id;
|
||||||
|
setAiTargetRegion(actualTargetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const targetRegionEl = editorRef.current?.querySelector(`.ai-region[data-ai-id="${actualTargetId}"] .ai-content`) as HTMLElement | null;
|
||||||
|
```
|
||||||
|
|
||||||
|
**变更点**:
|
||||||
|
1. 引入 `actualTargetId` 变量,初始值为 `aiTargetRegion`
|
||||||
|
2. 若 `aiModifyEnabled` 为 true 且 `actualTargetId === 'none'`,调用 `checkAiRegions()` 获取可用区域列表
|
||||||
|
3. 若有可用区域,将 `actualTargetId` 设为第一个区域的 id,并同步更新 React state(`setAiTargetRegion`)
|
||||||
|
4. 后续所有依赖目标区域 ID 的地方(querySelector、diffModal targetId)均使用 `actualTargetId`
|
||||||
|
|
||||||
|
### 修改 2:优化 systemPrompt 文案
|
||||||
|
|
||||||
|
**原代码**(line 897-899):
|
||||||
|
```tsx
|
||||||
|
const systemPrompt = aiModifyEnabled
|
||||||
|
? '你是一名专业的外科医生助理。我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写或生成目标区域的 HTML。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }'
|
||||||
|
: '你是一名专业的外科医生助理。请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:严格返回合法的 JSON 对象。\nJSON 格式如下:\n{ "reply": "你的分析和回答" }';
|
||||||
|
```
|
||||||
|
|
||||||
|
**新代码**:
|
||||||
|
```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 标记';
|
||||||
|
```
|
||||||
|
|
||||||
|
**变更点**:
|
||||||
|
1. 修改模式 systemPrompt 明确标注「当前处于【修改模式】」
|
||||||
|
2. 明确要求必须包含 `reply` 和 `updatedHtml` 两个字段
|
||||||
|
3. 对话模式 systemPrompt 明确标注「当前处于【对话模式】」
|
||||||
|
4. 明确要求仅包含 `reply` 字段,不要返回 HTML
|
||||||
|
|
||||||
|
### 修改 3:diffModal targetId 使用 actualTargetId
|
||||||
|
|
||||||
|
**原代码**(line 936):
|
||||||
|
```tsx
|
||||||
|
targetId: aiTargetRegion
|
||||||
|
```
|
||||||
|
|
||||||
|
**新代码**:
|
||||||
|
```tsx
|
||||||
|
targetId: actualTargetId
|
||||||
|
```
|
||||||
|
|
||||||
|
**变更点**:确保 diff 弹窗确认注入时,使用实际修正后的区域 ID,而非可能仍为 `'none'` 的 `aiTargetRegion`。
|
||||||
55
工程分析/20260419_0359/测试方案.md
Normal file
55
工程分析/20260419_0359/测试方案.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# 测试方案
|
||||||
|
|
||||||
|
## 测试环境
|
||||||
|
- 浏览器访问 `http://localhost:4173/`
|
||||||
|
- 进入「图文报告生成」→ 新建报告
|
||||||
|
|
||||||
|
## 测试用例 1:修改模式 + 已选中区域 → Diff 弹窗
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. 编辑器中插入一个 AI 可编辑区域(如「手术步骤」)
|
||||||
|
2. AI 面板底部勾选「允许修改正文」
|
||||||
|
3. 下拉框手动选中「手术步骤」区域
|
||||||
|
4. 输入「请随机填充文本内容」并发送
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- AI 聊天面板有回复
|
||||||
|
- 弹出 diff 确认弹窗,左侧展示原稿,右侧展示 AI 生成内容(可直接编辑)
|
||||||
|
- 点击「确认并写入报告」后,目标区域内容更新
|
||||||
|
- 点击「放弃修改」后,编辑器内容不变
|
||||||
|
|
||||||
|
## 测试用例 2:修改模式 + 未选中区域 → 自动选中第一个 + Diff 弹窗
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. 编辑器中插入一个 AI 可编辑区域(如「手术步骤」)
|
||||||
|
2. AI 面板底部勾选「允许修改正文」
|
||||||
|
3. **不手动选择下拉框中的区域**(保持默认或未选状态)
|
||||||
|
4. 输入「请随机填充文本内容」并发送
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- 发送后下拉框自动跳转为「手术步骤」(第一个可用区域)
|
||||||
|
- AI 聊天面板有回复
|
||||||
|
- 弹出 diff 确认弹窗
|
||||||
|
- 确认后目标区域内容更新
|
||||||
|
|
||||||
|
## 测试用例 3:对话模式 → 仅聊天不修改
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. AI 面板底部取消勾选「允许修改正文」
|
||||||
|
2. 输入「请随机填充文本内容」并发送
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- AI 聊天面板有回复
|
||||||
|
- 编辑器内容不发生任何变化
|
||||||
|
- 不弹出 diff 弹窗
|
||||||
|
|
||||||
|
## 测试用例 4:编译与部署
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. 执行 `npm run build`
|
||||||
|
2. 确认无 TypeScript 编译错误
|
||||||
|
3. 预览服务正常启动并返回 200
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- `vite build` 成功完成
|
||||||
|
- 预览页面可正常访问
|
||||||
29
工程分析/20260419_0359/需求分析.md
Normal file
29
工程分析/20260419_0359/需求分析.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 需求分析
|
||||||
|
|
||||||
|
## 时间戳
|
||||||
|
2026-04-19 03:59
|
||||||
|
|
||||||
|
## 需求来源
|
||||||
|
用户希望 AI 辅助撰写功能实现两个明确的场景:
|
||||||
|
- **场景 A(修改模式)**:勾选「允许修改正文」→ AI 自动修改当前选定的 AI 可编辑区域 → 弹出 diff 对比弹窗(左侧原稿、右侧可二次编辑的 AI 稿)→ 用户可确认或放弃
|
||||||
|
- **场景 B(纯对话模式)**:取消勾选「允许修改正文」→ AI 只回复聊天内容,不修改编辑器
|
||||||
|
|
||||||
|
## 当前状态评估
|
||||||
|
|
||||||
|
从上一次修改后,当前代码已基本实现两个场景的核心逻辑:
|
||||||
|
1. `systemPrompt` 条件为 `aiModifyEnabled`(已解绑 targetRegionEl)
|
||||||
|
2. 接收逻辑为 `if (responseJson.updatedHtml && aiModifyEnabled)`,然后分支判断 targetRegionEl
|
||||||
|
3. `diffModal` 已实现左右分栏:左侧原稿只读,右侧 AI 稿可编辑(contentEditable)
|
||||||
|
|
||||||
|
## 剩余问题
|
||||||
|
|
||||||
|
### 问题 1:修改模式开启但未选区域时,AI 找不到修改目标
|
||||||
|
`aiTargetRegion` 默认值为 `'none'`。用户勾选「允许修改正文」后,如果未在下拉框中手动选择具体区域,`targetRegionEl` 为 `null`,prompt 中不会包含目标区域源码,导致 AI 虽然返回了 `updatedHtml`,但可能不是用户期望的修改位置。
|
||||||
|
|
||||||
|
### 问题 2:systemPrompt 文案可进一步优化
|
||||||
|
当前 systemPrompt 对修改模式和纯对话模式的区分可以更加明确,让大模型更清楚知道是否应该生成 HTML。
|
||||||
|
|
||||||
|
## 约束条件
|
||||||
|
- 自动修正目标区域时,若下拉框原本为 `'none'`,应同步更新 UI 状态(`setAiTargetRegion`)
|
||||||
|
- `diffModal` 的 `targetId` 应使用实际修正后的区域 ID
|
||||||
|
- 保持现有 diff 弹窗交互不变(左原稿右可编辑稿)
|
||||||
37
工程分析/经验记录.md
37
工程分析/经验记录.md
@@ -681,3 +681,40 @@ ReportEditor 采用 `useRef` 作为自动保存的数据快照机制(避免 Re
|
|||||||
**D. 后续如何避免问题**
|
**D. 后续如何避免问题**
|
||||||
- 设计「修改/生成」类 AI 功能时,**systemPrompt 的条件应尽量只依赖用户意图开关**(如 `aiModifyEnabled`),而非依赖具体 UI 状态(如某个下拉框是否选中)。UI 状态应只影响「如何注入结果」,不应影响「是否要求模型生成结果」。
|
- 设计「修改/生成」类 AI 功能时,**systemPrompt 的条件应尽量只依赖用户意图开关**(如 `aiModifyEnabled`),而非依赖具体 UI 状态(如某个下拉框是否选中)。UI 状态应只影响「如何注入结果」,不应影响「是否要求模型生成结果」。
|
||||||
- 任何「目标区域注入」逻辑都必须配备**降级方案**(如光标处插入),防止因用户未选中区域而导致功能完全失效。
|
- 任何「目标区域注入」逻辑都必须配备**降级方案**(如光标处插入),防止因用户未选中区域而导致功能完全失效。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 记录 31:AI 修改模式自动锁定目标区域 + System Prompt 模式语义强化
|
||||||
|
|
||||||
|
**A. 具体问题**
|
||||||
|
用户希望实现两个明确场景:
|
||||||
|
1. **修改模式**:勾选「允许修改正文」→ AI 修改目标区域 → 弹出 diff 对比弹窗
|
||||||
|
2. **对话模式**:取消勾选「允许修改正文」→ AI 只聊天不修改
|
||||||
|
|
||||||
|
实际使用时,用户勾选了修改模式但未在下拉框中选择具体区域(`aiTargetRegion` 仍为 `'none'`),导致 AI 虽然返回了 `updatedHtml`,但 prompt 中缺少目标区域源码,diff 弹窗中的「原稿」一侧为空。
|
||||||
|
|
||||||
|
**B. 产生问题原因**
|
||||||
|
1. **目标区域未自动锁定**:`aiTargetRegion` 默认 `'none'`,修改模式开启后,如果用户未手动选择区域,`targetRegionEl` 为 `null`,prompt 中不会注入目标区域源码。
|
||||||
|
2. **System Prompt 模式语义不够强烈**:大模型对「修改模式」vs「对话模式」的区分不够清晰,可能即使在对话模式下也返回 HTML。
|
||||||
|
|
||||||
|
**C. 解决问题方案**
|
||||||
|
1. **自动修正目标区域**:在 `handleAIGenerate` 开头增加:
|
||||||
|
```ts
|
||||||
|
let actualTargetId = aiTargetRegion;
|
||||||
|
if (aiModifyEnabled && actualTargetId === 'none') {
|
||||||
|
const availableRegions = checkAiRegions();
|
||||||
|
if (availableRegions.length > 0) {
|
||||||
|
actualTargetId = availableRegions[0].id;
|
||||||
|
setAiTargetRegion(actualTargetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
后续 querySelector 和 diffModal 的 `targetId` 均使用 `actualTargetId`。
|
||||||
|
2. **强化 System Prompt 模式语义**:
|
||||||
|
- 修改模式明确标注「当前处于【修改模式】」,并要求必须包含 `reply` + `updatedHtml`
|
||||||
|
- 对话模式明确标注「当前处于【对话模式】」,并要求仅包含 `reply`,不要返回 HTML
|
||||||
|
|
||||||
|
**D. 后续如何避免问题**
|
||||||
|
- 当功能存在「全局开关 + 局部选择器」两层控制时,**全局开关开启后应自动兜底局部选择器**,避免因用户遗漏局部配置而导致功能降级。
|
||||||
|
- System Prompt 中应显式标注当前模式名称(如「修改模式」「对话模式」),大模型对显式标签的遵循度远高于隐式条件推断。
|
||||||
|
|||||||
Reference in New Issue
Block a user