From e81026b9f5533a46078c11165a084d9d15bff446 Mon Sep 17 00:00:00 2001 From: subochev Date: Mon, 7 Sep 2026 04:27:05 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BB=D0=BE=D0=B8=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=B8=20=D0=B8=20=D0=BE=D0=B2=D0=B5=D1=80=D0=BB?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=B1=D0=B5=D1=80=D1=83=D1=82=20=D1=81=D1=82?= =?UTF-8?q?=D1=82-=D1=81=D1=82=D0=B0=D1=82=D1=83=D1=81=20=D0=B8=20=D1=81?= =?UTF-8?q?=D0=B5=D1=81=D1=81=D0=B8=D1=8E=20=D0=B8=D0=B7=20=D0=B5=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B3=D0=BE=20GlassesStt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pw/binom/viewmate/glasses/GlassesApp.kt | 33 --------------- .../binom/viewmate/glasses/HostConnection.kt | 42 ++----------------- .../pw/binom/viewmate/glasses/MainActivity.kt | 4 +- .../glasses/gestures/AssistantGestures.kt | 2 +- .../glasses/ui/layers/ListeningLayout.kt | 27 ++---------- 5 files changed, 10 insertions(+), 98 deletions(-) diff --git a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/GlassesApp.kt b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/GlassesApp.kt index 03cb95f..15a0162 100644 --- a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/GlassesApp.kt +++ b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/GlassesApp.kt @@ -14,7 +14,6 @@ import pw.binom.viewmate.core.log.SystemClock import pw.binom.viewmate.glasses.gestures.NavFrame import pw.binom.viewmate.glasses.stt.GlassesStt import pw.binom.viewmate.glasses.ui.layers.AssistantEngine -import pw.binom.viewmate.glasses.ui.layers.ListeningLayout import pw.binom.viewmate.glasses.ui.layers.MoveLayout import pw.binom.viewmate.glasses.ui.layers.toFrame @@ -135,38 +134,6 @@ class GlassesApp : Application() { prefs.edit().putFloat("vertical", v).apply() } - // Хостовые STT-события с телефона. Микрофон и распознавание владеет слой записи - // [ListeningLayout]: маршрутизирую в *верхний слой* (транспорт о слоях не знает — - // сюда заходит из [HostConnection]). Слоя записи нет — событие неактуально, игнор. - // Activity о распознавании не знает. - - /** Пустой клик без голоса (SttCancel reason="user") — верхнему слою («не расслышал»). */ - fun stopSttListening() { - val layer = layoutManager.current as? ListeningLayout ?: run { - log("stt", "стоп-записи без слоя записи — игнор") - return - } - layer.onSttCancelUser() - } - - /** Таймаут тишины 30с с телефона (SttCancel reason="timeout") — верхнему слою. */ - fun onSttTimeout() { - val layer = layoutManager.current as? ListeningLayout ?: run { - log("stt", "стт-таймаут без слоя записи — игнор") - return - } - layer.onSttTimeout() - } - - /** Хостовая накопленная фраза (SttPhrase.full с телефона) — верхнему слою (живой текст). */ - fun onSttPhrase(full: String) { - val layer = layoutManager.current as? ListeningLayout ?: run { - log("stt", "стт-фраза без слоя записи — игнор") - return - } - layer.onSttPhrase(full) - } - override fun onCreate() { super.onCreate() instance = this diff --git a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/HostConnection.kt b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/HostConnection.kt index 09cbadb..6a63c85 100644 --- a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/HostConnection.kt +++ b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/HostConnection.kt @@ -40,9 +40,6 @@ import pw.binom.viewmate.core.protocol.SetScale import pw.binom.viewmate.core.protocol.SetVertical import pw.binom.viewmate.core.protocol.Shutdown import pw.binom.viewmate.core.protocol.ShowText -import pw.binom.viewmate.core.protocol.SttCancel -import pw.binom.viewmate.core.protocol.SttDone -import pw.binom.viewmate.core.protocol.SttPhrase import pw.binom.viewmate.core.protocol.Welcome import pw.binom.viewmate.core.protocol.protocolJson @@ -368,42 +365,6 @@ class HostConnection( ) } - is SttPhrase -> { - // Хостовую накопленную фразу отдаю *верхнему слою* (как и SttCancel): - // микрофон/распознавание владеет слой записи, а транспорт о слоях не знает - // (роутинг — в [GlassesApp]). - GlassesApp.instance.onSttPhrase(msg.full) - log("stt", "фраза: ${msg.phrase}") - } - - is SttDone -> { - log("stt", "ВЕСЬ ТЕКСТ: ${msg.full}") - } - - is SttCancel -> { - when (msg.reason) { - SttCancel.REASON_CANCEL -> { - // Явная отмена диктовки двойным тапом: очки САМИ уже сделали стоп - // (POP кадра «слушаю» с ОСТАВАНИЕМ в ассистенте — микрофон закрыт, - // чат не прерван, оверлей на месте). Это всего подтверждение, что - // телефон отменил STT-сессию (stt.reset()) — состояния не трогаем. - log("stt", "отмена диктовки (двойной тап) — стоп, остался в ассистенте, оверлей на месте") - } - SttCancel.REASON_TIMEOUT -> { - // Авто-отмена по тишине 30с: пользователь молчит/ушёл — ВЫХОДИМ из - // режима (микрофон закрыть, оверлей скрыть), как exitAssistantMode. - log("stt", "тишина 30с — выход из режима общения") - GlassesApp.instance.onSttTimeout() - } - else -> { - // reason="user" — пустой клик без голоса: не выходим из режима, - // стоп-записи с остаованием в ассистенте (idle), «не расслышал». - log("stt", "отменено — стоп, остаюсь в ассистенте (не расслышал)") - GlassesApp.instance.stopSttListening() - } - } - } - is Shutdown -> { addMessage("Shutdown: закрываю приложение") // как в pair-view: finish() Activity очков @@ -411,6 +372,9 @@ class HostConnection( // страховка: процесс завершится сам, но kill убирает зависшие потоки (RayNeo) android.os.Process.killProcess(android.os.Process.myPid()) } + + // Неподдерживаемое host-сообщение — молча игнор (в т.ч. legacy phone-STT: очки не ждут). + else -> Unit } } diff --git a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/MainActivity.kt b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/MainActivity.kt index c5224ee..71d3746 100644 --- a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/MainActivity.kt +++ b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/MainActivity.kt @@ -209,8 +209,8 @@ class MainActivity : BaseEventActivity() { // Мозг ассистента (стейт/эффекты) — в движке общения [GlassesApp.assistantEngine]: // слои AssistantLayout / ListeningLayout упираются в него, кино — в MoveLayout. - // Хостовые STT-события (SttPhrase/SttCancel) маршрутизирует [GlassesApp] в верхний - // слой — Activity о распознавании не знает. + // Activity о распознавании не знает: запись самозамкнута в [ListeningLayout] (локальная + // стт), host-STT-события очки не обрабатывают (молча игнор в HostConnection). /** * Звуки касания трекпада играет сам SDK (GestureDetectorWithSound, SoundPool: diff --git a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/gestures/AssistantGestures.kt b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/gestures/AssistantGestures.kt index c835d6d..b3137a2 100644 --- a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/gestures/AssistantGestures.kt +++ b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/gestures/AssistantGestures.kt @@ -30,7 +30,7 @@ data class AssistantState( val stack: List = listOf(NavFrame.Film), /** «Думаю»: фраза отправлена, ждём ответ (ShowText). Контент кадра ассистента. */ val thinking: Boolean = false, - /** Накопленные фразы (SttPhrase, full). Контент кадра «слушаю». */ + /** Живой текст записи (локальное распознавание в [ListeningLayout]). Контент кадра «слушаю». */ val sttText: String = "", /** Ответ ассистента (ShowText). Контент кадра ассистента. */ val assistantText: String = "", diff --git a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/ui/layers/ListeningLayout.kt b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/ui/layers/ListeningLayout.kt index 3612df0..5ad284e 100644 --- a/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/ui/layers/ListeningLayout.kt +++ b/app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/ui/layers/ListeningLayout.kt @@ -13,9 +13,9 @@ import kotlin.coroutines.resume /** * Слой записи — ЕДИНСТВЕННЫЙ, кто знает про микрофон и распознавание. Он сам открывает * микрофон ([stt.session.start] в [onEnter]), закрывает ([onExit]) и сам принимает - * стт-решение: клик распознаёт накопленное ([stt.session.send]), результат/события - * (локальный [onSttResult], хостовые [onSttPhrase]/[onSttCancelUser]/[onSttTimeout]) слой - * переваривает сам и сообщает вызывающему факт исхода кэлбэками [onSuccess]/[onCancel]. + * стт-решение: клик распознаёт накопленное ([stt.session.send]), локальный результат + * ([onSttResult]) слой переваривает сам и сообщает вызывающему факт исхода кэлбэками + * [onSuccess]/[onCancel]. Хостовых входов нет — в локальной архитектуре слой самозамкнут. * * Для всего остального (кино, чат, ассистент) «текст откуда-то берётся» — никто кроме этого * слоя не в курсе микрофона и распознавания. Зависимость — единый объект [GlassesStt] (весь @@ -118,7 +118,7 @@ class ListeningLayout( * - пусто + клик (без голоса) → отмена ([onCancel]) + POP слоя (не расслышал). * Текст дальше «уходит от куда-то» (в LLM на телефон) — слой это не видит. */ - fun onSttResult(text: String, auto: Boolean) { + private fun onSttResult(text: String, auto: Boolean) { sttText.value = text when { text.isNotBlank() -> { @@ -137,23 +137,4 @@ class ListeningLayout( } } } - - /** Хостовая накопленная фраза (SttPhrase.full): обновить живой текст слоя. */ - fun onSttPhrase(full: String) { - sttText.value = full - } - - /** Хостовая отмена (SttCancel, reason="user"): отмена, остаюсь в ассистенте (POP слоя). */ - fun onSttCancelUser() { - log("stt", "хост: отмена (user) → стоп, остаюсь в ассистенте (не расслышал)") - onCancel() - ctx.close() - } - - /** Хостовый таймаут (SttCancel, reason="timeout"): отмена, остаюсь в ассистенте (POP слоя). */ - fun onSttTimeout() { - log("stt", "хост: тишина 30с → отмена, остаюсь в ассистенте") - onCancel() - ctx.close() - } }