Ассистент Порфирий: тула времени, сессии, чат-UI, голосовой цикл; BT-транспорт очки↔телефон
- BT (RFCOMM SPP 00001105) вместо WiFi: BtGlassesTransport/BtServerTransport, реконнект 3с - PlaybackService (foreground, WAKE_LOCK) против морозов HONOR - Ассистент: AgentTools (TimeTool ISO 8601), TOOL_CALL-протокол, сессии SQLite (UUID) - Чат-UI: создание/сброс/переключение сессий, автообновление ленты 2с - LlmClient на прямом OkHttp (Koog давал пустой content на длинных историях) - Голосовой цикл: тройной тап на очках → STT → активная сессия → ShowText - Тесты: AgentToolsTest (3), GlassesServerTest переписан (6) — зелёные
This commit is contained in:
@@ -39,8 +39,13 @@ class PhoneApp : Application() {
|
||||
|
||||
val state: PhoneState = PhoneState()
|
||||
|
||||
/** WS-сервер для очков. */
|
||||
val server: GlassesServer = GlassesServer(port = PhoneConfig.SERVER_PORT, state = state)
|
||||
/** Сервер связи очков: транспорт по [PhoneConfig.USE_BLUETOOTH] — дефолт WiFi (WS), Bluetooth SPP — флагом. */
|
||||
val server: GlassesServer = GlassesServer(
|
||||
port = PhoneConfig.SERVER_PORT,
|
||||
state = state,
|
||||
useBluetooth = PhoneConfig.USE_BLUETOOTH,
|
||||
btContext = this,
|
||||
)
|
||||
|
||||
/** Публикация сервера в локальной сети (mDNS `_viewmate._tcp`) для авто-обнаружения очками. */
|
||||
val nsd: NsdPublisher by lazy { NsdPublisher(this) }
|
||||
@@ -103,13 +108,21 @@ class PhoneApp : Application() {
|
||||
*/
|
||||
private suspend fun assistantLoop() {
|
||||
for (phrase in assistantChannel) {
|
||||
log("assistant", "получена фраза: $phrase")
|
||||
val assistant = ensureAssistant()
|
||||
if (assistant == null) {
|
||||
log("assistant", "LLM-ключ не задан — фраза пропущена")
|
||||
continue
|
||||
}
|
||||
server.hub.broadcast(AssistantStateMsg(AssistantState.THINKING))
|
||||
val answer = runCatching { assistant.process(phrase) }
|
||||
val answer = runCatching {
|
||||
val active = _activeSessionId.value
|
||||
if (active != null) {
|
||||
assistant.processInSession(active, phrase)
|
||||
} else {
|
||||
assistant.process(phrase)
|
||||
}
|
||||
}
|
||||
.onFailure { log("assistant", "ошибка: ${it.message}") }
|
||||
.getOrElse { "Ассистент недоступен: ${it.message}" }
|
||||
server.hub.broadcast(ShowText(answer))
|
||||
@@ -117,6 +130,36 @@ class PhoneApp : Application() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправка текста в ассистента из чата на телефоне (не с очков).
|
||||
* Та же очередь, что и голосовые фразы — сериализация LLM сохраняется.
|
||||
*/
|
||||
fun sendToAssistant(text: String) {
|
||||
scope.launch { assistantChannel.send(text) }
|
||||
}
|
||||
|
||||
/** Активная сессия чата (для UI телефона). null — «последняя» (дефолт). */
|
||||
private val _activeSessionId = MutableStateFlow<Long?>(null)
|
||||
val activeSessionId: StateFlow<Long?> = _activeSessionId
|
||||
|
||||
/** Создать новую сессию и сделать её активной. */
|
||||
fun newChatSession(): Long {
|
||||
val id = chatDao.createSession()
|
||||
_activeSessionId.value = id
|
||||
return id
|
||||
}
|
||||
|
||||
/** Переключить активную сессию. null — вернуться к «последней». */
|
||||
fun selectChatSession(id: Long?) {
|
||||
_activeSessionId.value = id
|
||||
}
|
||||
|
||||
/** Сбросить (очистить сообщения) активной или последней сессии. */
|
||||
fun resetChatSession() {
|
||||
val id = _activeSessionId.value ?: chatDao.latestSessionId() ?: return
|
||||
chatDao.clearSession(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Лениво создаёт стриминговое распознавание (модели во filesDir/models —
|
||||
* кладутся через adb push + run-as cp, как в этапе 1). Вызывается из хаба
|
||||
@@ -189,6 +232,9 @@ class PhoneApp : Application() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
|
||||
PlaybackService.start(this)
|
||||
log("server", "playback-сервис запущен (foreground, WAKE_LOCK)")
|
||||
|
||||
server.start()
|
||||
log("server", "WS-сервер запущен на ${PhoneConfig.SERVER_PORT}")
|
||||
nsd.publish()
|
||||
@@ -264,6 +310,7 @@ class PhoneApp : Application() {
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
PlaybackService.stop(this)
|
||||
nsd.unpublish()
|
||||
sttStreamer?.close()
|
||||
server.stop()
|
||||
|
||||
Reference in New Issue
Block a user