Commit Graph
500 Commits
Author SHA1 Message Date
Andrey 6c04ce30d8 ✨ feat(webchat): добавить настройки формы перед чатом 2026-10-01 17:51:52 +03:00
Andrey 8b9fae8a8d ✨ feat(conversations): показывать данные сайта в карточке контакта 2026-10-01 17:17:58 +03:00
Andrey 61b0dc2416 ✨ feat(webchat): добавить настройки оформления подключения 2026-10-01 13:11:16 +03:00
Andrey 52524287ae 📝 docs(webchat): добавить статью о классах виджета 2026-10-01 12:54:24 +03:00
Andrey 29e2232fc3 ✨ feat(webchat): передавать поля сайта через лоадер и виджет 2026-10-01 12:42:41 +03:00
Andrey b160335bf6 ✨ feat(webchat): применять оформление кнопки из конфигурации 2026-10-01 00:27:29 +03:00
Andrey 223a962cb9 ✨ feat(webchat): добавить раздел данных с сайта 2026-09-30 23:18:58 +03:00
Andrey 5527a13230 ✨ feat(web-chat): стабильные классы и оформление окна виджета 2026-09-30 18:59:53 +03:00
Andrey a940dcc2e3 ✨ feat(webchat): события и обновление данных сайта в диалогах 2026-09-30 18:59:34 +03:00
Andrey 416e37e441 ✨ feat(webchat): настроить форму перед чатом на сервере 2026-09-30 17:29:45 +03:00
Andrey 6e36bdc725 ✨ feat(ai): учитывать данные сайта в промпте агента 2026-09-30 17:25:46 +03:00
Andrey c3ea8395d0 ✨ feat(webchat): сохранять данные сайта в контакте 2026-09-30 09:34:11 +03:00
Andrey dc6e38b689 ✨ feat(webchat): перенести настройки WEB-подключения на страницу 2026-09-30 09:33:34 +03:00
Andrey 36c95199a0 ♻️ refactor(portals): вынести меню разделов в shared 2026-09-29 13:09:19 +03:00
Andrey 21a7626f95 ✨ feat(webchat): настройки оформления виджета на сервере
config.appearance подключения: цвет #RRGGBB, положение, размер 48/56/64,
форма и иконки кнопки, свой CSS до 10 КБ. Из CSS вырезаются @import,
внешние url() и expression. Публичная конфигурация отдаёт appearance,
config.accent продолжает работать; конфигурация не кэшируется, лоадер —
не дольше 5 минут.
2026-09-29 12:47:05 +03:00
Andrey 0f01a8ce47 ✨ feat(web-chat): схема своих полей в веб-подключении
config.fields WEB-подключения проверяется при сохранении: формат и
уникальность ключа, запрет смены ключа (по служебному id поля), название
до 60 символов, тип из списка, значения только у списка, не больше 30
полей, ключи name/email/phone заняты. Ошибки — через t(). Публичная
конфигурация виджета отдаёт схему без aiVisible.
2026-09-29 12:33:53 +03:00
Andrey 811a164c44 ✨ feat(web-chat): загрузка иконок виджета с очисткой SVG
POST /api/v1/organizations/{id}/integrations/{id}/assets/ принимает SVG или
PNG до 256 КБ (PNG не меньше 96×96) и возвращает { url }. SVG очищается до
сохранения; файл лежит в хранилище организации на диске или в S3 и
отдаётся виджету без сессии по непредсказуемому UUID.
2026-09-29 12:32:53 +03:00
AndreyandClaude Opus 5.5 cc091dbd03 🔖 release: 1.15.3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1.15.3
2026-09-24 21:29:24 +03:00
AndreyandClaude Opus 5.5 8f3b1e43a4 🐛 fix(web-chat): на телефоне окно чата на весь экран
На экранах до 480px лоадер раскрывает панель на весь экран без
скруглений и прячет круглую кнопку, пока панель открыта: иначе она
висит поверх поля ввода. Кнопку «Развернуть окно» панель не показывает —
разворачивать некуда. Ширину экрана iframe не видит, поэтому режим
сообщает лоадер (chatballs-chat-layout), а панель после монтирования
сама его запрашивает.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:27:59 +03:00
Andrey Ermolaev d5c9d5c638 Merge pull request #4 from dartdavros/codex/release-1.15.2-cleanup
Подготовка релиза v1.15.2: очистка устаревших терминов
v1.15.2
2026-09-24 18:03:41 +03:00
Andrey 7e0a12a2c9 ♻️ refactor: убрать устаревшие упоминания Hub и CustoAI 2026-09-24 18:02:27 +03:00
Andrey Ermolaev fc370fafe1 Merge pull request #3 from dartdavros/codex/help-center-navigation-related
Навигация и связанные статьи портала помощи
v1.15.1
2026-09-24 14:09:33 +03:00
Andrey df9c6ad6fd 🚨 fix(lint): порядок импортов портала 2026-09-24 14:08:04 +03:00
Andrey 8c618691f6 ✨ feat(portals): навигация и связанные статьи 2026-09-24 14:05:21 +03:00
AndreyandClaude Opus 5.5 f57b1ee54a 🔖 release: 1.15.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1.15.0
2026-09-23 10:05:44 +03:00
AndreyandClaude Opus 5.5 64266dcc9c ✨ feat(settings): шаблоны ответов в настройках и переменные в тексте
Раздел «Настройки → Шаблоны ответов»: список по образцу «Групп», создание
кнопкой в шапке раздела, правка и удаление окнами, как у интеграций. Без права
на настройки список только читается.

В тексте шаблона можно использовать {{client_name}}, {{operator_name}} и
{{company}}; кнопка «Вставить переменную» ставит код в позицию курсора.
Композер подставляет значения при выборе шаблона. Переменная без значения
остаётся в тексте, над полем — предупреждение, отправка заблокирована. Гость
виджета распознаётся по подписи сессии (isGuest в карточке диалога), его
«Гость · код» за имя не считается. Сервер отклоняет неизвестные переменные.

Переименование шаблона в занятое название теперь отвечает 409, а не падает
на ограничении базы. Логика шаблонов вынесена из Composer в
useComposerTemplates и ComposerTemplatesMenu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 10:05:32 +03:00
Andrey cce87a4973 🔖 release: 1.14.3 v1.14.3 2026-09-22 07:29:25 +03:00
Andrey 556246865e 🚨 fix(lint): исправить сортировку импортов 2026-09-22 07:29:03 +03:00
Andrey 0aff0c564b 🔖 release: 1.14.2 v1.14.2 2026-09-22 07:13:46 +03:00
Andrey 0d307c7d06 🐛 fix(ai): сбрасывать circuit breaker после настройки провайдера
Повторы одного вызова теперь считаются одним логическим сбоем, поэтому предохранитель не открывается посреди второго сообщения. Изменение runtime-конфигурации и успешная проверка увеличивают ревизию интеграции: каждый event-worker заменяет открытый breaker при следующем запросе.

Удаление используемого провайдера возвращает 409 с понятной причиной и не пишет ложное событие об успешном удалении. Добавлены миграция и регрессионные тесты.
2026-09-22 07:13:20 +03:00
AndreyandClaude Opus 5 4e56b10e6a 🔖 release: 1.14.1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v1.14.1
2026-09-20 10:49:00 +03:00
AndreyandClaude Opus 5 bcb66fadbc 🐛 fix(webchat): первое открытие чата — с анимацией и кнопкой «свернуть»
Первый кадр силуэта рисовался раньше, чем создавался слой, на котором он
живёт. Обращение к нему падало, и остаток обработчика не выполнялся: окно
появлялось рывком, а кнопка оставалась со знаком агента вместо шеврона —
свернуть чат было нечем. Со второго раза слой уже существовал, и всё
работало, поэтому дефект был виден только при первом открытии.

Слой создаётся вместе с панелью, до первого кадра.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 10:49:00 +03:00
AndreyandClaude Opus 5 2ffad1e787 🔖 release: 1.14.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v1.14.0
2026-09-20 10:36:10 +03:00
AndreyandClaude Opus 5 c69cd1b436 🔥 chore(resources): убрать неиспользуемые файлы
Иконка приложения и два hero-изображения ни на что не ссылались: ни сборка,
ни README, ни документация их не читают. Удалены владельцем.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 10:35:39 +03:00
AndreyandClaude Opus 5 1f46ed62bd 💄 feat(webchat): виджет по утверждённому образцу и разворот окна
Виджет переложен по образцу, который дал владелец.

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

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

В шапке появилась кнопка разворота: окно становится почти вдвое шире и
немного выше. Размер держит лоадер на сайте-хосте — панель живёт в iframe и
сама себя не растянет.

Появление и исчезновение — эффект джина, как в доке macOS: окно вытягивается,
изгибается горловиной и втягивается в кнопку. Аффинными преобразованиями
такое не делается, поэтому силуэт считается контуром SVG покадрово, тем же
контуром режется контейнер панели, а само окно внутри получает матрицу,
построенную по этому силуэту, — вместе с окном деформируется и переписка.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 10:35:29 +03:00
AndreyandClaude Opus 5 83b1119d6b ✨ feat(conversations): ответ AI считается отдельно от приёма входящих
Ход AI выполнялся прямо в приёме сообщения: цикл опроса мессенджеров и
HTTP-запрос виджета ждали провайдера, держа открытой транзакцию организации.
Один ход — это два обращения к модели (эмбеддинг и чат) по тридцать секунд с
двумя повторами, то есть до трёх минут, и всё это время ни одно входящее по
всей установке не забиралось. Владелец видел это как «бот залипает»: сайт и
MAX на одном агенте отвечали с задержками или молчали.

Приём теперь доводит дело до записи сообщения и ставит событие
`conversation.ai_turn_requested`. Ход считает роль событий воркера
(`run_worker --role=events`) короткими транзакциями, между которыми остаются
походы к провайдеру и в мессенджер. Туда же уехала расшифровка голосовых —
последнее обращение наружу из цикла опроса.

Воркер разделён на роли: `poller` опрашивает подключения и ведёт периодические
работы (один экземпляр — курсоры и паузы после сбоя живут в его памяти),
`events` разбирает outbox и масштабируется репликами (`CHATBALLS_EVENT_WORKERS`,
по умолчанию две). Роль `all` осталась для разработки.

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

Попутно убраны мины, которые тот же залип и продлевали:
- ход клиенту ограничен своим таймаутом (CHATBALLS_AI_TURN_TIMEOUT, 20 с)
  и сроком годности (CHATBALLS_AI_TURN_DEADLINE_SECONDS, 120 с) — просроченный
  ход не зовёт модель, а передаёт диалог оператору;
- отказ провайдера по существу запроса (4xx, кроме 429) больше не повторяется
  трижды по таймауту;
- предохранитель провайдера считает сбои по ключу «организация + интеграция»,
  а не один на процесс: отозванный ключ одной организации гасил AI у всех;
- потолок паузы после сбоя опроса — минута вместо четверти часа: он был
  компромиссом ради журнала однопоточного воркера.

Виджет узнаёт, что ответ считается, по признаку `thinking` в ленте.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 10:35:00 +03:00
AndreyandClaude Opus 5 c8b8246b35 🔖 release: 1.13.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v1.13.0
2026-09-19 11:32:54 +03:00
AndreyandClaude Opus 5 b95a4edf6e 🐛 fix(settings): адрес установки помнит порт, ссылки ведут на него
Мастер первого запуска и «Настройки» отбрасывали порт из адреса. На
установке, опубликованной как ip:8081, ссылки на файлы, звонки, приглашения
и сброс пароля уходили без порта и вели в пустоту.

Порт хранится отдельно от хоста (InstanceSettings.public_port): по хосту
проверяются входящие Host, строятся домены порталов и адреса TURN, и порт им
чужой. В ссылки он добавляется, когда не совпадает с портом схемы. В поле
адреса его вписывают через двоеточие; смена одного порта прежний адрес не
сдвигает.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 11:32:46 +03:00
AndreyandClaude Opus 5 19448eb05a ✨ feat(ai): размер контекста диалога задаётся в карточке агента
Модель получала последние 20 сообщений диалога — число было зашито в
ingest.py, и поменять его можно было только правкой кода, которую стирало
первое же обновление. Длинному разговору 20 мало, локальной модели с
маленьким окном — бывает много.

Теперь это поле «Контекст диалога, сообщений» в блоке «Модель» карточки
агента: от 1 до 200, по умолчанию 20. Проверочный чат карточки видит то же
окно, что и живой диалог.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 11:32:46 +03:00
AndreyandClaude Opus 5 a321109a8f 🐛 fix(gateway): установка на нестандартном порту принимает формы
Шлюз передавал дальше Host без порта ({host} в Caddy, $host в nginx). Браузер
при этом шлёт Origin с портом, и CSRF-проверка Django отвергала любой POST на
установке, опубликованной как ip:8081: «Origin checking failed».

Теперь Host уходит с портом. X-Forwarded-Proto и X-Forwarded-For Caddy
принимает от прокси из частных сетей — установку часто ставят за прокси
панели, который снимает TLS и ходит к шлюзу по http; без этого CSRF падал бы
снова, как только перед установкой появлялся https.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 11:32:25 +03:00
Andrey e1cda4dd8b 🔖 release: 1.12.0 v1.12.0 2026-09-17 12:29:02 +03:00
AndreyandClaude Opus 5 fed079e02d ✨ feat(notifications): открытый диалог гасит свои уведомления
Прочтением считалось только нажатие на строку в шторке. Сотрудник мог уже
отвечать клиенту, а оклик «клиент ждёт» по этому же диалогу продолжал висеть
непрочитанным и держать счётчик в шапке. Счётчик переставал что-либо значить:
он звал туда, где человек уже был.

Теперь при открытии диалога гасятся все уведомления о нём — не только те,
что клиент успел загрузить в шторку: в неё помещается полсотни последних, и
гасить по загруженному списку означало бы оставлять хвост. Отбор идёт по
адресу уведомления, включая маршруты, которыми на диалог ссылались старые
строки в базе.

Отметка прочтения теперь шлёт сотруднику событие: счётчик живёт в шапке
каждой открытой вкладки, и без события они разъезжались до следующего опроса.

Проверено: тесты уведомлений (гасится только свой диалог, гасятся все его
уведомления, старый маршрут узнаётся, нечисловой идентификатор отклоняется),
рассылка событий, каталоги переводов, ruff, проверка типов рабочего места.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 11:55:01 +03:00
AndreyandClaude Opus 5 89c44db2bc 🐛 fix(chat): точка режима держится на аватаре в любой строке диалога
Строка списка — flex, и её ячейки по умолчанию растягиваются на всю высоту.
Обёртка аватара растягивалась вместе с ними, а точка режима привязана к её
низу — в строке с метками или таймером ожидания обёртка вырастала с 38 до 62
пикселей, и точка уезжала под аватар на четверть сантиметра. Чем больше меток
у диалога, тем дальше.

Обёртка прижата к началу поперечной оси и снова ростом с аватар, поэтому
точка стоит в его углу независимо от того, что показано в строке ниже.

Проверено на короткой строке и на строке с меткой группы и таймером: высота
обёртки 38 в обеих, точка на 1 пиксель за краем аватара.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 11:43:00 +03:00
AndreyandClaude Opus 5 ca23cd285f 🐛 fix(vk): марка ВКонтакте вместо чужого значка в ленте и карточке
Канал ВКонтакте появился, а марки у него не было: в ленте диалогов и в
карточке контакта рисовался общий пузырь сообщения, а в строке логина —
значок отправки, который для Telegram выглядел уместно только по
совпадению. Клиент из ВКонтакте выглядел пришедшим непонятно откуда.

Теперь `ChannelGlyph` знает ВКонтакте и отдаёт тот же официальный
монохромный глиф, что стоит на плитке подключения, а строка логина берёт
марку своего канала вместо общего значка: Telegram, MAX и ВКонтакте —
каждый свой.

Проверено: глифы на 13-18px в тёмной теме рядом с Telegram и MAX, лента
диалогов, строка логина и плитки каналов в карточке контакта; tsc.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 11:38:40 +03:00
Andrey d34aea8a8a 🔖 release: 1.11.0 v1.11.0 2026-09-17 11:02:20 +03:00
AndreyandClaude Opus 5 eb98b9b781 ✨ feat(settings): у каждого подключения ссылка на статью справки
Токен бота, ключ доступа сообщества, пароль ящика — всё это выдаёт не наша
система, и человек уходит искать инструкцию в поиске, хотя она написана и
лежит в Центре помощи. Теперь под выбором типа стоит ссылка на статью
именно про этот тип: Telegram, MAX, ВКонтакте, почтовый ящик, веб-виджет.

Адрес Центра помощи один на продукт — это документация Chatballs, а не
портал организации, поэтому он лежит константой в shared/help и от
установки не зависит. Статья у типа подключения необязательна: у
LLM-провайдеров ссылки пока нет, и форма просто её не рисует.

Ссылка оформлена общим классом `.link` в роли второстепенной и стоит
подписью к полю «Тип» — своего стиля у неё нет.

Проверено: tsc, тесты каталога переводов и модели подключений, вид формы
на изолированном макете со штатными стилями.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 11:02:20 +03:00
AndreyandClaude Opus 5 31985fc35c ✨ feat(vk): канал ВКонтакте — сообщество как точка входа
Сообщения сообщества ВКонтакте принимаются через Bots Long Poll: тот же
способ, что у Telegram и MAX, поэтому подключению не нужен ни публичный
адрес, ни доступ извне — установка за NAT работает наравне с остальными.

Позиция потока живёт в poll_marker, адрес сервера и ключ — в памяти
процесса: их выдают на несколько часов, и колонка под них означала бы
запись в базу на каждом цикле опроса. Ответы failed 1-3 восстанавливаются
в том же цикле, иначе подключение висело бы с протухшим ключом до
перезапуска воркера.

Идентификатор сообщества владелец не вводит: его называет сам ключ
доступа, и проверка подключения кладёт его в конфигурацию — как имя бота
у Telegram и MAX. Проверка заодно смотрит настройки сообщества: без Long
Poll и события о входящем сообщении приём невозможен, и об этом честнее
сказать сразу, а не молчать зелёным статусом. Настройки чужого сообщества
при этом не меняются. Нехватка прав у ключа объясняется словами: ВКонтакте
отвечает на неё английским «no access», из которого не видно, что включить.

Имя, логин и фото отправителя ВКонтакте в апдейте не присылает — их
забирает один users.get на пачку сообщений, а не на каждое: на оживлённом
сообществе запрос на реплику упёрся бы в частоту обращений.

Телефона и кнопки «поделиться контактом» у ВКонтакте нет, поэтому просьба
уходит обычным сообщением, как и почтой. Голосовые принимаются, но не
отправляются: провайдер ждёт ogg/opus, а композер пишет webm.

Токен уходит строкой запроса — заголовка авторизации у ВКонтакте нет.
Поэтому адрес больше нигде не печатается как есть: и журнал, и поле
последней ошибки подключения, которое видно в интерфейсе, проходят через
маскирование секретных параметров.

Проверено на живом сообществе: проверка подключения, опрос Long Poll,
приём текста, голосового и фото с подстановкой имени, логина и аватара.
Тесты транспорта и проверки подключения, каталоги переводов, ruff из
корня, tsc и vitest по internal-ui.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 11:02:20 +03:00
Andrey Ermolaev 1ea0faf1c6 Update README.md 2026-09-17 09:38:32 +03:00
AndreyandClaude Opus 5 a41b23a5e2 docs: обложка и ссылки в README, design/ вне гита
Дизайн-хендоффы (design/) — рабочие материалы, а не часть продукта:
сняты с отслеживания и добавлены в .gitignore.

В обоих README перед оглавлением — обложка рабочего места (1024px,
сжата из исходных 3840px) и строка ссылок: телеграм-канал, сайт,
центр помощи.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 12:03:27 +03:00
Andrey 089dd6ddc2 🔖 release: 1.10.0 v1.10.0 2026-09-15 08:45:54 +03:00