docs: 用真机验证结果更新拍照提问设计文档
记录 vue-tsc/构建/端到端全部验证通过,修正设计与实现的几处出入: ChatPanel 实际是单轮问答不是消息列表、vision 配置字段做了裁剪(去掉 maxImageDimension/jpegQuality/defaultPrompt 改成硬编码)、真实照片 完整回答实测 ~31s(早期用1x1测试图估算的3-8s严重偏低)。风险清单 标注哪些已解决、哪些还没验证,新增"后续可选优化"区分阻塞项和锦上添花项。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
21f9c7e453
commit
ab01effd5b
@ -1,7 +1,7 @@
|
|||||||
# 拍照提问模式 —— 设计方案
|
# 拍照提问模式 —— 设计方案
|
||||||
|
|
||||||
状态:**草案,未实现**。本文档只做设计,`feature_pic` 分支尚未修改任何业务代码。
|
状态:**MVP 已实现并通过真机端到端验证**(2026-09-03)。类型检查、构建、真实摄像头拍照到
|
||||||
唯一的硬阻塞项(后端视觉能力)已于 2026-09-03 复查解除,见 §2.2。
|
真实视觉模型作答的完整链路已跑通,细节见 §10。
|
||||||
|
|
||||||
## 1. 背景与目标
|
## 1. 背景与目标
|
||||||
|
|
||||||
@ -65,8 +65,12 @@ OpenAI 风格的 `image_url` content-part,前端可以直接照 §5.2 的格
|
|||||||
`prompt_tokens` 却达到了 **808**——印证了启动日志里的警告"Qwen-VL models require at minimum
|
`prompt_tokens` 却达到了 **808**——印证了启动日志里的警告"Qwen-VL models require at minimum
|
||||||
1024 image tokens",即**无论照片内容/分辨率如何,视觉编码本身有一个较高的固定 token 开销**
|
1024 image tokens",即**无论照片内容/分辨率如何,视觉编码本身有一个较高的固定 token 开销**
|
||||||
(896×896 的 ViT 编码器决定的,不是按源图片大小线性变化)。prefill 速度约 373 tokens/s,
|
(896×896 的 ViT 编码器决定的,不是按源图片大小线性变化)。prefill 速度约 373 tokens/s,
|
||||||
换算下来光是"看懂一张图"这一步就要 ~2 秒起,生成回答的时间还要另外算——真实拍照场景(图片本身
|
换算下来光是"看懂一张图"这一步就要 ~2 秒起,生成回答的时间还要另外算。
|
||||||
+ 问题文字)单次问答预计在 3-8 秒量级,UI 必须要有明确的"识别中"等待态,不能让用户以为卡死了。
|
|
||||||
|
> **2026-09-03 补充:真实照片实测数据(见 §10)**,一次完整的"描述图片"回答(454 tokens)
|
||||||
|
> 实际耗时 **~31 秒**(14.7 tokens/s),比这里用极简测试图估算的"3-8秒"长不少——真实照片信息量大,
|
||||||
|
> 模型生成的描述篇幅也更长,这才是真实用户会遇到的等待时长。UI 的等待态提示要按这个量级设计,
|
||||||
|
> 不能只按几秒钟的"识别中"来做。
|
||||||
|
|
||||||
## 3. 功能范围(建议先做 MVP)
|
## 3. 功能范围(建议先做 MVP)
|
||||||
|
|
||||||
@ -162,18 +166,22 @@ visionQuery: (payload: { imageBase64: string; question: string }) => Promise<voi
|
|||||||
|
|
||||||
### 5.4 配置项(`AppConfig` 新增字段)
|
### 5.4 配置项(`AppConfig` 新增字段)
|
||||||
|
|
||||||
|
> **实现时做了裁剪**:`maxImageDimension`/`jpegQuality`/`defaultPrompt` 最终没有做成配置项,
|
||||||
|
> 改成代码里的硬编码值(JPEG 质量 0.85,默认问题"请描述这张图片的内容"),且**没有做
|
||||||
|
> 上传前压缩**——拍照直接用视频原始分辨率生成 dataURL。原因:真机验证时发现视觉模型的
|
||||||
|
> `prompt_tokens` 是固定开销(895×896 ViT 编码器决定,跟源图片分辨率无关,见 §2.2),
|
||||||
|
> 客户端压缩分辨率对推理速度没有帮助,只对传输体积有一点点好处,MVP 阶段先不加这层配置,
|
||||||
|
> 避免为一个还没证明有必要的问题预先设计开关。实际字段:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
vision: {
|
vision: {
|
||||||
enabled: boolean // 总开关,默认 false,等后端具备能力再打开
|
enabled: boolean // 总开关,默认 false
|
||||||
cameraDeviceId: string // getUserMedia deviceId,空字符串 = 使用默认/第一个
|
cameraDeviceId: string // getUserMedia deviceId,空字符串 = 使用第一个可用设备
|
||||||
maxImageDimension: number // 上传前压缩的最长边(如 1024),控制 payload 大小和推理时延
|
|
||||||
jpegQuality: number // 0-1,默认 0.85
|
|
||||||
defaultPrompt: string // 用户没打字时的默认问题,如"描述这张图片的内容"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`enabled` 默认关闭:在 er_backend 视觉能力就绪之前,这个入口在设置里默认不显示,避免用户点了
|
`enabled` 默认关闭,需要在设置页手动打开(现在后端已具备视觉能力,但默认关闭仍然是合理的——
|
||||||
拍照却发现后端直接报错。
|
不是所有部署的 er_backend 都会配置视觉模型,不能假设每台设备都支持)。
|
||||||
|
|
||||||
### 5.5 是否持久化照片
|
### 5.5 是否持久化照片
|
||||||
|
|
||||||
@ -184,39 +192,79 @@ vision: {
|
|||||||
|
|
||||||
## 6. UI 改动点(`ChatPanel.vue`)
|
## 6. UI 改动点(`ChatPanel.vue`)
|
||||||
|
|
||||||
- 输入区工具栏新增相机图标按钮(现有已有语音输入按钮,风格保持一致)
|
> **写这份设计时的一个错误假设**:以为 `ChatPanel.vue` 是"消息列表"结构(数组形式的多轮对话历史),
|
||||||
- 新增一个取景弹层组件(新文件,如 `CameraCapture.vue`),职责单一:拿 stream、渲染预览、拍照出 dataURL、
|
> 实现时才发现现有代码根本不是这样——`ChatPanel` 只维护"当前这一轮"的单个问题+单个流式回答
|
||||||
emit 给父组件,弹层自己不关心业务逻辑
|
> (`sentQuestion` + `store.rawAnswer`,每次提问直接覆盖上一轮,没有历史记录数组)。这让实现比设计时
|
||||||
- 消息气泡的数据结构(渲染消息列表用的 message 类型)加一个可选 `imageDataUrl` 字段,
|
> 想的更简单:不需要改消息数据结构,只要给"当前这一轮"平行加一个 `sentImage` 就行。
|
||||||
用户消息如果带图就在文字上方渲染一个缩略图(点击可放大,简单用原生 `<img>` + 一个全屏预览态即可,
|
|
||||||
不需要引入新组件库)
|
实际做法:
|
||||||
|
- 输入区工具栏新增相机图标按钮,跟现有语音按钮同一排、同一视觉风格,仅 `vision.enabled` 为
|
||||||
|
`true` 时显示
|
||||||
|
- 新增 `CameraCapture.vue`,职责单一:拿 stream、渲染预览、拍照出 dataURL、emit 给父组件,
|
||||||
|
弹层自己不关心业务逻辑
|
||||||
|
- `ChatPanel.vue` 新增三个本地 ref:`cameraOpen`(弹层开关)、`pendingImage`(已拍但未发送,
|
||||||
|
显示成输入框上方一个可移除的缩略图 chip)、`sentImage`(已发送,跟 `sentQuestion` 一起显示在
|
||||||
|
问题气泡里)
|
||||||
|
- 抽出 `beginQuery()` 把文字提问和拍照提问共用的准备逻辑(重置状态、打断语音输入、停止旧的
|
||||||
|
TTS 播报)合并成一个函数,避免两条路径各写一遍
|
||||||
|
|
||||||
## 7. 分阶段实施计划
|
## 7. 分阶段实施计划
|
||||||
|
|
||||||
| 阶段 | 内容 | 前置条件 |
|
| 阶段 | 内容 | 状态 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 0 | ~~跟 er_backend/硬件团队确认视觉模型可用性~~ **已完成**(2026-09-03,见 §2.2,端到端验证通过) | — |
|
| 0 | 跟 er_backend/硬件团队确认视觉模型可用性 | ✅ 完成(2026-09-03,见 §2.2) |
|
||||||
| 1 | 摄像头取景/拍照 UI(`CameraCapture.vue`),先只做交互,用假数据验证体验 | 无 |
|
| 1 | 摄像头取景/拍照 UI(`CameraCapture.vue`) | ✅ 完成,真机验证过真实拍照可用(见 §10) |
|
||||||
| 2 | IPC + `visionGenerator.ts` 打通,`generator.ts` content 类型改造 | 无(阶段0已就绪,可直接对真实后端联调) |
|
| 2 | IPC + `generator.ts` 改造支持 image_url content-part | ✅ 完成,真机验证过完整回答(见 §10) |
|
||||||
| 3 | 配置项、设置页开关、真机联调(含真实摄像头 + 真实视觉模型,重点测真实照片而非1x1测试图的延迟/效果) | 阶段1、2完成 |
|
| 3 | 配置项、设置页开关、真机联调 | ✅ 完成(配置项范围比设计时缩减,见 §5.4) |
|
||||||
| 4 | 打包验证(照片相关无新增大文件,不影响现有打包体积) | 阶段3完成 |
|
| 4 | 打包验证(照片相关无新增大文件,不影响现有打包体积) | ⬜ 未做——这次验证走的是 `dev:linux` 开发模式,没有重新打包 deb |
|
||||||
|
|
||||||
阶段 0 已解除,1/2 可以直接开始且互不依赖,建议并行推进。
|
## 8. 风险与未决问题(更新为已验证/未验证状态)
|
||||||
|
|
||||||
## 8. 风险与未决问题
|
1. ~~Electron 权限处理器对摄像头请求的 `permission` 字符串需要实测确认~~
|
||||||
|
**已确认**:真机日志 `[permission] media -> granted`,getUserMedia 摄像头请求确实归为 `media`,
|
||||||
|
跟麦克风共用同一个权限判断,不需要额外代码。
|
||||||
|
2. **未解决**:两个摄像头哪个是实际安装朝向的那个,仍未现场确认物理安装位置——目前实现是
|
||||||
|
`CameraCapture.vue` 里有下拉框给用户自己选,没有做默认值的硬编码判断,算是绕开了这个问题
|
||||||
|
(用户自己选,不需要我们替他决定),但如果产品上想要"打开就是对的那个摄像头",还是要现场确认。
|
||||||
|
3. **已验证,风险比预想低**:真实分辨率照片(未压缩,直接原始 dataURL)经 er_backend 代理转发
|
||||||
|
`/v1/chat/completions` 没有遇到任何请求体大小限制,正常返回。**没有做 §5.4 提到的客户端压缩**——
|
||||||
|
实测证明视觉模型的 `prompt_tokens` 开销是固定的(跟源图片分辨率无关,见 §2.2),压缩分辨率对
|
||||||
|
推理速度没有帮助,暂时不需要加这层。如果以后遇到极端大尺寸照片(比如高分辨率单反直出)传输
|
||||||
|
变慢,再回头考虑加压缩。
|
||||||
|
4. **已用真实数据更新**:完整一段描述类回答实测 **~31 秒**(454 tokens,14.7 tokens/s),不是
|
||||||
|
§2.2 早期用 1×1 测试图估算的"3-8秒"。当前实现**还没有做专门的长等待 UI 提示**——现有的
|
||||||
|
`stage-row`"识别中"loading 态是复用文字问答那套,能用但没有针对"这可能要等半分钟"做优化,
|
||||||
|
是一个值得后续改进但不阻塞可用性的点。
|
||||||
|
5. `--reasoning-budget 0` 这个启动参数暗示当前配置关闭了推理/think 模式——真机测试的回答是
|
||||||
|
直接输出内容,没有 `<think>` 块,跟这条预判一致,现有 `<think>` 渲染逻辑没有被拍照提问路径
|
||||||
|
触发,也没有报错,两条路径共用同一套 markdown 渲染没有问题。
|
||||||
|
|
||||||
1. Electron 权限处理器对摄像头请求的 `permission` 字符串需要实测确认(§5.1)。
|
## 9. 后续可选优化(不阻塞当前可用性)
|
||||||
2. 两个摄像头哪个是实际安装朝向的那个(500万 vs 800万像素那颗),需要现场看一下物理安装位置,
|
|
||||||
UI 上是默认写死还是给用户切换待定。
|
|
||||||
3. 图片 base64 塞进 `/v1/chat/completions` 请求体,走 er_backend 代理转发时有没有请求体大小限制
|
|
||||||
(nginx/代理层常见的 `client_max_body_size` 之类),已验证极小图片可以正常通过代理,但**没测过
|
|
||||||
真实分辨率照片**(896×896 甚至更大的 base64 体积),§5.4 的 `maxImageDimension` 压缩正是为了
|
|
||||||
控制这个风险,阶段3要专门测一次。
|
|
||||||
4. 揽月 NPU 视觉推理延迟:见 §2.2 实测数据,极简图片仅 prefill 就要 ~2s,真实照片 + 生成完整回答
|
|
||||||
预计 3-8 秒量级,UI 必须要有明确的"识别中"等待态。
|
|
||||||
5. `--reasoning-budget 0` 这个启动参数暗示当前配置关闭了推理/think 模式,视觉问答场景要不要开
|
|
||||||
thinking(现有 ChatPanel 的 `<think>` 块渲染能力目前用不上),待联调时确认输出格式是否有变化。
|
|
||||||
|
|
||||||
## 9. 下一步
|
- 针对 §8.4 的长等待时间,给拍照提问单独设计一个更明确的等待态提示(比如预估时间、进度感的动画)
|
||||||
|
- 针对 §8.2,现场确认两个摄像头哪个是产品实际会用的那个,考虑是否需要写死默认设备
|
||||||
|
- 打包验证(§7 阶段4):确认这次改动不影响现有 664M 左右的 deb 体积(新增代码本身很小,
|
||||||
|
不应该有明显影响,但要走一次真实打包确认)
|
||||||
|
- 如果以后有极端大尺寸照片的性能问题,再考虑加回 §5.4 提到的客户端压缩
|
||||||
|
|
||||||
阻塞项已解除,可以直接开始 §7 阶段 1(UI 壳子)和阶段 2(后端联调),两者互不依赖,建议同时推进。
|
## 10. 实现与验证记录(2026-09-03)
|
||||||
|
|
||||||
|
代码实现:`feature_pic` 分支 commit `21f9c7e`。改动文件清单、每个文件做了什么,见该 commit
|
||||||
|
的提交信息,这里不重复。
|
||||||
|
|
||||||
|
验证方式:rsync 到 pad → `npx vue-tsc --noEmit`(**一次性通过,零错误**,没有需要修的类型问题)
|
||||||
|
→ `npx electron-vite build`(成功)→ `DISPLAY=:0` 直接跑编译产物(未打包 deb,走的是开发产物
|
||||||
|
直接运行)→ `ffmpeg -f x11grab` 截图 + `xdotool` 模拟点击操作,验证到:
|
||||||
|
|
||||||
|
- 拍照弹层打开、真实摄像头画面可见(拍到桌面上的 Mac mini、显示器)、多摄像头下拉正确列出
|
||||||
|
真实设备名 `Hy-8M(8079)-Camera`
|
||||||
|
- 拍照 → 图片正确附加到问题气泡(缩略图渲染正常)→ 默认问题文案 `VISION_DEFAULT_QUESTION`
|
||||||
|
正确生效 → 流式回答完整渲染
|
||||||
|
- **回答内容真实针对图片作答**:准确识别出 Mac Mini、AOC 显示器,读出包装盒上的具体文字
|
||||||
|
"100Gbps Super Fast SSD Storage Anywhere"、充电器上的"22W"——细到具体文本级别,
|
||||||
|
不是通用套话或幻觉
|
||||||
|
- 进程全程稳定,没有因为这次改动崩溃;唯一的无关日志是 SSL 证书握手失败(pad 时钟问题,
|
||||||
|
跟这次改动无关,见项目里其他关于 pad 时钟的记录)
|
||||||
|
|
||||||
|
**这次验证没有覆盖到的**:正式打包(deb)流程、设置页手动切换开关的 UI 操作(是直接改
|
||||||
|
`config.json` 打开的 `vision.enabled`,没有走设置页表单)、长时间使用的稳定性。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user