Skip to content

Releases: Sonicspeedfly/TaleEngine

TaleEngine v2.5.0 Beta

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 26 Sep 03:02
c171719

Память длинных чатов стала мастер-снимком строгой схемы: он собирается пакетами,
проверяется после каждого ответа модели и пересобирается вручную с прогрессом.
Заодно исправлены две тихие потери: снимок обрезался до 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
    ...
Read more

TaleEngine v2.4.1 Beta

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 26 Sep 03:02
6d8fa3b

Добавлено — локальные копии библиотек без правки кода на сервере

Там, где 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

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 26 Sep 03:02
4fcb761

Четыре жалобы из одного разбора: на телефоне разваливалась строка действий под
репликой, а десяток вложений выталкивал «Отправить» за край экрана; закреплённые
чаты путались с обычными, а список чатов обновлялся только по 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 уже дорого, промежуточного значения
поставить не мог.

  • Во вкладке «Генерация» «📎 Файлы в памяти диалога» (МБ на ход) и «…только из
    последних сообщений», во вкладке «Память» — активное о...
Read more

TaleEngine v2.1.1 Beta

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 26 Sep 03:02
9cc6717

Исправлено — проверка в scripts/scrub_secrets.py была ложной

Скрипт очистки смотрел, продублировано ли значение, через os.environ. Но
приложение читает настройки через pydantic-settings из файла .env, а туда
переменные окружения процесса не попадают. В результате скрипт писал
«НЕ ЗАДАН — будет потеряно» даже тогда, когда всё было прописано правильно.

Предупреждение при этом было мягким: набрал «да» и продолжил. Один раз это
обошлось потерей кода доступа, пароля администратора и токена бота — их пришлось
доставать из резервной копии базы.

  • Проверка читает сперва окружение процесса, затем файл .env, как приложение.
  • При отсутствии значений скрипт теперь отказывается работать (код 3), а не
    предупреждает. Подтверждение набирается на автомате, а потерянный код доступа
    означает открытое наружу приложение.
  • Сознательно стереть без дубля можно флагом --force.

TaleEngine v2.1.0 Beta

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 26 Sep 03:02
4a36836

Подготовка к публичному доступу. Три вещи, которые в редизайн не входили, но
без которых открывать приложение посторонним нельзя.

Исправлено — панель отладки больше не показывает чужую переписку

Буфер отладки был ОДИН на всё приложение, а эндпоинты /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

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 26 Sep 03:02
b489fe1

Полный редизайн интерфейса, четыре волны. Мажорная версия не из-за объёма, а
потому что изменилась модель навигации: чаты больше не живут внутри персонажа,
а выбор персонажа больше не открывает и не создаёт чат. Данные, 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

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 05 Aug 17:19

Добавлено — перемещение по чату

Раньше внутри чата двигаться было нечем: кнопки «вниз» не существовало вовсе, а
до начала длинного разговора можно было добраться только многократной прокруткой
вверх — история подгружается порциями по 40 сообщений, и каждая порция требовала
докрутить до самого верха ещё раз.

  • Панель навигации справа над полем ввода. Появляется, только когда от низа
    реально отъехали: при чтении свежих реплик она не нужна и не мозолит глаза.
    • ⤒ К началу чата — сначала дотягивает всю недостающую историю, потом
      прыгает наверх. Без этого «начало» оказалось бы началом загруженного куска, а
      не разговора. Проверено: с 40 загруженных сообщений дотянуло до 63 и встало в
      самый верх, появился маркер «— начало чата —».
    • ⌃ / ⌄ Предыдущее и следующее сообщение — шаг ровно по границам реплик, а
      не «примерно на экран».
    • ⤓ К последнему сообщению — попадает точно в низ.
  • ⇞ К началу этого сообщения — кнопка появляется у длинных ответов (от 800
    символов), где верх уехал за экран и вернуться к нему прокруткой на глаз
    неудобно.
  • Подсветка после прыжка — короткая рамка вокруг сообщения, чтобы было видно,
    куда именно попал.
  • Клавиши: Home — в начало чата, End — в конец, Alt+↑ / Alt+↓ — по
    сообщениям. В полях ввода не перехватываются: иначе Home прыгал бы по
    чату вместо перемещения курсора в тексте.

Заметки

  • Отступ панели от низа считается от реальной высоты поля ввода: на мобильном
    композер выше, чем на десктопе (147px против 90), и ещё растёт при наборе
    длинного текста. Любое фиксированное значение его перекрывало — проверено на
    375px и 1280px, зазор 6px в обоих случаях.
  • Прыжок в начало защищён предохранителем от бесконечного цикла на очень
    длинном чате.

TaleEngine v1.19.0 Beta

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 05 Aug 17:02

Исправлено

  • Место в списке чатов на самом деле не освобождалось. В 1.18.0 редкие кнопки
    спрятались через opacity: 0 — но прозрачность не убирает элемент из потока:
    невидимые кнопки продолжали занимать ширину, и название чата обрезалось
    многоточием даже короткое («Список покупок»). Теперь группа действий выводится
    из потока и всплывает поверх строки справа только при наведении. Замер после
    правки: «Разбор сцены со стороны» помещается целиком, обрезки нет ни в одной
    строке.

Добавлено

  • Закрепление чатов и персонажей (кнопка 📌 в строке). Закреплённые всегда
    сверху, между собой — последний закреплённый первым; у закреплённой строки
    цветная полоска слева и значок. Порядок считает сервер, а не браузер: две
    реализации сортировки неизбежно разъехались бы.

    Внутри хранится не флаг, а время закрепления (pinned_at) — иначе
    закреплённые нечем упорядочить между собой. Наружу API отдаёт простое
    pinned: true/false. Колонка приезжает миграцией при старте.

  • Строка чата стала информативной. Раньше было только «Новый чат #7» — по
    такому вспомнить чат нечем, отсюда и «легко потеряться». Теперь в строке:
    название, последняя реплика и когда она была (сегодня — часы, эта
    неделя — день недели, раньше — дата). Время уступает место кнопкам при
    наведении, поэтому строка не растёт.

  • Поиск по чатам и персонажам (появляется, когда их больше пяти). По чатам
    ищет и в названии, и в тексте последней реплики — проверено: запрос
    «молоко» находит чат «Список покупок», хотя в названии этого слова нет.

Заметки

  • Превью для всего списка берётся двумя запросами к БД, а не по одному на
    чат.
  • На тач-устройствах кнопки в строках остаются видимыми и в потоке (правило
    ограничено hover: hover и pointer: fine) — проверено на эмуляции: боковой
    прокрутки нет, ничего не пропадает.

TaleEngine v1.18.0 Beta

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 04 Aug 18:38

Добавлено

  • Кнопка «Копировать» в блоках кода. У каждого блока появилась шапка: слева
    язык (определяется из подсветки — 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

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 04 Aug 18:18

Исправлено

  • Интерфейс уезжал за экран после тапа по любому полю (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.