Очки: история диалога в свайпе + распознавание в LLM только по клику (касяки 1-3)

This commit is contained in:
2026-08-23 20:15:21 +03:00
parent 51049d56a8
commit 706cc10d1e
9 changed files with 254 additions and 28 deletions
@@ -105,6 +105,14 @@ class GlassesHub(
@Volatile
var stt: SttStreamer? = null
/**
* Единственный путь текста в LLM: вызывает PhoneApp (assistantChannel.send)
* на StopStt(cancel=false) с непустым текстом. VAD-фразы в LLM НЕ уходят —
* только повторный клик пользователя.
*/
@Volatile
var onStopFullText: ((String) -> Unit)? = null
/** Фабрика SttStreamer на первый SttAudio (тяжёлая — создаётся один раз). */
@Volatile
var sttFactory: (() -> SttStreamer?)? = null
@@ -200,8 +208,17 @@ class GlassesHub(
} else {
val full = stt?.finish() ?: ""
log("stt", "ВЕСЬ ТЕКСТ: $full")
send(connId, SttDone(full))
stt?.reset()
if (full.isNotBlank()) {
send(connId, SttDone(full))
val phrase = full.trim()
log("stt", "фраза в LLM (по клику): $phrase")
onStopFullText?.invoke(phrase)
} else {
// Фразы не было — очки возвращаются в «слушаю» (SttCancel).
log("stt", "фразы не было — очки в «слушаю»")
broadcast(SttCancel())
}
}
}
}
@@ -23,6 +23,8 @@ import pw.binom.viewmate.core.phone.PhoneActions
import pw.binom.viewmate.core.phone.PhoneState
import pw.binom.viewmate.core.protocol.AssistantState
import pw.binom.viewmate.core.protocol.AssistantStateMsg
import pw.binom.viewmate.core.protocol.ChatHistoryItem
import pw.binom.viewmate.core.protocol.ChatHistoryMsg
import pw.binom.viewmate.core.protocol.ShowText
import pw.binom.viewmate.core.protocol.SttCancel
import pw.binom.viewmate.core.protocol.SttPhrase
@@ -30,6 +32,7 @@ import pw.binom.viewmate.phone.agent.AgentToolkit
import pw.binom.viewmate.phone.agent.Assistant
import pw.binom.viewmate.phone.agent.ChatDao
import pw.binom.viewmate.phone.agent.ChatDb
import pw.binom.viewmate.phone.agent.ChatRole
import pw.binom.viewmate.phone.agent.Gemma4E2B
import pw.binom.viewmate.phone.agent.GemmaModelDownloader
import pw.binom.viewmate.phone.agent.JellyfinMediaSource
@@ -290,10 +293,25 @@ class PhoneApp : Application() {
.onFailure { log("assistant", "ошибка: ${it.message}") }
.getOrElse { "Ассистент недоступен: ${it.message}" }
server.hub.broadcast(ShowText(answer))
// Очки показывают историю всей сессии (свайпами); ответ уже в ней.
server.hub.broadcast(chatHistoryMsg())
server.hub.broadcast(AssistantStateMsg(AssistantState.IDLE))
}
}
/**
* История активной (или последней) сессии для очков: только роли user/assistant,
* tool-сообщения и toolCalls очкам не нужны (ТЗ-касяк №1).
*/
private fun chatHistoryMsg(): ChatHistoryMsg {
val id = _activeSessionId.value ?: chatDao.latestSessionId()
if (id == null) return ChatHistoryMsg(sessionId = null, messages = emptyList())
val items = chatDao.messagesForSession(id)
.filter { it.role == ChatRole.USER || it.role == ChatRole.ASSISTANT }
.map { ChatHistoryItem(role = it.role.wire, content = it.content) }
return ChatHistoryMsg(sessionId = id.toString(), messages = items)
}
/**
* Отправка текста в ассистента из чата на телефоне (не с очков).
* Та же очередь, что и голосовые фразы — сериализация LLM сохраняется.
@@ -352,8 +370,8 @@ class PhoneApp : Application() {
vadModelPath = vadModel.absolutePath,
onPhrase = { phrase, full ->
log("stt", "фраза: $phrase")
// Только показ на очках — в LLM фраза НЕ уходит (только по клику).
scope.launch { server.hub.broadcast(SttPhrase(phrase, full)) }
scope.launch { assistantChannel.send(phrase) }
},
onSilence30s = {
log("stt", "тишина 30 с — автo-отмена")
@@ -404,6 +422,7 @@ class PhoneApp : Application() {
DebugHttpServer.start(this)
nsd.publish()
server.hub.sttFactory = { ensureStt() }
server.hub.onStopFullText = { phrase -> scope.launch { assistantChannel.send(phrase) } }
scope.launch { assistantLoop() }
val lastPositionAt = AtomicLong(0)