STORAGE.md: где хранятся настройки, группы, кэш истории и список диалогов; дырки в API
This commit is contained in:
@@ -15,6 +15,10 @@ approved/001-sidebar-utility.html # ОСНОВА: привычный
|
|||||||
approved/004-settings-agents.html # настройки агентов + проверка связи
|
approved/004-settings-agents.html # настройки агентов + проверка связи
|
||||||
approved/005-new-chat-picker.html # модалка «Новый диалог»
|
approved/005-new-chat-picker.html # модалка «Новый диалог»
|
||||||
approved/006-groups-manage/ # группы: 4 файла по состояниям
|
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 # что одобрено и когда
|
approved/README.md # что одобрено и когда
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -26,17 +30,13 @@ sketches/003-three-pane-command/index.html # три панели + состо
|
|||||||
sketches/004-settings-agents/index.html # черновик настроек агентов
|
sketches/004-settings-agents/index.html # черновик настроек агентов
|
||||||
sketches/005-new-chat-picker/index.html # черновик модалки нового диалога
|
sketches/005-new-chat-picker/index.html # черновик модалки нового диалога
|
||||||
```
|
```
|
||||||
```
|
|
||||||
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 # удаление: диалоги остаются
|
|
||||||
```
|
|
||||||
|
|
||||||
`BORROW-FROM-ASSISTENT.md` — что берём из `ai/assistent` (приёмы, плагины, версии),
|
`BORROW-FROM-ASSISTENT.md` — что берём из `ai/assistent` (приёмы, плагины, версии),
|
||||||
а что не берём (архитектура, экраны, дизайн — своё).
|
а что не берём (архитектура, экраны, дизайн — своё).
|
||||||
`MIC-ASR-SEARCH.md` — библиотеки для микрофона и распознавания: своё готовое
|
`MIC-ASR-SEARCH.md` — библиотеки для микрофона и распознавания: своё готовое
|
||||||
(`mic-kmp`, `asr-kmp`, `vad-kmp`), вместо копирования кода из assistent.
|
(`mic-kmp`, `asr-kmp`, `vad-kmp`), вместо копирования кода из assistent.
|
||||||
|
`STORAGE.md` — где что хранится: настройки, группы, кэш истории, список
|
||||||
|
диалогов; почему файлы, а не база; что менять в API (превью и число непрочитанных).
|
||||||
`CACHE.md` — кэш сообщений на клиенте: что для него есть в библиотеке, схема
|
`CACHE.md` — кэш сообщений на клиенте: что для него есть в библиотеке, схема
|
||||||
загрузки «только новое», где может порваться.
|
загрузки «только новое», где может порваться.
|
||||||
`THEMES.md` — цветовые темы: цвета не пишем в коде, берём из схемы. Тем будет
|
`THEMES.md` — цветовые темы: цвета не пишем в коде, берём из схемы. Тем будет
|
||||||
|
|||||||
+250
@@ -0,0 +1,250 @@
|
|||||||
|
# Где что хранится на диске
|
||||||
|
|
||||||
|
**Вопрос:** где живут настройки, кэш истории и список диалогов.
|
||||||
|
|
||||||
|
**Ответ коротко:** всё лежит в одном каталоге клиента. Никакой базы не нужно —
|
||||||
|
история и есть поток строк, а список диалогов вообще кэшировать не надо так,
|
||||||
|
как историю. Ниже — почему, и что для этого уже есть в библиотеке.
|
||||||
|
|
||||||
|
Всё сверено с исходниками `agentik`, не по памяти.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 штук и подгружает следующие страницы по мере надобности.
|
||||||
|
Отдельно ничего придумывать не надо.
|
||||||
|
|
||||||
|
### Но пустое окно при запуске — реальная беда
|
||||||
|
|
||||||
|
Проблема не в размере списка, а в том, что **пока он едет, окно пустое.**
|
||||||
|
Поэтому в карте выше и лежит `dialogs.json` — **снимок** списка с прошлого раза.
|
||||||
|
|
||||||
|
Схема такая:
|
||||||
|
|
||||||
|
1. Запустились — рисуем список из снимка. Мгновенно, окно не пустое.
|
||||||
|
2. Одновременно спрашиваем сервер свежий список.
|
||||||
|
3. Пришёл — заменяем нарисованное целиком.
|
||||||
|
|
||||||
|
Это **не кэш**, а занавеска, чтобы не смотреть в пустоту. Никакой сверки
|
||||||
|
прироста, никаких сложных правил: пришёл свежий список — взяли его целиком.
|
||||||
|
Снимок всегда считается устаревшим.
|
||||||
|
|
||||||
|
Если снимка нет (первый запуск) — показываем «загружаю».
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Что в API не хватает
|
||||||
|
|
||||||
|
Проверено по коду библиотеки. Вот что есть и чего нет для того, что
|
||||||
|
нарисовано в макетах.
|
||||||
|
|
||||||
|
| Что нужно в списке | Есть в библиотеке? |
|
||||||
|
|---|---|
|
||||||
|
| Название диалога | **Есть** — `title` |
|
||||||
|
| Время последнего изменения | **Есть** — `updatedAt`, список уже отсортирован по свежести |
|
||||||
|
| Понять, что диалог удалён или переименован | **Есть** — события `Deleted`, `Renamed` |
|
||||||
|
| Постраничная выдача | **Есть** — по 100 штук |
|
||||||
|
| **Последнее сообщение строкой** | **Нет!** |
|
||||||
|
| **Сколько непрочитанных** | **Нет!** |
|
||||||
|
| **Событие «в диалоге что-то произошло»** | **Нет!** |
|
||||||
|
| **Группа диалога** | **Нет!** |
|
||||||
|
|
||||||
|
Три дырки разберём отдельно — они разные по последствиям.
|
||||||
|
|
||||||
|
### 6.1. Превью последнего сообщения — нет
|
||||||
|
|
||||||
|
В макете в каждой строке под именем стоит текст: «Собрал отчёт, жду правок».
|
||||||
|
**Взять его неоткуда.** В списке диалогов только имя и дата.
|
||||||
|
|
||||||
|
Варианты:
|
||||||
|
|
||||||
|
- **Добавить в библиотеку.** В том же хранилище лежат сообщения — превью
|
||||||
|
берётся одним запросом рядом со списком. Это правильный путь: один запрос
|
||||||
|
за списком, как и было.
|
||||||
|
- **Спрашивать по диалогу.** Сто диалогов — сто запросов. Не годится.
|
||||||
|
- **Убрать из макета.** Строки станут суше, зато ничего не меняем.
|
||||||
|
|
||||||
|
### 6.2. Счётчик непрочитанных — нет
|
||||||
|
|
||||||
|
В макете справа в строке стоит «2». **Числа взять неоткуда.**
|
||||||
|
|
||||||
|
Тут два уровня, и они разной цены:
|
||||||
|
|
||||||
|
- **Точка «есть новое»** — сервер менять **не нужно**. У нас уже есть
|
||||||
|
`updatedAt` (когда диалог последний раз менялся) и свой `read.json`
|
||||||
|
(докуда дочитали). Есть новое = `updatedAt` новее нашей отметки.
|
||||||
|
- **Число непрочитанных** — **нужен сервер.** Чтобы получить «2», надо знать,
|
||||||
|
сколько сообщений в диалоге всего: тогда число = всего минус прочитанное.
|
||||||
|
Одно поле в ответе списка, считается по той же базе.
|
||||||
|
|
||||||
|
### 6.3. «В диалоге что-то произошло» — события нет
|
||||||
|
|
||||||
|
В потоке агента есть три события: **создан, удалён, переименован.**
|
||||||
|
События «пришло новое сообщение» **нет** — хотя сервер в этот момент как раз
|
||||||
|
обновляет `updatedAt` диалога (это видно по коду: `touch(id, now)`).
|
||||||
|
|
||||||
|
**Что это значит на практике:** агент работает в другом диалоге, а мы в этот
|
||||||
|
момент смотрим список — **список сам не обновится.** Ни порядок не поедет,
|
||||||
|
ни точка «есть новое» не загорится, пока мы не спросим сервер заново.
|
||||||
|
|
||||||
|
Пути:
|
||||||
|
|
||||||
|
- **Обновлять список самому, раз в несколько секунд.** Без изменения API.
|
||||||
|
Список лёгкий (см. §5) — это честно и дёшево. Плюс обновлять при
|
||||||
|
возвращении окна в фокус.
|
||||||
|
- **Добавить событие.** Тогда список живёт сам, без опроса. Но опрос всё
|
||||||
|
равно остаётся как страховка от обрыва связи.
|
||||||
|
|
||||||
|
Первый путь достаточен. Второй — приятная добавка на потом.
|
||||||
|
|
||||||
|
### 6.4. Итог: что менять в API
|
||||||
|
|
||||||
|
**Минимум, который закрывает макеты:**
|
||||||
|
|
||||||
|
1. **Превью последнего сообщения** в ответе списка диалогов.
|
||||||
|
2. **Число непрочитанных** в ответе списка диалогов.
|
||||||
|
|
||||||
|
Обе правки — в том же хранилище, рядом с тем, что уже читается для списка.
|
||||||
|
Ни новых таблиц, ни новых запросов со стороны клиента: как был один запрос
|
||||||
|
за списком, так и остался.
|
||||||
|
|
||||||
|
**Не обязательно:** событие «в диалоге что-то произошло» — вместо него опрос
|
||||||
|
раз в несколько секунд. Можно отложить.
|
||||||
|
|
||||||
|
**Нужно решить отдельно:** группы (§3). Это не правка ответа, это новое
|
||||||
|
понятие в библиотеке.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Открытые вопросы
|
||||||
|
|
||||||
|
1. **Группы — на устройстве или на сервере?** От этого зависит, поедут ли они
|
||||||
|
между десктопом и телефоном (§3).
|
||||||
|
2. **Меняем API: превью и число непрочитанных?** Или убираем их из макета
|
||||||
|
и живём на том, что есть (§6.4).
|
||||||
|
3. **Что засчитывать сообщением** при подсчёте непрочитанных: только ответы
|
||||||
|
агента или ещё вызовы инструментов, которые в истории тоже лежат
|
||||||
|
отдельными записями.
|
||||||
|
4. **Бейдж — число или точка?** Точка не требует менять сервер вообще (§6.2).
|
||||||
Reference in New Issue
Block a user