Files
view-mate/README.md
T
Hermes Agent a434d5bb12 feat: потоковый локальный ассистент — фраза → LLM → ответ на очки
- agent/Assistant.kt: оркестратор диалога (SQLite-история + Koog), таймаут 30с,
  сессия одна живая, история — последние 20 сообщений
- ChatDao.latestSessionId(); SYSTEM_PROMPT нейтральный (TODO: персона)
- PhoneApp: очередь фраз (Channel UNLIMITED), assistantLoop (THINKING/ShowText/IDLE),
  ensureAssistant ленивый; ключ/модель из BuildConfig (local.properties)
- Очки: assistantText StateFlow, ShowText → оверлей (белый текст речи + серый ответ),
  оверлей остаётся после Click до новой сессии
- AssistantTest (androidTest, 3 теста: сессия/история/таймаут) — OK (6 tests всего)
- README: строка про ассистента
2026-08-20 12:35:49 +03:00

162 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ViewMate
Очки RayNeo X2 + компаньон на телефон: кино и чат с ассистентом.
## Статус реализации (19.08.2026)
| Возможность | Статус |
|---|---|
| Бинокулярный видеорендер на очках (ExoPlayer → EGL-блит в оба глаза), fullscreen | ✅ этап 1 |
| Пульт: пауза/плей/±30с/закрыть (с телефона и из консоли) | ✅ |
| Связка очки↔телефон по WiFi (WS), два эмулятора + adb forward | ✅ |
| Каталог Jellyfin на телефоне (Фильмы/Сериалы/Папки, поиск, постеры, бейджи зеркала) | ✅ |
| Детали: выбор озвучки, статус конвертации, заказ зеркала | ✅ |
| Реальное скачивание с S3 (SigV4, прогресс, докачка с места, персистентный статус) | ✅ |
| Экран «Очки»: связь, батарея/память, пульт | ✅ |
| **Синхронизированный звук на телефоне** (очки без динамиков) | ✅ 19.08 (diff < 1000 мс, авто-ресинк) |
| Чат с ассистентом (режим CHAT, сессии Hermes, голосовой ассистент) | ❌ заглушка |
| Жесты RayNeo (пауза/перемотка жестом) | ❌ в планах |
| NSD-дискавери телефона (реальные очки находят телефон по сети) | ✅ 19.08: mDNS + скан /24 (на RayNeo multicast режется — работает скан; эмулятор — fallback 10.0.2.2) |
Подробный журнал решений и инцидентов: `/root/notes/2026-08-19.md` (внутреннее).
## Структура
```
app-glasses/ RayNeo X2 — AR-приложение: видео (ExoPlayer), жесты, связь по WiFi
app-phone/ компаньон на телефон — каталог, дубль-управление, чат
app-host/ отладочный хост — «бэк» без UI, к которому цепляются очки
lib-core/ общий слой (KMP): режимы, состояния, команды, DTO
sketches/ эскизы UI: раскладка экранов + сценарии использования
```
## Эскизы интерфейса
- `sketches/phone-ui/index.html` — эскизы 4 экранов телефона (раскладка компонентов, открыть в браузере)
- `sketches/phone-ui/preview.png` — скриншот эскизов
- `sketches/phone-ui/FLOW.md` — **сценарии использования**: как посмотреть фильм, проверить очки, поговорить с ассистентом, что видно при не-сконвертированном/частично скачанном контенте
## Режимы
### Два режима на очках
| Режим | Состояния | Что происходит |
|-------|-----------|----------------|
| **MOVIE** (кино) | `PLAYING`, `PAUSED` | видео на очках (ExoPlayer), звук выбранной дорожки на телефоне, синхронизация по позиции |
| **CHAT** (общение) | `SESSION_SELECT`, `TALKING` | выбор сессии Hermes, затем разговор в рамках сессии |
Переключение режимов — **с телефона** (и жестом на очках). Оба дёргают один локальный агент (lib-core).
### Каталог — НЕ режим очков
Каталог контента (Jellyfin) живёт **только на телефоне** (и в будущем — у голосового ассистента, который тоже будет по нему ходить). Выбрал в каталоге → «включить» → очки переходят в режим MOVIE с готовым контентом.
Очки — «экран кинотеатра»: они ждут, когда им скажут «включи», и не листают каталог сами.
### Локальный голосовой ассистент (в будущем)
Вызывается **в любом режиме** (двойной клик в чате), поверх всего. Умеет: ходить по каталогу, включать/ставить кино, переключать режимы, переключать сессии, отвечать на вопросы (локально, без сервера).
## Жесты (распознаёт RayNeo SDK, `TempleAction`)
### Вне распознавания (обычный режим)
| Жест (SDK) | Режим КИНО | Режим ЧАТ |
|------------|-----------|-----------|
| `Click` | пауза ↔ плей | старт / стоп диктовки |
| `DoubleClick` | игнор | отмена диктовки |
| `TripleClick` | **безусловный вызов ассистента** (пауза видео + затемнение + слушаем) | **безусловный вызов ассистента** (слушаем) |
| `SlideBackward` / `SlideForward` | перемотка | в `SESSION_SELECT` — листание сессий; в `TALKING` — скролл текста ответа |
| `LongClick` | (резерв) | показать / спрятать текст последнего ответа |
### Во время распознавания речи (LISTENING)
| Жест | Действие |
|------|----------|
| `Click` | **завершить распознавание**: вывести весь текст (все фразы) в консоль; затемнение уходит, видео остаётся на паузе |
| `DoubleClick` | **отмена распознавания** (текст отбрасывается) |
| `TripleClick` | игнор |
| VAD молчит 30 с | автоматическая отмена распознавания |
> **TripleClick — безусловный вызов локального ассистента из любого места**
> (поверх всего). Распознавание идёт по фразам: VAD отсёк фразу — распознал —
> отобразил на очках — слушаем дальше; ввод считается открытым до Click.
>
> Жесты распознаёт сам SDK (`TempleAction.Click/DoubleClick/SlideBackward/
> SlideForward/LongClick`) — своя детекция не нужна (проверено в pair-view,
> `MainActivity.kt`). `TripleClick`: если SDK его не отдаёт — детект по
> таймстампам кликов в `onTouchEvent`.
Ассистент (потоковый): TripleClick → говорите → каждая фраза уходит в LLM (llm.binom.pw), ответ текстом на очки; история диалога — в SQLite на телефоне. Модель/ключ: local.properties llm.model/llm.apiKey.
## Состояния ассистента (единые для обоих ассистентов)
```
IDLE ──► LISTENING ──► THINKING ──► SPEAKING ──► IDLE
(слушает) (думает) (говорит)
```
| Состояние | Индикатор |
|-----------|-----------|
| IDLE | ничего |
| LISTENING | красная точка ● мигает + время записи (0:07, 0:23…) |
| THINKING | спиннер / точки «думает» |
| SPEAKING | «говорит» (другим цветом / без мигания) |
Один и тот же индикатор для Порфирия и локального ассистента — очкам неважно, кто за ним стоит.
## Панель индикаторов
**Всегда видна, по центру снизу.** Содержимое меняется от режима:
| Режим/состояние | Содержимое панели |
|-----------------|-------------------|
| КИНО | `просмотрено / осталось` (12:34 / 56:00) + 🔋 батарея + ⏰ часы |
| ЧАТ, диктовка | `● 0:07` (красная точка + время записи) + 🔋 + ⏰ |
| ЧАТ, думает/говорит | индикатор состояния (⏳/🔊) + 🔋 + ⏰ |
| **Потеря связи с телефоном** | **вместо времени — «связь потеряна» (⚠️)** + 🔋 |
- Часы — **время очков** (часы очков, просто время)
- Батарея — очков
- Просмотрено/осталось — из `PlaybackState` (positionMs/durationMs)
- Если связи с телефоном нет — панель показывает ⚠️ вместо времени (см. Автоматика)
## Текст ответа на экране
Длинные ответы можно показать текстом (по `LongClick`) и скроллить свайпами. Полезно, когда ответ многословный — перечитать спокойно.
## Автоматика
| Событие | Действие |
|---------|----------|
| Очки выключились / экран погас | **пауза** (видео и аудио) |
| Потеря связи очки ↔ телефон (WiFi) | **пауза** (и на очках, и на телефоне) + на панели ⚠️ «связь потеряна» вместо времени |
| Связь восстановилась | продолжить с того же места, панель — как обычно |
## Связь и поток
```
очки (RayNeo X2) телефон (компаньон) сервер
┌─────────────────┐ ┌──────────────────┐
│ режимы: │ │ каталог Jellyfin │ Jellyfin
│ MOVIE/CHAT │◄──WiFi────►│ статусы зеркал │──media-mirror API──► координатор
│ жест = switch │ │ заказ конвертации │ → воркеры → S3
│ видео ExoPlayer │ │ переключение │
│ │ │ режимов очков │
│ │ │ чат с Hermes │──assistent/chat──► Hermes API
└─────────────────┘ └──────────────────┘ → TTS → в ухо
```
- **Один локальный агент** (на телефоне) — очки и телефон два рендера одного состояния
- **WiFi-связь** очки ↔ телефон (вместо Bluetooth в старом pair-view)
- **Кино**: видео 480p H.264 из S3 (media-mirror) на очках, звук выбранной дорожки на телефоне, синхронизация по позиции (очки — мастер, телефон подстраивает аудио)
- **Чат**: с Hermes через сервер; медиа-интенты («включи сериал X») — напрямую в media-mirror API, без LLM
## Медиа-конвейер (внешний, уже живой)
- Координатор: `https://mirror.binom.pw` (X-API-Key) — заказ конвертации, статусы, файлы
- Формат зеркала: `video.mkv` (H.264/NVENC, 480p, без звука) + `audio-N.ogg` (все дорожки, мета title/language)
- S3: `https://s3.binom.pw`, bucket `media`, ключи `mirror/{itemId}/...`; клиентский ключ (read-only) — в конфиге
Полное описание API: `media-mirror-api/README.md` (https://git.binom.pw/subochev/media-mirror-api).