Перейти к основному содержимому
Каналы

Виджеты для сайта

Встраивайте голосовых агентов на сайт с настраиваемыми виджетами

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

Ищете callback-виджет?

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

Типы виджетов​

Говорило предлагает четыре типа браузерных виджетов плюс отдельный Виджет обратного звонка для телефонных callback:

ВиджетИмя тегаОписаниеЛучше всего для
Плавающий виджетgovorilo-floating-callOrb-кнопка, плавающая над вашей страницейВсегда видимый призыв к действию
Pill-виджетgovorilo-pill-callКомпактная пилюлевидная кнопка, размещённая inline в вашем контентеМинимальный footprint внутри существующего макета
Плавающий Pill-виджетgovorilo-pill-floating-callТа же компактная форма pill, что и у Pill, но плавает со страницей, как виджет FloatingКогда нужна эстетика pill, следующая за посетителем при скролле
Инлайн-виджетgovorilo-inline-callПолноразмерная кнопка вызова, встроенная в контент страницыВыделенные секции «Поговорить с нами»
Виджет обратного звонкаgovorilo-callbackФорма для номера телефона; агент перезванивает посетителюЛидген-страницы, high-intent лендинги — см. Виджет обратного звонка

В кабинете плавающая «пилюля» — это код Pill, переключённый в режим Float. Её тег — govorilo-pill-floating-call: проверьте, что в скопированном коде стоит именно он.

Ссылка «Посмотреть пример» на вкладке «Виджеты» открывает живую демо-страницу с виджетом агента.


Общие атрибуты​

Все типы виджетов поддерживают эти ключевые атрибуты:

АтрибутОбязательноОписаниеПо умолчанию
agent-idДаУникальный идентификатор агента—
voice-service-urlНетПереопределить URL голосового сервисаАвтоопределение
api-base-urlНетПереопределить базовый URL APIАвтоопределение
support-session-tokenНетКороткоживущий токен, который ваш сервер выдаёт вошедшему пользователю; передаётся вашим MCP-серверам. Только Floating, Pill, Pill Floating и Inline — см. Опознание вошедшего пользователя—

Атрибуты отображения​

АтрибутОписаниеЗначенияПо умолчанию
positionПозиция виджета на страницеbottom-right, bottom-left, top-right, top-left, staticbottom-right
sizeРазмер виджета в пикселяхЧисло120 (Floating), 370 (Inline; на маленьких экранах уменьшается)
orb-sizeДиаметр сферы в виджетах PillЧисло48
themeИмя цветовой темыСм. Цветовые темыdefault

Атрибуты текста кнопки​

АтрибутОписаниеПо умолчанию
button-start-textТекст на кнопке до звонка"Call" (Floating), "Start Call" (Inline), "Talk to AI Agent" (Pill)
button-connecting-textТекст при соединении"Connecting..."
button-end-textТекст во время звонка"End call" (Pill)

Подписи по умолчанию — на английском; для русскоязычного сайта задайте свои, например button-start-text="Позвонить".

Атрибуты сферы​

Трёхмерная сфера реагирует на голос. Самые полезные настройки:

АтрибутОписаниеПо умолчанию
glow-intensityСила свечения, 0–20.8
audio-reactivityНасколько сильно сфера реагирует на звук3.0
theme-modeauto (как в системе), dark или lightauto

Остальные (morph-strength, noise-speed, rotation-speed и другие) перечислены в README пакета.

Атрибуты Terms​

АтрибутОписаниеПо умолчанию
terms-enabledВключить диалог согласия до звонкаfalse
terms-contentMarkdown-форматированный текст согласия""
terms-urlСсылка на вашу страницу с условиями использования"https://example.ru/terms"
privacy-urlСсылка на вашу страницу с политикой конфиденциальности"https://example.ru/privacy"
к сведению

Terms-атрибуты, заданные на HTML-элементе, переопределяются настройками виджета агента, полученными из API, если только skip-fetch не выставлен в true.

Атрибуты звука​

АтрибутОписаниеПо умолчанию
sound-enabledВключить звуки старта/окончания звонкаtrue
sound-volumeГромкость звуковых эффектов0.25
sound-presetИдентификатор пресета звука"1"

Цветовые темы​

Кастомизируйте внешний вид виджета с помощью 11 встроенных цветовых тем:

ТемаЗначение
Defaultdefault
Purplepurple
Blueblue
Cyancyan
Emeraldemerald
Amberamber
Tangerinetangerine
Roserose
Emberember
Blackblack
Whitewhite

У каждой темы есть тёмный и светлый варианты. Задайте тему через атрибут theme в embed-коде или сконфигурируйте её в настройках виджета агента.


Примеры встраивания​

Плавающий виджет​

<govorilo-floating-call agent-id="YOUR_AGENT_ID"></govorilo-floating-call>
<script src="https://app.govorilo.tech/widget/govorilo-widget.js" async></script>

Плавающий виджет с темой и позицией​

<govorilo-floating-call
agent-id="YOUR_AGENT_ID"
theme="emerald"
position="bottom-left"
size="140"
></govorilo-floating-call>
<script src="https://app.govorilo.tech/widget/govorilo-widget.js" async></script>

Pill-виджет​

<govorilo-pill-call agent-id="YOUR_AGENT_ID"></govorilo-pill-call>
<script src="https://app.govorilo.tech/widget/govorilo-widget.js" async></script>

Инлайн-виджет​

<govorilo-inline-call agent-id="YOUR_AGENT_ID"></govorilo-inline-call>
<script src="https://app.govorilo.tech/widget/govorilo-widget.js" async></script>

Плавающий Pill-виджет​

<govorilo-pill-floating-call agent-id="YOUR_AGENT_ID"></govorilo-pill-floating-call>
<script src="https://app.govorilo.tech/widget/govorilo-widget.js" async></script>

Фреймворки и приложения​

Виджеты — обычные веб-компоненты и работают в любом стеке. Несколько замечаний:

СтекЧто сделать
React ниже 19Подключите скрипт виджета, как показано выше, — он сам регистрирует теги govorilo-…. React ниже 19 передаёт веб-компонентам только строки: объекты и обработчики событий задавайте через ref или оберните тег в createComponent из @lit/react с классом customElements.get('govorilo-…'). React 19 поддерживает веб-компоненты сам
Next.jsЗагружайте виджет только на клиенте: dynamic(..., { ssr: false }) вокруг обёртки @lit/react
VueПодключите скрипт виджета, как показано выше, — теги govorilo-… зарегистрируются сами; используйте их в шаблоне (в compilerOptions.isCustomElement отметьте теги, начинающиеся с govorilo-)
WebView на iOS / AndroidПриложение должно дать WebView доступ к микрофону и разрешить WebRTC, иначе звонок не начнётся
Обновление виджета

Скрипт по адресу https://app.govorilo.tech/widget/govorilo-widget.js всегда последней версии: исправления и новые возможности приходят сами, код на сайте менять не нужно.


События виджета​

Виджеты генерируют события, которые можно слушать в JavaScript:

СобытиеОписание
status-changedСрабатывает при смене статуса звонка
connectingЗвонок устанавливается
connectedЗвонок активен
idleНет активного звонка
errorПроизошла ошибка
audio-trackПолучен удалённый аудиотрек (для визуализации)
local-audio-trackЛокальный аудиотрек микрофона (для визуализации)
microphone-enabledМикрофон был включён
microphone-disabledМикрофон был выключен
call-startСрабатывает при успешном старте звонка
call-endСрабатывает при завершении звонка

Пример: слушаем события​

const widget = document.querySelector('govorilo-floating-call');

widget.addEventListener('call-start', () => {
console.log('Call started');
});

widget.addEventListener('call-end', () => {
console.log('Call ended');
});

Как разрешить агенту открывать страницы​

Готовый код обработчика показан в блоке «Навигация по сайту» на вкладке «Виджеты». Во время звонка из браузера агент может попросить ваш сайт открыть страницу — «давайте покажу тарифы» — и посетитель видит это, не прерывая разговор.

Решение остаётся за вашей страницей. Агент присылает запрос, ваш код решает, что он означает. Открыть адрес, переключить вкладку, прокрутить к разделу и раскрыть аккордеон — всё это допустимые ответы.

Нужны три шага, и по отдельности ни один не работает.

Шаг 1 — Скажите агенту, какие страницы есть​

Агент не видит карту вашего сайта. Он просит только тот путь, который вы ему дали, — перечислите их в промпте агента или в базе знаний:

Страницы сайта:
/pricing — тарифы и цены
/contact — форма связи и телефон
/product/crm — CRM

Пропустите этот шаг — и агенту нечего просить, он просто не попытается.

Шаг 2 — Обработайте запрос на своей странице​

Добавьте это один раз, где угодно после скрипта виджета. Событие всплывает по документу, так что document — подходящее место:

<script>
document.addEventListener('agent-command', (event) => {
const { type, payload } = event.detail;

if (type === 'navigate') {
// SPA: переход без перезагрузки — звонок продолжается.
router.push(payload.path);

// Обычный сайт: открыть вторую вкладку, эта (и звонок) останется жива.
// window.open(payload.path, '_blank');

event.preventDefault(); // ← это и говорит агенту «принято»
}
});
</script>
Полная перезагрузка страницы обрывает звонок

Звонок живёт в этой странице. window.location.href = … выгружает документ, и разговор уходит вместе с ним — посетителя обрывает на полуслове. Если есть роутер, переходите без перезагрузки; иначе открывайте страницу в новой вкладке. Переподключения нет: перезагрузку не переживает ничто.

preventDefault() обязателен

Это единственный способ сказать «я обработал». Без него агенту сообщат, что сайт не поддерживает навигацию: он перестанет пытаться до конца звонка и вернётся к объяснениям, куда нажать. В консоли ничего не появится — страница просто выглядит так, будто проигнорировала запрос, потому что так и есть.

Шаг 3 — Проверьте​

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


Как отвечать агенту данными​

Некоторые команды — это вопросы, а не указания. Приходят они так же, но отвечать нужно через respond():

КомандаО чём спрашивает агентЧем отвечаете
navigate«открой этот путь»ничем — достаточно preventDefault()
page_context«что сейчас перед посетителем?»чем угодно полезным: путь, заголовок, товар
cart_state«что у него в корзине?»позиции, суммы, валюта
<script>
document.addEventListener('agent-command', (event) => {
const { type, respond } = event.detail;

if (type === 'page_context') {
event.preventDefault();
respond({ path: location.pathname, title: document.title });
}
});
</script>

На ответ после preventDefault() есть около секунды, так что await допустим, а медленный запрос к API — нет. Отвечайте тем, что уже под рукой.


Что агент может просить, а что нет​

  • Только пути внутри вашего сайта. Путь обязан начинаться с /. Всё, что уводит с домена — //evil.com, https://…, обратные слэши — отсекается до того, как дойдёт до страницы. Увести посетителя на чужой сайт агент не может.
  • Только звонки из браузера. На телефонном звонке открывать нечего, поэтому там таких команд нет.
  • Одного отказа достаточно. Если страница не подтвердила первый запрос, агент перестаёт спрашивать до конца звонка. Он не будет пытаться снова и — что важнее — не скажет посетителю, что открыл то, чего не открывал.
Ничего не происходит?

Выводите каждую команду до того, как фильтруете: document.addEventListener('agent-command', e => console.log(e.detail)). Видите в консоли navigate — агент свою часть сделал, значит дело в preventDefault() или в вашем обработчике. Не видите ничего — агенту не сказали, что такой путь существует; вернитесь к шагу 1.


Опознание вошедшего пользователя​

Если посетители входят на вашем сайте в личный кабинет, агент может работать с их аккаунтом — заказами, записями, обращениями — через ваши MCP-серверы. Сам виджет не знает, кто вошёл: это сообщает ему ваша страница с помощью токена.

Как это работает​

  1. Ваш сервер выдаёт отдельный короткоживущий токен (не дольше 15 минут, audience — ваш MCP-сервер), а страница передаёт его в атрибут виджета support-session-token. Атрибут работает в виджетах Floating, Pill, Pill Floating и Inline; виджет обратного звонка (Callback) его не поддерживает.
  2. Говорило передаёт токен без изменений каждому MCP-подключению агента в HTTP-заголовке X-Govorilo-Support-Token.
  3. Говорило токен не разбирает, не проверяет и не хранит. Подпись и срок действия проверяет ваш MCP-сервер — он и решает, кто этот пользователь.
осторожно

Этот заголовок получает каждое MCP-подключение агента. Никогда не передавайте токен сессии или cookie вашего сайта.

MCP-сервер обязан отказывать без действующего токена

Если на странице никто не вошёл или атрибут не задан, заголовок просто не приходит — а инструменты ваших MCP-подключений агенту всё равно доступны. Поэтому MCP-сервер должен решать сам: если X-Govorilo-Support-Token нет или токен не прошёл проверку, отклоняйте любой запрос к данным аккаунта — возвращайте ошибку или только те данные, которые и так общедоступны.

Какой токен передавать​

Не передавайте то, что посетитель может угадать или подставить от чужого имени, — например, открытый ID пользователя. Передавайте то, что может подтвердить только выдавший токен, и на своём сервере спрашивайте у него, кто этот пользователь.

ПодходМожно подделать?Оценка
Открытый идентификатор пользователяТривиальноНедопустимо
Зашифрованный идентификаторНет, но его можно повторять бесконечноНедопустимо
Токен вашего провайдера входа (JWT)Нет, но его получает каждое MCP-подключение и может повторитьНедопустимо
Разовый короткоживущий токен, выданный вашим серверомНельзя, и повторно предъявить тожеЛучший вариант

Встраивание виджета с токеном​

Подставьте токен в страницу на своём сервере:

<govorilo-floating-call
agent-id="YOUR_AGENT_ID"
support-session-token="{{ support_token }}"
></govorilo-floating-call>
<script src="https://app.govorilo.tech/widget/govorilo-widget.js" async></script>

Или задайте его из JavaScript после входа пользователя, до начала звонка:

const res = await fetch('/api/support-token');   // ваш эндпоинт, выдаёт короткоживущий токен
const { token } = await res.json();
document
.querySelector('govorilo-floating-call')
.setAttribute('support-session-token', token);

В приложении для iOS или Android загружайте ту же страницу в WebView после входа, чтобы атрибут уже содержал токен пользователя.

Проверка токена в MCP-сервере​

Набросок на Node.js с библиотекой jose: инструмент с данными аккаунта читает заголовок, проверяет токен, выданный вашим сервером, — подпись, issuer, audience и срок действия — и отказывает, если заголовка нет или токен неверный. Как обработчик инструмента получает HTTP-запрос, зависит от вашего MCP-фреймворка.

import { createRemoteJWKSet, jwtVerify } from 'jose';

// Публичные ключи вашего сервера, который выдаёт токены поддержки
const JWKS = createRemoteJWKSet(new URL('https://example.com/.well-known/jwks.json'));

async function signedInUser(headers: Record<string, string | undefined>): Promise<string | null> {
const token = headers['x-govorilo-support-token'];
if (!token) return null; // на странице никто не вошёл
try {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://example.com/',
audience: 'https://mcp.example.com/',
});
return payload.sub ?? null; // это подписал ваш сервер, значению можно доверять
} catch {
return null; // неверная подпись или истёк срок
}
}

// MCP-инструмент, который отдаёт данные аккаунта
async function getMyOrders(headers: Record<string, string | undefined>) {
const userId = await signedInUser(headers);
if (!userId) {
return {
isError: true,
content: [{ type: 'text', text: 'Собеседник не вошёл в аккаунт. Данные аккаунта недоступны.' }],
};
}
const orders = await db.orders.findByUser(userId);
return { content: [{ type: 'text', text: JSON.stringify(orders) }] };
}

Если вы выдаёте разовые токены сами, замените jwtVerify поиском в своём хранилище: найдите токен, проверьте, что он не истёк и не использован, отметьте его использованным и возьмите пользователя из этой записи.


Технические требования​

Виджеты требуют, чтобы браузер посетителя поддерживал:

  • WebGL 2.0 — для отрисовки
  • Web Audio API — для обработки звука
  • WebRTC — для реал-тайм голосовой связи

Все современные браузеры (Chrome, Firefox, Safari, Edge) поддерживают эти технологии.


Ограничения доменов​

Контролируйте, какие сайты могут встраивать виджет вашего агента.

Всегда разрешённые домены​

Следующие домены всегда разрешены, независимо от конфигурации:

  • govorilo.tech (и поддомены)
  • localhost

Allow All Domains​

По умолчанию ваш виджет можно встраивать на любой сайт. Переключите «Allow all domains» в настройках виджета, чтобы ограничить это.

Ограничение конкретными доменами​

При ограничении добавьте каждый домен, которому должно быть разрешено встраивание:

  • Вводите имена доменов без https:// (например, example.com)
  • Поддомены нужно добавлять отдельными записями (например, www.example.com, shop.example.com)
  • Порты можно указывать (например, localhost:3000)
  • Максимум 50 доменов можно добавить в whitelist
Список доменов — не защита данных

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


Условия использования​

Включите диалог согласия до того, как звонящие смогут начать разговор.

Конфигурация​

НастройкаОписание
Enable TermsПереключатель диалога terms
Terms ContentMarkdown-форматированный текст согласия, показываемый пользователям (макс. 5000 символов)
URL условийСсылка на вашу полную страницу с условиями использования
URL политики конфиденциальностиСсылка на вашу страницу с политикой конфиденциальности

Когда включено:

  • Пользователи видят диалог согласия до старта звонка
  • Они должны нажать «Agree», чтобы продолжить
  • Согласие хранится локально в браузере
  • Кнопка Reset Consent очищает сохранённое согласие для тестирования

Форматирование контента​

Контент terms поддерживает форматирование Markdown:

  • Используйте #### для заголовков
  • Используйте **bold** для акцента
  • Используйте переводы строк для читаемости

Что виджет хранит в браузере​

Виджет держит несколько небольших значений в localStorage посетителя:

КлючЧто хранит
user_identityСлучайный ID браузера посетителя, чтобы повторные звонки из того же браузера узнавались
privacy_acceptedЧто посетитель принял условия использования — окно больше не показывается
govorilo_callback_timestampsКогда посетитель запрашивал обратные звонки — для ограничения частоты в форме обратного звонка

Содержимое звонков в браузере не хранится.


Связанное​

На этой странице