fix(editor): Checkbox点击失效 + AI全局上下文注入
- 将'允许修改正文'复选框从id/htmlFor绑定改为label直接包裹input,增加e.stopPropagation防止事件冒泡被拦截 - handleAIGenerate中新增editorRef.current.innerText作为全局上下文注入prompt - currentHtml增加过滤​零宽字符 - 优化systemPrompt,明确告知大模型全局参考内容+目标区域源码的双信息源结构
This commit is contained in:
97
工程分析/20260419_0333/实现方案.md
Normal file
97
工程分析/20260419_0333/实现方案.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 实现方案
|
||||
|
||||
## 修改文件
|
||||
- `src/pages/ReportEditor.tsx`
|
||||
|
||||
## 修改位置 1:Checkbox 包裹结构(约 line 2258-2268)
|
||||
|
||||
**原代码**:
|
||||
```tsx
|
||||
<div className="flex items-center gap-1.5 shrink-0 pl-2 border-l border-slate-300">
|
||||
<input
|
||||
type="checkbox" id="aiModifyEnabled"
|
||||
checked={aiModifyEnabled}
|
||||
onChange={(e) => setAiModifyEnabled(e.target.checked)}
|
||||
className="w-3.5 h-3.5 text-blue-600 rounded border-slate-300 focus:ring-blue-500 cursor-pointer"
|
||||
/>
|
||||
<label htmlFor="aiModifyEnabled" className="text-[11px] text-slate-600 cursor-pointer font-bold">
|
||||
允许修改正文
|
||||
</label>
|
||||
</div>
|
||||
```
|
||||
|
||||
**新代码**:
|
||||
```tsx
|
||||
<label className="flex items-center gap-1.5 shrink-0 pl-2 border-l border-slate-300 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={aiModifyEnabled}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
setAiModifyEnabled(e.target.checked);
|
||||
}}
|
||||
className="w-3.5 h-3.5 text-blue-600 rounded border-slate-300 focus:ring-blue-500 cursor-pointer"
|
||||
/>
|
||||
<span className="text-[11px] text-slate-600 font-bold">
|
||||
允许修改正文
|
||||
</span>
|
||||
</label>
|
||||
```
|
||||
|
||||
**变更点**:
|
||||
1. 外层 `div` 改为 `label`,直接包裹 `input`
|
||||
2. 移除 `id`/`htmlFor`,避免绑定冲突
|
||||
3. `onChange` 增加 `e.stopPropagation()` 防止事件冒泡被拦截
|
||||
4. `label` 文本改为 `span`
|
||||
|
||||
## 修改位置 2:handleAIGenerate Prompt 构建(约 line 877-897)
|
||||
|
||||
**原代码**:
|
||||
```tsx
|
||||
const targetRegionEl = editorRef.current?.querySelector(`.ai-region[data-ai-id="${aiTargetRegion}"] .ai-content`) as HTMLElement | null;
|
||||
const currentHtml = targetRegionEl ? targetRegionEl.innerHTML : '';
|
||||
// ...
|
||||
let promptText = `【医生指令】: ${text}`;
|
||||
if (aiModifyEnabled && targetRegionEl) {
|
||||
promptText = `【当前区域 HTML 源码】:\n${currentHtml}\n\n${promptText}`;
|
||||
}
|
||||
```
|
||||
|
||||
**新代码**:
|
||||
```tsx
|
||||
const targetRegionEl = editorRef.current?.querySelector(`.ai-region[data-ai-id="${aiTargetRegion}"] .ai-content`) as HTMLElement | null;
|
||||
const currentHtml = targetRegionEl ? targetRegionEl.innerHTML.replace(/​/g, '').trim() : '';
|
||||
const globalContextText = editorRef.current?.innerText || '';
|
||||
// ...
|
||||
let promptText = `【全局手术报告参考内容】:\n${globalContextText}\n\n`;
|
||||
if (aiModifyEnabled && targetRegionEl) {
|
||||
promptText += `【你需要进行修改的目标区域 HTML 源码】:\n${currentHtml || '(当前区域为空)'}\n\n`;
|
||||
}
|
||||
promptText += `【医生指令】: ${text}`;
|
||||
```
|
||||
|
||||
**变更点**:
|
||||
1. `currentHtml` 增加 `.replace(/​/g, '').trim()` 过滤零宽字符
|
||||
2. 新增 `globalContextText` 读取整个编辑器的纯文本
|
||||
3. `promptText` 重构:先放全局上下文,再放目标区域(如果存在),最后放医生指令
|
||||
4. 目标区域为空时显示 `(当前区域为空)` 提示
|
||||
|
||||
## 修改位置 3:System Prompt 优化(约 line 895-897)
|
||||
|
||||
**原代码**:
|
||||
```tsx
|
||||
const systemPrompt = aiModifyEnabled && targetRegionEl
|
||||
? '你是一名专业的外科医生助理。你需要根据用户的指令及可能提供的截图,修改给定的 HTML 源码。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }'
|
||||
: '你是一名专业的外科医生助理。请根据用户的指令和截图进行分析解答。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记。\nJSON 格式如下:\n{ "reply": "你的分析和回答" }';
|
||||
```
|
||||
|
||||
**新代码**:
|
||||
```tsx
|
||||
const systemPrompt = aiModifyEnabled && targetRegionEl
|
||||
? '你是一名专业的外科医生助理。我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据全局内容和用户的【医生指令】,直接重写并输出目标区域的 HTML。\n重要指令:你必须严格返回合法的 JSON 对象,绝对不要包含任何 Markdown 标记(如 ```json)。\nJSON 格式如下:\n{ "reply": "简短的回复话术", "updatedHtml": "修改后的完整内部 HTML 代码" }'
|
||||
: '你是一名专业的外科医生助理。请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:严格返回合法的 JSON 对象。\nJSON 格式如下:\n{ "reply": "你的分析和回答" }';
|
||||
```
|
||||
|
||||
**变更点**:
|
||||
1. 修改模式 systemPrompt 明确告知大模型有两个信息源:全局参考内容 + 目标区域源码
|
||||
2. 非修改模式 systemPrompt 明确提到「全局手术报告参考内容」
|
||||
55
工程分析/20260419_0333/测试方案.md
Normal file
55
工程分析/20260419_0333/测试方案.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# 测试方案
|
||||
|
||||
## 测试环境
|
||||
- 浏览器访问 `http://localhost:4173/`
|
||||
- 进入「图文报告生成」→ 新建报告
|
||||
|
||||
## 测试用例 1:Checkbox 可正常切换
|
||||
|
||||
**步骤**:
|
||||
1. 打开右侧「AI 撰写」面板
|
||||
2. 观察底部「允许修改正文」复选框,当前应为勾选状态(默认 `aiModifyEnabled = true`)
|
||||
3. 点击复选框,观察勾选状态是否消失
|
||||
4. 再次点击,观察勾选状态是否恢复
|
||||
5. 点击复选框左侧的文字「允许修改正文」,观察勾选状态是否切换
|
||||
|
||||
**预期结果**:
|
||||
- 点击复选框本身:状态正常切换
|
||||
- 点击文字标签:状态正常切换
|
||||
- 切换时上方「区域锚定」select 的 `disabled` 状态同步变化(禁用/启用)
|
||||
|
||||
## 测试用例 2:AI 能看到全局报告内容
|
||||
|
||||
**步骤**:
|
||||
1. 在编辑器中输入一些文本,例如「气腹压力为 12mmHg」
|
||||
2. 插入一个 AI 可编辑区域(如「手术步骤」)
|
||||
3. 在 AI 面板中输入:「你能看到当前气腹压力吗?」
|
||||
4. 不勾选「允许修改正文」,直接发送
|
||||
|
||||
**预期结果**:
|
||||
- AI 回复中应提到「12mmHg」或「气腹压力」,表明它读取了全局上下文
|
||||
|
||||
## 测试用例 3:AI 能基于全局上下文修改目标区域
|
||||
|
||||
**步骤**:
|
||||
1. 编辑器中有完整报告内容(含患者信息、手术步骤等)
|
||||
2. 在 AI 可编辑区域(如「手术步骤」)中已有部分内容
|
||||
3. 勾选「允许修改正文」
|
||||
4. 输入指令:「根据全局报告内容,将手术步骤中提到的止血方法更新为电凝止血」
|
||||
5. 发送并查看 diff 确认弹窗
|
||||
|
||||
**预期结果**:
|
||||
- AI 返回的 `updatedHtml` 应能引用全局报告中的其他信息
|
||||
- Diff 弹窗能正确展示原文 vs 修改后的内容
|
||||
- 确认注入后目标区域内容更新
|
||||
|
||||
## 测试用例 4:编译与部署
|
||||
|
||||
**步骤**:
|
||||
1. 执行 `npm run build`
|
||||
2. 确认无 TypeScript 编译错误
|
||||
3. 预览服务正常启动并返回 200
|
||||
|
||||
**预期结果**:
|
||||
- `vite build` 成功完成
|
||||
- 预览页面可正常访问
|
||||
37
工程分析/20260419_0333/需求分析.md
Normal file
37
工程分析/20260419_0333/需求分析.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 需求分析
|
||||
|
||||
## 时间戳
|
||||
2026-04-19 03:33
|
||||
|
||||
## 需求来源
|
||||
用户反馈 AI 辅助撰写功能存在两个 Bug:
|
||||
1. 「允许修改正文」复选框无法被点击切换
|
||||
2. AI 大模型无法看到编辑器中的报告内容,无法执行修改正文的指令
|
||||
|
||||
## 问题一:Checkbox 无法切换
|
||||
|
||||
**现象**:AI 面板底部的「允许修改正文」复选框点击无反应,无法关闭或开启。
|
||||
|
||||
**根因分析**:
|
||||
- 当前实现使用独立的 `<input id="aiModifyEnabled">` + `<label htmlFor="aiModifyEnabled">` 组合
|
||||
- 在复杂的 React 组件树中,`id`/`htmlFor` 绑定可能因事件冒泡、DOM 结构覆盖或 React 重渲染导致失效
|
||||
- 外层 `.ai-region` 等元素可能对点击事件有拦截
|
||||
|
||||
**约束条件**:
|
||||
- 最小化改动,只改包裹结构,不改样式语义
|
||||
- 必须保留 `cursor-pointer` 和原有视觉样式
|
||||
|
||||
## 问题二:AI 无法读取编辑器内容
|
||||
|
||||
**现象**:用户在 AI 区域外写入了「气腹压力为 12mmHg」等信息,但问 AI「你能看到当前气腹压力吗?」时,AI 回答无法看到。
|
||||
|
||||
**根因分析**:
|
||||
- `handleAIGenerate` 目前只将「目标 AI 区域的 HTML 源码」发送给大模型
|
||||
- 目标区域可能为空(默认只有 `​`),导致大模型收到的上下文只有用户指令
|
||||
- AI 看不到编辑器中其他区域(如基本信息、其他手术步骤)的内容
|
||||
|
||||
**约束条件**:
|
||||
- 必须保留现有 `currentHtml` 作为修改目标(用于 diff 注入)
|
||||
- 全局上下文使用纯文本而非 HTML,减少 token 消耗和格式干扰
|
||||
- 需要过滤 `​` 零宽字符
|
||||
- systemPrompt 需同步更新,明确告知大模型有两个信息源:全局参考内容 + 目标区域源码
|
||||
Reference in New Issue
Block a user