136 lines
3.9 KiB
Markdown
136 lines
3.9 KiB
Markdown
|
|
# 修复文本输入时仍调用摄像头的问题
|
|||
|
|
|
|||
|
|
## 问题描述
|
|||
|
|
|
|||
|
|
当用户关闭视频对话,改用文本输入聊天时,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 消耗和网络带宽
|
|||
|
|
```
|