From ed95ce56a8059cc3bb141a8c17ad6a0eae4a8b5b Mon Sep 17 00:00:00 2001 From: cfy666 <3087823110@qq.com> Date: Sat, 20 Jun 2026 22:21:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=B3=E9=97=AD=E6=91=84=E5=83=8F?= =?UTF-8?q?=E5=A4=B4=E5=90=8E=E6=96=87=E6=9C=AC=E8=BE=93=E5=85=A5=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E5=8F=91=E9=80=81=E5=9B=BE=E5=83=8F=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复了用户关闭摄像头后,使用文本输入时 AI 仍会分析黑色画面的问题。 变更: - sendTextMessage: 只在摄像头开启时捕获画面 - 待发消息队列: 根据摄像头状态决定是否携带画面 效果: - 摄像头关闭时纯文本对话,不提及画面 - 节省 token 消耗和网络带宽 Co-Authored-By: Claude Opus 4.8 --- docs/fix-text-input-camera-issue.md | 135 +++++++++++++++++++++++++ frontend/src/hooks/useVisionSession.ts | 8 +- 2 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 docs/fix-text-input-camera-issue.md diff --git a/docs/fix-text-input-camera-issue.md b/docs/fix-text-input-camera-issue.md new file mode 100644 index 0000000..f613063 --- /dev/null +++ b/docs/fix-text-input-camera-issue.md @@ -0,0 +1,135 @@ +# 修复文本输入时仍调用摄像头的问题 + +## 问题描述 + +当用户关闭视频对话,改用文本输入聊天时,AI 仍然会说"画面是黑色的",然后才回答问题。这是因为即使摄像头关闭,前端仍然发送了图像数据(黑屏)给后端。 + +## 问题根源 + +### 前端问题 +在 `frontend/src/hooks/useVisionSession.ts` 中: + +1. **sendTextMessage 函数**(第 441 行):无论摄像头是否开启,都会调用 `captureFrame()` 捕获画面 +2. **待发消息队列处理**(第 140 行):连接成功后 flush 待发消息时,也会无条件捕获画面 + +```typescript +// 问题代码 +const frame = captureFrame(); // 即使摄像头关闭也会捕获(返回黑屏) +send({ + type: "query", + image: frame ? dataUrlToBase64(frame) : "", // 发送黑屏数据 + text: text.trim(), +}); +``` + +### 后端行为 +在 `backend/internal/eino/nodes_history.go` 中: + +- 第 68-86 行:只要收到图像数据(`len(imageData) > 0`),就会构建多模态消息发送给 LLM +- LLM 会分析这个黑色画面,导致回复中提到"画面是黑色的" + +## 解决方案 + +### 修改内容 + +修改了两处代码,都在 `frontend/src/hooks/useVisionSession.ts`: + +#### 1. sendTextMessage 函数(第 421-462 行) + +```typescript +// 修改前 +const frame = captureFrame(); + +// 修改后 +// 只在摄像头开启时才捕获画面,避免发送黑屏给 AI +const frame = isCameraOn ? captureFrame() : null; +``` + +并在依赖数组中添加了 `isCameraOn`: +```typescript +[captureFrame, send, connect, accessToken, isCameraOn] +``` + +#### 2. 待发消息队列处理(第 124-156 行) + +```typescript +// 修改前 +const frame = captureFrame(); + +// 修改后 +// 只在摄像头开启时才捕获画面,避免发送黑屏给 AI +const frame = isCameraOn ? captureFrame() : null; +``` + +### 效果 + +修改后: +- ✅ 摄像头**开启**时,文本输入仍然会携带当前画面(多模态对话) +- ✅ 摄像头**关闭**时,文本输入只发送文字(纯文本对话) +- ✅ AI 不会再说"画面是黑色的" +- ✅ 节省 token 消耗(不发送无用的图像数据) + +## 测试场景 + +1. **开启视频对话 → 关闭摄像头 → 文本输入聊天** + - 预期:AI 只回答文本问题,不提及画面 + +2. **直接文本输入(未开启摄像头)** + - 预期:纯文本对话,不发送图像数据 + +3. **开启摄像头 → 文本输入** + - 预期:AI 可以看到画面并结合文本回答 + +4. **未连接时文本输入 → 自动连接** + - 预期:待发消息在连接成功后发送,根据当时的摄像头状态决定是否携带画面 + +## 替代方案(未采用) + +### 方案二:后端过滤空图像 +在后端 `nodes_history.go` 中增加图像有效性检测: + +```go +// 检测图像是否有效(非全黑/全白) +func isValidImage(data []byte) bool { + // 检测文件大小(黑屏图像通常很小) + if len(data) < 1024 { // 小于 1KB + return false + } + // 可以进一步检测像素方差等 + return true +} + +// 在 NewHistoryLambda 中 +if len(imageData) > 0 && isValidImage(imageData) { + // 构建多模态消息 +} else { + // 纯文本消息 +} +``` + +**未采用原因**: +- 前端方案更直接,从源头解决问题 +- 避免后端做额外的图像检测(性能开销) +- 节省网络传输(不发送无用的图像数据) + +## 相关文件 + +- `frontend/src/hooks/useVisionSession.ts` - 核心会话逻辑 +- `backend/internal/eino/nodes_history.go` - 后端历史消息组装 +- `docs/02-接口文档.md` - WebSocket 协议定义 + +## 提交信息 + +``` +fix: 关闭摄像头后文本输入不再发送图像数据 + +修复了用户关闭摄像头后,使用文本输入时 AI 仍会分析黑色画面的问题。 + +变更: +- sendTextMessage: 只在摄像头开启时捕获画面 +- 待发消息队列: 根据摄像头状态决定是否携带画面 + +效果: +- 摄像头关闭时纯文本对话,不提及画面 +- 节省 token 消耗和网络带宽 +``` diff --git a/frontend/src/hooks/useVisionSession.ts b/frontend/src/hooks/useVisionSession.ts index 4177345..53f8ab2 100644 --- a/frontend/src/hooks/useVisionSession.ts +++ b/frontend/src/hooks/useVisionSession.ts @@ -137,7 +137,8 @@ export function useVisionSession(accessToken?: string | null, conversationId?: s const pending = pendingMessagesRef.current; pendingMessagesRef.current = []; for (const msg of pending) { - const frame = captureFrame(); + // 只在摄像头开启时才捕获画面,避免发送黑屏给 AI + const frame = isCameraOn ? captureFrame() : null; send({ type: "query", request_id: msg.requestId, @@ -438,7 +439,8 @@ export function useVisionSession(accessToken?: string | null, conversationId?: s } // 已连接:直接发送 - const frame = captureFrame(); + // 只在摄像头开启时才捕获画面,避免发送黑屏给 AI + const frame = isCameraOn ? captureFrame() : null; send({ type: "query", request_id: requestId, @@ -458,7 +460,7 @@ export function useVisionSession(accessToken?: string | null, conversationId?: s setIsProcessing(true); }, - [captureFrame, send, connect, accessToken], + [captureFrame, send, connect, accessToken, isCameraOn], ); return {