14.4 语音叠加层

语音叠加层是 OpenClaw macOS 应用中显示语音识别结果的浮动界面。它管理唤醒词和按键语音(Push-to-Talk)两种模式的生命周期。

设计目标

保持语音叠加层在唤醒词和按键语音重叠时的可预测性:

  • 如果叠加层已因唤醒词可见,用户按下热键时,热键会话会接管现有文本而不是重置它
  • 按住热键期间叠加层保持显示,松开时:如果有修剪后的文本则发送,否则关闭
  • 单独的唤醒词在静音时自动发送;按键语音在松开时立即发送

会话管理

当前实现(2025年12月9日)

  • 会话令牌:每个叠加层会话携带一个令牌(唤醒词或按键语音)。部分/最终/发送/关闭/音量更新在令牌不匹配时会被丢弃,避免过时回调
  • 文本接管:按键语音会接管任何可见的叠加层文本作为前缀(因此在唤醒叠加层显示时按热键会保留文本并追加新语音)。它会等待最多1.5秒以获取最终转录,否则回退到当前文本
  • 提示音和日志:在 info 级别的类别 voicewake.overlayvoicewake.pttvoicewake.chime 中发出(会话开始、部分、最终、发送、关闭、提示音原因)

架构组件

VoiceSessionCoordinator(Actor)

  • 同时只拥有一个 VoiceSession
  • API(基于令牌):beginWakeCapturebeginPushToTalkupdatePartialendCapturecancelapplyCooldown
  • 丢弃携带过时令牌的回调(防止旧识别器重新打开叠加层)

VoiceSession(模型)

字段包括:

  • token:会话标识符
  • source:wakeWord 或 pushToTalk
  • 已提交/易变文本
  • 提示音标志
  • 计时器(自动发送、空闲)
  • overlayMode:display | editing | sending
  • 冷却截止时间

叠加层绑定

  • VoiceSessionPublisher(ObservableObject)将活动会话镜像到 SwiftUI
  • VoiceWakeOverlayView 仅通过发布者渲染;它不直接修改全局单例
  • 叠加层用户操作(sendNowdismissedit)使用会话令牌回调到协调器

统一发送路径

  • endCapture 时:如果修剪后的文本为空 → 关闭;否则 performSend(session:)(播放发送提示音一次、转发、关闭)
  • 按键语音:无延迟;唤醒词:自动发送的可选延迟
  • 按键语音完成后对唤醒运行时应用短暂冷却,这样唤醒词不会立即重新触发

日志记录

协调器在子系统 bot.molt、类别 voicewake.overlayvoicewake.chime 中发出 .info 日志:

  • 关键事件:session_startedadopted_by_push_to_talkpartialfinalizedsenddismisscancelcooldown

调试检查清单

在重现粘性叠加层时流式传输日志:

sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact
  • 验证只有一个活动会话令牌;过时的回调应被协调器丢弃
  • 确保按键语音松开始终使用活动令牌调用 endCapture;如果文本为空,期望 dismiss 而没有提示音或发送

迁移步骤(建议)

  1. 添加 VoiceSessionCoordinatorVoiceSessionVoiceSessionPublisher
  2. 重构 VoiceWakeRuntime 以创建/更新/结束会话,而不是直接触碰 VoiceWakeOverlayController
  3. 重构 VoicePushToTalk 以接管现有会话并在松开时调用 endCapture;应用运行时冷却
  4. VoiceWakeOverlayController 连接到发布者;删除来自运行时/PTT 的直接调用
  5. 为会话接管、冷却和空文本关闭添加集成测试
提示
语音叠加层的设计确保了两种语音输入模式(唤醒词和按键语音)能够无缝协作,避免状态冲突和界面卡顿。