Pięć komponentów webowych, dzięki którym odwiedzający porozmawia z Twoim agentem — głosem, w przeglądarce, bez instalowania czegokolwiek. Rozmowa idzie po WebRTC, odpowiedź wraca w około sekundę, a agent potrafi otwierać strony i widzieć, co jest na ekranie.
Ta sfera to widget. Naciśnij i mów.
Ten sam agent, pięć sposobów, by do niego dotrzeć. Łącz je swobodnie — dwa tagi z jednym agent-id to zwykły układ.
Sfera w treści
370 px domyślnie, stoi w toku strony. Nadaj jej potrzebną szerokość; na wąskim ekranie zmniejsza się sama.
<hanc-ai-inline-call> Sekcja na landingu, strona kontaktu — tam, gdzie rozmowa jest sensem bloku.
Sfera pływająca
120 px, przypięta 32 px od prawego dolnego rogu. Trzy inne rogi do wyboru albo static, by wstawić ją w tok.
<hanc-ai-floating-call> Dostępna z każdej strony i nie zajmuje miejsca w układzie.
Pigułka
Poziomy przycisk: po lewej sfera 48 px, po prawej Twój własny napis. Stoi w toku jak każdy inny przycisk.
<hanc-ai-pill-call> W rzędzie przycisków, w nagłówku, wewnątrz karty.
Pływająca pigułka
Ten sam przycisk, przypięty 32 px od prawego dolnego rogu. Te same cztery rogi do wyboru.
<hanc-ai-pill-floating-call> Gdy okrągła sfera zbyt rzuca się w oczy, a chcesz mieć ją zawsze pod ręką.
Formularz oddzwonienia
Wybór kraju, numer formatowany w trakcie wpisywania i jeden przycisk. Ile razy oddzwaniać, ustawia się na agencie, nie w znacznikach.
<hanc-ai-callback> Dla tych, którzy nie będą rozmawiać przez przeglądarkę — i wszędzie tam, gdzie mikrofon jest nie na miejscu.
Jeden atrybut obowiązkowy: identyfikator agenta z panelu. Klucze API nie mają czego szukać w znacznikach — wszystko, co trafia do HTML, widzi odwiedzający.
Weź identyfikator agenta
Ciąg w rodzaju 69d20781de6244c89509eb08, z panelu.
Dodaj skrypt
Jedna linia w <head>. Przez npm to jeden import — tagi rejestrują się same.
Umieść tag
Tam, gdzie rozmowa ma sens. Obowiązkowy jest tylko agent-id.
Serwuj po HTTPS
Na http:// przeglądarka nie da mikrofonu. Podczas pracy wystarczy 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
Poniżej wersji 19 nieznane propsy przekazywane są jako ciągi, więc obiektów tak nie oddasz. Owiń przez @lit/react — oficjalna droga, przy okazji daje typowanie i zdarzenia.
Next.js
Widget potrzebuje przeglądarki: 'use client' i dynamiczny import z ssr: false. Inaczej build wysypie się na renderowaniu serwerowym.
Vue
Działa bez opakowań. Powiedz bundlerowi, że tagi hanc-ai- to elementy niestandardowe.
Odpowiedź głosem często jest złym nośnikiem — nikt nie chce, by czytano mu tabelę dostaw. Dlatego agent może wysłać stronie polecenie i zapytać ją, na co właśnie patrzy odwiedzający. Kontrolę zachowuje Twój kod: to on decyduje, co wykona.
navigate page_context cart_state navigate Agent otwiera stronę u Ciebie — produkt, sekcję, wcześniej wypełniony formularz.
page_context Strona odpowiada, gdzie jest odwiedzający i co jest pokazane, żeby agent mówił o tym produkcie, a nie ogólnie.
cart_state Strona odpowiada, co leży w koszyku i ile kosztuje, żeby agent podał sumę na głos.
Każde polecenie przychodzi jako anulowalne zdarzenie DOM agent-command. Wywołanie preventDefault() znaczy «obsłużone»; respond(data) odsyła odpowiedź do agenta. Zignorujesz zdarzenie — agent dowie się, że nie zostało obsłużone, i wróci do mówienia.
Polecenie to prośba, nie rozkaz: sprawdzaj każdą ścieżkę, zanim nią pójdziesz. Przykład odrzuca wszystko, co nie jest ścieżką wewnątrz witryny.
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',
});
}
}); Działa na
milotec.com.ua
Agent doprowadza odwiedzającego do produktu, mówi, co jest na bieżącej stronie, i odczytuje koszyk.
iOS i Android pokazują go w widoku webowym. Zbuduj małą stronę HTML w kodzie i wczytaj ją z bazą HTTPS — bez bezpiecznego źródła silnik nie odda stronie mikrofonu.
Agenta nie słychać
Widok webowy nie odtworzy dźwięku WebRTC, dopóki sesja audio aplikacji stoi w trybie odtwarzania. Przełącz ją na tryb rozmowy z wyjściem na głośnik na czas połączenia i przywróć po zakończeniu.
Agent odpowiada w złym języku
Widget bierze język z navigator.language, a w widoku webowym to język systemu, nie ten wybrany w aplikacji. Nadpisz go na starcie dokumentu — i załataj fetch, dopisując browser_language do żądania pokoju, którego wbudowany widget sam nie wysyła.
Mikrofon wymaga obu połówek
Uprawnienie systemowe dla aplikacji i uprawnienie dla strony w widoku webowym. Na Androidzie zapytaj o systemowe przed otwarciem ekranu; na iOS przyznaj żądanie strony w delegacie.
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")) Przekaż token w support-session-token. Widget dołącza go do rozmowy bez zmian, my przekazujemy go Twojemu systemowi w nagłówku X-Hanc-Support-Token, a Twój serwer MCP decyduje, kto to jest. Token jest dla nas nieprzezroczysty: nie rozbieramy go, nie weryfikujemy i nie przechowujemy.
Nigdy nie ufaj identyfikatorowi, który przychodzi wewnątrz tokena jako twierdzenie. Zapytaj wystawcę tokena, kto to jest, i pracuj z odpowiedzią.
| Podejście | Do podrobienia | Ocena |
|---|---|---|
| Jawny identyfikator | Trywialnie | Nie do przyjęcia |
| Zaszyfrowany identyfikator | Nie, ale można go użyć ponownie | Połowa rozwiązania |
| Token Twojego dostawcy logowania | Nie: podpis stawia dostawca | Działa |
| Jednorazowy talon serwerowy | Nie, i ponownie też nie | Najlepszy |
Włącz na połączeniu «wymagany rozpoznany rozmówca», a przy anonimowej rozmowie Twoje narzędzia w ogóle się nie podniosą — agent nie opowie o cudzym koncie, nawet jeśli kiedyś w Twoim kodzie pojawi się błąd.
Rozmowa bez tokena nie dostaje żadnego dostępu do konta. To zachowanie domyślne, nie opcja.
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 Jedenaście motywów, jasny i ciemny wedle ustawienia systemu lub wymuszone, i jeszcze około piętnastu atrybutów samej sfery.
| Atrybut | Co ustala | Domyślnie |
|---|---|---|
agent-id | Który agent odpowiada. Jedyny obowiązkowy | — |
theme | Jeden z jedenastu motywów kolorystycznych | default |
size | Rozmiar w pikselach; na wąskich ekranach zmniejsza się sam | 370 |
button-start-text | Napis na przycisku | Try to call |
position | Róg dla form pływających | bottom-right |
glow-intensity | Siła poświaty, 0–2 | 0.8 |
audio-reactivity | Jak mocno sfera reaguje na głos | 3.0 |
terms-enabled | Okno zgody przed rozmową | false |
sound-enabled | Dźwięki początku i końca | true |
support-session-token | Token zalogowanego użytkownika | — |
Zdarzenia dla Twojego kodu
Żadne ze zdarzeń nie niesie danych: oba to zwykły Event, dostajesz więc sam fakt i moment. Treści rozmowy nie ma w żadnym z nich — transkrypcje i nagrania żyją w panelu i w API.
Kanał jest szyfrowany
Żądania sterujące po HTTPS, dźwięk po WSS i SRTP. Bez HTTPS przeglądarka i tak nie da mikrofonu.
Agent jest sprawdzany
Połączenie podnosi się tylko dla istniejącego identyfikatora agenta.
Częstotliwość jest ograniczona
Wydawanie dostępu do rozmowy jest dławione — przeciw atakom siłowym i nadużyciom.
Token sesji nie osiada
Czytany na nowo przy każdej rozmowie i nigdy nie trafia do naszej bazy.
Lista dozwolonych domen nie jest granicą bezpieczeństwa
Ta kontrola działa w przeglądarce. Przydaje się, żeby widget nie wystartował przez nieuwagę na cudzej kopii Twojej strony, ale nie przeszkodzi nikomu zwrócić się do usługi bezpośrednio. Traktuj jako publiczne wszystko, co agent powie anonimowemu rozmówcy, a resztę zamykaj tokenem sesji.
Co i gdzie zostaje
W przeglądarce
Dwa wpisy w localStorage: znacznik odwiedzającego i fakt zgody
Dźwięk
Strumieniem. Nagrywany tylko, jeśli nagrywanie jest włączone dla agenta
Transkrypcja
Zapisywana razem z nagraniem, do odczytu w panelu
Lokalizacja
Przetwarzanie i przechowywanie w UE
Bez HTTPS nie będzie mikrofonu
Testuj na localhoście albo bezpiecznej domenie
Safari chce gestu przed dźwiękiem
Rozmowa musi zaczynać się od kliknięcia
Blokery i proxy potrafią ciąć WebRTC
Zostaw formularz oddzwonienia jako drogę zapasową
W oknie prywatnym znacznik nie zostaje
Każda rozmowa wygląda jak pierwsza — to normalne
React poniżej 19 nie przekaże obiektów atrybutem
Owiń przez @lit/react
Renderowanie serwerowe psuje się przy bezpośrednim imporcie
Dynamiczny import z ssr: false
Zacznij za darmo w 60 sekund. Bez karty kredytowej. Anuluj w każdej chwili. Telefon sam odbiera połączenia.