搞定 macOS 語音懸浮窗:Wake-word 與 Push-to-talk 的完美協作
你有遇過開發語音功能時,懸浮窗狀態亂跳的情況嗎?特別是當 wake-word 觸發到一半,使用者又突然按住 push-to-talk 快捷鍵,處理不好的話文字就會遺失、重複發送,甚至讓 UI 進入無法預測的狀態。
要讓語音懸浮窗在 macOS 上保持穩定,你需要一套清晰的生命週期管理機制,確保不同觸發來源能無縫接軌,而不是互相打架。
需要準備的東西
Section titled “需要準備的東西”- macOS App 專案程式碼
- SwiftUI 框架(用於 UI 綁定)
bot.molt子系統的日誌存取權限
為了確保語音懸浮窗行為可預測,我們採用了「Token 驗證」與「Session 繼承」的機制。當 push-to-talk (PTT) 在 wake-word 懸浮窗顯示時觸發,PTT 會直接繼承現有文字,而不是重置它。
1. 核心邏輯設定
Section titled “1. 核心邏輯設定”- Session 繼承:如果懸浮窗已由 wake-word 開啟,使用者按下快捷鍵時,PTT 會接管該 session 並保留文字。
- 發送規則:Wake-word 在偵測到靜音時自動發送;PTT 則是在放開快捷鍵時立即發送。
- Token 驗證:每個 capture session 都帶有一個唯一的 token,所有非當前 token 的 callback 都會被捨棄,避免過期的回傳干擾 UI。
2. 實作 VoiceSessionCoordinator
Section titled “2. 實作 VoiceSessionCoordinator”這是管理語音狀態的核心 Actor,確保同一時間只有一個 active session:
- 提供
beginWakeCapture與beginPushToTalkAPI。 - 透過 token 過濾
updatePartial、endCapture等過期呼叫。 - 在 PTT 結束後套用短暫的 cooldown,防止 wake-word 立即重新觸發。
3. 綁定 SwiftUI UI
Section titled “3. 綁定 SwiftUI UI”不要讓 View 直接修改全域狀態,而是透過 VoiceSessionPublisher 進行單向綁定:
VoiceWakeOverlayView只根據 publisher 的內容進行渲染。- 使用者的 UI 操作(如
sendNow、dismiss)必須帶著當前的 session token 回傳給 coordinator。
4. 統一發送路徑
Section titled “4. 統一發送路徑”在 endCapture 時,系統會判斷:
- 如果修剪後的文字為空,則直接 dismiss。
- 如果有文字,則執行
performSend,播放發送音效並關閉懸浮窗。
如果你發現懸浮窗卡住或行為異常,可以使用以下命令即時監控日誌:
sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact常見問題排查:
- 懸浮窗沒關閉:檢查 PTT 放開時是否正確呼叫
endCapture並帶上正確的 token。 - 文字被覆蓋:確認 PTT 觸發時是否有檢查現有的
VoiceSession並將其文字作為 prefix 繼承。 - 過期回傳:如果看到
partial更新發生在 session 結束後,代表 coordinator 的 token 過濾機制失效。
想要更進一步自動化你的開發流程?試試 AI Setup Assistant。
- VoiceWakeRuntime 重構:將原本直接操作 UI 的邏輯改為呼叫
VoiceSessionCoordinator。 - 整合測試:針對 session 繼承、cooldown 機制與空文字關閉進行測試。
OpenClaw Expert
還是卡住了?
如果這篇文件沒有解決你的情境,直接問 OpenClaw Expert,拿到可執行步驟。