Фреймы агента
Фрейм — свой HTML, который открывается в чате вместо приветственного экрана, когда посетитель выбирает агента: форма, примеры, загрузка фото. Отправленная форма уходит агенту скрытым сообщением, и он отвечает как обычно.
Как это работает
Фрейм — тот же шаблон, что свой пейволл, но в слоте frame: те же методы загрузки, изменения, превью и удаления (см. Свои HTML-шаблоны), плюс словари переводов. К агенту фрейм привязывается полем frame_id, один фрейм можно повесить на несколько агентов.
Посетитель открывает агента → чат вместо приветствия показывает фрейм на всю область → посетитель заполняет форму и отправляет → чат загружает файлы, собирает из формы текст и отправляет его агенту скрытым сообщением → агент отвечает в чате (например, вызывает скилл и заполняет панель справа).
Чем отличается от пейволла
Пейволл (paywall) | Фрейм (frame) | |
|---|---|---|
| Где показывается | Окно поверх чата на лимите | Вместо приветствия при открытии агента |
| Как выбирается | По типу диалога, фиче и строке Default | Напрямую по frame_id агента; types, фича и Default не участвуют |
| Переменные | Цены и тексты пейволла {{PRICE_1}}, {{TITLE}}… | Переводы {{t.key}} и {{LOCALE}}, {{USER_NAME}}, {{PROJECT_NAME}}, {{AGENT_LABEL}} |
| Мост | window.paywall: checkout(N), close() | window.frame: submit(), close(), message(), t(), on() |
| Результат | Оплата тарифа | Скрытое сообщение агенту с данными и файлами |
| Размер | Окно ужимается под карточку | Вся область чата, высота по содержимому |
| Песочница | allow-scripts allow-popups allow-popups-to-escape-sandbox | То же плюс allow-forms |
Загрузка фрейма не включает фичу своих пейволлов.
Свойства фрейма
Кроме общих свойств шаблона (id, name, enabled, files, entry, size, html…):
| Свойство | Тип | Описание |
|---|---|---|
translations | object | Словари: «код языка → ключ → строка». Всего до 200 КБ в JSON. Код языка — xx, xxx или xx-yy; ключ — латиница, цифры, _, ., -, начинается не с точки и не с дефиса. |
locales | array | Языки, для которых есть словарь. |
assets | object | Произвольный JSON владельца до 200 КБ. Хранится и отдаётся, чат его не использует. |
warnings | array | Подсказки по HTML: есть словарь, а в HTML нет ни {{t., ни data-i18n; есть внешний <script src>, который в песочнице не загрузится. Сохранению не мешают. |
Переводы
Словарь подбирается так: язык посетителя → язык ассистента по умолчанию → en → первый из имеющихся. Подстановка идёт в двух местах.
| Где | Что |
|---|---|
| Сервер, до показа | {{t.key}} → строка словаря (нет ключа — пусто). {{LOCALE}}, {{USER_NAME}} (у гостя пусто), {{PROJECT_NAME}}, {{AGENT_LABEL}}. Любая другая {{ИМЯ}} заглавными — пусто. |
| Браузер, мостом | data-i18n="key" — текст элемента, data-i18n-attr="placeholder:key" — атрибут (несколько пар через ; или ,). Заполняются при открытии и при каждой смене языка в шапке, без перезагрузки и без потери введённого. |
Для живой смены языка нужен второй способ: {{t.key}} подставляется один раз при загрузке. Надёжнее ставить оба: <h1 data-i18n="title">{{t.title}}</h1>.
Мост с чатом
HTML работает в iframe без доступа к кукам и сессии чата. Мост чат вставляет сам, сразу после <head> (если его нет — в начало). Сообщения из iframe помечены source: 'host', из чата — source: 'parent'; чат принимает сообщения только от своего iframe.
Декларативно
| Разметка | Действие |
|---|---|
<form data-frame-submit> | Отправка формы: поля уходят в payload (повторяющиеся имена и name[] — массивом), выбранные файлы из input[type=file] — в files. |
data-frame-submit на кнопке вне такой формы | Отправка ближайшей формы (или первой на странице). |
data-frame-summary="…" на форме или кнопке | Строка-резюме, которая встанет второй строкой сообщения. |
data-frame-close | Закрыть фрейм и вернуться к приветствию. |
data-i18n, data-i18n-attr | Тексты из словаря. |
window.frame
| Вызов | Что делает |
|---|---|
frame.submit(payload, files, summary) | Отправить данные агенту. payload — объект, files — массив File, summary — необязательная строка. |
frame.close() | Закрыть фрейм. |
frame.message(text) | Отправить в чат обычное сообщение от посетителя, без формы. |
frame.t(key) | Строка словаря текущего языка; нет ключа — сам ключ. |
frame.locale, frame.translations | Текущий язык и словарь. |
frame.on('init' или 'locale' или 'state', cb) | Подписка на сообщения чата; возвращает функцию отписки. |
frame.resize() | Пересчитать высоту вручную (обычно не нужно). |
Файлы мост копирует в память в момент выбора, поэтому отправка не ломается, если файл на диске успел измениться.
Сообщения
Из iframe в чат:
| Сообщение | Когда |
|---|---|
{type: 'frame:ready', height, width} | Загрузились шрифты и страница; до этого iframe скрыт. Крайний срок — 1,8 секунды. |
{type: 'frame:height', height} | Изменилась высота содержимого. |
{type: 'frame:submit', payload, files, summary?} | Отправка формы. |
{type: 'frame:close'} | Закрыть фрейм. |
{type: 'frame:message', text} | Обычное сообщение в чат. |
Из чата в iframe:
| Сообщение | Когда |
|---|---|
{type: 'frame:init', locale, translations, user: {name, is_anonymous}, agent: {id, slug, label}, project: {name}} | При загрузке и после frame:ready. translations — словарь текущего языка. project — ассистент (имя ключа историческое). |
{type: 'frame:locale', locale, translations} | Смена языка в шапке. |
{type: 'frame:state', submitting, error?} | Пока чат загружает файлы и отправляет: submitting: true; по завершении false, при ошибке — с текстом error. |
Что получает модель
Чат загружает файлы посетителя, затем отправляет агенту скрытое сообщение (посетитель его не видит). Подписи строк — по-русски, если язык интерфейса русский, иначе по-английски:
[frame:pomelli-post] Данные из формы агента «Пост из фото»
Пример: «Кофейня утром» (id: cafe-morning)
Комментарий: сделай тепло и по-домашнему
Фото пользователя: https://framesuite.app/storage/chat-attachments/abc.jpg (image/jpeg, 1.2 MB)
Параметры: network=instagram; tone=friendly
Последней частью сообщения идёт блок в тройных обратных кавычках с пометкой json, внутри — одна строка:
{"frame":"pomelli-post","example":{"id":"cafe-morning","title":"Кофейня утром"},"comment":"сделай тепло и по-домашнему","params":{"network":"instagram","tone":"friendly"},"files":[{"name":"abc.jpg","url":"https://framesuite.app/storage/chat-attachments/abc.jpg","mime":"image/jpeg","size":1258291}]}
Как собирается текст:
| Строка | Откуда |
|---|---|
[frame:<имя>] | payload.frame, иначе id фрейма. Маркер для промпта агента: это данные формы, а не вопрос. |
| Резюме | summary, если задано. |
| Пример | payload.example: объект {id, title} или строка. |
| Комментарий | payload.comment. |
| Фото или файл | Каждый загруженный файл: адрес, тип, размер. Адреса стоят в тексте, потому что скиллы получают только текст сообщения. |
| Параметры | Скалярные значения из payload.params и остальные поля payload, кроме служебных (frame, example, comment, params, summary, files). |
| JSON-блок | Весь payload плюс frame и files. |
Опишите в промпте агента, что делать с сообщением, которое начинается с [frame:<имя>] (Агенты). Каждая отправка пишет событие frame_submitted с id фрейма, выбранным примером и признаками фото и комментария — текст комментария в аналитику не уходит (События и аналитика).
Методы
Список фреймов
GET /assistants/{assistantId}/custom-templates?slot=frame
| Параметр | Тип | Обяз. | Описание |
|---|---|---|---|
assistantId | int | да | Путь. |
slot | string | да | Строка запроса. frame. |
curl -s "$BASE/assistants/$ASSISTANT/custom-templates?slot=frame" \
-H "Authorization: Bearer $KEY"
{
"success": true,
"feature_enabled": false,
"default_enabled": true,
"default_types": ["*"],
"dialog_types": ["…"],
"templates": [
{
"id": 41,
"slot": "frame",
"locale": null,
"types": [],
"filename": "index.html",
"name": "P12",
"enabled": true,
"size": 18240,
"files": ["index.html", "styles.css", "translations.json"],
"entry": "index.html",
"updated_at": "2026-09-13T10:00:00+00:00",
"translations": {"ru": {"title": "Пост из фото"}, "en": {"title": "Post from a photo"}},
"locales": ["ru", "en"],
"assets": {},
"warnings": []
}
]
}
feature_enabled, default_* и dialog_types к фреймам не относятся.
Загрузить фрейм
POST /assistants/{assistantId}/custom-templates/frame/templates
| Параметр | Тип | Обяз. | Описание |
|---|---|---|---|
assistantId | int | да | Путь. |
files[] или file | file | да | Тело. .html, .zip или комплект до 5 МБ. Главный файл: index.html, index.htm, paywall.html, frame.html, иначе первый .html. |
translations | string | нет | Тело. JSON словарей. Не задано — берётся translations.json из загрузки, если он есть. |
assets | string | нет | Тело. JSON-объект. |
name | string | нет | Тело. До 32 знаков. |
Сборка комплекта — как у пейволла: CSS, скрипты, картинки и шрифты встраиваются в HTML.
curl -s -X POST "$BASE/assistants/$ASSISTANT/custom-templates/frame/templates" \
-H "Authorization: Bearer $KEY" \
-F "files[]=@index.html" \
-F "files[]=@styles.css" \
-F "files[]=@translations.json" \
-F "name=Пост из фото"
{
"success": true,
"template": {
"id": 41,
"slot": "frame",
"name": "Пост из фото",
"enabled": true,
"files": ["index.html", "styles.css", "translations.json"],
"entry": "index.html",
"translations": {"ru": {"title": "Пост из фото", "send": "Отправить"}, "en": {"title": "Post from a photo", "send": "Send"}},
"locales": ["ru", "en"],
"assets": {},
"warnings": [],
"html": "…",
"…": "…"
},
"warnings": []
}
| HTTP | Когда |
|---|---|
| 404 | Ассистента нет или нет доступа. |
| 422 | Ошибки загрузки, как у пейволла. Ошибки словаря: «translations: невалидный JSON», «translations: неверный код локали «…»», «translations.ru: недопустимый ключ «…» (латиница, цифры, _ . -)», «translations: словарь больше 200 КБ (…)». |
Изменить переводы и настройки
translations и assets заменяются целиком. name и enabled — как у пейволла. Выключенный фрейм чат не отдаёт, и агент открывается с обычным приветствием.
PATCH /assistants/{assistantId}/custom-templates/item/{id}
| Параметр | Тип | Обяз. | Описание |
|---|---|---|---|
assistantId | int | да | Путь. |
id | int | да | Путь. Id фрейма. |
translations | object | нет | Тело. {} снимает словарь. |
assets | object | нет | Тело. |
name | string | нет | Тело. |
enabled | bool | нет | Тело. |
curl -s -X PATCH "$BASE/assistants/$ASSISTANT/custom-templates/item/41" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"translations": {
"ru": {"title": "Пост из фото", "send": "Отправить", "cancel": "Отмена", "comment_ph": "Пожелания к посту"},
"en": {"title": "Post from a photo", "send": "Send", "cancel": "Cancel", "comment_ph": "Wishes for the post"}
}}'
Ответ: {"success": true, "template": {…}}.
| HTTP | Когда |
|---|---|
| 404 | Ассистента или фрейма нет. |
| 422 | Ошибка в словаре, текст как при загрузке. |
Превью на языке
HTML с подставленными переводами и переменными. USER_NAME — имя того, кто смотрит, AGENT_LABEL пустой. Язык ограничивается языками ассистента.
GET /assistants/{assistantId}/custom-templates/item/{id}/preview
| Параметр | Тип | Обяз. | Описание |
|---|---|---|---|
assistantId | int | да | Путь. |
id | int | да | Путь. |
locale | string | нет | Строка запроса. По умолчанию язык ассистента. |
curl -s "$BASE/assistants/$ASSISTANT/custom-templates/item/41/preview?locale=en" \
-H "Authorization: Bearer $KEY"
{
"success": true,
"locale": "en",
"html": "<h1 data-i18n=\"title\">Post from a photo</h1>…",
"translations": {"ru": {"…": "…"}, "en": {"…": "…"}}
}
Удалить фрейм
DELETE /assistants/{assistantId}/custom-templates/item/{id}
curl -s -X DELETE "$BASE/assistants/$ASSISTANT/custom-templates/item/41" \
-H "Authorization: Bearer $KEY"
{"success": true, "deleted": 1}
Агенты, привязанные к удалённому фрейму, открываются с обычным приветствием, а в их настройках фрейм помечен как не найденный. Привязку лучше снять.
Привязать к агенту
frame_id — поле агента: null или id фрейма этого ассистента. Передаётся в PUT /assistants/{assistantId}/agents/{agentId} на верхнем уровне или внутри config. Присылайте только его: PUT агента перезаписывает все присланные поля (Агенты).
curl -s -X PUT "$BASE/assistants/$ASSISTANT/agents/1234" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"frame_id": 41}'
Снять — {"frame_id": null}. Id пейволла, чужого ассистента или несуществующий — 422 «frame_id: фрейм не найден в этом проекте.». Значение отдаётся в списке агентов и в данных агента для чата.
Как фрейм получает посетитель
Публичный метод чата, без ключа. Отдаёт только включённые фреймы ассистента с этим frame_slug, остальное — 404.
GET https://framesuite.app/f/{frame_slug}/frame/{id}?locale=ru&agent_id=1234
{
"success": true,
"id": 41,
"name": "Пост из фото",
"locale": "ru",
"html": "…",
"translations": {"ru": {"…": "…"}, "en": {"…": "…"}}
}
agent_id нужен для {{AGENT_LABEL}}. Ответ не кэшируется.
Минимальный фрейм
<!doctype html>
<html>
<head>
<style>body { font-family: system-ui; margin: 0; padding: 24px }</style>
</head>
<body>
<h1 data-i18n="title">{{t.title}}</h1>
<form data-frame-submit data-frame-summary="Пост из фото">
<input type="hidden" name="frame" value="demo">
<input type="file" name="photo" accept="image/*" required>
<textarea name="comment" data-i18n-attr="placeholder:comment_ph"></textarea>
<select name="network">
<option value="instagram">Instagram</option>
<option value="telegram">Telegram</option>
</select>
<button type="submit" data-i18n="send">{{t.send}}</button>
</form>
<button type="button" data-frame-close data-i18n="cancel">{{t.cancel}}</button>
<script>
frame.on('state', function (s) {
document.querySelector('[type=submit]').disabled = s.submitting;
if (s.error) alert(s.error);
});
</script>
</body>
</html>
Агент получит [frame:demo], ссылку на фото, комментарий и network=… в строке параметров.
Дальше
- Свои HTML-шаблоны — общие правила загрузки и песочницы.
- Агенты — промпт агента и поле
frame_id. - Свойства config — панель справа, куда агент выводит результат.