Repository navigation
Releases: Sonicspeedfly/TaleEngine
Release list
TaleEngine v2.5.0 Beta
Память длинных чатов стала мастер-снимком строгой схемы: он собирается пакетами,
проверяется после каждого ответа модели и пересобирается вручную с прогрессом.
Заодно исправлены две тихие потери: снимок обрезался до 6000 символов, а
выбранный вручную бюджет хода в 1 млн сбрасывался при каждой загрузке.
Добавлено — иерархическая пакетная память
Хроника длинного чата была свободным конспектом: не больше шести кусков за ход и
только после ответа ассистента, без пауз, повторов и прогресса. Большой бэклог
(импорт на тысячу сообщений) догонялся за несколько ходов, пересобрать память
вручную было нельзя, а при повторных сжатиях конспект мог молча выронить пункт
списка или персонажа второго плана.
- Мастер-снимок строгой схемы из четырёх разделов: «Хроника и событийный
каркас», «Активные персонажи и их статусы», «Фактологический реестр и лор»,
«Списки и медиа-анкоры». Это Markdown: его читают во вкладке «Память» и в
экспорте, в контекст он идёт как есть. - Свёртка по пакетам:
снимок_N = слияние(снимок_{N−1}, пакет_N), пакеты по
20 сообщений (не больше 80 000 символов). Один движок и один промпт — и для
прохода после хода, и для полной пересборки. Новые модули:
backend/hierarchical_memory.py(чистое ядро, только stdlib) и
backend/memory_service.py(адаптер к БД). - Проверка каждого ответа: нет раздела, пустой ответ или снимок «сдулся»
больше чем вдвое — модель получает корректирующий ход (до двух). Брак после
них в память не пишется, указатель «учтено до» не двигается. - Обрыв лимитом вывода (обёртка
<master_state>не закрыта или шлюз сообщил
finish_reason=lengthу непустого ответа) — не брак, а сразу сжатие хроники с
арками и один повтор слияния: корректирующий ход с тем же лимитом оборвался бы
снова. Если реестр и списки сами больше лимита вывода, модель не зовётся —
понятная ошибка вместо платных попыток. - Страж записей: персонаж, запись реестра или элемент списка, которые модель
выронила, возвращаются из предыдущего снимка дословно. Обновлённую на месте или
слегка переименованную запись страж узнаёт (регистр, ё/е и пунктуация не
важны) и не дублирует; записи с одинаковым началом («Linkin Park — «Numb»» и
«Linkin Park — «In the End»») друг друга не прикрывают. - Бюджет снимка 12 000 токенов: сверх него старые записи хроники сворачиваются
в арки[#a–#b] Арка «…»; последние 12 записей хроники и разделы персонажей,
реестра и списков не трогаются — из ответа сжатия берётся только хроника, а
ответ без свежих записей хроники отвергается. - Пауза и повторы: 1,5 с между любыми запросами памяти (слияние, факты,
сжатие); при 429, таймауте, 5xx и обрыве связи — до 4 повторов через 2/4/8/16 с
(не меньшеRetry-After). Отказ по ключу, неверный запрос и фильтр провайдера —
ошибка сразу, без платных повторов. «Остановить» и «Сбросить память» обрывают
паузы сразу. Задания памяти идут по одному на процесс, остальные ждут в очереди. - Сбой обновления виден и не повторяется на каждом ходу: ошибка ежеходного
прохода записывается и показывается во вкладке «Память» («Память не
обновилась: … Следующая попытка — после ЧЧ:ММ или кнопкой «Догнать»»), а
следующий проход ждёт 10 мин, 20, 40… до 6 ч. - Лимит вывода модели памяти:
max_tokensпамяти и рабочий бюджет снимка
прижимаются к тому, что модель успевает написать (по карте LiteLLM). У модели
по умолчаниюgpt-4o(вывод до 16 384) запрос с 17 824 получал бы 400 на
каждом проходе. Модель неизвестна, а провайдер отвергmax_tokens— один
повтор сDEFAULT_MAX_TOKENSи предупреждение. - Вкладка «Память» → «Мастер-память этого чата»: размер снимка, до какого
сообщения он учтён и сколько сообщений ждут сжатия; предупреждения памяти и
ошибка последнего обновления; «Пересобрать с нуля» (подтверждение с оценкой
числа платных запросов), «Догнать» (с подтверждением, если пакетов больше
трёх), «Экспорт .md», «Сбросить память»; прогресс[Обработано 140/800 сообщений | Сжато до 4 200 токенов]с номером пакета и кнопкой «Остановить»;
поля «Размер пакета», «Пауза между запросами», «Бюджет снимка» (до 40 000 — с
предупреждением, если модель памяти столько не напишет). Итог задания честный:
«Сжимать нечего — весь чат в окне» вместо «0 сообщений → 0 токенов». - Пересборка идёт в буфер, а старый снимок работает до конца — подмена одним
коммитом. Прерванную перезапуском сервера пересборку продолжает кнопка
«Продолжить» или обычные проходы после ходов; остановленную кнопкой — только
«Продолжить», а проходы тем временем обновляют живой снимок. - Монитор токенов: из чего складывается ход — системный промпт и якоря,
мастер-снимок и факты, дословное окно; доля от бюджета хода и от лимита модели
(MODEL_CONTEXT_LIMIT, 1 000 000). Та же разбивка — в инспекторе хода. Для
групповых чатов не считается (tiers: null): ход группы собирается иначе. - Снимок входит в бюджет хода: снимок, база знаний, аватары и статичный хвост
резервируются до обрезки истории. Раньше они шли сверх бюджета — при бюджете
30 000 в модель уходило до 41 тыс. токенов. - Факты пакета, чей запрос фактов не удался (сбой, остановка), доизвлекает
следующий пакет. - В пакетах — настоящие имена авторов (персона, персонаж, говорящий в группе) и
время в часовом поясе чата; base64, data-URI, HTML-оформление и<think>
вырезаются, вложения подписываются типом и именем файла. - Сводки 2.4.x (свободный конспект) переводятся в новую схему ближайшим проходом
без отдельных затрат; чистая конвертация — «Пересобрать с нуля». - API:
GET/DELETE /api/sessions/{id}/memory,POST …/memory/rebuild,
POST …/memory/cancel,GET …/memory/export,report.tiers(у групп —
tiers_unavailable: "group") вGET /api/sessions/{id}/context; в статусе —
snapshot.last_error,snapshot.retry_after,settings.max_snapshot_tokens,
job.warnings,job.snapshot_tokens,staging.paused(см.
docs/API.md; подробно — docs/HORAE.md).
Исправлено — бюджет 1M сбрасывался на 200k при каждой загрузке
В 1.12.0 сохранённое окно контекста в 1 млн токенов обещали опускать до 200 тыс.
один раз, но проверка шла при каждой загрузке интерфейса: выбранный вручную
1 000 000 в «Окне контекста» тихо возвращался к 200 000 при следующем открытии
страницы. Теперь миграция выполняется один раз (флаг ctx_budget_v: 2 в
настройках ui), и выбранный после неё 1 млн остаётся.
Исправлено — снимок памяти обрезался до 6000 символов
_adopt_summary сохранял авто-сводку слайсом [:6000], так же резался и буфер
пересборки. Хвостовые разделы — персонажи, локации, незакрытые линии — пропадали
молча, без ошибки и предупреждения, и модель «забывала» договорённости из начала
чата. Теперь снимок не режется: от мусора защищает проверка ответа (снимок
длиннее max(200 000, 8 × бюджет снимка) символов не принимается), от
раздувания — бюджет снимка и сжатие хроники в арки.
Изменено — активное окно по умолчанию 50
Дословно модель видела последние 20–23 сообщения — меньше одной связной сцены, а
всё старше видит только через снимок.
DEFAULT_WINDOW = 50на сервере и в интерфейсе: дословно идут 50–53 сообщения.- Сохранённое прежнее значение по умолчанию 20 один раз поднимается до 50 (флаг
memory_defaults_v: 2); выставленное после этого вручную 20 не трогается. - Кнопки окна во вкладке «Память»: 20 / 50 / 80 / 150 / «вся история» (было
20 / 40 / 80 / 150).
Обновление
- Изменился серверный код, поэтому после
git pullсервис нужно перезапустить.
В Docker код бэкенда и бота запечён в образ — нужна пересборка:
docker compose up -d --build(простойrestartоставит старый бэкенд со
свежим интерфейсом). Без Docker — перезапускstart.shили systemd-сервиса. - Новые переменные
.env— все необязательные, умолчания заданы в коде:
MEMORY_BATCH_SIZE=20,MEMORY_BATCH_CHARS=80000,MEMORY_DELAY_MS=1500,
MEMORY_SNAPSHOT_TOKENS=12000,MEMORY_MAX_RETRIES=4,MEMORY_TEMPERATURE=0.2,
MEMORY_MAX_BATCHES_PER_TURN=6,MODEL_CONTEXT_LIMIT=1000000(образец — в
.env.example). Новых зависимостей и ручных миграций БД нет. summary_model(«Подключение») должна выдавать длинный ответ: снимок
переписывается целиком, и потолок вывода —max(DEFAULT_MAX_TOKENS, round(бюджет снимка × 1,4) + 1024), то есть 17 824 токена при бюджете 12 000.
Если LiteLLM знает модель, вывод и бюджет снимка прижимаются к её лимиту сами
(вкладка «Память» предупредит); иначе, если модель столько не выдаёт, уменьшите
«Бюджет снимка». Если же в лимит вывода не влезают реестр и списки снимка
(ошибка «реестр и списки больше лимита вывода»), уменьшение бюджета не поможет:
нужна модель с большим выводом или ручная чистка этих разделов.- Постоянная стоимость выросла. Снимок до «Бюджета снимка» (12 000 токенов)
идёт в каждый ход — в хвост, вне кэшируемого префикса; до 2.5.0 сводка была не
длиннее 6000 символов (1,5–2,5 тыс. токенов). И раз вsummary_every
сообщений модель памяти переписывает снимок целиком — до 17 824 токенов вывода
на обновление, в 5–10 раз больше, чем в 2.4.x. Снизить: меньше «Бюджет
снимка», реже «Как часто обновлять сводку» (каждые 20 сообщений вместо 10),
дешёваяsummary_model. - Пересборка платная: «Пересобрать с нуля» стоит ⌈(сообщений − окно) / размер
пакета⌉ запросов слияния (плюс изредка сжатие хроники). Факты заново не
извлекаются там, где они уже есть: у чата с фактами пересборка запросов фактов
не делает, а без фактов (после «Сбросить память», у сводки до 2.4.0) — до
одного на пакет. Чат на 1 050 сообщений при окне 50 и пакете 20 — около 50
запросов кsummary_model. Оценку показывает подтверждение перед запуском.
Сама по себе после обновления пересборка не запускается: сводки 2.4.x
переходят на новую схему обычными проходами после ходов, а сводки до 2.4.0
...
TaleEngine v2.4.1 Beta
Добавлено — локальные копии библиотек без правки кода на сервере
Там, где jsdelivr медленный или недоступен, Vue, markdown-it, DOMPurify, KaTeX и
lamejs приходилось класть в frontend/vendor/ и менять адреса CDN на /vendor/
прямо в index.html и app.js на сервере. Из-за этих правок git pull
отказывался обновлять оба файла, и релиз 2.4.0 на такой сервер не вставал.
- Если в
frontend/vendor/лежит файл с тем же именем, что в адресе CDN
(vue.global.prod.js,katex.min.js, …), сервер сам отдаёт страницу с
/vendor/<имя>. Чего в папке нет — по-прежнему идёт с CDN; без папки всё как
раньше. - KaTeX и lamejs, которые
app.jsподгружает по надобности, берутся по тому же
списку: сервер кладёт его в<meta name="tale-vendor">. frontend/vendor/и ручные резервные копии*.bakв.gitignore: у каждого
сервера свой набор библиотек, в репозиторий он не входит.
Обновление с локальными правками: сбросьте их (git checkout -- frontend/app.js frontend/index.html), затем git pull и перезапуск. Папку frontend/vendor/
не трогайте — она теперь подхватывается сама.
TaleEngine v2.4.0 Beta
Четыре жалобы из одного разбора: на телефоне разваливалась строка действий под
репликой, а десяток вложений выталкивал «Отправить» за край экрана; закреплённые
чаты путались с обычными, а список чатов обновлялся только по F5; в длинных чатах
модель теряла начало истории. Последнее потребовало новой архитектуры памяти.
Исправлено — строка действий под репликой на телефоне
Пагинация свайпов, время, бейдж модели и до девяти кнопок переносились как
придётся: три-четыре неровные строки, кнопки наезжали друг на друга.
- Строка разделена на две. Сверху — метаданные одной строкой без переноса:
сжимается только имя модели (с многоточием), свайпы и время сохраняют размер. - Снизу — кнопки одной горизонтально прокручиваемой полосой (
nowrap,
overflow-x: auto,gap: 6px). На виду четыре главных — 📋 копировать,
✎ править, ↻ перегенерировать (только у последнего ответа), 🗑 удалить — и «⋯»;
ответ, переход к началу, продолжение, 🎨 и ответвление ⑂ убраны за «⋯». - Главные кнопки и «⋯» стоят первыми и не сдвигаются; уезжать за край может
только раскрытое вторичное. Когда полоса не помещается, у правого края
появляется затухание — видно, что её можно прокрутить. - Сокращённый режим включается на тач-устройствах и в окне уже 768px, причём по
живому медиазапросу: поворот экрана или сужение окна перестраивают строку без
перезагрузки.
Исправлено — вложения выталкивали поле ввода за экран
При 10+ картинках полоса вложений росла на всю высоту вьюпорта, и поле ввода с
кнопкой «Отправить» уезжали под системную панель навигации. Отправить сообщение
было физически невозможно.
- Полоса вложений ограничена 110px с прокруткой; на телефоне это одна
строка-карусель высотой до 75px. - Все плашки над полем ввода (режим ассистента, режиссёр, ошибка, повтор, ответ,
вложения, загрузки) собраны в контейнер.composer-barsс потолком 38% высоты
экрана (30% на телефоне, 22% на низком экране) и собственной прокруткой. - Композер прижат к низу (
position: sticky), поле ввода растёт не выше
min(200px, 30dvh), высота страницы считается вdvh. На Android клавиатура
теперь сжимает страницу, а не перекрывает «Отправить»
(interactive-widget=resizes-contentвviewport).
Исправлено — закреплённые чаты уходили вниз
Клиентская сортировка переставляла реактивный массив на месте, а «Недавние
диалоги» и палитра брали свои срезы в собственном порядке, где закреп не
учитывался.
- Одно правило на сервере и в клиенте (
_sort_by_activityиsortChats):
закреплённые всегда выше; внутри обеих групп — последняя активность, свежие
выше. Новая реплика поднимает чат лишь внутри его группы и никогда не
перепрыгивает закреп. - Изменено поведение: закреплённые между собой теперь тоже идут по
активности, а не по времени закрепа (как было с 1.19.0): закреплённый чат, в
котором только что писали, больше не стоит под закреплённым позже, но молчащим. - Активность (
activity_atв ответах/api/sessions,/api/groups,
/api/sessions/shared) — последняя реплика либо создание чата, что позже.
Прежде ключом был только id последней реплики, и новый пустой чат (у него
реплик нет) падал в самый низ — под брошенные полгода назад. Форк и импорт
тоже встают наверх. - Правило действует в сайдбаре, в палитре (при равной релевантности поиска) и в
«Недавних диалогах» — теперь это голова того же единого списка вместе с
группами и расшаренными чатами. Расшаренные идут по своей активности; закреп
их владельца на мой список не влияет. - Тесты закрепляют, что реплика в незакреплённом чате не поднимает его выше
закреплённого (и для групп тоже), что новый пустой чат встаёт над старой
перепиской и что при ничьей по секундам чат с репликой выше пустого.
Исправлено — список чатов обновлялся только по F5
Созданный, удалённый, переименованный или закреплённый чат появлялся в сайдбаре
только после ручной перезагрузки страницы.
- Любая мутация шлёт событие
taleengine:chatlist-updated
(notifyChatListChanged, дебаунс 120 мс), сайдбар перечитывает список без
перезагрузки страницы. Действия, результат которых нужен сразу (создание,
форк, удаление, переименование, закреп, группы, импорт), обновляют список
немедленно (syncChatList). - Чат поднимается в момент отправки: сигнал идёт по событию
job, которое сервер
шлёт сразу после сохранения реплики, а не только по окончании ответа. - Соседние вкладки синхронизируются через
BroadcastChannel("taleengine"). Чат,
удалённый в другой вкладке, закрывается здесь только после того, как сервер
подтвердил его отсутствие (403/404): сетевая ошибка чат не закрывает. - Реплики из Telegram сервер в веб не присылает, поэтому список догоняет их при
возврате во вкладку (не чаще раза в 10 с) и опросом раз в 60 с, пока вкладка
видна. - Без моргания и без потери фокуса: строки держат стабильные ключи, список не
очищается на время загрузки, неудачная загрузка оставляет старые строки
(прежде она стирала весь список), фокус в строке сайдбара восстанавливается. - Заметка, персона, фон и часовой пояс чата больше не откатываются при повторном
открытии: сохранённое значение сразу попадает в строку списка.
Добавлено — трёхслойная память Horae для длинных чатов
В длинном чате модель получала всю переписку, насколько хватало бюджета, и тонула
в ней (Context Dilution): факты и хронология из начала размывались сотнями
реплик, а не влезшее в бюджет отрезалось — вместе с обещанием, данным на сотом
сообщении. Теперь контекст собирается конвейером из трёх слоёв (новый модуль
backend/horae_recall.py, подробно — docs/HORAE.md).
- Активное окно. Последние 20 сообщений идут в модель дословно. Всё старше
выбрасывается, но только если уже учтено сводкой: пока сводка догоняет длинный
или импортированный чат, модель видит историю целиком. Граница окна двигается
шагом в 4 сообщения, чтобы не ломать кэш промпта провайдера на каждом ходу, —
дословно идут 20–23 сообщения. Размер окна — во вкладке «Память» (0 = вся
история). - Хроника. Фоновая сводка (раз в
summary_everyсообщений, по умолчанию 10)
ведёт инкрементальный конспект по разделам: состояние сюжета и цель, решения и
события, персонажи со статусами и инвентарём, локации, незакрытые линии. В
контекст он идёт блоком[ХРОНИКА И СОСТОЯНИЕ ЧАТА]у конца. Сводка берёт
только сообщения старше окна — свайпы, «Продолжить» и правки свежих ответов
попадают в память в окончательном виде — и каждое сообщение целиком (прежде
реплика обрезалась до 1500 символов). - Факты. В тот же проход модель выписывает атомарные факты — до 20 на
фрагмент, до 300 символов каждый, с именами вместо местоимений — в новую
таблицуhorae_facts. На каждом ходу по реплике пользователя (и хвосту
разговора) ищутся до 5 похожих: по смыслу (косинус ≥ 0,75), если задана модель
эмбеддингов, иначе по словам (основы слов, IDF по фактам чата, вес имён;
порог 0,2). Ранжирование учитывает свежесть: полураспад 300 сообщений с полом
0,5 — старые факты ослабляются, но не обнуляются. Найденное идёт блоком
[HORAE RECALLED MEMORY: …]; нет похожих — нет и блока, чтобы модель не
«дополняла» пустую память выдумкой. - Короткие реплики вроде «ок» или «иду дальше» факты не вызывают: слово, которое
есть в четверти фактов чата (и не меньше чем в трёх), само по себе совпадением
не считается. - Факты вспоминаются и в групповых чатах. Активного окна у групп пока нет —
история там по-прежнему режется только бюджетом. - Сбой памяти не роняет ход: без модели эмбеддингов и при её ошибке поиск идёт по
словам, а если память не собралась целиком — модель получает историю, как
раньше. - Сводки, собранные до 2.4.0, пересобираются. Прежний сводчик резал каждую
реплику до 1500 символов, а бэклог импортированного чата — до первых 24 000
символов, и всё равно считал учтённым всё до последнего сообщения. Такому
указателю окно не верит: у старой сводки оно не выбрасывает ничего, и сразу
после обновления модель видит историю как раньше. Новый сводчик в фоне
пересобирает память с нуля (и заодно выписывает факты из всей старой истории),
а старая сводка остаётся в контексте, пока новая её не догонит. Ручные правки
старой авто-сводки в новую не переносятся — если вы её правили, перенесите
свои заметки в отдельную запись памяти до обновления. - Сводка, начатая заново (удалили запись «Память чата (авто)»), не извлекает
факты повторно из уже разобранных сообщений. А если сообщения куска переписали
или удалили, пока модель его пересказывала, такой проход не записывается и
повторяется: иначе память считала бы учтённым текст, которого не видела. - Сообщение длиннее куска сводки (вставленный документ, гигантский ответ) идёт в
сводку началом и концом. Целиком оно превышало бы окно быстрой модели: запрос
падал, и каждый следующий ход повторял ту же платную попытку. - Новые настройки: в подключении —
summary_model(быстрая фоновая модель для
сводки и фактов; пусто = модель чата) иembedding_model(пусто = поиск по
словам); во вкладке «Память» — активное окно (memory_window) и выключатель
фактов (horae_facts). Расход эмбеддингов виден в статистике строкой
embedding. - Инспектор промпта показывает раздел «Память Horae: окно, хроника, факты»:
размер окна, сколько сообщений сжато в хронику и до какого она доведена, режим
поиска фактов и каждый отобранный факт с его сходством.
Изменено — лимиты файлов и окна памяти задаются своим числом
Сколько мегабайт прежних вложений уходит модели за ход, из скольких последних
сообщений и какое активное окно памяти — выбиралось только из трёх-четырёх
готовых вариантов. Кому 8 МБ мало, а 20 уже дорого, промежуточного значения
поставить не мог.
- Во вкладке «Генерация» «📎 Файлы в памяти диалога» (МБ на ход) и «…только из
последних сообщений», во вкладке «Память» — активное о...
TaleEngine v2.1.1 Beta
Исправлено — проверка в scripts/scrub_secrets.py была ложной
Скрипт очистки смотрел, продублировано ли значение, через os.environ. Но
приложение читает настройки через pydantic-settings из файла .env, а туда
переменные окружения процесса не попадают. В результате скрипт писал
«НЕ ЗАДАН — будет потеряно» даже тогда, когда всё было прописано правильно.
Предупреждение при этом было мягким: набрал «да» и продолжил. Один раз это
обошлось потерей кода доступа, пароля администратора и токена бота — их пришлось
доставать из резервной копии базы.
- Проверка читает сперва окружение процесса, затем файл
.env, как приложение. - При отсутствии значений скрипт теперь отказывается работать (код 3), а не
предупреждает. Подтверждение набирается на автомате, а потерянный код доступа
означает открытое наружу приложение. - Сознательно стереть без дубля можно флагом
--force.
TaleEngine v2.1.0 Beta
Подготовка к публичному доступу. Три вещи, которые в редизайн не входили, но
без которых открывать приложение посторонним нельзя.
Исправлено — панель отладки больше не показывает чужую переписку
Буфер отладки был ОДИН на всё приложение, а эндпоинты /api/debug/log вообще
не спрашивали, кто пришёл. В режиме аккаунтов туда попадали ходы всех
пользователей, и любой залогиненный видел в панели 🐞 сводку чужих сообщений.
- Каждая запись помечается владельцем, выдача фильтруется по нему.
- Право на общий просмотр есть только у администратора, и решается это в слое
доступа, а не в модуле лога. Параметрall=trueот обычного пользователя
игнорируется, а не выполняется. - Очистка тоже изолирована: свои записи удаляются, чужие остаются.
- Служебное поле владельца наружу не отдаётся.
- Владелец проставляется контекстной переменной, а не протаскиванием параметра
через шлюз LLM:llm_gatewayпро пользователя ничего не знает и знать не
должен, а фоновая задача генерации наследует пометку вместе с контекстом. - Заодно буфер увеличен со 100 до 400 записей: теперь он делится на всех.
Изменено — стандартная фильтрация по умолчанию
disable_safety был True: каждый новый пользователь получал снятые фильтры
провайдера, при этом сам интерфейс красил этот переключатель в цвет опасности.
Два сигнала противоречили друг другу.
- Новые профили получают стандартную фильтрацию. Снятие фильтров остаётся
доступным, но становится осознанным выбором, а не тем, что достаётся молча. - Уже сохранённые настройки существующих пользователей не меняются: их
параметры грузятся поверх умолчаний. Тихо менять поведение работающей
установки хуже, чем оставить как есть. - Одноразовая подсказка при первом старте и бейдж состояния во вкладке
«Генерация»: режим виден с одного взгляда, без чтения абзаца.
Изменено — секреты переезжают в переменные окружения
Пароль администратора, код доступа и токен Telegram-бота хранились в таблице
app_settings открытым текстом. Резервная копия базы, её экспорт или чужой
доступ к файлу означали компрометацию всего сразу.
- Новые переменные
ADMIN_PASSWORDиACCESS_CODE(TELEGRAM_BOT_TOKENуже
был). Заданное в окружении перекрывает базу, а не дополняет её: значения
уже сохранены, и «запасной вариант» проиграл бы базе — перенос вышел бы
бессмысленным. - Поля таких секретов в админке помечаются замком и блокируются, а запись их из
интерфейса игнорируется на сервере. Иначе сохранение «проходило» бы, а
работало всё равно значение из.env— худший вид молчаливого расхождения. scripts/scrub_secrets.pyвычищает секреты из базы после переноса. Ничего не
делает молча: показывает найденное, предупреждает о значениях без пары в
окружении, требует подтверждения и делает резервную копию. Значения на экран
не печатаются..gitignoreс.env,data/*.dbи*.sqlite3закоммичен.
TaleEngine v2.0.0 Beta
Полный редизайн интерфейса, четыре волны. Мажорная версия не из-за объёма, а
потому что изменилась модель навигации: чаты больше не живут внутри персонажа,
а выбор персонажа больше не открывает и не создаёт чат. Данные, API и формат
экспорта не менялись — обновление не требует миграций.
Исправлено — связь больше не «висит» до перезагрузки
Главный раздражитель: постоянный реконнект, который на телефоне и на компьютере
нельзя было вылечить ничем, кроме перезагрузки страницы. Причин оказалось три,
и все три починены.
- Соединение застревало навсегда. Намеренное закрытие сокета помечалось одним
общим флагом. ЕслиconnectWsвызывался на УЖЕ закрытом сокете,close()не
порождал события, флаг оставался поднятым — и следующий НАСТОЯЩИЙ обрыв
считался намеренным. Реконнект не планировался вообще никогда. Теперь у каждого
сокета своё поколение, и обработчики устаревшего соединения не могут погасить
живое. - Молчащий канал закрывали снаружи. Пауза в переписке — норма: люди читают
ответ по несколько минут. Промежуточные прокси, мобильные операторы и
энергосбережение телефона рвут такой сокет через 30–60 секунд тишины. Добавлен
прикладной пульс: клиент шлётpingраз в 25 секунд, сервер отвечаетpong.
Если на пульс не ответили, соединение считается мёртвым и переустанавливается,
не дожидаясь, пока это заметит браузер. - Возвращение к вкладке ничего не запускало. На телефоне таймеры в фоне
заморожены, поэтому запланированный реконнект мог не сработать вовсе. Теперь
соединение восстанавливается сразу поvisibilitychange,onlineиfocus,
без ожидания бэкоффа: если человек смотрит на приложение, оно обязано работать. - Потолок бэкоффа снижен с 15 до 8 секунд, добавлена обработка
onerror.
Добавлено — приборы (волна 4)
- Полоса заполнения окна контекста под полем ввода: сколько занято, сколько
обрезано, цвет по приближению к границе. Раньше о переполнении узнавали
постфактум, когда персонаж «забывал» события. - Инспектор хода — что РЕАЛЬНО уходит в модель: системный промпт с раскрытием
текста, вес каждого блока в токенах, сработавшие записи памяти Horae с
указанием вызвавшего слова, и явный блок «обрезано бюджетом». Разбор собирает
сама сборка контекста, поэтому числа те же, что уйдут провайдеру. - Скорость и задержка первого токена во время генерации. Экран перестал
выглядеть одинаково на второй секунде и на девяностой. - Стоимость хода — только если вы задали свой тариф: у каждого прокси он свой.
Добавлено — режимы ленты и сцена (волна 4)
- Переключатель Обычный / Сцена / Работа с документом.
- В режиме «Сцена» реплики лежат на собственной подложке: непрозрачность 0.88
вычислена по худшему случаю (белое фото), текст держит 12.16:1, имя говорящего
7.51:1. Раньше имя ложилось прямо на пользовательский фон и падало до ~1.55:1. - Именные реплики разрядкой, ремарки курсивом.
Добавлено — оболочка и поиск (волна 3)
- Единый список чатов: личные, групповые и общие в одной ленте, отсортированы
по активности, с фильтром по персонажу чипами. Разделы «Группы» и «Доступные
мне» слились с ним. - Поиск по репликам — новый
GET /api/searchс переходом в найденное
сообщение и догрузкой недостающей истории. - Командная палитра по
Ctrl+K/Cmd+K: чаты, персонажи, команды, поиск. - Ветка чата —
POST /api/sessions/{id}/fork: копия истории до выбранной
реплики, исходный чат не меняется. - Один режим композера вместо трёх независимых флагов. Надпись на кнопке
отправки и реальное действие теперь читают одно поле и не могут разойтись. - Шапка разгружена: 4 кнопки вместо 12, статус связи виден только при обрыве.
Добавлено — доступность (волна 2)
- Строки списков стали настоящими кнопками: раньше с клавиатуры можно было
удалить чат, но нельзя открыть. - 84 доступных имени кнопкам, включая четыре необратимых удаления, у которых не
было даже подсказки. - Ловушка фокуса и возврат фокуса во всех оверлеях,
role="dialog". - Поля выбора файла достижимы с клавиатуры (было
display: none, что выключает
элемент целиком). - Вежливый регион объявляет начало, завершение и ошибку генерации.
- Блок быстрого старта вместо бледной строки «выберите слева», которая на
телефоне указывала туда, где ничего нет.
Добавлено — фундамент (волна 1)
- Семантические токены: поверхности, состояния, текст, границы, акцент, статусы,
радиусы, отступы, шкала кегля 12–30 без микро-размеров. - Объявлен
color-scheme— системные<select>перестали выпадать светлыми
поверх тёмной панели. Светлая палитра подготовлена подdata-theme="light".
Исправлено — фундамент и данные
- Порядок чатов: сортировка шла по дате создания, а показывалось время
последней активности. Чат, где писали час назад, лежал под пустыми. - Стирание метаданных группы: открытие группы затирало её Author's Note и
часовой пояс. Сервер отбрасываетnull, но пустую СТРОКУ записывал. - Несуществующий класс
.btn: три кнопки загрузки файлов отрисовывались
серым текстом 12px с контрастом 2.05:1. - Контрасты: ховер главной кнопки ронял пару с 4.26 до 2.50 (теперь темнеет,
6.89), ползунок прокрутки был 1.12 к дорожке (стало 3.79), индикатор обрыва
связи 2.71 (стало 4.94+).
Мобильный адаптив
- Порог поднят с 760 до 768px — планшеты попадают в мобильную раскладку.
- Инспектор и настройки открываются нижней шторкой, а не сжимают чат сбоку.
- Канвас открывается на весь экран с подписанной кнопкой «← К чату».
- Цели нажатия 44×44 вместо 40×40.
- Строка действий под сообщением переносится: раньше она уезжала за левый край,
до которого прокруткой не добраться. - Телеметрия сворачивается в микро-бейдж, не отнимая высоту переписки.
TaleEngine v1.20.0 Beta
Добавлено — перемещение по чату
Раньше внутри чата двигаться было нечем: кнопки «вниз» не существовало вовсе, а
до начала длинного разговора можно было добраться только многократной прокруткой
вверх — история подгружается порциями по 40 сообщений, и каждая порция требовала
докрутить до самого верха ещё раз.
- Панель навигации справа над полем ввода. Появляется, только когда от низа
реально отъехали: при чтении свежих реплик она не нужна и не мозолит глаза.- ⤒ К началу чата — сначала дотягивает всю недостающую историю, потом
прыгает наверх. Без этого «начало» оказалось бы началом загруженного куска, а
не разговора. Проверено: с 40 загруженных сообщений дотянуло до 63 и встало в
самый верх, появился маркер «— начало чата —». - ⌃ / ⌄ Предыдущее и следующее сообщение — шаг ровно по границам реплик, а
не «примерно на экран». - ⤓ К последнему сообщению — попадает точно в низ.
- ⤒ К началу чата — сначала дотягивает всю недостающую историю, потом
- ⇞ К началу этого сообщения — кнопка появляется у длинных ответов (от 800
символов), где верх уехал за экран и вернуться к нему прокруткой на глаз
неудобно. - Подсветка после прыжка — короткая рамка вокруг сообщения, чтобы было видно,
куда именно попал. - Клавиши:
Home— в начало чата,End— в конец,Alt+↑/Alt+↓— по
сообщениям. В полях ввода не перехватываются: иначеHomeпрыгал бы по
чату вместо перемещения курсора в тексте.
Заметки
- Отступ панели от низа считается от реальной высоты поля ввода: на мобильном
композер выше, чем на десктопе (147px против 90), и ещё растёт при наборе
длинного текста. Любое фиксированное значение его перекрывало — проверено на
375px и 1280px, зазор 6px в обоих случаях. - Прыжок в начало защищён предохранителем от бесконечного цикла на очень
длинном чате.
TaleEngine v1.19.0 Beta
Исправлено
- Место в списке чатов на самом деле не освобождалось. В 1.18.0 редкие кнопки
спрятались черезopacity: 0— но прозрачность не убирает элемент из потока:
невидимые кнопки продолжали занимать ширину, и название чата обрезалось
многоточием даже короткое («Список покупок»). Теперь группа действий выводится
из потока и всплывает поверх строки справа только при наведении. Замер после
правки: «Разбор сцены со стороны» помещается целиком, обрезки нет ни в одной
строке.
Добавлено
-
Закрепление чатов и персонажей (кнопка 📌 в строке). Закреплённые всегда
сверху, между собой — последний закреплённый первым; у закреплённой строки
цветная полоска слева и значок. Порядок считает сервер, а не браузер: две
реализации сортировки неизбежно разъехались бы.Внутри хранится не флаг, а время закрепления (
pinned_at) — иначе
закреплённые нечем упорядочить между собой. Наружу API отдаёт простое
pinned: true/false. Колонка приезжает миграцией при старте. -
Строка чата стала информативной. Раньше было только «Новый чат #7» — по
такому вспомнить чат нечем, отсюда и «легко потеряться». Теперь в строке:
название, последняя реплика и когда она была (сегодня — часы, эта
неделя — день недели, раньше — дата). Время уступает место кнопкам при
наведении, поэтому строка не растёт. -
Поиск по чатам и персонажам (появляется, когда их больше пяти). По чатам
ищет и в названии, и в тексте последней реплики — проверено: запрос
«молоко» находит чат «Список покупок», хотя в названии этого слова нет.
Заметки
- Превью для всего списка берётся двумя запросами к БД, а не по одному на
чат. - На тач-устройствах кнопки в строках остаются видимыми и в потоке (правило
ограниченоhover: hoverиpointer: fine) — проверено на эмуляции: боковой
прокрутки нет, ничего не пропадает.
TaleEngine v1.18.0 Beta
Добавлено
-
Кнопка «Копировать» в блоках кода. У каждого блока появилась шапка: слева
язык (определяется из подсветки —python,javascript, иначе «код»), справа
кнопка. После нажатия она на полторы секунды превращается в «✓ Скопировано».Важная деталь реализации: приложение раздаётся по http, а браузерный
navigator.clipboardработает только в защищённом контексте (https или
localhost). На сервере он попросту недоступен, поэтому кнопка молча не
срабатывала бы именно в бою. Сделан запасной путь через скрытое поле и
execCommand— проверено с принудительно отключённым Clipboard API.
Изменено — интерфейс стал тише
Ощущение «нагруженного и непрактичного» интерфейса удалось измерить: на чате из
трёх сообщений на экране одновременно было 57 кнопок — 9 в шапке, 20 в
сайдбаре и по 5–12 под каждым сообщением. Все действия показывались всегда, и
экран читался как приборная панель, а не как разговор.
- Редкие действия проявляются при наведении. Экспорт, переименование и
удаление в строках списка, кнопки под сообщениями — теперь возникают, когда
курсор на элементе. У последнего сообщения кнопки приглушены, но видны: это
самая частая цель. Итог: видимых кнопок 30 вместо 57, в сайдбаре 6 вместо
20. Ничего не удалено — всё возвращается по наведению. - На тач-устройствах ничего не прячется. Правило ограничено
@media (hover: hover) and (pointer: fine): на телефоне наведения нет, и
спрятанные кнопки стали бы недоступны.
Изменено — живость
- Сообщение появляется коротким подъёмом (0.18 с), а не возникает рывком.
- Кнопка проседает при нажатии — тактильный отклик без звука и цвета.
- Плавные переходы цвета и фона при наведении вместо мгновенного переключения.
- Видимый фокус с клавиатуры — раньше его не было вовсе, навигация табом шла
вслепую. - Всё это отключается системной настройкой «уменьшить движение»
(prefers-reduced-motion): анимации мешают части людей, и это не мелочь.
TaleEngine v1.17.0 Beta
Исправлено
- Интерфейс уезжал за экран после тапа по любому полю (iPhone). Главная
причина «на телефоне не вмещается»: 18 полей ввода имели шрифт 12px, а iOS
Safari принудительно зумит страницу при фокусе на поле мельче 16px — и
обратно сам не отъезжает. Один тап по полю — и весь интерфейс за краем экрана.
На мобильных полям задан шрифт 16px. - Текст вылезал из пузыря сообщения. У
.bubbleне было переноса
неразрывных строк: длинная ссылка, путь к файлу, строка base64 или длинное
слово распирали пузырь и выходили за его границы. Добавленoverflow-wrap,
ссылки и inline-код рвутся принудительно. - Таблицы в ответах ломали вёрстку — теперь прокручиваются внутри пузыря, а
не сплющиваются в нечитаемые столбики по букве. - Панель настроек прокручивалась вбок на телефоне. Ползунки с
width:100%
плюс браузерные поля по 2px вылезали за подпись на 4px. Проверено: переполнений
в панели больше нет. - По кнопкам было трудно попасть пальцем — 22 элемента были высотой 24–32px
при рекомендованных 44px. На мобильных задан минимум 40px, ширина не тронута,
чтобы плотные ряды не разъехались. На десктопе размеры прежние. - Учтены вырезы и полоса жестов iPhone (
viewport-fit=cover+ safe-area):
панель ввода больше не заезжает под скруглённые углы.
Изменено — скорость загрузки
Сайт долго открывался на мобильном интернете. Найдены три причины:
- Ничего не кэшировалось. Стояло
Cache-Control: no-storeна весь интерфейс —
чтобы обновления доходили без хард-рефреша. Цель верная, средство слишком
грубое: КАЖДЫЙ заход заново тянул ~266 КБ. Теперь адрес файла версионируется
хэшем содержимого (/app.js?v=…), сам файл кэшируется навсегда, а index.html —
нет. Обновления по-прежнему доходят сразу, но повторный заход берёт всё из кэша. - Не было сжатия. Включён gzip: 266 КБ → 69 КБ, в 3.8 раза меньше.
- Скрипты блокировали отрисовку. Пять
<script>в<head>безdefer:
браузер останавливал разбор страницы на каждом и не рисовал ничего, пока все не
скачаются. Добавленdefer— качаются параллельно.
Плюс к этому:
- KaTeX и lamejs больше не грузятся заранее. Вдвоём они тяжелее всего
остального фронтенда, а нужны редко — только если в ответе есть формула или
нажата запись голоса. Теперь подгружаются по факту надобности; формула в уже
показанном сообщении проявляется сама, без перезагрузки. Кодировщик голоса
тянется параллельно с записью, поэтому задержки не появляется. - Библиотеки переведены на один CDN (было два — unpkg и jsdelivr): лишние
DNS + TLS на мобильном стоят дороже, чем кажется. Добавленpreconnect.