enВойти в Senler

Public API виджета

Коротко

Public API нужен, чтобы установить виджет на сайт и управлять им из JavaScript: открыть, закрыть, встроить в контейнер, включить выбор элемента и передать данные пользователя. Если нужен только обычный popup, достаточно базовой инициализации с channel_id.

Для подсказок по элементам сайта важна настройка features.element_selection: true. Она включает кнопку выбора элемента в виджете, но качество ответа все равно зависит от data-ai-* разметки на сайте и связанной Markdown-документации.

Базовая инициализация

Инициализация выполняется один раз после загрузки скрипта.

SenlerWidget.init({
  channel_id: "xxx",
  user: { external_id: "user_123" },
});

Для embedded-режима:

SenlerWidget.init({
  channel_id: "xxx",
  display_mode: "embedded",
  container: "#senler-widget",
  theme: {
    height: 600,
  },
  user: { external_id: "user_123" },
});

Методы

  • SenlerWidget.open(config?) - показать popup- или embedded-wrapper; если передан config, сначала применить runtime-настройки.
  • SenlerWidget.close() - скрыть wrapper в любом режиме, не удаляя iframe и состояние диалога.
  • SenlerWidget.toggle() - переключить видимость wrapper в любом режиме.
  • SenlerWidget.isOpen() - вернуть true, если wrapper сейчас открыт, и false, если он скрыт.
  • SenlerWidget.selectDialog(dialogId) - выбрать существующий диалог без отправки сообщения.
  • SenlerWidget.setPageContext(items) - заменить постоянный контекст текущей страницы.
  • SenlerWidget.updateRuntime(config) - обновить часть runtime-настроек без пересоздания виджета и без сохранения этих значений в настройках канала.
  • SenlerWidget.createInlineTextEdit(config) - создать headless-controller для inline-правок в поле сайта.
  • SenlerWidget.destroy() - полностью удалить wrapper, iframe, кнопку и обработчики со страницы. Чтобы вернуть виджет после destroy(), нужен новый SenlerWidget.init(...).

Для обычного скрытия используйте close(), а не destroy(): после close() достаточно вызвать open(), и текущий экземпляр продолжит работу.

Runtime-параметры

open(config?) и updateRuntime(config) принимают одинаковую runtime-конфигурацию:

ПараметрЧто меняет
langЯзык ru, en или auto.
display_modeРежим popup или embedded. Для перехода в embedded контейнер должен быть передан ещё при init; при переходе в popup wrapper переносится в document.body и остаётся закрытым до open().
theme_modeСветлую, тёмную или автоматическую тему текущего экземпляра.
border_radiusСкругление текущего экземпляра от 0 до 50.
shellОбъект с collapse_button и mobile_edge_swipe.
customActionsДоступные действия сайта и их browser-side handler.
customActionsLanguageЯзык описаний действий: ru или en.
autoExecuteCustomActionNamesИмена действий, которые разрешено автоматически выполнить в текущем сценарии.
dialogIdСуществующий диалог, который нужно выбрать.
startNewDialogСоздать новый пустой диалог.
focusInputПоставить фокус в поле ввода.
pageContextItemsЗаменить постоянный контекст страницы; для обычной навигации понятнее использовать setPageContext(items).
contextItemsПередать одноразовый контекст следующего сообщения.
messageПодготовить или автоматически отправить сообщение.

Если передан dialogId, виджет открывает или выбирает этот диалог; startNewDialog: true создаёт пустой новый диалог, а message.startNewDialog: true отправляет сообщение в новый диалог. autoExecuteCustomActionNames сбрасывается при выборе другого диалога или нового сценария без этого поля. Если обновляются customActions, loader отправляет в iframe их описания, а handler остаётся на странице сайта и заменяет прежний обработчик действия с тем же именем.

message принимает { text, requestId?, startNewDialog?, autoSend? }. text должен быть непустой строкой до 10 000 символов. requestId - непустая строка до 200 символов для корреляции runtime-сообщения с событием результата. startNewDialog: true начинает отправку без текущего dialog_id, а autoSend: true отправляет текст автоматически после готовности виджета. message, contextItems и pageContextItems относятся к runtime-обновлению: SenlerWidget.init принимает только contextProvider для начального контекста страницы.

В SPA виджет инициализируется один раз. Не вызывайте destroy() и повторный init() при каждом переходе: это сбрасывает состояние iframe и может выглядеть как перезагрузка чата. Для навигации обновляйте только контекст через SenlerWidget.setPageContext(items), а для точечного сценария вроде "открыть виджет по заказу" используйте SenlerWidget.open({ contextItems, message }).

Крестик в popup и embedded

В popup обычный крестик уже есть в шапке. Пока iframe загружается, loader также показывает крестик на рамке окна. Добавлять собственную кнопку поверх iframe не нужно: нажатие скрывает окно, а плавающая кнопка открывает его снова.

В embedded-режиме обычный крестик закрытия скрыт, потому что виджет занимает контейнер сайта. Чтобы показать крестик внутри шапки самого виджета, передайте shell.collapse_button: true. Нажатие сообщает сайту о намерении свернуть виджет, но само по себе не скрывает embedded-wrapper: сайт должен вызвать SenlerWidget.close() или скрыть более крупную область своего интерфейса.

SenlerWidget.init({
  channel_id: "xxx",
  display_mode: "embedded",
  container: "#senler-widget",
  shell: {
    collapse_button: true,
  },
  onCollapse(detail) {
    console.log("Пользователь нажал крестик", detail);
    SenlerWidget.close();
  },
});

После этого SenlerWidget.open() снова покажет тот же embedded-экземпляр и сохранённое состояние диалога. Если сайт скрывает не wrapper, а свою панель или модальное окно целиком, в onCollapse нужно закрыть эту область вместо вызова SenlerWidget.close().

Вместо onCollapse можно один раз подписаться на событие window:

window.addEventListener("senler-widget:collapse-request", (event) => {
  if (event.detail.display_mode === "embedded") {
    SenlerWidget.close();
  }
});

В detail приходят channel_id и display_mode. Выберите один способ обработки: callback или событие, иначе один клик будет обработан дважды. Отдельного onClose для обычного popup-крестика нет.

Важно различать команды:

  • SenlerWidget.close() - публичный host-метод, который скрывает popup- или embedded-wrapper;
  • SenlerWidget.destroy() - удаляет экземпляр и подходит только для полного завершения интеграции;
  • внутреннее сообщение CLOSE_WIDGET приходит от обычного popup-крестика и игнорируется loader-ом в embedded-режиме;
  • внутреннее сообщение COLLAPSE_WIDGET приходит от кнопки shell.collapse_button, после чего loader вызывает onCollapse(detail) и отправляет senler-widget:collapse-request.

CLOSE_WIDGET и COLLAPSE_WIDGET относятся к протоколу iframe. Сайт не должен отправлять их вручную через postMessage.

Жест от левого края

shell.mobile_edge_swipe: true включает распознавание свайпа от левого края внутри виджета. Loader не закрывает интерфейс автоматически, а отправляет на window событие 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". Это отдельный мобильный навигационный сигнал, а не замена onCollapse.

Жизненный цикл runtime-сообщения

Если host-страница передает message.requestId, loader отправляет в window событие senler-widget:runtime-message-result. Это событие нужно кастомным runtime-сценариям, где host-странице важно связать отправку сообщения с результатом без опроса истории диалога. Для inline-правок в текстовых полях используйте createInlineTextEdit(config): он уже отслеживает requestId, регистрирует нужный custom_action и возвращает готовый preview.

const requestId = crypto.randomUUID();

window.addEventListener("senler-widget:runtime-message-result", (event) => {
  if (event.detail.request_id !== requestId) return;

  if (event.detail.status === "sent") {
    // event.detail.dialog_id можно сохранить, чтобы позже открыть этот диалог.
  }

  if (event.detail.status === "answered") {
    // Ответ AI сформирован.
  }

  if (event.detail.status === "failed") {
    // event.detail.error_message содержит причину, если она известна.
  }
});

SenlerWidget.open({
  contextItems,
  message: {
    text: "Сделай текст понятнее.",
    requestId,
    startNewDialog: true,
    autoSend: true,
  },
});

Статус sent означает, что сообщение принято и известен dialog_id. Статус answered приходит по streaming done или по финальному событию сообщения ассистента. Статус failed приходит при ошибке отправки или streaming. Если нужно открыть этот же диалог в большом виджете, вызовите SenlerWidget.open({ dialogId: event.detail.dialog_id }) или SenlerWidget.selectDialog(event.detail.dialog_id).

Inline-правки в полях сайта

createInlineTextEdit(config) дает сайту headless-controller для сценария "выделить текст → спросить AI → показать before/after". Loader не рисует dropdown и не меняет поле: внешний сайт сам показывает кнопку, меню быстрых действий, состояние загрузки и preview в своем стиле. Controller связывает выбранный текст, сообщение в виджет, кнопку применения senler.previewTextEdit, requestId и готовый preview.

const textarea = document.querySelector("#description");

const inlinePendingStatuses = new Set(["sending", "message_sent", "answered"]);
const inlineFailureStatuses = new Set([
  "message_send_failed",
  "message_answer_failed",
  "preview_failed",
]);

const inlineEdit = SenlerWidget.createInlineTextEdit({
  fieldId: "product:123:description",
  fieldLabel: "Описание товара",
  getValue: () => textarea.value,
  getSelection: () => ({
    text: textarea.value.slice(textarea.selectionStart, textarea.selectionEnd),
  }),
  getContextItems: () => [
    {
      id: "product:123",
      kind: "product",
      role: "business_context",
      display: { label: "Товар #123" },
      ref: { entity_type: "product", entity_id: "123", route: "/products/123" },
    },
  ],
  onStatusChange: ({ status, error }) => {
    renderInlineLoading(inlinePendingStatuses.has(status));
    if (inlineFailureStatuses.has(status)) {
      renderInlineFallback(error?.message);
    }
  },
  onPreview: (preview) => {
    renderTextEditReview({
      before: preview.sourceText,
      after: preview.replacementText,
      changedFrom: preview.selectedText,
      changedTo: preview.selectedTextReplacement,
      onAccept: () => {
        textarea.value = preview.replacementText;
        textarea.dispatchEvent(new Event("input", { bubbles: true }));
      },
    });
  },
});

document.querySelector("#ai-improve").addEventListener("click", () => {
  inlineEdit.ask("Сделай понятнее");
});

document.querySelector("#ai-chat").addEventListener("click", () => {
  inlineEdit.openChat();
});

fieldId должен стабильно идентифицировать поле на странице. getValue() возвращает полный текущий текст поля. getSelection() возвращает выделение как строку или объект { text }; если сайт сам хранит выделение, можно передать его напрямую: inlineEdit.ask({ text: "Исправь ошибки", selectedText }). getContextItems() возвращает бизнес-контекст страницы: товар, заказ, проект или другую сущность. Controller сам добавляет теги для поля, выделенного текста и задачи ассистента.

Метод ask(text | { text, actionLabel?, selectedText? }) создает новый фоновый диалог, отправляет запрос AI и вызывает onPreview(preview), когда правка готова. Метод openChat({ selectedText? }) открывает большой виджет с тем же контекстом: если фоновый диалог уже создан, откроется он; иначе будет создан новый пустой диалог с фокусом в поле ввода. Метод getDialogId() возвращает последний известный диалог, а destroy() снимает обработчики controller-а.

Preview всегда содержит полный текст поля до и после изменения: sourceText, replacementText, selectedText, selectedTextReplacement, summary. Если пришла только замена выделенного фрагмента, loader соберет полный replacementText сам, но только когда исходный фрагмент найден в поле однозначно. Применение изменения остается на стороне сайта, потому что у разных редакторов разные события ввода, undo/redo, autosave и валидация.

Inline-controller использует свой набор статусов, а не общий sent/answered/failed из senler-widget:runtime-message-result:

  • sending - controller начал отправку запроса;
  • message_sent - сообщение принято, фоновый диалог создан или известен;
  • answered - ответ сформирован, но preview еще может ожидаться;
  • preview_ready - правка готова, onPreview получает before/after;
  • message_send_failed - не удалось отправить runtime-сообщение;
  • message_answer_failed - сообщение ушло, но ответ не завершился успешно;
  • preview_failed - ответ был, но preview-правку не удалось получить или разобрать.

Host-страница должна показывать свое состояние загрузки до preview_ready или ошибки, а при message_send_failed, message_answer_failed и preview_failed давать понятный fallback: повторить запрос или открыть чат через openChat(). openChat() использует уже известный фоновый диалог, если он был создан, поэтому пользователь видит тот же контекст, а не отдельную пустую переписку.

Эти статусы нужны разработчику интеграции. В обычной поддержке объясняйте состояние человеческими словами: AI готовит вариант, правку не удалось получить, можно повторить запрос или открыть чат.

Callback-параметры

В SenlerWidget.init можно передать callback-параметры:

  • contextProvider() - синхронно возвращает массив постоянного контекста текущей страницы при инициализации. Promise не поддерживается; при SPA-переходах обновляйте контекст через SenlerWidget.setPageContext(items).
  • onCollapse(detail) - вызывается при нажатии кнопки сворачивания, включённой через shell.collapse_button. В detail приходят channel_id и display_mode. Callback может вызвать SenlerWidget.close() или закрыть внешнюю панель сайта.

Custom action кнопки обрабатываются не отдельным top-level callback, а через handler внутри customActions.

События страницы

Событие windowКогда приходит
senler-widget:collapse-requestПользователь нажал кнопку shell.collapse_button; содержит channel_id и display_mode.
senler-widget:mobile-edge-swipeПользователь сделал разрешённый свайп от левого края; дополнительно содержит side: "left".
senler-widget:runtime-message-resultRuntime-сообщение с requestId принято, завершено или завершилось ошибкой.
senler-widget:stageИзменился диагностический этап загрузки, соединения, отправки, истории или загрузки файла. Используйте для наблюдаемости, а не для бизнес-логики.

Custom actions для кнопок в чате

Чтобы в ответе появилась кнопка, которую обработает сайт, host-страница должна явно объявить доступные customActions. В публичном loader API это объект, где ключ - имя действия, а значение содержит title, description, опциональную payloadSchema и обязательный handler. Виджет передает в AI только описание действия, а сам handler остается в браузере и вызывается при клике по кнопке custom_action.

Если title и description написаны только на одном языке, укажите рядом customActionsLanguage: "ru" или customActionsLanguage: "en". Это поле не переводит тексты, а задает язык описаний custom actions; если его не передать, используется двуязычный поиск по ru+en.

SenlerWidget.init({
  channel_id: "xxx",
  customActionsLanguage: "ru",
  customActions: {
    "site.openOrder": {
      title: "Открыть заказ",
      description: "Открывает карточку заказа по order_id.",
      payloadSchema: {
        type: "object",
        properties: {
          order_id: { type: "string" },
        },
        required: ["order_id"],
        additionalProperties: false,
      },
      handler(detail) {
        openOrder(detail.payload?.order_id);
      },
    },
  },
});

Имя действия берется из ключа объекта customActions. Оно должно начинаться с латинской буквы и может содержать латинские буквы, цифры, _, ., : и -. В одном контексте можно объявить до 20 действий. title и description помогают понять, когда использовать действие. title ограничен 80 символами, description - 240 символами. payloadSchema описывает JSON object payload: корневая схема должна быть object, схема не должна быть глубже 8 уровней и сериализуется не больше чем в 1200 символов. Общий descriptor payload всех действий ограничен 6000 символами.

Когда в диалоге виджета включены кнопки, доступны только действия, которые host-страница явно передала через customActions. Если страница не передала действие, такую кнопку нельзя обещать или выполнять.

custom_action.name нельзя придумывать: используются только имена, явно переданные host-страницей через customActions. open_url в виджете обрабатывается host-страницей: target: "self" меняет текущую страницу, а без self ссылка открывается в новой вкладке. Для переходов внутри сайта лучше объявлять custom_action, а не открывать прямой URL через open_url.

Контекст сообщения

Host-страница может передать два вида контекста. Постоянный контекст страницы задается через contextProvider при SenlerWidget.init или через SenlerWidget.setPageContext(items) при переходах без перезагрузки. Одноразовый контекст следующего сообщения передается как contextItems через SenlerWidget.open({ contextItems, message }) или SenlerWidget.updateRuntime({ contextItems, message }). Это нужно, когда контекст известен без ручного выбора элемента: открытая карточка заказа, проект, рекомендация AI-сводки, цель действия или другая бизнес-сущность.

contextProvider должен вернуть уже готовый массив синхронно. Если название страницы или сущность загружаются позже, сначала можно вернуть базовый page item, а после загрузки данных вызвать SenlerWidget.setPageContext(items) с актуальной подписью, route и avatar_url, если он есть.

function buildPageContext() {
  return [
    {
      id: `page:${window.location.pathname}`,
      kind: "page",
      role: "technical",
      display: {
        label: document.title || "Текущая страница",
        subtitle: window.location.pathname,
      },
      ref: {
        url: window.location.href,
        route: window.location.pathname,
      },
    },
  ];
}

SenlerWidget.init({
  channel_id: "xxx",
  contextProvider: buildPageContext,
});

SenlerWidget.setPageContext(buildPageContext());

SenlerWidget.open({
  contextItems: [
    {
      id: "order:123",
      kind: "order",
      role: "business_context",
      display: { label: "Заказ #123", subtitle: "Открытая карточка" },
      ref: { entity_type: "order", entity_id: "123", route: "/orders/123" },
    },
  ],
  message: {
    text: "Опиши, что можно сделать с этим заказом.",
    startNewDialog: true,
    autoSend: true,
  },
});

Каждый item должен иметь id, kind, role, display.label и может иметь ref, snapshot, payload. Роль может быть technical, user_selected, business_context или action_target. В одном сообщении можно передать до 12 items. Лимиты: id до 120 символов, kind до 80, display.label до 80, display.subtitle до 140, display.icon до 40, display.avatar_url до 500. ref, snapshot и payload должны быть объектами, если переданы.

Используйте стабильные id: например page:/orders/123, project:019..., order:123. Если один и тот же объект приходит из page context и draft contextItems, совпадающий id оставит один тег вместо дублей. Для разных сущностей используйте разные kind и разные id, даже если подписи похожи.

Выбранный элемент тоже показывается как тег контекста с типом selected_element. Поэтому отдельное поле выбранного элемента не нужно: нужный контекст уже находится в тегах рядом с сообщением.

В поле ввода постоянный page context, выбранный элемент и draft contextItems показываются вместе как теги и дедуплицируются по id. Пользователь может удалить любой тег перед отправкой; после отправки draft-контекст и выбранный элемент очищаются, а постоянный page context остается для следующих сообщений. В истории чата теги уже отправленного сообщения нельзя удалить: их можно только раскрыть, открыть ref.route/ref.url или посмотреть детали kind, role, ref, snapshot и payload.

После отправки итоговые теги сохраняются в истории диалога: страница, проект, выбранный элемент, цель действия или другая сущность. Если у item есть display.avatar_url, виджет показывает аватар в теге; если аватара нет, используется display.icon или стандартная иконка по kind.

Встроенные сценарии используют тот же формат. Виджет может добавить страницу, ассистент кабинета добавляет проект, а кнопка применения рекомендации AI-сводки добавляет целевого агента, выбранную рекомендацию и сценарий применения. Проектный item добавляется только когда текущий route проекта совпал с загруженным проектом; в display и snapshot передаются имя проекта, public_id и, если есть, avatar_url. В диалогах эти items отображаются как теги рядом с сообщением.

Служебные элементы виджета

Сам виджет может иметь служебные маркеры для тестов, аналитики и стабильной работы UI, но они не являются целями AI-подсказок. Подсветка указывает на элементы страницы сайта, кабинета или iframe диалогов; UI самого виджета пользователь читает как обычный чат.

Если нужно объяснить кнопку или состояние внутри виджета, пишите это словами в документации. Для подсветки на странице используйте разметку внешнего сайта или кабинета: data-ai-context-id, data-ai-label и data-ai-kb-query.

Встроенная история чатов

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

Поиск в истории включается от двух символов. Без поиска история загружается порциями и догружает следующие элементы при прокрутке. Если /init вернул текущий диалог, виджет старается открыть его при первом входе; иначе открывает первый доступный диалог.

Top-level параметры

Loader принимает только поддерживаемые ключи:

КлючНазначение
channel_idОбязательный ID канала типа «Виджет».
userДанные посетителя и, при включённой привязке к авторизации, external_id с серверной подписью user_hash.
themeОформление, размеры, тексты и плавающая кнопка.
featuresДоступные функции чата.
langru, en или auto.
config_sourcelocal для настроек в коде или remote для сохранённых настроек канала; по умолчанию remote.
display_modepopup или embedded; по умолчанию popup.
button_onlyПоказать только неинтерактивный вид плавающей кнопки без iframe чата. Не совместим с embedded; методы чата в этом режиме не работают.
containerCSS-селектор или DOM Element для embedded-wrapper.
shellОбъект с collapse_button и mobile_edge_swipe.
onCollapseCallback запроса на сворачивание.
contextProviderСинхронный поставщик постоянного контекста страницы.
customActionsОбъект действий сайта.
customActionsLanguageЯзык описаний действий: ru или en.
debugtrue включает дополнительные сообщения loader-а в консоли.

Другие top-level ключи считаются ошибкой конфигурации.

Если виджет не запускается, сначала проверьте channel_id, доступность loader-скрипта, корректность container для embedded-режима и отсутствие неподдерживаемых top-level ключей.

Данные пользователя

Объект user поддерживает external_id, user_hash, email, phone, first_name, last_name, avatar_url и объект data. user_hash формируется только на сервере сайта с секретом канала; секрет нельзя помещать в браузерный код.

Shell

  • collapse_button: true показывает кнопку сворачивания внутри шапки виджета;
  • mobile_edge_swipe: true включает мобильный жест от левого края.

display_mode по умолчанию равен popup. Для embedded нужно передать container с CSS-селектором или использовать auto-init через элемент с data-senler-config.

Комбинация display_mode: "embedded" и button_only: true не поддерживается.

У popup есть плавающая кнопка и встроенное закрытие окна. Embedded-виджет занимает контейнер сайта и не использует плавающую кнопку. Публичные методы open(), close() и toggle() управляют wrapper-ом в обоих режимах. Крестик shell.collapse_button только запрашивает сворачивание: обработайте onCollapse или senler-widget:collapse-request.

Theme

В theme поддерживаются:

  • chat_title;
  • default_dialog_title;
  • theme_mode;
  • position;
  • width;
  • height;
  • border_radius;
  • shadow_enabled;
  • welcome_message;
  • empty_state_message;
  • button.

Локализованные тексты принимают объект { ru?: string, en?: string }, а не обычную строку.

theme.position управляет положением popup-окна и принимает только bottom-right, bottom-left, top-right, top-left.

theme.button.position управляет toggle-кнопкой и дополнительно поддерживает hidden. Чтобы скрыть кнопку, используйте theme.button.position = "hidden". Значение theme.position = "hidden" не поддерживается.

theme.button поддерживает position, а также цветовые объекты light и dark. В каждом цветовом объекте допустимы background и icon в формате #RRGGBB.

Скругление и источник настроек

  • theme: { border_radius: 18 } задаёт начальное скругление от 0 до 50 при config_source: "local";
  • SenlerWidget.updateRuntime({ border_radius: 18 }) сразу меняет wrapper и содержимое iframe только у текущего экземпляра и не сохраняет значение в канал;
  • при config_source: "remote" постоянным источником темы служат сохранённые настройки канала. Не используйте несохранённый theme.border_radius в init как замену серверной настройке: для временного изменения вызовите updateRuntime, для постоянного измените радиус в кабинете и сохраните;
  • предпросмотр в кабинете сразу применяет текущий черновик и выбранный режим темы. Это ещё не публикация: после сохранения remote-код подхватит значение с сервера, а для local-кода нужно скопировать обновлённый код на сайт.

Features

В features поддерживаются:

  • file_upload;
  • voice_messages;
  • emoji;
  • split_view;
  • element_selection.

element_selection по умолчанию выключен. Если включить features.element_selection: true, посетитель сайта сможет нажать в виджете "Выбрать элемент", кликнуть по элементу страницы и отправить вопрос вместе с контекстом этого элемента.

Для качественных ответов владельцу сайта стоит добавить data-ai-* разметку на важные кнопки, поля, карточки товаров, тарифы и шаги оформления. Подробная публичная инструкция: site-actions/website-data-ai-markup.md.

Где это в интерфейсе

Основные элементы раздела:

  • список каналов
  • пункт «Каналы» в боковом меню
  • кнопка добавления канала
  • кнопка выбора платформы Виджет
  • страница настроек канала