Макеты — один sketches/index.html; approved/ удалён

- sketches/index.html: все экраны одним документом в порядке 1·… (14–19 — бывшие approved)
- approved/ (4 файла групп + 3 макета + README) удалён как дубль
- стили вынесены в sketches/style.css
- README, THEMES, BORROW-FROM-ASSISTENT, MARKDOWN-SOURCE, MIC-ASR-SEARCH
  приведены к фактическому состоянию
This commit is contained in:
2026-09-28 10:07:57 +03:00
parent 960cd60120
commit 27f349550c
25 changed files with 2416 additions and 4448 deletions
+129 -107
View File
@@ -1,131 +1,153 @@
# agentik-desktop — дизайн десктопного клиента
# agentik-desktop — десктопный клиент агента agentik
Десктопный клиент агента **agentik** (Jetpack Compose Desktop). UI общается с агентом
через опубликованную библиотеку `pw.binom.agentik:client` — она даёт готовые
`Agent` / `Conversation`, живой поток событий и `interrupt()` для кнопки «Стоп».
Клиент агента **agentik** на Kotlin Multiplatform + **Compose Desktop** (цель сборки —
только JVM). UI общается с агентом через готовую библиотеку `pw.binom.agentik:client`:
она даёт `Agent` / `Conversation`, живой поток событий (SSE), авто-reconnect,
кэш списка диалогов и `interrupt()` для кнопки «Стоп».
Это пока **только дизайн**, не код приложения.
Проект уже **собирается и запускается** (`./gradlew run`), а не только спроектирован.
## Что где лежит
**`approved/` — принятое. Это решение, на него опираемся в реализации.**
```
approved/001-sidebar-utility.html # ОСНОВА: привычный мессенджер
approved/004-settings-agents.html # настройки агентов + проверка связи
approved/005-new-chat-picker.html # модалка «Новый диалог»
approved/006-groups-manage/ # группы: 4 файла по состояниям
approved/006-groups-manage/1-list.html # Группы: изменить, удалить, добавить
approved/006-groups-manage/2-add.html # новая группа
approved/006-groups-manage/3-edit.html # изменение и удаление
approved/006-groups-manage/4-delete.html # удаление: диалоги остаются
approved/README.md # что одобрено и когда
sketches/index.html # ВСЕ экраны одним документом: список диалогов, чат, запись,
# группы, агенты, проверка связи, пустые состояния.
# Это и черновик, и «одобренное» — отдельного approved/ больше нет.
sketches/style.css # палитра и стили макетов (цвета числами — так и надо, это макет)
src/jvmMain/kotlin/... # код приложения
src/jvmTest/kotlin/... # тесты
```
**`sketches/` — предложения. Не решение, а варианты и черновики.**
Документы рядом:
- `REQUIREMENTS.md` — требования (накиданы, ни один пункт не обязателен в текущей форме).
- `STORAGE.md` — что и где хранится на клиенте (SQLite + JSON).
- `CACHE.md` — кэш сообщений: что есть в библиотеке, схема «только новое».
- `THEMES.md` — цвета не пишем в коде, только из семантической схемы.
- `MARKDOWN-SOURCE.md` — откуда взята отрисовка Markdown.
- `BORROW-FROM-ASSISTENT.md` — что берём из `ai/assistent`, а что нет.
- `MIC-ASR-SEARCH.md` — библиотеки микрофона/распознавания (`mic-kmp`, `asr-kmp`, `vad-kmp`).
## Структура кода
```
sketches/002-rail-voice-first/index.html # голос — главное действие
sketches/003-three-pane-command/index.html # три панели + состояние агента
sketches/004-settings-agents/index.html # черновик настроек агентов
sketches/005-new-chat-picker/index.html # черновик модалки нового диалога
pw.binom.agentik.desktop
├── Main.kt # точка входа: окно, реестр, состояние
├── settings/ # AppPaths, AppSettings/AgentConfig, SettingsRepository
├── persistence/ # ConversationMetaRepository (группы/lastSeen/preview/watermark)
├── session/ # LiveTurn, ChatSession, AgentConnection, AgentRegistry
├── model/ # UiModels + Mappers (журнал/стрим → UI)
├── media/ # AvatarStorage (копия картинки агента)
├── net/ # ConnectionChecker (проверка связи)
├── markdown/ # вендоренный парсер Markdown (из assistent)
└── ui/ # App, AppState, theme/, components/, screens/
```
`BORROW-FROM-ASSISTENT.md` — что берём из `ai/assistent` (приёмы, плагины, версии),
а что не берём (архитектура, экраны, дизайн — своё).
`MIC-ASR-SEARCH.md` — библиотеки для микрофона и распознавания: своё готовое
(`mic-kmp`, `asr-kmp`, `vad-kmp`), вместо копирования кода из assistent.
`STORAGE.md` — где что хранится: **хранилище делаем абстракцией, реализация —
SQLite, настройки — JSON.** Три отдельные абстракции (сообщения / настройки /
снимок списка), почему SQLite, что менять в API (превью и число непрочитанных).
`CACHE.md` — кэш сообщений на клиенте: что для него есть в библиотеке, схема
загрузки «только новое», где может порваться.
`THEMES.md` — цветовые темы: цвета не пишем в коде, берём из схемы. Тем будет
несколько, поэтому ни одного цвета числом.
`MARKDOWN-SOURCE.md` — откуда брать готовую отрисовку Markdown (файлы и адреса).
`REQUIREMENTS.md` — требования. Статус: накидываем, ни один пункт не обязателен
к исполнению в том виде, как записан.
Ключевые решения по слоям — в `STORAGE.md`; кратко:
## Одобренное
- **Транспорт целиком в `:client`.** HTTP/JSON/SSE/Bearer, reconnect, кэш списка
диалогов — не переписываем.
- **Сообщения** — `KsqliteJournalStore` из `:journal-ksqlite` (offline-история).
- **Группы, `lastSeen`, превью, watermark синхронизации** — наш
`ConversationMetaRepository` (сервер про это не знает).
- **Отдельная SQLite-база на агента**: id диалогов серверные, у двух агентов
могут совпасть; плюс группы локальны для агента.
`approved/` — макеты, которые пользователь **принял**. Это решение, на него
опираемся в реализации. Отличие от `sketches/`: там предложения, здесь принятое.
## Текущее состояние реализации
- **`001-sidebar-utility.html`** — **основа дизайна**: привычный мессенджер,
список диалогов + чат, папки («Все чаты / Работа / Дом»). Выбран как основа.
- **`004-settings-agents.html`** — настройки агентов: список, форма нового,
проверка связи отдельным окном поверх. Одобрена 2026-09-19.
- **`005-new-chat-picker.html`** — модалка «Новый диалог» при нескольких
ассистентах. Одобрена 2026-09-19.
- **`006-groups-manage/`** — управление группами, четыре файла по состояниям.
Одобрена 2026-09-19. Пояснительной панели над окном в ней нет
(в `sketches/005` была — это была заметка для проверки, не часть интерфейса).
Готово и покрыто тестами:
**Правило:** одобренное живёт **только** в `approved/`. Если макет приняли —
он переезжает туда, а не остаётся «выбранным» среди вариантов.
- **Настройки** — `settings.json` (агенты, тема, выбранная группа, последний диалог),
`clientId` генерируется один раз. Атомарная запись.
- **Живой список диалогов** — `AgentConnection.conversationsFlow` опрашивает
встроенный кэш либы (`conversationStore.listFlow`, warmup-цикл на старте) и
дополнительно подписан на SSE-поток агента: диалог, созданный с другого
клиента (или самим сервером), появляется в списке сам; свои создания и
удаления отражаются сразу. Ошибки сервера не считаются «пустым списком» —
последний известный список сохраняется.
- **Реестр агентов** — добавление/правка/удаление, подключение, деградация в
«не отвечает» без падения приложения.
- **Стриминг** — `LiveTurn` применяет события `Working → StartReasoning →
AppendText → ToolCall/ToolResult → End/Interrupted/Error`; `ChatSession`
догоняет историю (`after = synced_at`), пишет в локальный кэш, склеивает
live + history, обновляет превью и `lastSeen`.
- **История/UI-модели** — мапперы `MessageRecord → UiMessage` со склейкой
`ToolCall`+`ToolResult` в одну карточку.
- **Группы** — CRUD групп + раскладка диалогов (каскад: удаление группы не
удаляет диалоги).
- **Тема** — семантические цвета (`AgentikTheme.colors`), тёмная + светлая
схемы; отдельно пользовательские цвета агентов.
- **Markdown** — вендоренный рендер из `ai/assistent` (см. `MARKDOWN-SOURCE.md`).
- **Аватары** — копирование выбранного файла в `~/.agentik-desktop/avatars/`.
- **Проверка связи** — «ответ сервера» + создание/удаление временного диалога,
с таймингами.
- **UI (Compose)** — рейка агентов, список диалогов (поиск, папки, бейджи),
чат с живым стримингом, композер, окна «Агенты», «Группы» и «Новый диалог»
(при нескольких агентах — выбор, при одном — сразу), пустые состояния,
адаптивная раскладка (узкое окно — список или чат).
- **Голос** — микрофон → VAD → распознавание (`voice/`): кнопка микрофона
или `Ctrl+M` из чата запускает запись (`mic-api`), Silero VAD (`vad-kmp`) отмечает наличие речи,
Vosk (`asr-vosk`, модель внутри артефакта) отдаёт промежуточный текст над
полем ввода, финал дописывается в черновик. Тишина текстом не становится.
Нативные модели считаются на одном выделенном потоке. На старте `main()`
делает [`VoiceNative.warmUp()`] — обязательный прогрев Vosk/ONNX ДО
инициализации skia/Compose (иначе создание модели после отрисовки окна
роняет JVM в C++-логах; подробности в `MIC-ASR-SEARCH.md`).
- **Реальная интеграция** — интеграционные тесты гоняются против живого
`:standalone`-агента (Qwen через vLLM), а не только in-memory-фейков:
`RealServerIT` (диалог → отправка → живой ответ LLM → локальный кэш) и
`RealAppStateIT` (весь `AppState`: реестр → подключение → живой список →
создание диалога → стриминг → превью/lastSeen), `RealSpeechIT` (речь:
прогрев нативных ASR → инициализация Compose → распознавание реального wav
→ «раз два три четыре пять»). См. `AGENTIK_E2E_URL`.
Проверено вживую (окно приложения + живой агент): список диалогов подтягивается
с сервера, кнопка «+» создаёт диалог, сообщение уходит и ответ приходит в UI,
`Ctrl+M` переводит микрофон в запись и обратно без падения JVM.
Не начато / заглушки:
- **Свёрнутый вид хода работы агента** — сейчас живой ход показывается
развёрнуто (reasoning, карточки ToolCall/ToolResult, спиннер). Как его
складывать — вопрос дизайна, не решён (REQUIREMENTS §9).
- **Кусок прерванного ответа в кэше** — R39 выполнен (прерванный ответ в
историю не пишется), а вот хранить ли его обрывок в кэше — вопрос не решён
(REQUIREMENTS §9).
- **Светлая тема визуально не проверялась глазами** (`THEMES.md`); в
`ui/ThemeRenderTest.kt` проверено программно: фон код-блоков берётся из
слота темы, порядок яркостей в светлой палитре корректный.
- **Голос: движок зашит на Vosk** (`VoskVoiceRecognizer`) — Qwen3/Whisper из
`asr-kmp` подключаются заменой `VoiceRecognizerFactory`; выбор движка/модели
в настройках не сделан.
## Как запустить
```bash
./gradlew run # окно приложения
./gradlew jvmTest # тесты (реальная SQLite, фейковый транспорт)
# Интеграционные тесты против живого агента (по умолчанию пропускаются):
AGENTIK_E2E_URL=http://127.0.0.1:8080/agentik ./gradlew jvmTest \
--tests '*RealServerIT' --tests '*RealAppStateIT'
./gradlew jvmJar # собрать jar
```
## Что решено
- **Сначала десктоп, затем Android — по тем же лекалам.** Десктопный клиент
делаем образцом: понятный, удобный и **читаемый по коду**. Когда получится —
Android повторяет те же решения. Поэтому цель — максимально простой код,
который пользователь открывает и понимает.
- **Основа — вариант 1** (`approved/001-sidebar-utility.html`). Остальные остаются рядом как
источник идей.
- Стиль — тёмный, из уже принятой темы клиента assistent: фон `#121218`,
панели `#17212B`, акцент `#6AB2F2`, текст `#EBEBEB`.
- **Тем будет несколько — цвета берём из цветовой схемы, не пишем числом в коде.**
Имена по смыслу («фон», «панель», «акцент»), чтобы смена темы ничего не ломала.
В макетах числа — это нормально. Разбор и список того, что забывают: `THEMES.md`.
- **Узкое окно** — на экране что-то одно: либо список диалогов, либо чат.
Переключение кнопкой «Назад», как в Телеграме. Широкое — обе части сразу.
- **Подсказка о горячих клавишах** под полем ввода убрана.
- **Счётчиков токенов и кольца заполнения не будет.** В библиотеке `client`
таких данных нет вообще: ни модели, ни отправлено/получено, ни предела
контекста. Правило: нет данных — нет цифры.
- **Несколько агентов** — предусмотреть. Различение: **цвет или картинка** на выбор.
Цвет помечает диалоги агента (полоска у строки, точка у группы); картинка
показывается кружком вместо цвета. **Картинку клиент копирует себе**, а не берёт
по исходному адресу — иначе значок пропадёт вместе с файлом.
- **Markdown** — показывать разметкой, а не сырыми символами. Готовый рендер
лежит в репозитории `ai/assistent` — писать свой не надо, брать оттуда.
Точные координаты файлов — в `MARKDOWN-SOURCE.md`.
- **Папки над списком диалогов** («Все чаты», «Работа», «Дом») — часть выбранного
варианта 1, не терять их при доработках.
- **Проверка связи с агентом** — в отдельном окне поверх настроек: идёт → отвечает
→ не отвечает, с подробностями и подсказкой. Добавить агента можно только
после успешной проверки.
- **Новый диалог** — при нескольких ассистентах спрашиваем, в каком создавать
(диалог живёт внутри ассистента, отдельно его не создать). Один ассистент —
окна нет, создаётся сразу. Ноль ассистентов — кнопки «+» нет вообще.
## Заимствования: только приёмы, не архитектура
Из репозитория `ai/assistent` берём **приёмы отрисовки, плагины и версии**.
Архитектуру, слои, готовые экраны и окна — **не берём**: дизайн у нас свой,
нарисованный (см. `sketches/`). Разбор по пунктам — в `BORROW-FROM-ASSISTENT.md`,
там же таблица «берём / не берём».
## Как смотреть
Открыть в браузере любой из файлов — каждый самодостаточный, без сборки.
В четвёртом сверху переключатель состояний: список агентов, форма, проверка
связи (успех и ошибка), список диалогов с двумя агентами.
- **Сначала десктоп, затем Android — по тем же лекалам.** Десктоп делаем
образцом: понятный и читаемый по коду. Потом Android повторяет решения.
- **Несколько агентов** — различение цветом или картинкой (выбирает пользователь).
Картинку клиент **копирует себе**, а не берёт по адресу.
- **Markdown** — показывать разметкой. Готовый рендер взят из `ai/assistent`.
- **Папки над списком диалогов** — сохранены.
- **Проверка связи** — отдельным окном, с таймингами шагов.
- **Ориентир по размеру:** ~9 небольших кусков логики поверх библиотеки, а не
собственная инфраструктура (см. `STORAGE.md`, `CACHE.md`).
## Что предстоит решить
1. **Запись** — по нажатию (нажал — говоришь — нажал) или на удержание.
1. **Запись** — по нажатию или на удержание.
2. Форма показа хода работы агента в свёрнутом виде.
3. Различение агентов: одного цвета может оказаться мало.
4. Где хранится список агентов.
5. Нужны ли папки для диалогов, как в мобильном клиенте, или хватит поиска.
6. **Где хранится кэш сообщений** — файл или лёгкая база (см. `CACHE.md`).
7. **Что делать с куском прерванного ответа** в кэше — иначе схема даст сбой
на первом же нажатии «Стоп» (`CACHE.md`, п. 3.2).
## Что сознательно не решается здесь
- **Откуда берётся распознавание речи.** Отдельная тема, к интерфейсу не
относится: в дизайне показано только место кнопки и что происходит на экране
во время записи. Кто именно превращает голос в текст — решается потом.
Готовые библиотеки уже найдены, см. `MIC-ASR-SEARCH.md`.
4. **Что делать с куском прерванного ответа** в кэше (`CACHE.md`, п. 3.2).
5. Где показывать несколько агентов в одном списке диалогов (сейчас список —
у активного агента).