Public API и события виджета
Когда нужен Public API
Обычное подключение и настройка канала выполняются по инструкции «Виджет на сайте». Public API нужен после установки кода, если сайт должен сам открывать чат, менять контекст, выбирать диалог, отправлять подготовленный вопрос или реагировать на события виджета.
Отдельно включать Public API в кабинете не нужно: методы появляются в window.SenlerWidget после загрузки кода канала. Настройки, для которых нужен интерфейс кабинета, показаны со скриншотами в связанной инструкции; на этой странице приведены только действия, выполняемые из кода сайта.
Инициализация
Создайте канал по инструкции со скриншотами, затем скопируйте скрипт-загрузчик и channel_id из раздела «Код для встраивания». На одной странице один раз вызовите SenlerWidget.init(config):
<script src="URL_ИЗ_ГОТОВОГО_КОДА" crossorigin="anonymous"></script>
<script>
SenlerWidget.init({
channel_id: "xxx",
});
</script>
Замените оба плейсхолдера значениями из кабинета; не подставляйте адрес скрипта-загрузчика из примера другого проекта или окружения. Параметры init, режимы размещения, пользователь и тема описаны в «Параметрах инициализации». Инициализация создаёт один экземпляр. Повторный init сначала удаляет текущий экземпляр, поэтому после запуска управляйте им методами SenlerWidget, не пересоздавая iframe и не теряя состояние диалога.
Методы
| Метод | Возвращает | Результат |
|---|---|---|
SenlerWidget.open(config?) | undefined | Применяет runtime-конфиг, если он передан, и показывает виджет. |
SenlerWidget.close() | undefined | Скрывает виджет, сохраняя iframe и состояние диалога. |
SenlerWidget.toggle() | undefined | Переключает видимость. |
SenlerWidget.isOpen() | boolean | Показывает, открыт ли виджет. |
SenlerWidget.selectDialog(dialogId) | undefined | Выбирает существующий диалог без отправки сообщения и без автоматического открытия скрытого виджета. |
SenlerWidget.setPageContext(items) | undefined | Заменяет постоянный контекст страницы. |
SenlerWidget.updateRuntime(config) | undefined | Меняет runtime-настройки без пересоздания виджета и сохранения в канал. |
SenlerWidget.createInlineTextEdit(config) | объект-контроллер | Создаёт контроллер для inline-правок. |
SenlerWidget.destroy() | undefined | Полностью удаляет iframe, кнопку и обработчики. Переданный сайтом embedded-контейнер остаётся на странице. |
Для обычного скрытия используйте close(), а не destroy(). После close() достаточно вызвать open(); после destroy() нужен новый SenlerWidget.init(...).
До готовности экземпляра isOpen() возвращает false. В режиме button_only методы открытия и закрытия ничего не показывают; подробности приведены в настройках.
После загрузки скрипта-загрузчика в SenlerWidget.runtimeProtocolVersion доступен номер текущего протокола (number). Это диагностическое значение для проверки совместимости; не связывайте с конкретным номером бизнес-логику сайта.
Runtime-параметры
Runtime-параметры — это временное состояние текущего экземпляра. Они не сохраняются в настройках канала и сбрасываются после destroy() или повторного init. open(config?) и updateRuntime(config) принимают одинаковую конфигурацию:
| Параметр | Тип | Что меняет |
|---|---|---|
lang | "ru" | "en" | "auto" | Язык интерфейса. |
display_mode | "popup" | "embedded" | Режим размещения. Контейнер для embedded должен быть передан ещё при init. |
theme_mode | "light" | "dark" | "auto" | Тему текущего экземпляра. |
border_radius | number | Скругление текущего экземпляра от 0 до 50. |
shell | object | collapse_button и mobile_edge_swipe. |
customActions | object | Полный набор доступных действий сайта и их обработчики handler в браузере. |
customActionsLanguage | "ru" | "en" | Язык описаний действий. |
autoExecuteCustomActionNames | string[] | Имена действий, которые разрешено автоматически выполнить в текущем сценарии. |
dialogId | string | Существующий диалог, который нужно выбрать. |
startNewDialog | boolean | При true создаёт новый пустой диалог. |
focusInput | boolean | При true ставит фокус в поле ввода. |
pageContextItems | array | Заменяет постоянный контекст страницы. Для навигации понятнее setPageContext(items). |
contextItems | array | Передаёт одноразовый контекст следующего сообщения. |
message | object | Подготавливает или автоматически отправляет сообщение. |
Если передан dialogId, виджет выбирает этот диалог; ID должен быть непустой строкой длиной до 200 символов. startNewDialog: true создаёт пустой диалог, а message.startNewDialog: true отправляет сообщение без текущего dialog_id. Если вместе передать dialogId и любой вариант startNewDialog: true, приоритет получает новый диалог, поэтому не объединяйте эти намерения в одном вызове. Не считайте autoExecuteCustomActionNames постоянным разрешением: передавайте список заново в каждом сценарии, где нужен автоматический запуск. Подходящие кнопки скрываются; из одного ответа виджет автоматически выполняет только первое совпавшее действие.
В autoExecuteCustomActionNames можно передать не более 20 уникальных имён. Каждое имя должно соответствовать тем же правилам, что и имя custom action: от 1 до 120 символов, первая буква — латинская, далее допустимы латинские буквы, цифры, _, ., : и -. Повторяющееся имя делает конфигурацию недопустимой. Указывайте только объявленные действия: имя без соответствующего customActions не сможет вызвать обработчик сайта.
message принимает { text, requestId?, startNewDialog?, autoSend? }. Текст должен быть непустой строкой до 10 000 символов, requestId — непустой строкой до 200 символов. autoSend: true отправляет сообщение после готовности виджета; false или отсутствие параметра только подставляет текст в поле ввода. Для такого черновика не передавайте requestId: события результата предназначены для автоматической отправки, а скрипт-загрузчик будет ждать начало запроса и сообщит об ошибке, если пользователь не отправит его в течение 10 секунд.
Неизвестный временный ключ, неверный тип или значение вне допустимого диапазона приводят к синхронной ошибке вызова. В рабочем коде передавайте только поля из этой таблицы и перехватывайте ошибки вокруг динамически собранной конфигурации.
В SPA не вызывайте destroy() и повторный init() при каждом переходе. Обновляйте страницу через setPageContext(items), а для точечного сценария используйте open({ contextItems, message }).
Сворачивание embedded-виджета
В popup крестик уже есть в шапке и скрывает окно. Плавающая кнопка открывает тот же экземпляр снова.
В embedded-режиме включите shell.collapse_button: true, если пользователю нужна кнопка сворачивания в шапке. Кнопка только сообщает сайту о намерении свернуть виджет: сайт сам вызывает SenlerWidget.close() или закрывает внешнюю панель.
SenlerWidget.init({
channel_id: "xxx",
display_mode: "embedded",
container: "#senler-widget",
shell: {
collapse_button: true,
},
onCollapse(detail) {
console.log("Пользователь запросил сворачивание", detail);
SenlerWidget.close();
},
});
Вместо onCollapse можно один раз подписаться на событие:
window.addEventListener("senler-widget:collapse-request", (event) => {
if (event.detail.display_mode === "embedded") {
SenlerWidget.close();
}
});
В detail приходят channel_id и display_mode. Если настроить и callback, и событие, сработают оба обработчика. Обычно выбирайте один способ, чтобы не выполнить сворачивание дважды.
CLOSE_WIDGET и COLLAPSE_WIDGET — внутренние сообщения протокола iframe. Сайт не должен отправлять их через postMessage.
Мобильный жест
shell.mobile_edge_swipe: true включает свайп от левого края внутри виджета. Скрипт-загрузчик не закрывает интерфейс, а отправляет senler-widget:mobile-edge-swipe.
window.addEventListener("senler-widget:mobile-edge-swipe", (event) => {
if (event.detail.side === "left") {
closeMobilePanel();
}
});
В detail приходят channel_id, display_mode и side: "left". Это навигационный сигнал для сайта, а не замена кнопки сворачивания.
Результат runtime-сообщения
Если передать message.requestId, скрипт-загрузчик отправляет событие senler-widget:runtime-message-result. Оно связывает запрос сайта с отправкой и ответом без опроса истории.
const requestId = crypto.randomUUID();
window.addEventListener("senler-widget:runtime-message-result", (event) => {
if (event.detail.request_id !== requestId) return;
if (event.detail.status === "message_sent") {
console.log("Диалог", event.detail.dialog_id);
}
if (event.detail.status === "answered") {
console.log("Ответ готов");
}
if (["message_send_failed", "message_answer_failed", "preview_failed"].includes(
event.detail.status,
)) {
console.error(event.detail.error_message);
}
});
SenlerWidget.open({
message: {
text: "Сделай текст понятнее.",
requestId,
startNewDialog: true,
autoSend: true,
},
});
Возможные статусы:
| Статус | Значение |
|---|---|
accepted | Конфигурация автоматически отправляемого сообщения принята виджетом, но отправка ещё не началась. |
sending | Началась отправка. |
message_sent | Сообщение отправлено; в событии доступен dialog_id. |
answered | Ответ завершён. |
message_send_failed | Сообщение не удалось отправить. |
message_answer_failed | Формирование ответа завершилось ошибкой. |
preview_failed | Не удалось сформировать preview для связанного сценария правки. |
В detail всегда есть request_id и status, а dialog_id и error_message появляются там, где применимы. Скрипт-загрузчик контролирует два отдельных этапа: у iframe есть 10 секунд, чтобы стать готовым и принять запрос; после передачи у виджета есть ещё 10 секунд, чтобы сообщить о начале отправки. Тайм-аут на любом из этапов приводит к message_send_failed. Диалог можно открыть через SenlerWidget.open({ dialogId }) или SenlerWidget.selectDialog(dialogId).
Для правок текста используйте inline-controller: он сам отслеживает requestId и возвращает готовый preview.
Запрос на пополнение лимита
Если в настройках канала включено «Предлагать оплатить лимит», кнопка «Пополнить» отправляет iframe служебное сообщение скрипту-загрузчику. Скрипт проверяет origin, источник iframe и канал, а затем создаёт на window событие senler-widget:credit-purchase-requested:
const expectedChannelId = "xxx";
window.addEventListener("senler-widget:credit-purchase-requested", (event) => {
if (event.detail.channel_id !== expectedChannelId) return;
openCreditPayment({
channelId: event.detail.channel_id,
leadId: event.detail.lead_id,
});
});
В event.detail находятся проверенные channel_id и lead_id. Событие только сообщает о намерении пользователя: оно не проводит оплату и не меняет кредитный остаток. Сайт всё равно должен убедиться, что channel_id относится к его интеграции, и открыть собственную форму оплаты.
Начисление после оплаты
После подтверждённой оплаты сервер сайта должен начислить купленные кредиты лиду отдельным запросом к API Senler:
POST /api/projects/{projectId}/leads/{leadId}/credits
Authorization: Bearer senler_sk_...
Content-Type: application/json
{
"credits": 50000,
"type": "purchase",
"reason": "Оплата заказа shop-order-123",
"idempotency_key": "widget-credit-purchase:shop-order-123"
}
В путь запроса подставьте projectId проекта, а leadId возьмите из события. Перед открытием оплаты проверьте, что event.detail.channel_id совпадает с каналом этой интеграции. Проектный API-ключ должен принадлежать тому же проекту и иметь право can_manage_leads. Храните ключ только на сервере: не помещайте его в конфигурацию скрипта-загрузчика, JavaScript страницы или сетевые запросы браузера.
Поле credits принимает целое количество минимальных кредитных единиц: один показываемый пользователю кредит равен 10 000 единиц, поэтому 50 000 начисляет 5 кредитов. Цена и состав заказа остаются данными сайта и в этот запрос не передаются.
Для одной оплаченной позиции всегда повторяйте один стабильный idempotency_key. При сетевой ошибке можно безопасно повторить запрос с тем же телом и ключом; новый ключ для той же позиции приведёт к повторному начислению. Начисление меняет только дополнительный остаток выбранного лида и не пополняет кредитный баланс проекта.
После начисления виджет получает обновление по уже открытому соединению в реальном времени, снимает блокировку и обновляет остаток. Дополнительный запрос из браузера и периодический опрос не нужны.
Функции обратного вызова и события
В SenlerWidget.init доступны:
contextProvider()— синхронно возвращает постоянный контекст страницы; Promise не поддерживается;onCollapse(detail)— сообщает о нажатии кнопкиshell.collapse_button.
Кнопки customActions обрабатываются через handler каждого действия, а не через общий callback.
Событие window | Когда приходит |
|---|---|
senler-widget:collapse-request | Пользователь нажал кнопку сворачивания. |
senler-widget:mobile-edge-swipe | Пользователь сделал разрешённый свайп от левого края. |
senler-widget:credit-purchase-requested | Пользователь запросил покупку дополнительных кредитов; в detail находятся channel_id и lead_id. |
senler-widget:runtime-message-result | Runtime-сообщение принято, завершено или завершилось ошибкой. |
senler-widget:stage | Изменился диагностический этап загрузки, соединения, отправки, истории или файла. Используйте для наблюдаемости, не для бизнес-логики. |
У senler-widget:stage в detail всегда есть area, phase и timestamp; дополнительно могут прийти attempt, duration_ms и итог result: "success" | "error" | "timeout".
area | Возможные phase |
|---|---|
loader | instance-created, iframe-created, iframe-loading, iframe-loaded, app-mounted, ready-timeout, error |
bootstrap | loading, success, error, timeout, interactive |
dialogs | idle, loading, refetching, success, empty, error |
history | idle, loading, refetching, loading-more, success, empty, error |
realtime | disabled, token-loading, connecting, connected, reconnecting, disconnected, error |
message | optimistic, sending, queued, sent, failed, waiting, typing, streaming, done, error |
upload | requesting-url, uploading, confirming, ready, error |
Это диагностический поток, а не конечный автомат бизнес-процесса. Набор этапов может расширяться, а отдельные фазы — повторяться или пропускаться, поэтому бизнес-сценарий не должен зависеть от их конкретной последовательности.
Действия во встроенном приложении
Обычным страницам достаточно разметки элементов. Для отдельного приложения со своим протоколом управления можно передать pageElementActions в init. Это локальный адаптер сайта, не инструмент MCP; через updateRuntime он не меняется.
execute(payload)получаетevent_id,attempt_id,action,targetлибоtarget_chainи, для ввода или выбора,value. Цель содержитcontext_id, необязательнуюroleи паруentity_type/entity_idдля конкретной сущности.- Верните
null, если цель не относится к приложению: её обработает загрузчик. Для своей цели верните результат с теми жеevent_id,attempt_id,action, временемexecuted_atи статусомsuccess,not_found,blockedилиfailed. Для отказа добавьтеerror_codeи понятныйerror_message. - Поддерживается Promise. Адаптер должен завершиться за 2,5 секунды. После исключения, неверного результата или тайм-аута загрузчик сообщает ошибку и не повторяет действие другим способом. Не отбрасывайте ограничения цели: если приложение не умеет выбирать конкретную сущность, верните
blocked. clear(scope)очищает подсветку адаптера:toolотносится к подсказке агента,selectedк выбранному пользователем элементу,allк обеим. Загрузчик вызывает очистку при новой команде, закрытии подсказки и уничтожении экземпляра.
SenlerWidget.init({
channel_id: "YOUR_CHANNEL_ID",
pageElementActions: {
async execute(payload) {
const target = payload.target ?? payload.target_chain.at(-1);
if (!target?.context_id.startsWith("my-app.")) return null;
return appBridge.execute(payload);
},
clear(scope) {
appBridge.clearHighlights(scope);
},
},
});
appBridge реализует разработчик приложения; это не метод Senler. Его execute должен сохранить полную цель и вернуть результат описанного выше формата. Не подключайте рядом второй слушатель PAGE_ELEMENT_ACTION: загрузчик уже принимает команду и отправляет результат.
При передаче команды дочерним iframe загрузчик ждёт ответ одного окна, прежде чем обращаться к следующему. Продолжить поиск позволяет только not_found; успех, отказ или ошибка завершают попытку. На поиск в дочерних окнах отводится 2,5 секунды. Если к этому моменту текущий iframe не ответил, возвращается failed с кодом child_frame_action_timeout, без повторного выполнения в другом окне. Тайм-аут не доказывает, что действие не произошло: перед повтором проверьте состояние приложения.
Связанные страницы
- Параметры инициализации — режим размещения, пользователь, тема и функции.
- Контекст страницы — постоянные и одноразовые данные сообщения.
- Custom actions — кнопки, которые обрабатывает сайт.
- Inline-правки — AI-правка текста с preview.