macOS companion app

Голосовое наложение

Жизненный цикл голосового оверлея (macOS)

Аудитория: участники разработки приложения для macOS. Цель: обеспечить предсказуемое поведение голосового оверлея при одновременном использовании слова активации и функции «нажми и говори».

Поведение

  • Если оверлей уже отображается после срабатывания слова активации и пользователь нажимает горячую клавишу, сеанс горячей клавиши принимает существующий текст вместо его сброса. Оверлей остаётся на экране, пока удерживается горячая клавиша. После отпускания: отправить, если после удаления пробелов текст не пуст; в противном случае закрыть.
  • При использовании только слова активации содержимое по-прежнему автоматически отправляется при наступлении тишины; функция «нажми и говори» отправляет его сразу после отпускания клавиши.

Реализация

  • VoiceSessionCoordinator (apps/macos/Sources/OpenClaw/VoiceSessionCoordinator.swift) — единственный владелец активного голосового сеанса. Это одноэлементный экземпляр @MainActor @Observable, а не актор. API: startSession, updatePartial, finalize, sendNow, dismiss, updateLevel, snapshot. Каждый сеанс содержит токен UUID; вызовы с устаревшим или несовпадающим токеном отбрасываются.
  • VoiceWakeOverlayController (VoiceWakeOverlayController+Session.swift) отображает оверлей и передаёт действия пользователя (requestSend, dismiss) обратно координатору через токен сеанса. Сам компонент никогда не владеет состоянием сеанса.
  • Функция «нажми и говори» (VoicePushToTalk.begin()) принимает текст любого видимого оверлея как adoptedPrefix (через VoiceSessionCoordinator.shared.snapshot()), поэтому нажатие горячей клавиши при отображаемом оверлее слова активации сохраняет текст и добавляет к нему новую речь. После отпускания функция ожидает окончательную расшифровку до 1.5 с, а затем при её отсутствии использует текущий текст.
  • При dismiss оверлей вызывает VoiceSessionCoordinator.overlayDidDismiss, что запускает VoiceWakeRuntime.refresh(state:), поэтому прослушивание слова активации возобновляется после ручного закрытия кнопкой X, закрытия из-за пустого текста и закрытия после отправки.
  • Единый путь отправки: если после удаления пробелов текст пуст, закрыть оверлей; в противном случае sendNow один раз воспроизводит звуковой сигнал отправки, передаёт текст через VoiceWakeForwarder, а затем закрывает оверлей.

Журналирование

Голосовая подсистема — ai.openclaw; каждый компонент ведёт журнал в собственной категории:

Категория Компонент
voicewake.coordinator VoiceSessionCoordinator
voicewake.overlay VoiceWakeOverlayController/VoiceWakeOverlay
voicewake.ptt Горячая клавиша и захват для функции «нажми и говори»
voicewake.runtime Среда выполнения слова активации
voicewake.chime Воспроизведение звукового сигнала
voicewake.sync Синхронизация глобальных настроек
voicewake.forward Пересылка расшифровки
voicewake.meter Монитор уровня микрофона

Контрольный список отладки

  • Просматривайте поток журналов при воспроизведении зависшего оверлея:

    bash
    sudo log stream --predicate 'subsystem == "ai.openclaw" AND category CONTAINS "voicewake"' --level info --style compact
  • Убедитесь, что активен только один токен сеанса; устаревшие обратные вызовы отбрасываются координатором.

  • Убедитесь, что при отпускании клавиши функции «нажми и говори» всегда вызывается end() с активным токеном; если текст пуст, ожидается закрытие без звукового сигнала и отправки.

Связанные материалы

Was this useful?
On this page

On this page