Skip to content

TaleEngine v2.4.0 Beta

Choose a tag to compare

@Sonicspeedfly Sonicspeedfly released this 26 Sep 03:02
· 73 commits to main since this release
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 уже дорого, промежуточного значения
поставить не мог.

  • Во вкладке «Генерация» «📎 Файлы в памяти диалога» (МБ на ход) и «…только из
    последних сообщений», во вкладке «Память» — активное окно: теперь это поля
    для любого целого числа от 0 до 1000 (0 — без ограничения / вся история).
    Прежние варианты остались кнопками в один клик.
  • Значение принимается по окончании ввода и приводится к целому в допустимых
    границах: пустое поле или «-5» не уходят на сервер. Сервер проверяет эти
    лимиты строго, и одно такое значение ломало бы ошибкой каждый следующий ход.

Исправлено — удаление сообщений пересобирало память с нуля

Если указатель сводки «учтено до» оказывался новее последнего сообщения (свежие
реплики удалили), он сбрасывался в 0, и весь чат заново пересказывался поверх
старой сводки — платно и с повторами. Теперь указатель прижимается к последнему
оставшемуся сообщению, а факты из удалённых сообщений удаляются вместе с ними.

Обновление

  • Изменился серверный код, поэтому после git pull сервис нужно перезапустить.
    В Docker код бэкенда и бота запечён в образ — нужна пересборка:
    docker compose up -d --build (простой restart оставит старый бэкенд со
    свежим интерфейсом). Без Docker — перезапуск start.sh или systemd-сервиса.
  • Новых зависимостей, переменных .env и ручных миграций нет: таблица
    horae_facts создаётся сама при первом запуске.
  • Первые ходы в длинных чатах запускают фоновую догонку памяти: пересборку
    старой сводки и извлечение фактов — до 12 служебных запросов за ход, пока
    бэклог не кончится. Перед обновлением задайте дешёвую быструю модель
    («Подключение» → summary_model), иначе догонка пойдёт на модели чата.
  • Когда сводка догонит чат, модель будет видеть дословно последние 20–23
    сообщения плюс хронику и факты: вход длинного чата сокращается в разы. Если
    хочется прежнего поведения — «Память» → «Активное окно» → 0.

Также в этом выпуске: 2.3.0

Разбор интерфейса на двух независимых оценках (дизайн и детектор с браузерными
доказательствами) нашёл 2 блокирующие проблемы, 3 крупные и десять расхождений
между кодом и записанной дизайн-системой. Исправлено всё.

Исправлено — первый клик нового пользователя не давал ничего

startNewChat() при нуле персонажей звал createCharacter(), который создавал
запись и возвращался: ничего не выбрано, чат не создан, экран байт в байт
прежний. Открытая регистрация объявлена аудиторией продукта — с этим встречался
каждый незнакомец.

  • Клик доводится до места, ради которого делался: персонаж выбран, чат создан и
    открыт, карточка показана с фокусом в «Описании».
  • У раздела «Персонажи» появилось настоящее пустое состояние. Прежде оно
    рисовалось только при активном фильтре, то есть при нуле персонажей человек
    видел две кнопки и пустоту.

Исправлено — список чатов разрушался на телефоне

Вынос действий строки из потока жил внутри @media (hover: hover), а на
тач-устройствах это правило не срабатывает никогда. Пять кнопок по 44px
занимали 234 из 315 пикселей сайдбара: на заголовок и превью оставалось около
58 — отличить один чат от другого было нельзя. Та же коллизия давала ≈92px
панели значков под каждой репликой.

  • Действия сворачиваются за одну кнопку «⋯», раскрытые уходят на собственную
    строку и не сжимают заголовок.
  • Под репликой на тач-устройствах остаются копирование, ответ и перегенерация,
    остальное — за «⋯».

Добавлено — режимы просмотра стали видимыми и честными

  • Сегментированный переключатель в шапке вместо трёх пунктов в безымянном «⋯»
    из двенадцати строк; три команды в палитре (Ctrl+K).
  • «Работа с документом» перестала быть ложью: прежде режим включался, только
    если документ уже существовал, — у кого его не было, подсвеченный пункт не
    делал ничего. Теперь режим либо открывает документ (создавая пустой
    черновик), либо называет причину.
  • «Тёмный зал» выполняет обещанное: шапка и сайдбар уходят на цвет холста,
    название чата отступает на ступень эмфазы ниже, приборы убираются. Кроме
    предупреждения «обрезано N» — это не прибор, а единственное сообщение о
    потере памяти, и второго места у него нет.

Исправлено — ворота входа и регистрации

Поля назывались только через placeholder, который исчезает при наборе
(нарушение 3.3.2), не было <form>, не было autocomplete (нарушение 1.3.5),
а значит менеджер паролей не предлагал сохранить пароль — в продукте, где
забытый пароль уничтожает все истории без самостоятельного восстановления.

  • <form>, видимые подписи, autocomplete на всех полях, подтверждение
    пароля, правило длины, переключатель показа.
  • Постоянная строка о том, что восстановления пароля нет.
  • Вкладки получили role="tab" и aria-selected; коробка ворот больше не
    вырастает за пределы недостижимого центрирования — слой прокручивается.

Исправлено — доступность

  • role="tab" / aria-selected на вкладках шторки и канваса, role="listbox"
    и aria-activedescendant в палитре (прежде перебор стрелками был для
    скринридера полностью беззвучным).
  • alt у всех изображений, <h1> и ориентиры разметки, подписи у полей,
    которые не имели их вовсе.
  • Четыре действия на div/span без роли и обработчика клавиш стали
    достижимыми с клавиатуры.
  • Убраны два outline: none без замены — фокус в палитре и редакторе канваса
    был невидим.

Изменено — дизайн-система

  • Автопереключение темы по prefers-color-scheme; явный data-theme
    перебивает системную настройку в обе стороны. До этого светлая тема была
    объявлена целиком и недостижима: ни медиазапроса, ни переключателя.
  • Цвет тревоги возвращён тревоге: --danger-fill красил четыре обычных
    переключателя, теперь остаётся только записи с микрофона.
  • Тень появилась у модалок и палитры, исчезла у точки-индикатора, вторая
    акцентная полоса у закреплённых строк заменена непозиционным признаком.
  • Зоны нажатия добраны псевдоэлементами там, где растить сам элемент нельзя.
  • Реакция на «меньше движения» перестала быть тотальным отключением.

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

PRODUCT.md утверждал, что WCAG 2.1 AA «уже реализована»; аудит нашёл отказы
1.3.5, 3.3.2, 4.1.2 и 2.4.7. Стандарт записан как цель с честным перечнем
недостигнутого. В DESIGN.md уточнены обещание зала, словарь теней и правило
длительностей.


Также в этом выпуске: 2.2.0

Дизайн-система записана и сведена: значения, которые раньше стояли числами
по месту, переехали в семантический слой.

Добавлено — PRODUCT.md, DESIGN.md и приложение к нему

  • PRODUCT.md — что за продукт, для кого и что менять нельзя. Отдельно
    зафиксированы открытые вопросы открытой регистрации: /api/auth/register
    ничем не ограничен, квот по пользователям нет (usage_days — общий агрегат),
    ограничения частоты нет, восстановления пароля нет.
  • DESIGN.md — визуальная система с посчитанными значениями контраста и
    тринадцатью именованными правилами.
  • .impeccable/design.json — тональные шкалы, тени, движение, слои и
    готовые фрагменты компонентов.

Исправлено — теги и полоса весов не переключались темой

Цвета тегов области видимости и шести сегментов полосы контекста были заданы
литералами прямо в правилах и потому оставались тёмными в светлой теме:
#8fb3ff на белом фоне давал 2.10:1.

  • Обе группы вынесены в токены со светлыми парами. В светлой теме теги дают
    7.74 / 6.53 / 6.29, сегменты полосы — от 5.19 до 8.70 при пороге 3:1.
  • У теней шторки, сайдбара, тоста и лайтбокса не было светлых значений: в
    светлой теме они рисовали бы чёрное пятно 45%. Теперь у каждой роли две пары.

Изменено — движение и слои стали шкалой

  • Четырнадцать длительностей сведены к четырём ролям: --dur-press (80ms),
    --dur-1 (120ms), --dur-2 (180ms), --dur-3 (260ms). Прежние 0.12 и 0.14
    стояли на одной и той же работе.
  • Кривая перехода — --ease вместо ease браузера, появление — --ease-out.
    Оба токена были объявлены ещё в первой волне и ни разу не использовались.
  • Пятнадцать z-index вразнобой заменены шкалой --z-nav … --z-gate.
    Числа сохранены как были — изменились только имена.
  • Три подложки наложения (0.4 / 0.5 / 0.55) сведены к одной --scrim;
    лайтбокс остался на --scrim-strong.

Удалено

--fs-600, --sp-6, --edge-highlight — ноль обращений. Первые два
расходились с записанной шкалой, третий остался от подхода к глубине,
от которого отказались в пользу границ.