Skip to content

feat(chat): 输入栏增加 Web Speech API 语音输入#276

Open
24baigei wants to merge 3 commits into
Stack-Cairn:mainfrom
24baigei:feat/composer-voice-input
Open

feat(chat): 输入栏增加 Web Speech API 语音输入#276
24baigei wants to merge 3 commits into
Stack-Cairn:mainfrom
24baigei:feat/composer-voice-input

Conversation

@24baigei

@24baigei 24baigei commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

摘要

  • 桌面 GUI 与安全上下文中的桌面浏览器 WebUI 在 Composer 工具栏提供语音转文字入口;运行环境不支持时,GUI 直接隐藏入口。
  • 使用浏览器 / WebView 提供的 Web Speech API(SpeechRecognition / webkitSpeechRecognition)。浏览器或操作系统可能调用远程识别服务,不承诺音频仅在本地处理。
  • 识别结果只写入输入框,不自动发送。
  • 手机端 WebUI 不展示内置语音按钮,继续使用系统输入法自带的语音输入,不增加麦克风适配。
  • GUI / WebUI 共享 lib/voice 实现,并保持 mirror-manifest 同步。

实现说明

  • 新增 lib/voice:Web Speech API 封装、可测试的 recognition session controller 与 useSpeechInput hook。
  • 自动重启不会重复触发消费者 onStart,已提交文本在 pause/end 重启后继续累积;致命错误不会无限重启。
  • MentionComposer 使用独立的临时语音文本段,只更新本次听写区域,保留已有 file / Skill mention 和 large-paste chip;结束或发送时转回普通文本。
  • 多个 interim result 会按顺序拼接;中英文片段按语言边界处理空格。
  • macOS bundle 增加 NSMicrophoneUsageDescriptionNSSpeechRecognitionUsageDescriptioncom.apple.security.device.audio-input entitlement,开发/发布配置均启用 hardened runtime entitlement。
  • Gateway WebUI 在 insecure context 下不请求麦克风权限,并提示改用 HTTPS 或 localhost。

已知限制

  • Web Speech API 的实际可用性取决于系统 WebView / 浏览器是否暴露构造器及其语音后端;代码会在运行时做能力门控。
  • 当前没有实体 Mac 做端到端录音验证。macOS 修复已通过配置测试、TypeScript 与双端构建验证,仍需要 macOS runner 或维护者机器做最终 smoke test。
  • Gateway 经 http://<内网 IP> 访问属于 insecure context,内置语音入口不可用;需使用 HTTPS/localhost。

测试

  • GUI pnpm build
  • Gateway WebUI pnpm build
  • GUI frontend tests:1267/1267
  • Gateway WebUI tests:438/438
  • voice 定向测试:15/15(含 fake recognition auto-restart / fatal error / multiple interim)
  • TypeScript 检查
  • node scripts/check-mirror.mjs
  • 相关文件 Biome 定向检查(无新增 error)
  • 实体 macOS 麦克风与系统权限 smoke test

24baigei added 2 commits July 26, 2026 05:08
桌面 GUI 与桌面浏览器 WebUI 在 Composer 工具栏提供麦克风按钮,
识别结果写入输入框(不自动发送)。手机端 WebUI 不展示内置语音按钮,
优先使用系统输入法语音。
@coder-hhx

Copy link
Copy Markdown
Collaborator

Code Review 意见

先说结论:方向和工程规范(双端镜像同步、CI、测试计划)都不错,但存在 1 个高频触发的正确性 bug 和 1 个对现有功能的回归,以及桌面 GUI 端实际可用性的疑问,建议修复/澄清后再合入。

P1 · 自动重启后文本重复(必须修)

引擎在说话停顿后会触发 onend,hook 会用同一个 recognition 对象自动重启(useSpeechInput.ts 的 auto-restart 分支)。重启会再次触发 onstart,从而重放消费者的 onStart 回调,而 ChatComposerBaronStart 里把 voiceBaseRef 重置为当前输入框全文——此时全文已经包含已识别的 committed 文本。但 committedRef 只在显式 start() 时清零,自动重启路径不清。

复现:开始听写 → 说 "world" → 停顿几秒(Chrome continuous 模式下必然 end/restart)→ 再说 "again",输入框会变成 Hello world world again,"world" 重复。触发条件非常常见。

修复方向:hook 内部区分首次 start 与 auto-restart,重启时不重放 onStart;或者组件改为在调用 start() 之前捕获 base。

P2 · 听写会打平输入框中的富文本结构(回归)

onUpdate 每次全量 setText(base + committed + interim),而 MentionComposer.setText 的实现是清空 innerHTML 后以纯文本重建,并且会 largePastesRef.clear()。用户草稿里如果已有 @file/@Skill mention chip 或大段粘贴 chip,一开口全部退化为纯文本。建议听写时改用追加式插入,至少在 PR 描述里说明这个降级。

P3 · 平台实际可用性与描述不符(请作者澄清)

  • 桌面 GUI 是 Tauri WebView:macOS WKWebView 不暴露 webkitSpeechRecognition;Windows WebView2 构造器存在但 Chromium 语音后端不可用,start 后直接报 network 错误;Linux WebKitGTK 也不支持。也就是说桌面 GUI 端的语音输入在三大平台内置 WebView 上大概率都不可用——macOS 表现为常驻灰色 MicOff,Windows 表现为点击报“网络错误”。测试计划里“桌面端点麦克风确认写入”想请教一下是在哪个平台实测通过的?
  • “本地识别” / 注释 "stays local" 的措辞不准确:Chrome/Edge 的 Web Speech API 会把音频送云端识别,对隐私敏感的用户有误导,建议修正描述。
  • Gateway WebUI 经 http://<内网IP> 访问是 insecure context,麦克风权限必被拒,每次点击都会报“未获得麦克风权限”。这是 gateway 的主场景,建议用 isSecureContext 预判并给出针对性提示。

P4 · 小问题

  • 不支持时按钮 disabled + disabled:pointer-events-none,tooltip 悬停不出来,“当前环境不支持语音识别”用户实际看不到;GUI 端建议像 WebUI 移动端那样直接隐藏按钮。
  • i18n key 插在 chat.runtime.webSearchTooltipchat.runtime.tunnelTool* 之间,把 runtime 分组拆散了,建议移出该分组。
  • onresult 中出现多个非 final result 时 interim 被覆盖只保留最后一段(循环内 nextInterim = piece.trim()),显示层小瑕疵。
  • 听写进行中用户手动编辑会被下一次 setText 覆盖,属产品取舍但建议确认是预期行为。
  • 测试只覆盖了纯函数(拼接/错误映射/UA 判断),恰好没有覆盖出 bug 的 auto-restart 路径,建议用 fake recognition 补一个 hook 行为测试。

做得好的地方:GUI/WebUI 双端镜像完全一致并更新了 mirror-manifest;识别结果不自动发送;错误码→i18n 映射、CJK 拼接不加空格、卸载清理、输入禁用时自动停止都考虑到了。

@24baigei

Copy link
Copy Markdown
Contributor Author

已根据本轮 Code Review 更新,最新提交:5735e6e7

  • P1:自动重启改为显式 session controller;pause/end 后重启不会再次触发消费者 onStart,且保留已 committed 文本。
  • P2:听写不再全量 setText()。MentionComposer 维护独立的临时语音文本段,只更新该段,已有 file/Skill mention 和 large-paste chip 均保留;用户在听写期间编辑其他位置也不会被覆盖。结束/发送时转回普通文本。
  • P3:保留 macOS Info.plist 隐私声明与 audio-input entitlement;Gateway WebUI 在 insecure context 下会在请求权限前提示需 HTTPS/localhost;手机端继续隐藏内置语音入口,没有增加麦克风适配。
  • P4:不支持的桌面 GUI 环境直接隐藏按钮;多段 interim 会拼接;voice i18n 已移出 runtime key 分组。
  • 测试:新增 fake recognition 覆盖 auto-restart、fatal error、multiple interim;GUI 1267/1267、Gateway WebUI 438/438,双端 build、TypeScript、mirror check 均通过。

说明:当前没有实体 Mac 做端到端录音验证;macOS 修复基于 Tauri/Wry 的权限配置,并由配置测试和双端构建验证。运行时仍会按 WebView 是否实际暴露 SpeechRecognition 做能力门控。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants