语音叠加层是 OpenClaw macOS 应用中显示语音识别结果的浮动界面。它管理唤醒词和按键语音(Push-to-Talk)两种模式的生命周期。
设计目标
保持语音叠加层在唤醒词和按键语音重叠时的可预测性:
- 如果叠加层已因唤醒词可见,用户按下热键时,热键会话会接管现有文本而不是重置它
- 按住热键期间叠加层保持显示,松开时:如果有修剪后的文本则发送,否则关闭
- 单独的唤醒词在静音时自动发送;按键语音在松开时立即发送
会话管理
当前实现(2025年12月9日)
- 会话令牌:每个叠加层会话携带一个令牌(唤醒词或按键语音)。部分/最终/发送/关闭/音量更新在令牌不匹配时会被丢弃,避免过时回调
- 文本接管:按键语音会接管任何可见的叠加层文本作为前缀(因此在唤醒叠加层显示时按热键会保留文本并追加新语音)。它会等待最多1.5秒以获取最终转录,否则回退到当前文本
- 提示音和日志:在
info级别的类别voicewake.overlay、voicewake.ptt和voicewake.chime中发出(会话开始、部分、最终、发送、关闭、提示音原因)
架构组件
VoiceSessionCoordinator(Actor)
- 同时只拥有一个
VoiceSession - API(基于令牌):
beginWakeCapture、beginPushToTalk、updatePartial、endCapture、cancel、applyCooldown - 丢弃携带过时令牌的回调(防止旧识别器重新打开叠加层)
VoiceSession(模型)
字段包括:
token:会话标识符source:wakeWord 或 pushToTalk- 已提交/易变文本
- 提示音标志
- 计时器(自动发送、空闲)
overlayMode:display | editing | sending- 冷却截止时间
叠加层绑定
VoiceSessionPublisher(ObservableObject)将活动会话镜像到 SwiftUIVoiceWakeOverlayView仅通过发布者渲染;它不直接修改全局单例- 叠加层用户操作(
sendNow、dismiss、edit)使用会话令牌回调到协调器
统一发送路径
- 在
endCapture时:如果修剪后的文本为空 → 关闭;否则performSend(session:)(播放发送提示音一次、转发、关闭) - 按键语音:无延迟;唤醒词:自动发送的可选延迟
- 按键语音完成后对唤醒运行时应用短暂冷却,这样唤醒词不会立即重新触发
日志记录
协调器在子系统 bot.molt、类别 voicewake.overlay 和 voicewake.chime 中发出 .info 日志:
- 关键事件:
session_started、adopted_by_push_to_talk、partial、finalized、send、dismiss、cancel、cooldown
调试检查清单
在重现粘性叠加层时流式传输日志:
sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact
- 验证只有一个活动会话令牌;过时的回调应被协调器丢弃
- 确保按键语音松开始终使用活动令牌调用
endCapture;如果文本为空,期望dismiss而没有提示音或发送
迁移步骤(建议)
- 添加
VoiceSessionCoordinator、VoiceSession和VoiceSessionPublisher - 重构
VoiceWakeRuntime以创建/更新/结束会话,而不是直接触碰VoiceWakeOverlayController - 重构
VoicePushToTalk以接管现有会话并在松开时调用endCapture;应用运行时冷却 - 将
VoiceWakeOverlayController连接到发布者;删除来自运行时/PTT 的直接调用 - 为会话接管、冷却和空文本关闭添加集成测试
提示
语音叠加层的设计确保了两种语音输入模式(唤醒词和按键语音)能够无缝协作,避免状态冲突和界面卡顿。