语音覆盖层
语音覆盖层生命周期 (macOS)
受众:macOS 应用贡献者。目标:在唤醒词和按键通话重叠时保持语音覆盖层可预测。
当前意图
- 如果覆盖层已经因唤醒词而可见,并且用户按下热键,热键会话会采用现有文本而不是重置它。覆盖层在按住热键时保持显示。当用户释放时:如果有修剪的文本则发送,否则关闭。
- 仅唤醒词仍然在静音时自动发送;按键通话在释放时立即发送。
已实现 (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)将会话镜像到 SwiftUI。VoiceWakeOverlayView仅通过发布器渲染;它从不直接修改全局单例。- 覆盖层用户操作(
sendNow,dismiss,edit)使用会话令牌回调到协调器。
- 统一发送路径
- 在
endCapture时:如果修剪的文本为空 → 关闭;否则performSend(session:)(播放发送铃声一次,转发,关闭)。 - 按键通话:无延迟;唤醒词:可选延迟用于自动发送。
- 按键通话完成后对唤醒运行时应用短暂冷却,以便唤醒词不会立即重新触发。
- 在
- 日志
- 协调器在子系统
ai.openclaw中发出.info日志,类别voicewake.overlay和voicewake.chime。 - 关键事件:
session_started,adopted_by_push_to_talk,partial,finalized,send,dismiss,cancel,cooldown。
- 协调器在子系统
调试清单
-
重现粘性覆盖层时流式传输日志:
sudo log stream --predicate 'subsystem == "ai.openclaw" AND category CONTAINS "voicewake"' --level info --style compact -
验证只有一个活动会话令牌;过时回调应由协调器丢弃。
-
确保按键通话释放总是使用活动令牌调用
endCapture;如果文本为空,预期dismiss而无需铃声或发送。
迁移步骤(建议)
- 添加
VoiceSessionCoordinator,VoiceSession和VoiceSessionPublisher。 - 重构
VoiceWakeRuntime以创建/更新/结束会话,而不是直接触摸VoiceWakeOverlayController。 - 重构
VoicePushToTalk以采用现有会话并在释放时调用endCapture;应用运行时冷却。 - 将
VoiceWakeOverlayController连接到发布器;从运行时/PTT 移除直接调用。 - 为会话采用、冷却和空文本关闭添加集成测试。