Vòng đời Voice Overlay (macOS)

Đối tượng: Các bạn đóng góp cho ứng dụng macOS. Mục tiêu: giữ cho voice overlay hoạt động dự đoán được khi wake-word và push-to-talk chồng lấn nhau.

Ý định hiện tại

  • Nếu overlay đang hiển thị từ wake-word và người dùng ấn phím tắt, session của phím tắt sẽ kế thừa văn bản hiện có thay vì reset lại. Overlay sẽ giữ nguyên trong khi phím tắt được giữ. Khi người dùng thả ra: gửi nếu có văn bản đã trim, nếu không thì đóng.
  • Wake-word đơn thuần vẫn tự động gửi khi im lặng; push-to-talk gửi ngay lập tức khi thả ra.

Đã triển khai (9/12/2025)

  • Các overlay session giờ mang theo một token cho mỗi lần capture (wake-word hoặc push-to-talk). Các cập nhật partial/final/send/dismiss/level sẽ bị bỏ qua khi token không khớp, tránh các callback cũ.
  • Push-to-talk kế thừa bất kỳ văn bản overlay hiển thị nào làm prefix (nên khi ấn phím tắt trong khi wake overlay đang hiển thị sẽ giữ văn bản và thêm giọng nói mới vào). Nó đợi tối đa 1.5 giây để có transcript cuối cùng trước khi fallback về văn bản hiện tại.
  • Chime/overlay logging được ghi ở mức info trong các category voicewake.overlay, voicewake.ptt, và voicewake.chime (session start, partial, final, send, dismiss, chime reason).

Các bước tiếp theo

  1. VoiceSessionCoordinator (actor)
    • Sở hữu chính xác một VoiceSession tại một thời điểm.
    • API (dựa trên token): beginWakeCapture, beginPushToTalk, updatePartial, endCapture, cancel, applyCooldown.
    • Bỏ qua các callback mang token cũ (ngăn các recognizer cũ mở lại overlay).
  2. VoiceSession (model)
    • Các field: token, source (wakeWord|pushToTalk), committed/volatile text, chime flags, timers (auto-send, idle), overlayMode (display|editing|sending), cooldown deadline.
  3. Overlay binding
    • VoiceSessionPublisher (ObservableObject) phản chiếu session đang hoạt động vào SwiftUI.
    • VoiceWakeOverlayView chỉ render thông qua publisher; nó không bao giờ thay đổi global singleton trực tiếp.
    • Các hành động của người dùng trên overlay (sendNow, dismiss, edit) gọi lại coordinator với session token.
  4. Unified send path
    • Khi endCapture: nếu văn bản đã trim rỗng → dismiss; nếu không thì performSend(session:) (phát send chime một lần, forward, dismiss).
    • Push-to-talk: không delay; wake-word: delay tùy chọn cho auto-send.
    • Áp dụng cooldown ngắn cho wake runtime sau khi push-to-talk kết thúc để wake-word không kích hoạt lại ngay lập tức.
  5. Logging
    • Coordinator ghi log .info trong subsystem bot.molt, các category voicewake.overlayvoicewake.chime.
    • Các sự kiện chính: session_started, adopted_by_push_to_talk, partial, finalized, send, dismiss, cancel, cooldown.

Checklist debug

  • Stream log trong khi tái hiện sticky overlay:

    sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact
  • Xác minh chỉ có một session token đang hoạt động; các callback cũ nên bị coordinator bỏ qua.

  • Đảm bảo push-to-talk release luôn gọi endCapture với token đang hoạt động; nếu văn bản rỗng, mong đợi dismiss mà không có chime hoặc send.

Các bước migration (đề xuất)

  1. Thêm VoiceSessionCoordinator, VoiceSession, và VoiceSessionPublisher.
  2. Refactor VoiceWakeRuntime để tạo/cập nhật/kết thúc session thay vì chạm vào VoiceWakeOverlayController trực tiếp.
  3. Refactor VoicePushToTalk để kế thừa các session hiện có và gọi endCapture khi thả ra; áp dụng runtime cooldown.
  4. Kết nối VoiceWakeOverlayController với publisher; xóa các lời gọi trực tiếp từ runtime/PTT.
  5. Thêm integration test cho session adoption, cooldown, và empty-text dismissal.