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.

Tocca per parlare

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

    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

    Sfera flottante

    <hanc-ai-floating-call>

    120 px, fissata a 32 px dall'angolo in basso a destra. Altri tre angoli a scelta, oppure static per rimetterla nel flusso.

    Raggiungibile da qualsiasi pagina senza occupare spazio nel layout.

  • Pillola

    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

    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

    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.

  1. Prendi l'id dell'agente Una stringa come 69d20781de6244c89509eb08, dalla dashboard.
  2. Aggiungi lo script Una riga in <head>. Via npm è un solo import, i tag si registrano da soli.
  3. Metti il tag Dove la chiamata ha senso. Solo agent-id è obbligatorio.
  4. Servilo in HTTPS I browser non danno il microfono su http://. Mentre sviluppi, localhost funziona.
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

    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.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',
    });
  }
});
In produzione su milotec.com.ua L'agente accompagna il visitatore fino a un prodotto, gli dice cosa c'è nella pagina in cui si trova e gli rilegge il carrello.

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.

  1. 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.

  2. 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.

  3. 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.

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"))
In produzione nell'app RunOrJog
In produzione nell'app RunOrJog

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.

  1. La tua app
  2. Il widget
  3. Hanc.AI
  4. Il tuo MCP
  5. 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.

Fai vedere all'agente l'utente autenticato, e solo lui
Approccio Falsificabile Giudizio
Un id utente in chiaroBanalmenteInaccettabile
Un id utente cifratoNo, ma è riutilizzabileMezza soluzione
Il token del tuo provider di accessoNo: la firma la mette il providerFunziona
Un tagliando server monousoNo, e nemmeno riutilizzabileIl migliore
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

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
Deve assomigliare al tuo sito, non al nostro
Attributo Cosa imposta Predefinito
agent-idQuale agente risponde. L'unico obbligatorio
themeTema di colore: 11 tra cui sceglieredefault
sizeDimensione in pixel; su schermi stretti si riduce da sé370
button-start-textL'etichetta del pulsanteTry to call
positionAngolo per le forme flottantibottom-right
glow-intensityIntensità del bagliore, 0-20.8
audio-reactivityQuanto la sfera reagisce alla voce3.0
terms-enabledPannello di consenso prima della chiamatafalse
sound-enabledSuoni di inizio e finetrue
support-session-tokenIl 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.

Sei dipendenti IA di Hanc con le cuffie, in piedi insieme sotto un anello di luce arancione e rosa