Agent głosowy na Twojej stronie. Jeden tag, bez backendu.
Pięć komponentów webowych, dzięki którym odwiedzający porozmawia z Twoim agentem, głosem, w przeglądarce, bez instalowania czegokolwiek.
Ta sfera to widget. Naciśnij i mów.
Wybierz formę, której potrzebuje strona
Ten sam agent, pięć sposobów, by do niego dotrzeć. Łącz je swobodnie, dwa tagi z jednym agent-id to zwykły układ.
Rozmowa idzie po WebRTC, odpowiedź wraca w około sekundę, a agent potrafi otwierać strony i widzieć, co jest na ekranie.
-
Sfera w treści
<hanc-ai-inline-call>370 px domyślnie, stoi w toku strony. Nadaj jej potrzebną szerokość; na wąskim ekranie zmniejsza się sama.
Sekcja na landingu, strona kontaktu, tam, gdzie rozmowa jest sensem bloku.
-
Sfera pływająca
<hanc-ai-floating-call>120 px, przypięta 32 px od prawego dolnego rogu. Trzy inne rogi do wyboru albo
static, by wstawić ją w tok.Dostępna z każdej strony i nie zajmuje miejsca w układzie.
-
Pigułka
<hanc-ai-pill-call>Poziomy przycisk: po lewej sfera 48 px, po prawej Twój własny napis. Stoi w toku jak każdy inny przycisk.
W rzędzie przycisków, w nagłówku, wewnątrz karty.
-
Pływająca pigułka
<hanc-ai-pill-floating-call>Ten sam przycisk, przypięty 32 px od prawego dolnego rogu. Te same cztery rogi do wyboru.
Gdy okrągła sfera zbyt rzuca się w oczy, a chcesz mieć ją zawsze pod ręką.
-
Formularz oddzwonienia
<hanc-ai-callback>Wybór kraju, numer formatowany w trakcie wpisywania i jeden przycisk. Ile razy oddzwaniać, ustawia się na agencie, nie w znacznikach.
Dla tych, którzy nie będą rozmawiać przez przeglądarkę, i wszędzie tam, gdzie mikrofon jest nie na miejscu.
Instalacja
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.
Agent potrafi otwierać strony i widzieć, co jest na ekranie
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 - 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',
});
}
}); Ten sam widget w Twojej aplikacji
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"))
Pozwól agentowi zobaczyć zalogowanego użytkownika, i tylko jego
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.
- Twoja aplikacja
- Widget
- Hanc.AI
- Twój MCP
- Twoje dane
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 |
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 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.
Ma wyglądać jak Twoja strona, nie jak nasza
11 motywów, jasny i ciemny wedle ustawienia systemu lub wymuszone, i jeszcze około piętnastu atrybutów samej sfery.
-
default -
emerald -
rose -
amber -
cyan -
purple -
blue -
white -
black -
tangerine -
ember
| Atrybut | Co ustala | Domyślnie |
|---|---|---|
agent-id | Który agent odpowiada. Jedyny obowiązkowy | |
theme | Jeden z 11 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.
Co chroni, a co ochroną nie jest
-
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
Ograniczenia, o których lepiej wiedzieć wcześniej
- 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
Twój telefon, który w końcu pracuje dla Ciebie.