Жизненный цикл голосового оверлея (macOS)
Аудитория: участники разработки приложения для macOS. Цель: сделать поведение голосового оверлея предсказуемым, когда пересекаются ключевое слово активации и push-to-talk.Текущий замысел
- Если оверлей уже виден из‑за ключевого слова активации и пользователь нажимает горячую клавишу, сеанс по горячей клавише подхватывает существующий текст вместо его сброса. Оверлей остаётся на экране, пока удерживается горячая клавиша. Когда пользователь отпускает её: отправить, если есть обрезанный текст, иначе закрыть.
- Ключевое слово активации само по себе по‑прежнему автоматически отправляет по тишине; push-to-talk отправляет сразу при отпускании.
Реализовано (9 декабря 2025)
- Сеансы оверлея теперь несут токен на каждый захват (ключевое слово активации или push-to-talk). Обновления partial/final/send/dismiss/level отбрасываются, если токен не совпадает, что предотвращает устаревшие колбэки.
- Push-to-talk подхватывает любой видимый текст оверлея как префикс (поэтому нажатие горячей клавиши, когда оверлей от ключевого слова уже открыт, сохраняет текст и добавляет новую речь). Он ждёт до 1,5 с финальной транскрипции, прежде чем откатиться к текущему тексту.
- Логирование звукового сигнала/оверлея выводится под
infoв категорияхvoicewake.overlay,voicewake.pttиvoicewake.chime(начало сеанса, partial, final, send, dismiss, причина звукового сигнала).
Следующие шаги
- VoiceSessionCoordinator (actor)
- Владеет ровно одним
VoiceSessionв каждый момент времени. - API (на основе токенов):
beginWakeCapture,beginPushToTalk,updatePartial,endCapture,cancel,applyCooldown. - Отбрасывает колбэки со старыми токенами (предотвращает повторное открытие оверлея старыми распознавателями).
- Владеет ровно одним
- VoiceSession (model)
- Поля:
token,source(wakeWord|pushToTalk), зафиксированный/временный текст, флаги звуковых сигналов, таймеры (автоотправка, простой),overlayMode(display|editing|sending), дедлайн кулдауна.
- Поля:
- Привязка оверлея
VoiceSessionPublisher(ObservableObject) зеркалирует активный сеанс в SwiftUI.VoiceWakeOverlayViewрендерит только через publisher; он никогда напрямую не мутирует глобальные синглтоны.- Действия пользователя в оверлее (
sendNow,dismiss,edit) вызывают координатор обратно с токеном сеанса.
- Единый путь отправки
- При
endCapture: если обрезанный текст пуст → закрыть; иначеperformSend(session:)(проигрывает звуковой сигнал отправки один раз, пересылает, закрывает). - Push-to-talk: без задержки; ключевое слово активации: опциональная задержка для автоотправки.
- Применить короткий кулдаун к рантайму ключевого слова активации после завершения push-to-talk, чтобы ключевое слово не срабатывало сразу же повторно.
- При
- Логирование
- Координатор выводит логи
.infoв подсистемеbot.molt, категорияхvoicewake.overlayиvoicewake.chime. - Ключевые события:
session_started,adopted_by_push_to_talk,partial,finalized,send,dismiss,cancel,cooldown.
- Координатор выводит логи
Контрольный список отладки
-
Стримьте логи при воспроизведении «залипшего» оверлея:
- Убедитесь, что активен только один токен сеанса; устаревшие колбэки должны отбрасываться координатором.
-
Убедитесь, что отпускание push-to-talk всегда вызывает
endCaptureс активным токеном; если текст пуст, ожидайтеdismissбез звукового сигнала и отправки.
Шаги миграции (рекомендуется)
- Добавить
VoiceSessionCoordinator,VoiceSessionиVoiceSessionPublisher. - Рефакторить
VoiceWakeRuntime, чтобы создавать/обновлять/завершать сеансы вместо прямого обращения кVoiceWakeOverlayController. - Рефакторить
VoicePushToTalk, чтобы подхватывать существующие сеансы и вызыватьendCaptureпри отпускании; применить кулдаун рантайма. - Подключить
VoiceWakeOverlayControllerк publisher; убрать прямые вызовы из рантайма/PTT. - Добавить интеграционные тесты для подхвата сеанса, кулдауна и закрытия при пустом тексте.