250 lines
22 KiB
Markdown
250 lines
22 KiB
Markdown
# Требования к десктопному клиенту agentik
|
|
|
|
**Статус:** накидываем. Ни один пункт не обязателен к исполнению в том виде, как
|
|
записан. Позже пересматриваем.
|
|
|
|
**Основа дизайна:** вариант 1 (`sketches/001-sidebar-utility`) — выбран.
|
|
|
|
**Сейчас описываем только поведение.** Кода не пишем, пока требования не устоятся.
|
|
|
|
---
|
|
|
|
## 1. Общее
|
|
|
|
- **R1.** Стиль — тёмная тема, уже принятая в клиентах пользователя
|
|
(фон `#121218`, панели `#17212B`, текст `#EBEBEB`).
|
|
- **R2.** Что видно на экране в любой момент: список диалогов, выбранный диалог
|
|
и поле ввода. Больше ничего обязательного нет.
|
|
|
|
## 2. Раскладка и адаптивность
|
|
|
|
- **R3.** **Широкое окно** — две части: список диалогов слева, открытый диалог справа.
|
|
- **R4.** **Узкое окно** — на экране что-то одно: либо список диалогов, либо
|
|
конкретный диалог. Переключение — как в Телеграме.
|
|
- **R5.** В узком окне у открытого диалога сверху слева — кнопка «Назад».
|
|
Нажатие возвращает к списку диалогов на всю ширину.
|
|
- **R6.** Порог «широкое/узкое» — по ширине окна, одна величина. Какой именно
|
|
размер считать порогом — решим позже.
|
|
|
|
## 3. Список диалогов
|
|
|
|
- **R7.** Группировка диалогов со временем появится — «Работа», «Дом» и подобное.
|
|
Сейчас это накидка, состав групп ещё не определён.
|
|
- **R8.** В строке диалога: название, последнее сообщение, время, счётчик
|
|
непрочитанных.
|
|
- **R9.** Есть поиск по диалогам.
|
|
- **R9.1.** Над списком — **папки** (круглые кнопки): «Все чаты», «Работа», «Дом»
|
|
и «+» для новой. Это часть одобренного варианта 1, не забывать при доработках.
|
|
- **R9.2.** В строке диалога — круглый значок непрочитанных, если есть.
|
|
|
|
## 4. Открытый диалог
|
|
|
|
- **R10.** Показ сообщений: мои и ассистента, с временем.
|
|
- **R11.** Ответ ассистента можно прервать — на видном месте кнопка «Стоп».
|
|
- **R12.** Сообщения приходят вживую, по мере генерации: текст дотекает куском
|
|
за куском, а не появляется целиком в конце. Отображать именно так.
|
|
- **R13.** **Разметка (markdown).** Если я или агент пишем текст, похожий на
|
|
разметку, показывать его разметкой, а не сырыми символами: списки, выделение,
|
|
код, таблицы, ссылки. Код для этого писать не надо — готовый рендер уже есть
|
|
в репозитории `ai/assistent`, брать оттуда.
|
|
- **R13.1.** **Откуда брать — точно.** См. `MARKDOWN-SOURCE.md`: репозиторий
|
|
`ai/assistent`, ветка `main`, коммит `2c5b3f9`. Копировать 4 файла:
|
|
модель разметки, парсер, отрисовку и десктопную обёртку открытия ссылок.
|
|
Одна внешняя зависимость — `ru.otpbank.ai:markdown:0.51.0` (есть в Nexus).
|
|
|
|
## 5. Панель ввода
|
|
|
|
- **R14.** Поле ввода, кнопка «Отправить» и **отдельная кнопка записи микрофона**.
|
|
- **R15.** Подсказка о горячих клавишах под полем **убирается** — её не должно быть.
|
|
- **R16.** Пока идёт запись, должно быть понятно, что сейчас пишется голос.
|
|
- **R17.** Отмена записи — явная.
|
|
- **R18.** Голос: кнопка включает микрофон и останавливает его. Точная механика
|
|
(по нажатию или на удержание) — решим позже.
|
|
- **R19.** Распознавание речи — **отдельная тема, здесь не решается.** Важно только:
|
|
где кнопка, что видно во время записи, что происходит после.
|
|
- **R19.1.** **Как читать микрофон — решено, пишем не мы.** Подключаем свою готовую
|
|
библиотеку `mic-kmp` (`pw.binom.mic:mic-api-jvm`, версия `1.0.0`, есть в Nexus).
|
|
Отдаёт звук потоком кусочков, а не файлом: можно распознавать сразу, не дожидаясь
|
|
конца записи. Микрофон освобождается сам при остановке. Формат — 16 кГц, моно.
|
|
Код из `ai/assistent` для этого **не берём**. Подробности — `MIC-ASR-SEARCH.md`.
|
|
|
|
## 6. Несколько агентов
|
|
|
|
- **R20.** Предусмотреть возможность подключать **несколько агентов** и видеть их
|
|
в одном клиенте. Не «много людей», а один клиент — несколько агентов.
|
|
- **R21.** Возможный способ различения — **по аватару**: у каждого агента свой
|
|
аватар, по нему и отличаем. Тема аватаров не продумана, это накидка.
|
|
- **R22.** Другие возможные способы (на выбор, обсудить):
|
|
- **Цветовой акцент** — у каждого агента свой цвет: полоса у пузыря ответа,
|
|
цвет имени в шапке;
|
|
- **Группировка в списке** — диалоги сгруппированы по агентам, с заголовком
|
|
группы (как секции каналов);
|
|
- **Переключатель агента** — как выбор аккаунта в Телеграме: все диалоги
|
|
одного агента разом;
|
|
- **Метка в строке диалога** — маленький значок/буква агента рядом с названием.
|
|
- **R23.** У библиотеки уже есть всё для этого: агент создаётся вызовом с двумя
|
|
значениями — имя и адрес. Имя клиент знает сам, сервер его не хранит. Каждому
|
|
агенту — своё имя и адрес.
|
|
- **R23.1.** Экран настроек агентов (`sketches/004-settings-agents`) — **окно поверх
|
|
чата**, не отдельная страница. Два состояния: список агентов и форма нового.
|
|
- **R23.2.** В строке агента видно: имя, адрес, состояние («на связи» / «не отвечает»),
|
|
кнопки изменить и удалить.
|
|
- **R23.3.** Форма нового агента: название, адрес сервера, и **чем отличать в списке —
|
|
цвет или картинка**. Под каждым полем — пояснение простым языком.
|
|
- **R23.3.1.** Переключатель «Цвет / Картинка»: выбирается что-то одно.
|
|
**Цвет** — простой выбор из готовых, им помечаются диалоги агента в списке
|
|
(полоска у строки, точка у группы).
|
|
**Картинка** — файл с диска; показывается круглым превью, рядом «Выбрать файл…»
|
|
и «Убрать».
|
|
- **R23.3.2.** **Картинку копировать себе, а не брать по исходному адресу.**
|
|
Выбранный файл сохраняется внутрь клиента (в свою папку), и дальше используется
|
|
только эта копия. Ссылку на исходный путь не хранить: файл могут удалить,
|
|
переименовать, флешку вынуть — и значок у агента пропадёт. Работает и для
|
|
картинки на диске, и для картинки по ссылке: скачать и положить себе.
|
|
- **R23.3.3.** Выбранная картинка показывается кружком там же, где был бы цвет:
|
|
в списке агентов, в списке диалогов, в шапке чата. Убрать её можно в любой
|
|
момент — тогда снова становится цвет.
|
|
- **R23.4.** **Проверка связи до сохранения** — в **отдельном окне поверх настроек**,
|
|
не строкой внутри формы. Нажал «Проверить связь» — открылось второе окно, в нём
|
|
идёт проверка. Пока идёт: «спрашиваю агента…» с крутящимся значком. Результат —
|
|
зелёный значок «отвечает» или красный «не отвечает», и под ним подробности:
|
|
ответ на запрос, адрес, число диалогов, отклик в миллисекундах.
|
|
Кнопка «Добавить агента» становится доступной только при успехе.
|
|
Есть «Проверить снова». Закрытие окна возвращает к настройкам.
|
|
Основа: агент отвечает на запрос состояния и отдаёт список диалогов; оба запроса
|
|
ничего не меняют.
|
|
- **R23.5.** Цвет агента работает не только в настройках: тем же цветом помечаются
|
|
его диалоги в списке (полоска у строки, точка у группы).
|
|
- **R23.6.** Список агентов сохраняется между запусками. Где именно — не решено.
|
|
- **R23.7.** Доступа по паролю/ключу у библиотеки нет — поля для него не делать.
|
|
- **R23.8.** **Новый диалог: выбор ассистента** (`sketches/005-new-chat-picker`).
|
|
По «+» спрашиваем, **в каком ассистенте** создать диалог — потому что диалог
|
|
живёт внутри агента, отдельно его не создать.
|
|
- **R23.9.** **Окно показывается только если ассистентов больше одного.**
|
|
Один — диалог создаётся сразу в нём, спрашивать не о чем, окна нет и в помине.
|
|
- **R23.10.** **При нуле ассистентов кнопки «+» нет вообще.** Создавать диалог
|
|
не в ком. Вместо списка — объяснение, что нужно сначала добавить ассистента.
|
|
- **R23.11.** В списке выбора видно: значок (цвет или картинка), имя, адрес
|
|
сервера, состояние связи. Отмечен первый по умолчанию — создать диалог можно
|
|
одним нажатием.
|
|
- **R23.12.** **Ассистента без связи выбирать можно** — окно предупреждает словами,
|
|
но не мешает. Связь может пропасть на секунду, а диалог человек создаёт осознанно.
|
|
Ассистентов без связи из списка не выкидываем.
|
|
|
|
## 7. Состояние агента: что показывать
|
|
|
|
Сверка с исходниками здесь важнее всего. Ниже — **по факту из кода**, не по догадкам.
|
|
|
|
### 7.1. Что есть в самой библиотеке `client` (что клиент реально получает)
|
|
|
|
- **У каждого события и каждого сообщения есть дата.** Значит доступно: длительность
|
|
хода, сколько времени агент думал, когда пришёл ответ.
|
|
- **Есть события:** начало ответа, рассуждение, текст по мере генерации, картинка,
|
|
вызов инструмента (имя, аргументы, заголовок), результат инструмента, конец хода,
|
|
прерывание, ошибка.
|
|
- **Есть события о диалогах:** создан, удалён, переименован.
|
|
- **По диалогу:** название, время последнего изменения, умеет ли принимать и отдавать
|
|
картинки, временный он или постоянный.
|
|
- **Токенов, модели, цены, предела контекста — в библиотеке НЕТ.** Ни одного числа.
|
|
Проверено сплошным поиском: числовых полей в протоколе не существует вообще.
|
|
|
|
### 7.2. Решение: счётчиков токенов и кольца в интерфейсе НЕ будет
|
|
|
|
- **R24.** Показывать израсходованные токены, долю от предела и кольцо заполнения
|
|
**не нужно**. Данных для этого в библиотеке нет — значит и рисовать нечего.
|
|
Кольцо из макета удалено.
|
|
- **R25.** Никаких полей наугад: **нет данных — нет цифры.** Не подставлять
|
|
правдоподобные числа вместо отсутствующих.
|
|
- **R26.** Если когда-нибудь агент начнёт отдавать такие числа — вернёмся к вопросу.
|
|
До тех пор тема закрыта.
|
|
|
|
### 7.3. Ход работы агента
|
|
|
|
- **R27.** Показывать не только ответы ассистента, но и происходящее вокруг них:
|
|
вызовы инструментов, рассуждения, ошибки.
|
|
- **R28.** Свёрнутый вид — одна строка: кратко, что происходит
|
|
(например, «вызов инструмента: такой-то»).
|
|
- **R29.** Развёрнутый вид — подробности шага: аргументы, результат, время.
|
|
- **R30.** Сворачивание и разворачивание — по нажатию на строку.
|
|
|
|
## 8. Простота кода и кэш сообщений
|
|
|
|
### 8.1. Цель: клиент должен читаться
|
|
|
|
- **R31.** **Главная цель — чтобы клиент был максимально прост по коду.** Я должен
|
|
открыть исходники и понять, что там происходит. Это важнее, чем сэкономить
|
|
строки или вынести лишнее.
|
|
- **R31.1.** **Сначала десктоп, затем Android по тем же лекалам.** Десктопный клиент
|
|
— образец. Когда он получится удобным и понятным, Android делается по его
|
|
решениям. Поэтому простота кода здесь — не пожелание, а условие.
|
|
- **R32.** Распознавание голоса, микрофон и детекция речи — **готовые библиотеки**,
|
|
свои (`mic-kmp`, `asr-kmp`, `vad-kmp`). Писать заново ничего не надо, и это уже
|
|
не «вынос в отдельное место» — библиотеки и есть отдельное место.
|
|
- **R33.** Материал найден, разобран: `MIC-ASR-SEARCH.md`. Там же сказано, почему
|
|
код микрофона из `ai/assistent` брать **не** надо.
|
|
- **R34.** Из `ai/assistent` берём только приёмы отрисовки, плагины и версии —
|
|
не архитектуру, не экраны, не дизайн. Разбор: `BORROW-FROM-ASSISTENT.md`.
|
|
|
|
### 8.2. Кэш сообщений на клиенте — решение принято
|
|
|
|
**Задача:** не тянуть всю историю диалога заново при каждом открытии окна.
|
|
|
|
**Схема:**
|
|
1. Открыли диалог — смотрим свой кэш.
|
|
2. Берём из кэша **последнее сообщение** и его дату.
|
|
3. Спрашиваем библиотеку `client`: есть ли сообщения **новее** этой даты.
|
|
4. Были — забираем только новые, добавляем в кэш, рисуем.
|
|
5. Не были — ничего не делаем.
|
|
|
|
- **R35.** **Это работает: в библиотеке `client` всё нужное есть.** Функция
|
|
`Conversation.getMessages(after, offset, limit)` отдаёт сообщения **строго новее**
|
|
указанной даты (проверено: в отборе `created_at > ?`). Пустой ответ = после
|
|
нашего сообщения ничего не появилось. Есть и версия потоком, страницами по 100
|
|
(`PAGE_SIZE = 100`). Отдельно создавать диалог для проверки не нужно — только
|
|
если открываем новый.
|
|
- **R36.** **Дубли отсекаются по `id` сообщения.** Идентификатор один и тот же
|
|
и в живом потоке, и в истории (`Message.id`). То есть при догрузке не нужно
|
|
угадывать, что уже нарисовано — сверяем по идентификатору.
|
|
- **R37.** **Самого кэша в библиотеке нет** — это пишем мы. Библиотека даёт только
|
|
«спроси, что новее».
|
|
|
|
**Три места, где схема может порваться (найдено в коде, не предположения):**
|
|
|
|
- **R38.** **Одинаковые даты.** Отбор строго «новее» (`created_at > ?`), поэтому
|
|
если два сообщения получили **ровно одну и ту же дату** — второе в ответ не
|
|
попадёт. Порядок внутри одной даты сервер задаёт сам (сортировка по времени,
|
|
затем по `id`). Реальный риск невелик (дата с точностью до миллисекунд), но
|
|
при сверке по `id` (R36) он не страшен вовсе.
|
|
- **R39.** **Прерванный ответ в историю не попадает.** Если ответ прервали кнопкой
|
|
«Стоп», кусок ответа остаётся только в живом потоке, в историю он не пишется
|
|
(`Event.Interrupted`). В кэше окажется то, чего на сервере нет.
|
|
- **R40.** **Живой поток тоже надо просить «с этого момента».** У подписки на события
|
|
есть тот же параметр «после»; без него после переподключения пропустим события.
|
|
|
|
- **R41.** **Решение:** кэш делаем. Схема — из R35. Где хранится (файл или лёгкая
|
|
база) — **не решено**, см. раздел 9.
|
|
|
|
### 8.3. Что не выносим
|
|
|
|
- **R42.** Экраны, пузырь сообщения, хранение списка агентов — **остаются в клиенте**.
|
|
Это и есть клиент, выносить их некуда.
|
|
- **R43.** Полезный признак, что что-то не вынесено: **в экране накопилась логика
|
|
длиннее пары десятков строк.** Значит, это место жить в экране не должно.
|
|
|
|
## 9. Решения, которые ещё не приняты
|
|
|
|
- Запись: по нажатию или на удержание.
|
|
- Нужны ли вложения (картинки) в этом клиенте.
|
|
- Как именно различать агентов (см. R21–R22): цвета или картинки достаточно?
|
|
- Нужен ли картинке размер/форма — показывать кружком без изменений или обрезать.
|
|
- Где хранится список агентов — в файле на диске или спрашивать сервер.
|
|
- Как выглядит показ хода работы агента в свёрнутом виде.
|
|
- **Где хранится кэш сообщений** — обычный файл на диске или лёгкая база (R41).
|
|
- Сколько держать в кэше и когда чистить (старые диалоги).
|
|
- Что делать с куском прерванного ответа в кэше (R39): хранить или выбрасывать.
|
|
|
|
## 10. Как проверяем
|
|
|
|
Проверка — **живой прогон**, а не «собралось». Правило пользователя: перед показом
|
|
гонять сценарий руками; на экран смотреть, а не верить описанию.
|