21 KiB
Требования к десктопному клиенту 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. Доступа по паролю/ключу у библиотеки нет — поля для него не делать.
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. Кэш сообщений на клиенте — решение принято
Задача: не тянуть всю историю диалога заново при каждом открытии окна.
Схема:
- Открыли диалог — смотрим свой кэш.
- Берём из кэша последнее сообщение и его дату.
- Спрашиваем библиотеку
client: есть ли сообщения новее этой даты. - Были — забираем только новые, добавляем в кэш, рисуем.
- Не были — ничего не делаем.
- 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. Как проверяем
Проверка — живой прогон, а не «собралось». Правило пользователя: перед показом гонять сценарий руками; на экран смотреть, а не верить описанию.