STORAGE.md переписан: хранилище — абстракция, реализация SQLite, настройки JSON; не файлы
This commit is contained in:
@@ -84,6 +84,12 @@ fun events(after: Instant): Flow<Event>
|
||||
**Самого кэша.** Хранилища на стороне клиента библиотека не даёт и не предлагает.
|
||||
Это пишем мы. Библиотека умеет только «спроси, что новее» и «подпишись с момента».
|
||||
|
||||
Заодно **нет и абстракции хранилища** — ни интерфейса «дай сообщения диалога»,
|
||||
ни готовой реализации под клиента. И то и другое наше, см. `STORAGE.md`.
|
||||
|
||||
Полезное: в самом `agentik` хранилище построено на **SQLDelight 2.3.2** — это
|
||||
SQLite, умеющий и JVM, и Android. Тот же подход берём и мы, а не выдумываем.
|
||||
|
||||
---
|
||||
|
||||
## 3. Где схема может порваться
|
||||
@@ -127,9 +133,20 @@ fun events(after: Instant): Flow<Event>
|
||||
|
||||
---
|
||||
|
||||
## 4. Что ещё нужно решить (не решено)
|
||||
## 4. Где хранить — РЕШЕНО
|
||||
|
||||
**Хранилище — абстракция, реализация — SQLite.** Подробно: `STORAGE.md`.
|
||||
|
||||
Клиент просит «дай сообщения диалога» и не знает, где они лежат. Реализацию
|
||||
можно будет заменить (это понадобится на Android) — экраны не тронутся.
|
||||
Ни одного упоминания SQLite вне реализации: если в экране встретилось
|
||||
`sqlite` / `SQL` / `query` — абстракция прохудилась.
|
||||
|
||||
База — один файл в каталоге клиента. Настройки — **отдельно, JSON**:
|
||||
их правит человек руками, в базу для этого лазить не должно быть нужно.
|
||||
|
||||
Осталось решить:
|
||||
|
||||
- **Где хранить кэш** — обычный файл на диске или лёгкая база.
|
||||
- **Сколько держать** и когда чистить старые диалоги.
|
||||
- **Что делать с прерванным ответом** в кэше (см. 3.2).
|
||||
- Связывать ли кэш с диалогом по `id` диалога — он стабилен, так что связка простая.
|
||||
@@ -169,7 +186,8 @@ fun events(after: Instant): Flow<Event>
|
||||
|
||||
## 6. Порядок действий (предложение)
|
||||
|
||||
1. Определиться, где хранится кэш (раздел 4).
|
||||
1. Описать абстракцию хранилища — интерфейсы (`STORAGE.md`, §1). Где лежит —
|
||||
решено: SQLite, абстракция сверху.
|
||||
2. Определиться с прерванным ответом (3.2) — иначе схема даст сбой на первом же «Стоп».
|
||||
3. При открытии диалога: кэш → спросить «новее» → догрузить → нарисовать.
|
||||
4. Подписка на живой поток — тоже «с момента».
|
||||
|
||||
@@ -35,8 +35,9 @@ sketches/005-new-chat-picker/index.html # черновик модалки
|
||||
а что не берём (архитектура, экраны, дизайн — своё).
|
||||
`MIC-ASR-SEARCH.md` — библиотеки для микрофона и распознавания: своё готовое
|
||||
(`mic-kmp`, `asr-kmp`, `vad-kmp`), вместо копирования кода из assistent.
|
||||
`STORAGE.md` — где что хранится: настройки, группы, кэш истории, список
|
||||
диалогов; почему файлы, а не база; что менять в API (превью и число непрочитанных).
|
||||
`STORAGE.md` — где что хранится: **хранилище делаем абстракцией, реализация —
|
||||
SQLite, настройки — JSON.** Три отдельные абстракции (сообщения / настройки /
|
||||
снимок списка), почему SQLite, что менять в API (превью и число непрочитанных).
|
||||
`CACHE.md` — кэш сообщений на клиенте: что для него есть в библиотеке, схема
|
||||
загрузки «только новое», где может порваться.
|
||||
`THEMES.md` — цветовые темы: цвета не пишем в коде, берём из схемы. Тем будет
|
||||
|
||||
+28
-4
@@ -267,8 +267,25 @@
|
||||
- **R40.** **Живой поток тоже надо просить «с этого момента».** У подписки на события
|
||||
есть тот же параметр «после»; без него после переподключения пропустим события.
|
||||
|
||||
- **R41.** **Решение:** кэш делаем. Схема — из R35. Где хранится (файл или лёгкая
|
||||
база) — **не решено**, см. раздел 9.
|
||||
- **R41.** **Решение:** кэш делаем. Схема — из R35.
|
||||
- **R41.1.** **Хранилище — абстракция.** Клиент просит «дай сообщения диалога»,
|
||||
«сохрани настройки» — и не знает, где это лежит. Реализацию можно заменить
|
||||
(понадобится на Android) — экраны не тронутся. Подробно: `STORAGE.md`.
|
||||
- **R41.2.** **Ни одного упоминания базы вне реализации.** Если в экране или
|
||||
в логике клиента встретилось `sqlite` / `SQL` / `query` — абстракция
|
||||
прохудилась. Тот же признак, что и с цветами числом в коде.
|
||||
- **R41.3.** **Реализация — SQLite**, тот же подход, что в `agentik`:
|
||||
**SQLDelight 2.3.2** (умеет и JVM, и Android). **Не файлы.**
|
||||
- **R41.4.** **Три отдельные абстракции, не одна на всё:** сообщения диалога;
|
||||
настройки; снимок списка диалогов. Разная жизнь — разными интерфейсами.
|
||||
- **R41.5.** **Настройки — JSON-файл, не в базе.** Их правит человек руками:
|
||||
сломался адрес агента — открыл, увидел, исправил. С базой для этого нужен
|
||||
инструмент. Там же тема и выбранная группа.
|
||||
- **R41.6.** **Секреты в JSON не пишем.** Пароль/ключ — в системное хранилище
|
||||
паролей, в настройках только ссылка.
|
||||
- **R41.7.** **Картинки — файлами, не в базе.** В сообщении картинка едет
|
||||
массивом байт; держать её в базе нельзя — база распухнет и станет тяжёлой
|
||||
для копирования. В базе — ссылка на файл.
|
||||
|
||||
### 8.3. Что не выносим
|
||||
|
||||
@@ -283,11 +300,18 @@
|
||||
- Нужны ли вложения (картинки) в этом клиенте.
|
||||
- Как именно различать агентов (см. R21–R22): цвета или картинки достаточно?
|
||||
- Нужен ли картинке размер/форма — показывать кружком без изменений или обрезать.
|
||||
- Где хранится список агентов — в файле на диске или спрашивать сервер.
|
||||
- Где хранится список агентов — в контроле настроек (JSON) или спрашивать сервер.
|
||||
- Как выглядит показ хода работы агента в свёрнутом виде.
|
||||
- **Где хранится кэш сообщений** — обычный файл на диске или лёгкая база (R41).
|
||||
- Сколько держать в кэше и когда чистить (старые диалоги).
|
||||
- Что делать с куском прерванного ответа в кэше (R39): хранить или выбрасывать.
|
||||
- **Группы — только на устройстве или на сервере?** Сервер про них не знает
|
||||
(`STORAGE.md`, §7). Если только у нас — раскладка не поедет между десктопом
|
||||
и телефоном.
|
||||
- **Меняем ли API: превью последнего сообщения и число непрочитанных.**
|
||||
Взять неоткуда, в макете они нарисованы (`STORAGE.md`, §6).
|
||||
- **Бейдж — число или точка.** Точка сервер не трогает вообще (`STORAGE.md`, §6.2).
|
||||
- **Где каталог клиента** — `~/.agentik/` или системный («Документы»).
|
||||
На Android понятие «домашний каталог» своё.
|
||||
|
||||
## 10. Как проверяем
|
||||
|
||||
|
||||
+218
-194
@@ -2,249 +2,273 @@
|
||||
|
||||
**Вопрос:** где живут настройки, кэш истории и список диалогов.
|
||||
|
||||
**Ответ коротко:** всё лежит в одном каталоге клиента. Никакой базы не нужно —
|
||||
история и есть поток строк, а список диалогов вообще кэшировать не надо так,
|
||||
как историю. Ниже — почему, и что для этого уже есть в библиотеке.
|
||||
**Ответ коротко:** хранилище — **абстракция**. Клиент просит «дай сообщения
|
||||
диалога», «сохрани настройки» — и не знает, где это лежит. Реализация —
|
||||
**SQLite**. Настройки — **JSON**, потому что их правит человек.
|
||||
|
||||
Всё сверено с исходниками `agentik`, не по памяти.
|
||||
Абстракция нужна не ради красоты: **потом будет Android**, и там то же самое
|
||||
хранилище надо будет собрать на другой основе. Если клиент всюду дёргает SQLite
|
||||
напрямую, на Android придётся переписывать экраны. Если дёргает абстракцию —
|
||||
меняется одна реализация, экраны не трогаются.
|
||||
|
||||
---
|
||||
|
||||
## 1. Карта: что где лежит
|
||||
## 1. Абстракция: два разных хранилища, не одно
|
||||
|
||||
```
|
||||
~/.agentik/ каталог клиента (на Windows — соответствующий системный)
|
||||
settings.json настройки: агенты, адреса, тема, выбранная группа
|
||||
groups.json группы и в какой группе какой диалог
|
||||
dialogs.json снимок списка диалогов — чтобы окно не было пустым
|
||||
read.json докуда дочитан каждый диалог (для счётчиков)
|
||||
history/
|
||||
<id диалога>.jsonl сообщения: одна строка — одно сообщение
|
||||
blobs/
|
||||
<id сообщения> картинки из сообщений, отдельными файлами
|
||||
```
|
||||
Тут важно не свалить всё в одну кучу. Это **два разных типа данных**, и ведут
|
||||
себя они по-разному:
|
||||
|
||||
Один каталог — простое правило: **удалил каталог, клиент чистый.** Ничего не
|
||||
прячется в других местах.
|
||||
|
||||
### Почему файлы, а не база
|
||||
|
||||
История диалога — **только дописывается**: сообщений не правят и не удаляют
|
||||
(в библиотеке так и написано: «Только `insert` и чтение. Никаких обновлений»).
|
||||
Для дописываемого потока база не нужна:
|
||||
|
||||
- **Строка на сообщение.** Читаем конец файла — знаем последнее сообщение.
|
||||
Дописываем в конец — вот и весь кэш.
|
||||
- **Понятно человеку.** Открыл файл — увидел сообщения. В случае беды можно
|
||||
посмотреть глазами и починить руками.
|
||||
- **Нет лишней зависимости.** База — это драйвер, версии, миграции схемы.
|
||||
|
||||
Плюс это прямо соответствует тому, ради чего мы вообще выбрали десктоп
|
||||
эталоном: код, который **можно прочитать и понять**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Настройки — один файл
|
||||
|
||||
Пара агентов, адреса, тема, выбранная группа. Это десяток полей —
|
||||
им не нужна база и не нужен отдельный файл на каждую настройку.
|
||||
|
||||
Важно: **настройки человек правит руками.** Если клиент однажды не запустится из-за
|
||||
кривого адреса, должно быть можно открыть `settings.json`, увидеть и исправить.
|
||||
Поэтому формат — обычный читаемый, а не сжатый.
|
||||
|
||||
Отдельно: **секреты не в этом файле.** Если у агента будет пароль/ключ,
|
||||
он кладётся в системное хранилище паролей, а в настройках остаётся только
|
||||
ссылка на него. Иначе ключ утечёт вместе с настройками, которые человек
|
||||
может кому-то переслать.
|
||||
|
||||
---
|
||||
|
||||
## 3. Группы — они существуют только у нас
|
||||
|
||||
**Это важное. Сервер про группы ничего не знает.**
|
||||
|
||||
В библиотеке диалог описывается шестью полями: `id`, `title`, `isTemporal`,
|
||||
`createdAt`, `updatedAt` и два признака про картинки. **Поля «группа» там нет.**
|
||||
Значит, группы — целиком наша выдумка на стороне клиента, и жить они будут
|
||||
в `groups.json`.
|
||||
|
||||
**Что из этого следует, и это надо решить:** телефон и десктоп **разойдутся**.
|
||||
Создал группу «Работа» на десктопе — на телефоне её нет. Отсюда два пути:
|
||||
|
||||
1. **Группы только на устройстве.** Просто, но раскладка не ездит между
|
||||
устройствами. Для одного человека за одним компьютером — нормально.
|
||||
2. **Группы на сервере.** Тогда нужно добавить в библиотеку хранение групп —
|
||||
это уже не «просто клиент», это расширение самого agentik.
|
||||
|
||||
Пока в макетах нарисован путь 1. Если хотим 2 — это отдельное решение,
|
||||
и его лучше принять **до** того, как начнём писать клиент.
|
||||
|
||||
---
|
||||
|
||||
## 4. Кэш истории — файл на диалог
|
||||
|
||||
Схема целиком описана в `CACHE.md`. Здесь — только где лежит.
|
||||
|
||||
**Файл на диалог, одна строка на сообщение.** Причина простая: диалоги друг
|
||||
с другом не связаны. Открыли один — читаем один файл, чужого не касаемся.
|
||||
Заодно один битый файл не портит остальные.
|
||||
|
||||
**Как читаем:** храним в памяти, на чём остановились (последняя прочитанная
|
||||
строка). При открытии диалога читаем конец файла — это последнее сообщение,
|
||||
с него и спрашиваем сервер «что новее». Весь файл при каждом открытии не
|
||||
перечитываем.
|
||||
|
||||
**Картинки — отдельно.** В сообщении картинка едет как массив байт. Держать
|
||||
её внутри строки истории нельзя: файл распухнет, и станет нечитаемым.
|
||||
Поэтому картинка кладётся в `blobs/<id сообщения>`, а в истории остаётся
|
||||
ссылка на неё.
|
||||
|
||||
### Три риска, честно
|
||||
|
||||
1. **Обрыв записи.** Клиент упал посреди записи строки — в конце файла
|
||||
остался обрубок. Тогда: обрубок отбрасываем, спрашиваем сервер заново
|
||||
по последней целой строке. Так как сервер — источник правды, потеря
|
||||
не страшна.
|
||||
2. **Очень длинный диалог.** Файл растёт. Пока спасает то, что читаем конец,
|
||||
а не весь файл. Если однажды станет тяжело — режем историю на части
|
||||
по месяцам, но это потом, не сейчас.
|
||||
3. **Файлов много.** Да, на каждый диалог свой файл. Но файлы мелкие,
|
||||
а система умеет держать миллионы файлов. Проблемой это станет на порядки
|
||||
позже, чем что-то другое.
|
||||
|
||||
---
|
||||
|
||||
## 5. Список диалогов — кэшировать не надо (в главном)
|
||||
|
||||
Вот тут отличие от истории, и оно важное.
|
||||
|
||||
| | История диалога | Список диалогов |
|
||||
| | Сообщения диалога | Настройки клиента |
|
||||
|---|---|---|
|
||||
| Сколько данных | много сообщений, каждое с текстом | сто строк, в каждой имя и дата |
|
||||
| Размер одной порции | сотни килобайт | несколько килобайт |
|
||||
| Растёт | на каждом ответе агента | медленно |
|
||||
| Сколько | тысячи, растёт постоянно | десяток полей |
|
||||
| Меняются | только дописываются | переписываются целиком |
|
||||
| Кто читает | приложение | приложение и **человек руками** |
|
||||
| Поиск/выборка | нужны (по диалогу, по дате) | не нужен |
|
||||
|
||||
**Вывод: список диалогов надо честно спрашивать у сервера.** Причины:
|
||||
Это **две разные абстракции**, и живут они раздельно:
|
||||
|
||||
- **Он лёгкий.** Сто диалогов — это несколько килобайт. Одна быстрая просьба,
|
||||
а не выкачивание истории.
|
||||
- **Он всегда свежий.** Список меняется от чужой работы агента (он может вести
|
||||
другой диалог, пока мы смотрим этот). Кэшировать его и синхронизировать
|
||||
приростом — это сложная механика ради нескольких килобайт.
|
||||
- **Много диалогов — уже решено.** В библиотеке есть постраничная выдача:
|
||||
берёт по 100 штук и подгружает следующие страницы по мере надобности.
|
||||
Отдельно ничего придумывать не надо.
|
||||
```kotlin
|
||||
/** Сообщения диалогов: дописываем и читаем. Где лежит — не дело клиента. */
|
||||
interface MessageRepository {
|
||||
suspend fun append(conversationId: String, message: CachedMessage)
|
||||
suspend fun read(conversationId: String, after: Instant, limit: Int): List<CachedMessage>
|
||||
suspend fun latest(conversationId: String): CachedMessage?
|
||||
suspend fun drop(conversationId: String)
|
||||
}
|
||||
|
||||
### Но пустое окно при запуске — реальная беда
|
||||
/** Настройки: прочитать целиком, записать целиком. */
|
||||
interface SettingsRepository {
|
||||
suspend fun load(): Settings
|
||||
suspend fun save(settings: Settings)
|
||||
}
|
||||
```
|
||||
|
||||
Проблема не в размере списка, а в том, что **пока он едет, окно пустое.**
|
||||
Поэтому в карте выше и лежит `dialogs.json` — **снимок** списка с прошлого раза.
|
||||
И **третья**, для списка диалогов:
|
||||
|
||||
Схема такая:
|
||||
```kotlin
|
||||
/** Снимок списка диалогов — занавеска, чтобы окно не было пустым при запуске. */
|
||||
interface ConversationListSnapshotRepository {
|
||||
suspend fun load(): List<ConversationSummary>
|
||||
suspend fun save(list: List<ConversationSummary>)
|
||||
}
|
||||
```
|
||||
|
||||
1. Запустились — рисуем список из снимка. Мгновенно, окно не пустое.
|
||||
2. Одновременно спрашиваем сервер свежий список.
|
||||
3. Пришёл — заменяем нарисованное целиком.
|
||||
Почему три, а не одна «на всё»:
|
||||
|
||||
Это **не кэш**, а занавеска, чтобы не смотреть в пустоту. Никакой сверки
|
||||
прироста, никаких сложных правил: пришёл свежий список — взяли его целиком.
|
||||
Снимок всегда считается устаревшим.
|
||||
- **Сообщения и настройки — разная жизнь.** Сообщения дописываются и читаются
|
||||
выборками, настройки переписываются целиком. Общий интерфейс заставит делать
|
||||
вид, что это одно и то же.
|
||||
- **Разные реализации — норма.** Сообщения — SQLite. Настройки — JSON-файл.
|
||||
Третье — тоже JSON. Под одним интерфейсом это выглядело бы как насилие.
|
||||
- **Меньше знает — легче менять.** Экрану нужны сообщения — он видит только
|
||||
сообщения. Как они лежат, его не касается.
|
||||
|
||||
Если снимка нет (первый запуск) — показываем «загружаю».
|
||||
### Правило, по которому это проверяется
|
||||
|
||||
**Ни одного упоминания SQLite вне реализации.** Если в экране или в логике
|
||||
клиента встретилось слово `sqlite`, `SQL`, `ResultSet`, `query` — абстракция
|
||||
прохудилась. Это — тот самый признак, как с цветами: цвета числом в коде
|
||||
не пишем, так и таблиц в экране не пишем.
|
||||
|
||||
---
|
||||
|
||||
## 2. Почему SQLite, а не файлы
|
||||
|
||||
**Решение: SQLite.**
|
||||
|
||||
- **То же самое будет на Android.** SQLite там родной. Одна реализация —
|
||||
два устройства. Ради этого всё и затевается.
|
||||
- **Не надо ничего придумывать.** Поиск, порядок, выборка «новее указанной
|
||||
даты», отсечение дублей по `id` — это обычные запросы. С файлами каждое
|
||||
такое место пришлось бы писать руками и потом отлаживать.
|
||||
- **Запись не рассыпается.** Клиент упал посреди записи — база откатит
|
||||
незавершённую сделку. С дописыванием строки в файл остаётся обрубок.
|
||||
- **В библиотеке уже так.** В `agentik` хранилище построено на **SQLDelight
|
||||
2.3.2** — это SQLite, умеющий и JVM, и Android. Не изобретаем: берём тот же
|
||||
подход. (`storage-sqlite` в самом `agentik` — ровно это.)
|
||||
|
||||
### Что для этого уже есть
|
||||
|
||||
- **SQLDelight 2.3.2** — в библиотеке `agentik` уже подключён, с драйвером
|
||||
и под JVM, и под Android.
|
||||
- **Готовый образец запроса «новее»** — в `agentik` есть хранилище сообщений
|
||||
на SQLite, где такой запрос уже написан. Повторяем приём, не выдумываем.
|
||||
|
||||
### Где лежит база
|
||||
|
||||
Один файл базы на клиента, в его каталоге:
|
||||
|
||||
```
|
||||
<каталог клиента>/
|
||||
client.db SQLite: сообщения, снимок списка диалогов, отметки
|
||||
«докуда дочитано», раскладка по группам
|
||||
settings.json настройки — их правит человек, поэтому JSON
|
||||
blobs/ картинки из сообщений отдельными файлами
|
||||
```
|
||||
|
||||
**Почему картинки не в базе:** в сообщении картинка едет как массив байт.
|
||||
Хранить её в базе можно, но база от этого распухает и копировать её становится
|
||||
тяжело. Поэтому картинка — файлом, а в базе только ссылка на файл.
|
||||
|
||||
---
|
||||
|
||||
## 3. Настройки — JSON
|
||||
|
||||
Единственное, что лежит не в базе. **Потому что их правит человек.**
|
||||
|
||||
Случилась беда, клиент не запускается из-за кривого адреса агента — открыл
|
||||
`settings.json`, увидел, исправил. С базой так не получится: там чтобы
|
||||
что-то поправить, нужен инструмент.
|
||||
|
||||
Там же — **тема** и **выбранная группа**: это тоже настройка, а не история
|
||||
переписки.
|
||||
|
||||
**Секреты в этот файл не пишем.** Если у агента будет пароль или ключ,
|
||||
он кладётся в системное хранилище паролей, а в настройках остаётся только
|
||||
ссылка. Иначе ключ утечёт вместе с файлом, который человек может кому-то
|
||||
переслать.
|
||||
|
||||
---
|
||||
|
||||
## 4. Сообщения диалога
|
||||
|
||||
Схема загрузки — в `CACHE.md`. Здесь только про хранение.
|
||||
|
||||
**Таблица сообщений.** Ключ — `id` сообщения, он стабилен: один и тот же
|
||||
и в живом потоке, и в истории с сервера. Поэтому дубли отсекаются простой
|
||||
проверкой, а не гаданием.
|
||||
|
||||
**Что кладём:** `id`, `id диалога`, дата, от кого, текст, признаки
|
||||
(прервано/не закончено), ссылка на картинку из `blobs/`, если она есть.
|
||||
|
||||
**Что НЕ кладём:** сами байты картинок (см. §2).
|
||||
|
||||
**Отметка «докуда дочитано»** — отдельная мелочь на диалог. Из неё выходит
|
||||
точка «есть новое»: сравнили дату последнего изменения диалога с отметкой —
|
||||
и видно, появилось ли что-то. Цифра непрочитанных так не получится (см. §6),
|
||||
только точка.
|
||||
|
||||
**Группы** — раскладка «какой диалог в какой группе». Сервер про группы ничего
|
||||
не знает (см. §7), значит и это хранится только у нас.
|
||||
|
||||
---
|
||||
|
||||
## 5. Снимок списка диалогов — занавеска
|
||||
|
||||
Отдельный случай, и его надо понять правильно.
|
||||
|
||||
**Список диалогов кэшировать не надо.** Он лёгкий — сто строк по имени и дате
|
||||
это несколько килобайт, а не история переписки. И он меняется от чужой работы:
|
||||
пока мы смотрим один диалог, агент уже поработал в другом. Ловить тут прирост
|
||||
сложнее, чем получить пользу.
|
||||
|
||||
**Но пустое окно при запуске — беда.** Пока список едет, смотреть не на что.
|
||||
Поэтому мы **держим прошлый снимок списка** и показываем его сразу:
|
||||
|
||||
1. Запустились — нарисовали список из снимка. Мгновенно, окно не пустое.
|
||||
2. Одновременно спросили у сервера свежий список.
|
||||
3. Пришёл — заменили нарисованное **целиком**.
|
||||
|
||||
Это **не кэш** и никакой сверки прироста не требует. Снимок всегда считается
|
||||
устаревшим, пришёл свежий — взяли его. Если снимка нет (первый запуск) —
|
||||
показываем «загружаю».
|
||||
|
||||
**Лежит в той же базе** — отдельной таблицей. Мелочь вроде бы, но и она идёт
|
||||
через абстракцию: экран просит «дай прошлый список», а не читает таблицу.
|
||||
|
||||
---
|
||||
|
||||
## 6. Что в API не хватает
|
||||
|
||||
Проверено по коду библиотеки. Вот что есть и чего нет для того, что
|
||||
нарисовано в макетах.
|
||||
Проверено по коду библиотеки. Что нужно для того, что нарисовано в макетах.
|
||||
|
||||
| Что нужно в списке | Есть в библиотеке? |
|
||||
| Что нужно в списке | Есть? |
|
||||
|---|---|
|
||||
| Название диалога | **Есть** — `title` |
|
||||
| Время последнего изменения | **Есть** — `updatedAt`, список уже отсортирован по свежести |
|
||||
| Понять, что диалог удалён или переименован | **Есть** — события `Deleted`, `Renamed` |
|
||||
| Постраничная выдача | **Есть** — по 100 штук |
|
||||
| **Последнее сообщение строкой** | **Нет!** |
|
||||
| **Сколько непрочитанных** | **Нет!** |
|
||||
| **Событие «в диалоге что-то произошло»** | **Нет!** |
|
||||
| **Группа диалога** | **Нет!** |
|
||||
|
||||
Три дырки разберём отдельно — они разные по последствиям.
|
||||
| **Последнее сообщение строкой** | **Нет** |
|
||||
| **Число непрочитанных** | **Нет** |
|
||||
| **Событие «в диалоге что-то произошло»** | **Нет** |
|
||||
| **Группа диалога** | **Нет** |
|
||||
|
||||
### 6.1. Превью последнего сообщения — нет
|
||||
|
||||
В макете в каждой строке под именем стоит текст: «Собрал отчёт, жду правок».
|
||||
**Взять его неоткуда.** В списке диалогов только имя и дата.
|
||||
В макете в строке списка под именем стоит текст «Собрал отчёт, жду правок».
|
||||
**Взять его неоткуда** — в списке диалогов только имя и дата.
|
||||
|
||||
Варианты:
|
||||
- **Добавить в библиотеку — правильный путь.** В том же хранилище лежат
|
||||
сообщения, превью берётся рядом со списком. Один запрос, как и был.
|
||||
- Спрашивать по диалогу — сто диалогов, сто запросов. Не годится.
|
||||
- Убрать из макета — строки станут суше, зато API не трогаем.
|
||||
|
||||
- **Добавить в библиотеку.** В том же хранилище лежат сообщения — превью
|
||||
берётся одним запросом рядом со списком. Это правильный путь: один запрос
|
||||
за списком, как и было.
|
||||
- **Спрашивать по диалогу.** Сто диалогов — сто запросов. Не годится.
|
||||
- **Убрать из макета.** Строки станут суше, зато ничего не меняем.
|
||||
### 6.2. Число непрочитанных — нет
|
||||
|
||||
### 6.2. Счётчик непрочитанных — нет
|
||||
Кружок «2» в макете ничем не наполняется. Два уровня, разной цены:
|
||||
|
||||
В макете справа в строке стоит «2». **Числа взять неоткуда.**
|
||||
|
||||
Тут два уровня, и они разной цены:
|
||||
|
||||
- **Точка «есть новое»** — сервер менять **не нужно**. У нас уже есть
|
||||
`updatedAt` (когда диалог последний раз менялся) и свой `read.json`
|
||||
(докуда дочитали). Есть новое = `updatedAt` новее нашей отметки.
|
||||
- **Точка «есть новое»** — сервер менять **не нужно**. Есть `updatedAt`
|
||||
и наша отметка «докуда дочитано». Есть новое = дата изменения новее отметки.
|
||||
- **Число непрочитанных** — **нужен сервер.** Чтобы получить «2», надо знать,
|
||||
сколько сообщений в диалоге всего: тогда число = всего минус прочитанное.
|
||||
Одно поле в ответе списка, считается по той же базе.
|
||||
сколько сообщений в диалоге всего. Одно поле в ответе списка.
|
||||
|
||||
### 6.3. «В диалоге что-то произошло» — события нет
|
||||
|
||||
В потоке агента есть три события: **создан, удалён, переименован.**
|
||||
События «пришло новое сообщение» **нет** — хотя сервер в этот момент как раз
|
||||
обновляет `updatedAt` диалога (это видно по коду: `touch(id, now)`).
|
||||
В потоке агента три события: **создан, удалён, переименован.** События «пришло
|
||||
новое сообщение» **нет** — хотя сервер в этот момент как раз обновляет дату
|
||||
диалога (в коде это видно: `touch(id, now)`).
|
||||
|
||||
**Что это значит на практике:** агент работает в другом диалоге, а мы в этот
|
||||
момент смотрим список — **список сам не обновится.** Ни порядок не поедет,
|
||||
ни точка «есть новое» не загорится, пока мы не спросим сервер заново.
|
||||
**Что это значит:** агент работает в другом диалоге, а мы смотрим список —
|
||||
**список сам не обновится.** Ни порядок не поедет, ни точка не загорится,
|
||||
пока не спросим сервер.
|
||||
|
||||
Пути:
|
||||
- **Обновлять список самому, раз в несколько секунд** — без изменения API.
|
||||
Список лёгкий, это честно и дёшево. Плюс обновлять при возвращении окна
|
||||
в фокус.
|
||||
- Добавить событие — список живёт сам, но опрос всё равно остаётся страховкой
|
||||
от обрыва связи.
|
||||
|
||||
- **Обновлять список самому, раз в несколько секунд.** Без изменения API.
|
||||
Список лёгкий (см. §5) — это честно и дёшево. Плюс обновлять при
|
||||
возвращении окна в фокус.
|
||||
- **Добавить событие.** Тогда список живёт сам, без опроса. Но опрос всё
|
||||
равно остаётся как страховка от обрыва связи.
|
||||
|
||||
Первый путь достаточен. Второй — приятная добавка на потом.
|
||||
Первого достаточно. Второе — приятная добавка на потом.
|
||||
|
||||
### 6.4. Итог: что менять в API
|
||||
|
||||
**Минимум, который закрывает макеты:**
|
||||
**Минимум, закрывающий макеты:**
|
||||
|
||||
1. **Превью последнего сообщения** в ответе списка диалогов.
|
||||
2. **Число непрочитанных** в ответе списка диалогов.
|
||||
|
||||
Обе правки — в том же хранилище, рядом с тем, что уже читается для списка.
|
||||
Ни новых таблиц, ни новых запросов со стороны клиента: как был один запрос
|
||||
за списком, так и остался.
|
||||
Обе — в том же хранилище, рядом с тем, что уже читается для списка. Новых
|
||||
запросов со стороны клиента не появляется.
|
||||
|
||||
**Не обязательно:** событие «в диалоге что-то произошло» — вместо него опрос
|
||||
раз в несколько секунд. Можно отложить.
|
||||
|
||||
**Нужно решить отдельно:** группы (§3). Это не правка ответа, это новое
|
||||
понятие в библиотеке.
|
||||
**Не обязательно:** событие «в диалоге что-то произошло» — вместо него опрос.
|
||||
**Решить отдельно:** группы (§7).
|
||||
|
||||
---
|
||||
|
||||
## 7. Открытые вопросы
|
||||
## 7. Группы — сервер про них не знает
|
||||
|
||||
1. **Группы — на устройстве или на сервере?** От этого зависит, поедут ли они
|
||||
между десктопом и телефоном (§3).
|
||||
2. **Меняем API: превью и число непрочитанных?** Или убираем их из макета
|
||||
и живём на том, что есть (§6.4).
|
||||
3. **Что засчитывать сообщением** при подсчёте непрочитанных: только ответы
|
||||
агента или ещё вызовы инструментов, которые в истории тоже лежат
|
||||
отдельными записями.
|
||||
4. **Бейдж — число или точка?** Точка не требует менять сервер вообще (§6.2).
|
||||
**Важное.** В описании диалога шесть полей: `id`, `title`, `isTemporal`,
|
||||
`createdAt`, `updatedAt` и два про картинки. **Поля «группа» нет.** Значит,
|
||||
группы — целиком наша выдумка, и живут они только у нас.
|
||||
|
||||
**Что из этого следует:** телефон и десктоп **разойдутся**. Создал «Работа»
|
||||
на компьютере — на телефоне её нет.
|
||||
|
||||
1. **Группы только на устройстве.** Просто. Раскладка не ездит между
|
||||
устройствами.
|
||||
2. **Группы на сервере.** Тогда это новое понятие в библиотеке `agentik`,
|
||||
а не правка клиента.
|
||||
|
||||
Это решение стоит принять **до** того, как начнём писать клиент.
|
||||
|
||||
---
|
||||
|
||||
## 8. Открытые вопросы
|
||||
|
||||
1. **Группы — на устройстве или на сервере?** (§7)
|
||||
2. **Меняем API: превью и число непрочитанных?** Или убираем их из макета (§6.4).
|
||||
3. **Что считать сообщением** при подсчёте: только ответы агента или ещё
|
||||
вызовы инструментов, которые в истории тоже лежат записями.
|
||||
4. **Бейдж — число или точка?** Точка сервер не трогает вообще (§6.2).
|
||||
5. **Где именно каталог клиента** — `~/.agentik/` или системный
|
||||
(«Документы пользователя»). На Android понятие «домашний каталог» своё.
|
||||
|
||||
Reference in New Issue
Block a user