Un agente vocale sul tuo sito. Un tag, nessun backend.
Cinque componenti web che permettono a un visitatore di parlare con il tuo agente, a voce, nel browser, senza installare nulla.
Questa sfera è il widget. Premila e parla.
Scegli la forma che serve alla pagina
Lo stesso agente, cinque modi per raggiungerlo. Mescolali liberamente, due tag con un solo agent-id è un impianto del tutto normale.
La conversazione viaggia su WebRTC, la risposta arriva in circa un secondo, e l'agente può aprire pagine e leggere quello che c'è sullo schermo.
-
Sfera in linea
<hanc-ai-inline-call>370 px di default, nel flusso della pagina. Dagli la larghezza che vuoi; su schermo stretto si riduce da sola.
Una sezione di una landing, una pagina contatti, dove la chiamata è il senso del blocco.
-
Sfera flottante
<hanc-ai-floating-call>120 px, fissata a 32 px dall'angolo in basso a destra. Altri tre angoli a scelta, oppure
staticper rimetterla nel flusso.Raggiungibile da qualsiasi pagina senza occupare spazio nel layout.
-
Pillola
<hanc-ai-pill-call>Un pulsante orizzontale: a sinistra una sfera da 48 px, a destra la tua etichetta. Sta nel flusso come qualsiasi altro pulsante.
In una fila di pulsanti, nell'header, dentro una card.
-
Pillola flottante
<hanc-ai-pill-floating-call>Lo stesso pulsante, fissato a 32 px dall'angolo in basso a destra. Gli stessi quattro angoli a scelta.
Quando una sfera tonda attira troppo l'attenzione ma la vuoi sempre a portata di mano.
-
Modulo di richiamata
<hanc-ai-callback>Selettore del paese, numero formattato mentre si digita, e un pulsante. Quanti tentativi fare si imposta sull'agente, non nel markup.
Per chi non parlerà attraverso il browser, e ovunque un microfono sia scomodo.
Installarlo
Un solo attributo obbligatorio: l'id dell'agente, dalla tua dashboard. Nessuna chiave API va nel markup, tutto ciò che sta nell'HTML è visibile al visitatore.
- Prendi l'id dell'agente Una stringa come 69d20781de6244c89509eb08, dalla dashboard.
- Aggiungi lo script Una riga in <head>. Via npm è un solo import, i tag si registrano da soli.
- Metti il tag Dove la chiamata ha senso. Solo agent-id è obbligatorio.
- Servilo in HTTPS I browser non danno il microfono su http://. Mentre sviluppi, localhost funziona.
<!-- 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
Sotto la v19 le prop sconosciute vengono passate come stringhe, quindi gli oggetti non si possono consegnare così. Avvolgi con @lit/react, la via ufficiale, e in più ti dà tipizzazione ed eventi.
-
Next.js
Il widget ha bisogno di un browser: 'use client' e un import dinamico con ssr: false. Altrimenti la build si rompe sul rendering lato server.
-
Vue
Funziona senza wrapper. Di' al bundler che i tag hanc-ai- sono custom element.
L'agente può aprire pagine e leggere cosa c'è sullo schermo
Una risposta parlata spesso è il mezzo sbagliato, nessuno vuole sentirsi leggere una tabella di spedizione. Perciò l'agente può inviare un comando alla pagina e chiederle cosa sta guardando il visitatore. Il controllo resta al tuo codice: è lui a decidere cosa eseguire.
-
navigate - L'agente apre una pagina del tuo sito, un prodotto, una sezione, un modulo già compilato.
-
page_context - La pagina risponde dov'è il visitatore e cosa viene mostrato, così l'agente parla di questo prodotto e non in generale.
-
cart_state - La pagina risponde cosa c'è nel carrello e quanto costa, così l'agente può dire il totale ad alta voce.
Ogni comando arriva come evento DOM annullabile agent-command. Chiamare preventDefault() significa «gestito»; respond(data) rimanda una risposta all'agente. Se ignori l'evento, all'agente viene detto che non è stato gestito e torna a rispondere a voce.
I comandi sono richieste, non ordini: valida ogni percorso prima di seguirlo. L'esempio rifiuta tutto ciò che non è un percorso interno al sito.
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',
});
}
}); Lo stesso widget dentro la tua app
iOS e Android lo mostrano entrambi in una web view. Costruisci una piccola pagina HTML nel codice e caricala con una base HTTPS, senza un'origine sicura il motore non consegna il microfono alla pagina.
-
L'agente non si sente
Una web view non riproduce l'audio WebRTC finché la sessione audio dell'app resta in playback. Passala a una modalità conversazionale con uscita in altoparlante per la durata della chiamata e rimettila com'era dopo.
-
L'agente risponde nella lingua sbagliata
Il widget prende la lingua da navigator.language, che in una web view è quella di sistema, non quella scelta nella tua app. Sovrascrivila all'avvio del documento, e applica una patch a fetch per aggiungere browser_language alla richiesta di stanza, che il widget incorporato non invia da solo.
-
Il microfono vuole entrambe le metà
Il permesso di sistema per l'app e il permesso per la pagina dentro la web view. Su Android chiedi quello di sistema prima di aprire la schermata; su iOS concedi la richiesta della pagina nel delegate.
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"))
Fai vedere all'agente l'utente autenticato, e solo lui
Passa un token in support-session-token. Il widget lo allega alla chiamata così com'è, noi lo inoltriamo al tuo sistema nell'header X-Hanc-Support-Token, e il tuo server MCP decide chi è. Per noi il token è opaco: non lo analizziamo, non lo verifichiamo e non lo conserviamo.
- La tua app
- Il widget
- Hanc.AI
- Il tuo MCP
- I tuoi dati
Non fidarti mai di un identificativo che arriva dentro un token come affermazione. Chiedi a chi ha emesso il token di chi si tratta, e lavora con la risposta.
| Approccio | Falsificabile | Giudizio |
|---|---|---|
| Un id utente in chiaro | Banalmente | Inaccettabile |
| Un id utente cifrato | No, ma è riutilizzabile | Mezza soluzione |
| Il token del tuo provider di accesso | No: la firma la mette il provider | Funziona |
| Un tagliando server monouso | No, e nemmeno riutilizzabile | Il migliore |
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 Attiva «richiede un interlocutore identificato» sulla connessione e una chiamata anonima non solleverà affatto i tuoi strumenti, l'agente non potrà parlare dell'account di qualcun altro nemmeno se più avanti nel tuo codice comparisse un errore.
Una chiamata senza token non ottiene alcun accesso all'account. È il comportamento predefinito, non un'opzione.
Deve assomigliare al tuo sito, non al nostro
11 temi, chiaro e scuro secondo l'impostazione di sistema o forzati, e altri quindici attributi circa per la sfera stessa.
-
default -
emerald -
rose -
amber -
cyan -
purple -
blue -
white -
black -
tangerine -
ember
| Attributo | Cosa imposta | Predefinito |
|---|---|---|
agent-id | Quale agente risponde. L'unico obbligatorio | |
theme | Tema di colore: 11 tra cui scegliere | default |
size | Dimensione in pixel; su schermi stretti si riduce da sé | 370 |
button-start-text | L'etichetta del pulsante | Try to call |
position | Angolo per le forme flottanti | bottom-right |
glow-intensity | Intensità del bagliore, 0-2 | 0.8 |
audio-reactivity | Quanto la sfera reagisce alla voce | 3.0 |
terms-enabled | Pannello di consenso prima della chiamata | false |
sound-enabled | Suoni di inizio e fine | true |
support-session-token | Il token dell'utente autenticato |
Eventi per il tuo codice
Nessuno dei due eventi porta dati: sono entrambi un Event semplice, quindi ottieni il fatto e il momento. In nessuno dei due c'è il contenuto della conversazione, trascrizioni e registrazioni vivono nella dashboard e nell'API.
Cosa ti protegge e cosa no
-
Il canale è cifrato
Richieste di controllo su HTTPS, audio su WSS e SRTP. Senza HTTPS il browser non dà comunque il microfono.
-
L'agente viene verificato
Una chiamata viene aperta solo per un id agente che esiste.
-
L'accesso è limitato in frequenza
La concessione dell'accesso alla conversazione è limitata, contro forza bruta e abuso.
-
Il token di sessione non si deposita
Viene riletto a ogni chiamata e non finisce mai nel nostro database.
L'elenco dei domini consentiti non è un confine di sicurezza
Quel controllo gira nel browser. Serve perché il widget non parta su una copia sbagliata della tua pagina, ma non impedisce a nessuno di chiamare il servizio direttamente. Considera pubblico tutto ciò che l'agente direbbe a un interlocutore anonimo, e proteggi il resto con un token di sessione.
Cosa resta e dove
- Nel browser
- Due voci in localStorage: un contrassegno del visitatore e il fatto del consenso
- Audio
- In streaming. Registrato solo se la registrazione è attiva per l'agente
- Trascrizione
- Conservata con la registrazione, leggibile nella dashboard
- Collocazione
- Elaborazione e conservazione nell'UE
Limiti da conoscere prima di iniziare
- Senza HTTPS niente microfono
- Prova su localhost o su un dominio sicuro
- Safari vuole un gesto prima dell'audio
- La chiamata deve partire da un clic
- Blocker e proxy possono tagliare WebRTC
- Tieni il modulo di richiamata come via alternativa
- Una finestra privata non conserva il contrassegno
- Ogni conversazione sembra la prima, è normale
- React sotto la 19 non passa oggetti come attributi
- Avvolgi con @lit/react
- Il rendering lato server si rompe con un import diretto
- Import dinamico con ssr: false
Il tuo telefono, che finalmente lavora per te.