From f36d04adc1369be7474bffa57826ebec2a415d45 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sun, 23 Aug 2026 01:08:38 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=BA=D0=B8:=20=D0=B6=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D1=8B=20=D0=B8=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=D1=8B=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=20=D0=B0?= =?UTF-8?q?=D1=81=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BD=D1=82=D0=BE=D0=BC=20?= =?UTF-8?q?(design)=20+=20TASK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TASK-gestures-modes.md | 47 +++++++++++++++++++++++++++++ docs/gestures-and-modes.md | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 TASK-gestures-modes.md create mode 100644 docs/gestures-and-modes.md diff --git a/TASK-gestures-modes.md b/TASK-gestures-modes.md new file mode 100644 index 0000000..2a99f6c --- /dev/null +++ b/TASK-gestures-modes.md @@ -0,0 +1,47 @@ +# Задача: жесты и режимы общения с ассистентом на очках (design в docs/gestures-and-modes.md) + +Прочитай `docs/gestures-and-modes.md` — там согласованная с пользователем матрица жестов и все решения. Реализуй в `app-glasses`. Механику тулсетов/ассистента (app-phone) НЕ трогать. + +## Что сделать + +### 1. Переразметка жестов по матрице (MainActivity.kt, collectTempleActions) + +| Состояние | Click | DoubleClick | TripleClick | Свайпы/SlideContinuous | +|---|---|---|---|---| +| Кино (listening=false, assistantText пуст) | TOGGLE (как сейчас) | игнор | вызов ассистента (как сейчас) | ±30с (как сейчас) | +| Прослушивание (listening=true) | отправить (как сейчас) | отмена, остаёмся в диалоге (как сейчас) | игнор | игнор | +| Ответ виден (listening=false, assistantText непуст) | **новая фраза**: стереть assistantText, открыть микрофон, listening=true | **выход из режима**: закрыть оверлей, стереть assistantText, фильм на паузе (НЕ трогать play/pause — пауза уже стоит с момента входа) | игнор | **скролл ответа** (плавный SlideContinuous, запасной — дискретные свайпы) | + +Важно: состояние «ответ виден» определяется `!listening && assistantText.isNotBlank()`. + +### 2. Скролл ответа (плавный) + +- SlideContinuous: накапливать `delta` (float) → смещать scrollState текста ответа. Направление: SlideForward = вниз по тексту (к концу), SlideBackward = вверх (к началу) — уточнить знак дельты на железе. +- Ограничить скролл границами текста (0..maxScroll). +- Запасной вариант: SlideForward/SlideBackward — скролл на страницу (например, ±200px или высоту видимой области), если SlideContinuous окажется шумным. +- Текст ответа в оверлее должен быть в scrollable-контейнере (Column + verticalScroll(rememberScrollState()) или LazyColumn — на твой выбор, но скролл обязателен). + +### 3. Индикатор «думаю...» + +- Состояние thinking: true с момента отправки фразы (Click при прослушивании / SttPhrase ушёл) до прихода ShowText. +- В оверлее: если thinking и assistantText пуст — показывать «думаю...» (вместо «слушаю...» после того как фраза отправлена). +- Как определить: пока listening=true — «слушаю...»/текст фразы; когда listening стал false, а ShowText ещё не пришёл — «думаю...». + +### 4. Выход из режима + +DoubleClick при «ответ виден»: закрыть оверлей (listening=false, sttText="", assistantText=""), НЕ трогать movieController (фильм остаётся на паузе — он уже на паузе с момента входа в режим). + +## Ограничения + +- Код только Kotlin +- НЕ трогать app-phone (ассистент, тулсеты, скиллы) +- НЕ трогать MovieController/playback-логику +- Тесты: юнит-тесты на чистую логику разметки жестов (новое состояние «ответ виден», переходы, скролл-дельта → смещение) — в духе GestureCommandsTest. Если логика в MainActivity (не чистая) — вынеси чистую часть (решение по жесту из состояния) в отдельный файл/функцию для тестируемости. +- Сборка: `./gradlew :app-glasses:assembleDebug` — зелёная; `./gradlew :app-glasses:testDebugUnitTest` — зелёный (старые + новые) +- НЕ коммитить + +## Проверка + +1. `./gradlew :app-glasses:testDebugUnitTest` — зелёные +2. `./gradlew :app-glasses:assembleDebug` — собирается +3. Опиши в ответе: какие файлы создал/изменил, как определил состояние «ответ виден», как работает скролл (SlideContinuous или запасной), что осталось проверить на железе. diff --git a/docs/gestures-and-modes.md b/docs/gestures-and-modes.md new file mode 100644 index 0000000..739691f --- /dev/null +++ b/docs/gestures-and-modes.md @@ -0,0 +1,60 @@ +# Жесты и режимы очков (design, согласовано 2026-08-22) + +> Согласовано с пользователем. Реализация — opencode по TASK (см. ниже). +> Это документ проекта: сначала думаем, потом хуячим, потом тесты, потом ОБЯЗАТЕЛЬНО мануальные тесты на железе. + +## Ограничения системы (факты) + +- Жестов всего шесть: Click, DoubleClick, TripleClick, SlideForward, SlideBackward, LongClick (+ SlideContinuous с delta). +- **LongClick НЕ работает** — перехвачен системным ассистентом RayNeo. Не рассчитывать, не использовать. +- **SlideContinuous существует** (SDK: `TempleAction.SlideContinuous(delta: Float, ...)` — подтверждено javap) — плавный скролл возможен, delta = дельту движения пальца. Частоту событий и амплитуду дельт проверить на железе. +- Экран очков — широкий 1280×480 (640×480 на глаз). Текста влезает мало → скролл ответа обязателен для длинных ответов. +- Оверлей общения затемняет кино (72% чёрного) → режим общения ЯВНЫЙ: пользователь видит, что он в чате, а не в кино. Это позволяет переназначать свайпы в режиме общения без конфликта. +- Нет клавиатуры — только жесты и голос. Выход из режима — только жестом. +- LLM думает 5-15с — нужен индикатор «думаю...», иначе экран замирает. + +## Общий паттерн жестов (единая семантика через все режимы) + +| Жест | Смысл везде | +|---|---| +| **TripleClick** | вызов локального ассистента (переход в режим общения) | +| **DoubleClick** | отмена / назад | +| **Click** | основное действие текущего контекста | + +## Матрица жестов по состояниям (ЗАКРЫТА) + +| Состояние | Click | DoubleClick | TripleClick | +|---|---|---|---| +| **Кино** | плей/пауза | игнор (назад некуда) | вызов ассистента (пауза + «слушаю...») | +| **Прослушивание** | отправить фразу | отмена, **остаёмся в диалоге** | игнор | +| **Ответ виден** | новая фраза (микрофон открывается) | **выход из режима** | игнор (уже в режиме) | + +Ключевой принцип: **клик = «главное действие здесь»**. В кино — плей/пауза, в диалоге — сказать новую фразу, при прослушивании — отправить. Один жест, контекст решает. Двойной — всегда «не хочу/назад». Тройной — всегда «хочу говорить с ассистентом». + +Конфликтов нет: подрежимы различимы по состоянию (listening = микрофон открыт / assistantText непуст = ответ виден). + +## Решения по деталям + +1. **Отмена диктовки (DoubleClick в прослушивании)** — просто отмена, остаёмся в диалоге (назад к «ответу виден», ничего не теряется). +2. **Фильм после выхода (DoubleClick при ответе)** — ВСЕГДА на паузе, независимо от того, был ли он на паузе до входа в режим ассистента. +3. **Скролл ответа** — плавный: SlideContinuous, отслеживать движение пальца (delta), а не факт свайпа с шагом N. Свайпы SlideForward/SlideBackward в режиме общения — тоже скролл (запасной вариант, если SlideContinuous окажется нестабильным на железе). +4. **Новая фраза в режиме «ответ виден»** — Click: микрофон открывается, «слушаю...». Старый ответ с экрана — стереть (история живёт в SQLite на телефоне, не теряется). +5. **Индикатор «думаю...»** — после отправки фразы (между прослушиванием и ответом) показывать «думаю...» вместо замирания на тексте фразы. + +## Что делать (TASK для opencode) + +В `app-glasses` (MainActivity.kt + оверлей): + +1. **Переразметка жестов по матрице**: + - Кино: как сейчас (Click=TOGGLE, TripleClick=вызов, свайпы=±30с, DoubleClick=игнор) + - Прослушивание: как сейчас (Click=отправить, DoubleClick=отмена, TripleClick=игнор) + - Ответ виден (listening=false, assistantText непуст): **Click → новая фраза** (открыть микрофон, стереть assistantText со старта), **DoubleClick → выход из режима** (закрыть оверлей, фильм на паузе, assistantText стереть), **TripleClick → игнор**, свайпы/SlideContinuous → скролл +2. **Скролл ответа**: плавный через SlideContinuous (delta), scrollState для текста ответа; запасной вариант — дискретные свайпы. Ограничить скролл границами текста. +3. **Индикатор «думаю...»**: состояние между отправкой фразы и получением ShowText; показывать в оверлее. +4. **Выход**: DoubleClick при «ответ виден» — закрыть оверлей, фильм остаётся на паузе (не трогать play/pause при выходе, только при входе ставим паузу). + +Тесты: юнит-тесты на чистую логику разметки жестов (переходы состояний, скролл-дельта → смещение), в духе GestureCommandsTest. Мануальные тесты — ОБЯЗАТЕЛЬНО на железе (142: очки 7687f645 + телефон AXGL024B05001337): полный цикл кино → тройной тап → фраза → «думаю...» → ответ → скролл → выход → фильм на паузе. + +## Смежные договорённости (архитектура агента) + +Полная архитектура скиллов/тулсетов — в `TASK-agent-tools.md`. Ключевое для очков: ассистент — локальный, на телефоне; очки — тонкий клиент (микрофон → STT → телефон → LLM → ShowText на очки). Режим общения — поверх любого режима (кино/чат), активируется тройным тапом из любого места.