Слои записи и оверлей берут стт-статус и сессию из единого GlassesStt

This commit is contained in:
2026-09-07 04:27:05 +03:00
parent 7db0fc71de
commit e81026b9f5
5 changed files with 10 additions and 98 deletions
@@ -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
@@ -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
}
}
@@ -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:
@@ -30,7 +30,7 @@ data class AssistantState(
val stack: List<NavFrame> = listOf(NavFrame.Film),
/** «Думаю»: фраза отправлена, ждём ответ (ShowText). Контент кадра ассистента. */
val thinking: Boolean = false,
/** Накопленные фразы (SttPhrase, full). Контент кадра «слушаю». */
/** Живой текст записи (локальное распознавание в [ListeningLayout]). Контент кадра «слушаю». */
val sttText: String = "",
/** Ответ ассистента (ShowText). Контент кадра ассистента. */
val assistantText: String = "",
@@ -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()
}
}