Голосовий агент на вашому сайті. Один тег, без бекенду.

П'ять вебкомпонентів, які дають відвідувачу поговорити з вашим агентом, голосом, у браузері, нічого не встановлюючи.

Натисніть, щоб говорити

Ця сфера і є віджет. Натисніть і говоріть.

Оберіть ту форму, яка потрібна сторінці

Один агент, п'ять способів до нього дістатися. Змішуйте вільно, два теги з одним agent-id це звичайна схема.

Розмова йде через WebRTC, відповідь надходить приблизно за секунду, а агент вміє відкривати сторінки й бачити, що на екрані.

  • Сфера в потоці

    Сфера в потоці

    <hanc-ai-inline-call>

    370 px за замовчуванням, стоїть просто в потоці сторінки. Задайте потрібну ширину; на вузькому екрані зменшується сама.

    Секція на лендингу, сторінка «Зв'язатися», там, де розмова і є сенсом блока.

  • Плаваюча сфера

    Плаваюча сфера

    <hanc-ai-floating-call>

    120 px, прибита за 32 px від правого нижнього кута. Ще три кути на вибір або static, щоб поставити її в потік.

    Доступна з будь-якої сторінки й не займає місця у верстці.

  • Кнопка-пігулка

    Кнопка-пігулка

    <hanc-ai-pill-call>

    Горизонтальна кнопка: ліворуч сфера 48 px, праворуч ваш підпис. Стоїть у потоці, як будь-яка інша кнопка.

    У ряду кнопок, у шапці, всередині картки.

  • Плаваюча пігулка

    Плаваюча пігулка

    <hanc-ai-pill-floating-call>

    Та сама кнопка, прибита за 32 px від правого нижнього кута. Ті самі чотири кути на вибір.

    Коли кругла сфера надто помітна, але потрібна під рукою на кожній сторінці.

  • Форма зворотного дзвінка

    Форма зворотного дзвінка

    <hanc-ai-callback>

    Вибір країни, номер форматується під час введення, одна кнопка. Скільки разів передзвонювати, налаштовується на агенті, а не в розмітці.

    Для тих, хто не говоритиме через браузер, і для місць, де мікрофон недоречний.

Як поставити

Обов'язковий атрибут один: ідентифікатор агента з кабінету. Ключам API в розмітці не місце, усе, що потрапляє в HTML, видно відвідувачу.

  1. Візьміть ідентифікатор агента Рядок на кшталт 69d20781de6244c89509eb08, з кабінету.
  2. Підключіть скрипт Один рядок у <head>. Через npm це один імпорт, теги реєструються самі.
  3. Поставте тег Там, де потрібен дзвінок. Обов'язковий лише agent-id.
  4. Віддавайте через HTTPS На http:// браузер не дасть мікрофон. Поки розробляєте, підійде localhost.
index.html
<!-- 1. the script, once, in <head> -->
<script src="https://unpkg.com/hanc-webrtc-widgets" async></script>

<!-- 2. the widget, wherever the call belongs -->
<hanc-ai-inline-call
  agent-id="69d20781de6244c89509eb08"
  size="320"
  theme="tangerine"
  button-start-text="Talk to us">
</hanc-ai-inline-call>
  • React

    До 19-ї версії невідомі пропси передаються рядками, тому об'єкти так не віддати. Оберніть через @lit/react, офіційний спосіб, заодно дає типізацію та події.

  • Next.js

    Віджету потрібен браузер: 'use client' і динамічний імпорт з ssr: false. Інакше збірка впаде на серверному рендерингу.

  • Vue

    Працює без обгорток. Скажіть збирачу, що теги з префіксом hanc-ai-, користувацькі елементи.

Агент уміє відкривати сторінки й бачити, що на екрані

Голосом відповідати зручно не завжди, таблицю доставки вголос ніхто слухати не стане. Тому агент може надіслати сторінці команду і запитати в неї, що зараз дивиться відвідувач. Вирішуєте при цьому ви: ваш код сам обирає, що виконувати.

navigate
Агент відкриває сторінку у вас на сайті, товар, розділ, заздалегідь заповнену форму.
page_context
Сторінка відповідає, де перебуває відвідувач і що показано, і агент говорить про цей товар, а не взагалі.
cart_state
Сторінка відповідає, що лежить у кошику і скільки коштує, і агент називає суму вголос.

Кожна команда приходить скасовуваною DOM-подією agent-command. Виклик preventDefault() означає «обробив», respond(data) надсилає відповідь назад агентові. Проігноруєте подію, агент дізнається, що команду не виконали, і повернеться до розмови.

Команда, це прохання, а не наказ: перевіряйте кожен шлях, перш ніж ним іти. У прикладі відкидається все, що не є шляхом усередині сайту.

widget.js
widget.addEventListener('agent-command', (event) => {
  const command = event.detail;

  if (command.type === 'navigate') {
    const path = command.payload?.path;
    // Only same-site paths. Never follow an absolute URL.
    if (!path?.startsWith('/') || path.includes('://')) return;
    event.preventDefault();          // "handled": the ACK protocol
    router.push(path);
  }

  if (command.type === 'cart_state') {
    event.preventDefault();
    command.respond({                // answer travels back to the agent
      items: cart.map(i => ({ name: i.name, price: i.price })),
      total: cart.total,
      currency: 'UAH',
    });
  }
});
Працює на milotec.com.ua Агент доводить відвідувача до товару, розповідає, що на поточній сторінці, і зачитує кошик.

Той самий віджет усередині вашого застосунку

І iOS, і Android показують його у вебв'ю. Зберіть маленьку HTML-сторінку в коді й завантажте її з HTTPS-базою, без захищеного джерела рушій не видасть сторінці мікрофон.

  1. Співрозмовника не чути

    Вебв'ю не виведе звук WebRTC, доки аудіосесія застосунку стоїть у режимі відтворення. Перемкніть її на розмовний режим із виведенням у динамік на час дзвінка і поверніть назад при закритті.

  2. Агент відповідає не тією мовою

    Віджет бере мову з navigator.language, а у вебв'ю це мова системи, а не обрана в застосунку. Підмініть її на старті документа, і пропатчте fetch, дописавши browser_language у запит кімнати: вбудовуваний віджет сам це поле не шле.

  3. Мікрофону потрібні обидві половини

    Системний дозвіл застосунку і дозвіл сторінці всередині вебв'ю. На Android питайте системний до відкриття екрана, на iOS видайте запит сторінки в делегаті.

Swift
let html = """
<hanc-ai-inline-call
    agent-id="\(agentId)"
    support-session-token="\(sessionToken)"></hanc-ai-inline-call>
<script src="https://unpkg.com/hanc-webrtc-widgets" async></script>
"""

// An HTTPS base is required: without a secure origin
// the engine will not hand the page a microphone.
webView.loadHTMLString(html, baseURL: URL(string: "https://hanc.ai"))
Працює в застосунку RunOrJog
Працює в застосунку RunOrJog

Дайте агентові побачити користувача, який увійшов, і лише його

Передайте токен у support-session-token. Віджет прикладає його до дзвінка як є, ми віддаємо його вашій системі заголовком X-Hanc-Support-Token, а ваш MCP-сервер вирішує, хто це. Для нас токен непрозорий: ми його не розбираємо, не перевіряємо і не зберігаємо.

  1. Ваш застосунок
  2. Віджет
  3. Hanc.AI
  4. Ваш MCP
  5. Ваші дані

Ніколи не довіряйте ідентифікатору, який прийшов усередині токена як заява. Запитайте в того, хто видав токен, хто це, і працюйте з відповіддю.

Дайте агентові побачити користувача, який увійшов, і лише його
Підхід Підробити Оцінка
Відкритий ідентифікаторТривіальноНеприпустимо
Зашифрований ідентифікаторНе можна, але можна пред'явити повторноПоловина рішення
Токен вашого провайдера входуНе можна: підпис ставить провайдерРобочий варіант
Разовий серверний талонНе можна, і перевикористати тежНайкращий варіант
mcp_server.py
async def verified_user_id(ctx) -> str:
    token = ctx.request.headers.get("x-hanc-support-token", "").strip()
    if not token:
        raise ValueError("No signed-in user on this call.")

    # Ask the issuer who this is. Never read the id out of the token.
    resp = await http.get(f"{AUTH_URL}/auth/v1/user",
                          headers={"Authorization": f"Bearer {token}"})
    if resp.status_code != 200:
        raise ValueError("Token invalid or expired.")

    return resp.json()["id"]        # the trusted identity

Увімкніть на підключенні «потрібен упізнаний співрозмовник», і на анонімному дзвінку ваші інструменти взагалі не піднімуться: агент не розповість про чужий акаунт, навіть якщо у вашому коді колись з'явиться помилка.

Дзвінок без токена не отримує доступу до акаунта взагалі. Це поведінка за замовчуванням, а не налаштування.

Він має виглядати як ваш сайт, а не як наш

11 тем, світла й темна за системним налаштуванням або примусово, і ще близько п'ятнадцяти атрибутів для самої сфери.

  • default
  • emerald
  • rose
  • amber
  • cyan
  • purple
  • blue
  • white
  • black
  • tangerine
  • ember
Він має виглядати як ваш сайт, а не як наш
Атрибут Що задає За замовчуванням
agent-idЯкий агент відповідає. Єдиний обов'язковий
themeОдна з 11 кольорових темdefault
sizeРозмір у пікселях; на вузьких екранах зменшується сам370
button-start-textНапис на кнопціTry to call
positionКут для плаваючих формbottom-right
glow-intensityСила світіння, 0-20.8
audio-reactivityНаскільки сфера відгукується на голос3.0
terms-enabledВікно згоди перед дзвінкомfalse
sound-enabledЗвуки початку й кінцяtrue
support-session-tokenТокен користувача, який увійшов

Події для вашого коду

Жодна з подій не несе даних: обидві, звичайний Event, тобто ви отримуєте сам факт і момент часу. Вмісту розмови немає ні в тій, ні в іншій, розшифровки й записи живуть у кабінеті та в API.

Що захищає, а що захистом не є

  • Канал зашифровано

    Керуючі запити через HTTPS, звук через WSS і SRTP. Без HTTPS браузер і так не дасть мікрофон.

  • Агента перевіряють

    З'єднання піднімається лише для наявного ідентифікатора агента.

  • Частоту обмежено

    Видача доступу до розмови обмежена, від перебору та накрутки.

  • Токен сесії не осідає

    Читається наново на кожен дзвінок і в нашу базу не пишеться.

Список дозволених доменів не є межею безпеки

Ця перевірка виконується в браузері. Вона зручна, щоб віджет не запустився через недогляд на чужій копії вашої сторінки, але не заважає звернутися до сервісу напряму. Вважайте публічним усе, що агент розповість анонімному співрозмовнику, а решту закривайте токеном сесії.

Що і де залишається

У браузері
Два записи в localStorage: мітка відвідувача і факт згоди
Звук
Потоком. Записується, лише якщо запис увімкнено в агента
Розшифровка
Зберігається разом із записом, доступна в кабінеті
Розміщення
Обробка і зберігання в ЄС

Обмеження, про які краще знати заздалегідь

Без HTTPS мікрофона не буде
Перевіряйте на localhost або захищеному домені
Safari вимагає жесту перед звуком
Дзвінок має починатися з натискання
Блокувальники та проксі можуть різати WebRTC
Залиште форму зворотного дзвінка як запасний шлях
У приватному вікні мітка не зберігається
Кожна розмова виглядає як перша, це нормально
React нижче 19 не передає об'єкти атрибутом
Обгортка через @lit/react
Серверний рендеринг ламається на прямому імпорті
Динамічний імпорт з ssr: false

Ваш телефон, який нарешті працює на вас.

Шестеро AI-співробітників Hanc у гарнітурах стоять разом під помаранчево-рожевим кільцем світла