Пет уеб компонента, които позволяват на посетителя да говори с вашия агент — на глас, в браузъра, без да инсталира нищо. Разговорът върви по WebRTC, отговорът идва за около секунда, а агентът може да отваря страници и да вижда какво е на екрана.
Тази сфера е уиджетът. Натиснете и говорете.
Един и същ агент, пет начина да се стигне до него. Смесвайте свободно — два тага с едно agent-id е обичайна схема.
Сфера в потока
370 px по подразбиране, стои в потока на страницата. Задайте нужната ширина; на тесен екран се смалява сама.
<hanc-ai-inline-call> Секция на лендинг, страница за контакт — там, където разговорът е смисълът на блока.
Плаваща сфера
120 px, закована на 32 px от долния десен ъгъл. Още три ъгъла по избор или static, за да я сложите в потока.
<hanc-ai-floating-call> Достъпна от всяка страница и не заема място в оформлението.
Хапче
Хоризонтален бутон: отляво сфера 48 px, отдясно вашият надпис. Стои в потока като всеки друг бутон.
<hanc-ai-pill-call> В ред от бутони, в хедъра, вътре в карта.
Плаващо хапче
Същият бутон, закован на 32 px от долния десен ъгъл. Същите четири ъгъла по избор.
<hanc-ai-pill-floating-call> Когато кръглата сфера привлича твърде много внимание, но я искате винаги подръка.
Форма за обратно обаждане
Избор на държава, номер, който се форматира докато го пишете, и един бутон. Колко пъти да се звъни повторно се настройва на агента, не в маркъпа.
<hanc-ai-callback> За тези, които не биха говорили през браузъра — и там, където микрофонът е неудобен.
Един задължителен атрибут: идентификаторът на агента от вашето табло. API ключове нямат място в маркъпа — всичко в HTML се вижда от посетителя.
Вземете идентификатора на агента
Низ като 69d20781de6244c89509eb08, от таблото.
Добавете скрипта
Един ред в <head>. През npm е един импорт — таговете се регистрират сами.
Поставете тага
Там, където разговорът има място. Задължителен е само agent-id.
Обслужвайте по HTTPS
На http:// браузърът не дава микрофон. Докато разработвате, localhost върши работа.
<!-- 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 непознатите props се подават като низове, така че обекти не могат да минат оттам. Обвийте с @lit/react — официалният път, който дава и типизация, и събития.
Next.js
Уиджетът има нужда от браузър: 'use client' и динамичен импорт с ssr: false. Иначе билдът се чупи при сървърния рендеринг.
Vue
Работи без обвивка. Кажете на бъндлера, че таговете hanc-ai- са персонализирани елементи.
Гласовият отговор често е грешната среда — никой не иска да му четат таблица с доставки. Затова агентът може да изпрати команда на страницата и да я попита какво гледа посетителят. Контролът остава у вашия код: той решава какво да изпълни.
navigate page_context cart_state navigate Агентът отваря страница във вашия сайт — продукт, раздел, предварително попълнена форма.
page_context Страницата отговаря къде е посетителят и какво се показва, за да говори агентът за този продукт, а не изобщо.
cart_state Страницата отговаря какво има в количката и колко струва, за да каже агентът сумата на глас.
Всяка команда пристига като отменяемо DOM събитие agent-command. Извикването на preventDefault() значи «обработено»; respond(data) връща отговор на агента. Ако игнорирате събитието, на агента се казва, че не е обработено, и той се връща към говоренето.
Командите са молби, не заповеди — проверявайте всеки път, преди да го последвате. Примерът отхвърля всичко, което не е път вътре в сайта.
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 база — без защитен източник двигателят няма да даде микрофон на страницата.
Агентът не се чува
Уеб изгледът няма да пусне WebRTC звука, докато аудио сесията на приложението стои в режим възпроизвеждане. Превключете я към разговорен режим с изход към високоговорителя за времето на разговора и я върнете след това.
Агентът отговаря на грешен език
Уиджетът взима езика от navigator.language, а в уеб изглед това е системният език, не избраният в приложението. Подменете го при старта на документа — и закърпете fetch, като добавите browser_language към заявката за стая, което вграденият уиджет сам не изпраща.
Микрофонът иска и двете половини
Системното разрешение за приложението и разрешението за страницата в уеб изгледа. На Android поискайте системното преди отварянето на екрана; на iOS удовлетворете заявката на страницата в делегата.
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")) Подайте токен в support-session-token. Уиджетът го прикача към разговора както е, ние го предаваме на вашата система в хедъра X-Hanc-Support-Token, а вашият MCP сървър решава кой е това. За нас токенът е непрозрачен: не го разглобяваме, не го проверяваме и не го съхраняваме.
Никога не се доверявайте на идентификатор, който пристига вътре в токен като твърдение. Питайте издателя на токена кой е това и работете с отговора.
| Подход | Може ли да се подправи | Оценка |
|---|---|---|
| Открит идентификатор | Тривиално | Недопустимо |
| Шифрован идентификатор | Не, но може да се предяви повторно | Половин решение |
| Токен на вашия доставчик за вход | Не: подписът е на доставчика | Работи |
| Еднократен сървърен талон | Не, и повторно също не | Най-доброто |
Включете на връзката «изисква разпознат събеседник» и при анонимен разговор вашите инструменти изобщо няма да се вдигнат — агентът няма да каже нищо за чужд акаунт, дори ако по-късно в кода ви се появи грешка.
Разговор без токен не получава никакъв достъп до акаунт. Това е поведението по подразбиране, не опция.
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 Единадесет теми, светла и тъмна по системна настройка или принудително, и още около петнадесет атрибута за самата сфера.
| Атрибут | Какво задава | По подразбиране |
|---|---|---|
agent-id | Кой агент отговаря. Единственият задължителен | — |
theme | Една от единадесетте цветови теми | default |
size | Размер в пиксели; на тесни екрани се смалява сам | 370 |
button-start-text | Надписът на бутона | Try to call |
position | Ъгъл за плаващите форми | bottom-right |
glow-intensity | Сила на сиянието, 0–2 | 0.8 |
audio-reactivity | Колко силно сферата откликва на гласа | 3.0 |
terms-enabled | Панел за съгласие преди разговора | false |
sound-enabled | Звуци в началото и края | true |
support-session-token | Токенът на влезлия потребител | — |
Събития за вашия код
Нито едно от събитията не носи данни: и двете са обикновен Event, тоест получавате самия факт и момента. Съдържание на разговора няма нито в едното, нито в другото — транскрипциите и записите живеят в таблото и в API.
Каналът е криптиран
Управляващите заявки по HTTPS, звукът по WSS и SRTP. Без HTTPS браузърът и без това не дава микрофон.
Агентът се проверява
Разговор се вдига само за съществуващ идентификатор на агент.
Достъпът е ограничен по честота
Издаването на достъп до разговор е ограничено — срещу brute force и злоупотреба.
Токенът на сесията не се задържа
Чете се наново при всеки разговор и никога не се записва в нашата база.
Списъкът с разрешени домейни не е граница на сигурността
Тази проверка работи в браузъра. Полезна е, за да не тръгне уиджетът по невнимание върху чуждо копие на вашата страница, но не пречи на никого да се обърне директно към услугата. Смятайте за публично всичко, което агентът би казал на анонимен събеседник, а останалото пазете с токен на сесията.
Какво и къде остава
В браузъра
Два записа в localStorage: маркер на посетителя и фактът на съгласието
Звук
На поток. Записва се само ако записът е включен за агента
Транскрипция
Пази се заедно със записа, чете се в таблото
Разположение
Обработка и съхранение в ЕС
Без HTTPS няма микрофон
Тествайте на localhost или защитен домейн
Safari иска жест преди звука
Разговорът трябва да започва с кликване
Блокери и проксита могат да режат WebRTC
Оставете формата за обратно обаждане като резервен път
В частен прозорец маркерът не се пази
Всеки разговор изглежда като първи — това е нормално
React под 19 не подава обекти като атрибут
Обвийте с @lit/react
Сървърният рендеринг се чупи при директен импорт
Динамичен импорт с ssr: false
Започнете безплатно за 60 секунди. Без кредитна карта. Отменете по всяко време. Просто телефонът, който сам вдига.