Πέντε web components που επιτρέπουν στον επισκέπτη να μιλήσει με τον πράκτορά σας — φωναχτά, μέσα στον περιηγητή, χωρίς εγκατάσταση. Η συνομιλία τρέχει πάνω από WebRTC, η απάντηση έρχεται σε περίπου ένα δευτερόλεπτο, και ο πράκτορας μπορεί να ανοίγει σελίδες και να διαβάζει τι υπάρχει στην οθόνη.
Αυτή η σφαίρα είναι το widget. Πατήστε και μιλήστε.
Ο ίδιος πράκτορας, πέντε τρόποι να τον φτάσετε. Συνδυάστε ελεύθερα — δύο ετικέτες με ένα agent-id είναι συνηθισμένη διάταξη.
Ενσωματωμένη σφαίρα
370 px εξ ορισμού, μέσα στη ροή της σελίδας. Δώστε της το πλάτος που θέλετε· σε στενή οθόνη μικραίνει μόνη της.
<hanc-ai-inline-call> Μια ενότητα σε landing, μια σελίδα επικοινωνίας — όπου η κλήση είναι το νόημα του μπλοκ.
Αιωρούμενη σφαίρα
120 px, καρφωμένη 32 px από την κάτω δεξιά γωνία. Άλλες τρεις γωνίες στη διάθεσή σας, ή static για να μπει στη ροή.
<hanc-ai-floating-call> Προσβάσιμη από κάθε σελίδα χωρίς να πιάνει χώρο στη διάταξη.
Χάπι
Ένα οριζόντιο κουμπί: αριστερά μια σφαίρα 48 px, δεξιά η δική σας ετικέτα. Στέκεται στη ροή όπως κάθε άλλο κουμπί.
<hanc-ai-pill-call> Σε σειρά κουμπιών, στην κεφαλίδα, μέσα σε κάρτα.
Αιωρούμενο χάπι
Το ίδιο κουμπί, καρφωμένο 32 px από την κάτω δεξιά γωνία. Οι ίδιες τέσσερις γωνίες στη διάθεσή σας.
<hanc-ai-pill-floating-call> Όταν η στρογγυλή σφαίρα τραβά υπερβολικά την προσοχή αλλά τη θέλετε πάντα κοντά.
Φόρμα επανάκλησης
Επιλογή χώρας, αριθμός που μορφοποιείται καθώς πληκτρολογείται, κι ένα κουμπί. Πόσες φορές θα ξαναγίνει προσπάθεια ορίζεται στον πράκτορα, όχι στο markup.
<hanc-ai-callback> Για όσους δεν θα μιλήσουν μέσω περιηγητή — και όπου το μικρόφωνο είναι άβολο.
Ένα υποχρεωτικό χαρακτηριστικό: το αναγνωριστικό του πράκτορα από τον πίνακά σας. Κανένα κλειδί API δεν έχει θέση στο markup — ό,τι βρίσκεται στο HTML το βλέπει ο επισκέπτης.
Πάρτε το αναγνωριστικό του πράκτορα
Μια συμβολοσειρά τύπου 69d20781de6244c89509eb08, από τον πίνακα.
Προσθέστε το script
Μία γραμμή στο <head>. Μέσω npm είναι ένα import — οι ετικέτες δηλώνονται μόνες τους.
Τοποθετήστε την ετικέτα
Εκεί που ανήκει η κλήση. Υποχρεωτικό είναι μόνο το 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
Κάτω από την v19 τα άγνωστα props περνούν ως συμβολοσειρές, οπότε αντικείμενα δεν παραδίδονται έτσι. Τυλίξτε με @lit/react — ο επίσημος δρόμος, που δίνει και τύπους και events.
Next.js
Το widget χρειάζεται περιηγητή: 'use client' και δυναμικό import με ssr: false. Αλλιώς το build σπάει στο server rendering.
Vue
Δουλεύει χωρίς περιτύλιγμα. Πείτε στον bundler ότι οι ετικέτες hanc-ai- είναι custom elements.
Η προφορική απάντηση συχνά είναι λάθος μέσο — κανείς δεν θέλει να του διαβάζουν πίνακα παραδόσεων. Γι' αυτό ο πράκτορας μπορεί να στείλει εντολή στη σελίδα και να τη ρωτήσει τι κοιτάζει ο επισκέπτης. Ο έλεγχος μένει στον κώδικά σας: αυτός αποφασίζει τι θα εκτελέσει.
navigate page_context cart_state navigate Ο πράκτορας ανοίγει μια σελίδα στον ιστότοπό σας — ένα προϊόν, μια ενότητα, μια ήδη συμπληρωμένη φόρμα.
page_context Η σελίδα απαντά πού βρίσκεται ο επισκέπτης και τι εμφανίζεται, ώστε ο πράκτορας να μιλήσει για αυτό το προϊόν και όχι γενικά.
cart_state Η σελίδα απαντά τι υπάρχει στο καλάθι και πόσο κοστίζει, ώστε ο πράκτορας να πει το σύνολο δυνατά.
Κάθε εντολή φτάνει ως ακυρώσιμο DOM event agent-command. Η κλήση preventDefault() σημαίνει «το χειρίστηκα»· το respond(data) στέλνει απάντηση πίσω στον πράκτορα. Αν αγνοήσετε το event, ο πράκτορας ενημερώνεται ότι δεν χειρίστηκε και επιστρέφει στην ομιλία.
Οι εντολές είναι αιτήματα, όχι διαταγές: ελέγξτε κάθε διαδρομή πριν την ακολουθήσετε. Το παράδειγμα απορρίπτει ό,τι δεν είναι διαδρομή εντός του ιστότοπου.
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 το εμφανίζουν και τα δύο σε web view. Φτιάξτε μια μικρή σελίδα HTML στον κώδικα και φορτώστε την με βάση HTTPS — χωρίς ασφαλή προέλευση η μηχανή δεν δίνει μικρόφωνο στη σελίδα.
Ο πράκτορας δεν ακούγεται
Το web view δεν θα αναπαράγει ήχο WebRTC όσο η συνεδρία ήχου της εφαρμογής βρίσκεται σε playback. Γυρίστε τη σε λειτουργία συνομιλίας με έξοδο στο ηχείο για όσο κρατά η κλήση και επαναφέρετέ τη μετά.
Ο πράκτορας απαντά σε λάθος γλώσσα
Το widget παίρνει τη γλώσσα από το navigator.language, που σε ένα web view είναι η γλώσσα του συστήματος, όχι αυτή που επιλέχθηκε στην εφαρμογή. Παρακάμψτε την στην εκκίνηση του εγγράφου — και μπαλώστε το fetch ώστε να προσθέτει browser_language στο αίτημα δωματίου, που το ενσωματωμένο widget δεν στέλνει από μόνο του.
Το μικρόφωνο θέλει και τα δύο μισά
Την άδεια συστήματος για την εφαρμογή και την άδεια για τη σελίδα μέσα στο web view. Στο Android ζητήστε τη συστημική πριν ανοίξει η οθόνη· στο iOS εγκρίνετε το αίτημα της σελίδας στον 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")) Περάστε ένα token στο support-session-token. Το widget το επισυνάπτει στην κλήση όπως είναι, εμείς το προωθούμε στο σύστημά σας στην κεφαλίδα X-Hanc-Support-Token, και ο MCP διακομιστής σας αποφασίζει ποιος είναι. Για εμάς το token είναι αδιαφανές: δεν το αναλύουμε, δεν το επαληθεύουμε και δεν το αποθηκεύουμε.
Μην εμπιστεύεστε ποτέ ένα αναγνωριστικό που φτάνει μέσα σε token ως ισχυρισμός. Ρωτήστε τον εκδότη του token ποιος είναι, και δουλέψτε με την απάντηση.
| Προσέγγιση | Πλαστογραφείται | Ετυμηγορία |
|---|---|---|
| Ανοιχτό αναγνωριστικό | Ασήμαντα εύκολα | Απαράδεκτο |
| Κρυπτογραφημένο αναγνωριστικό | Όχι, αλλά επαναχρησιμοποιείται | Μισή λύση |
| Token του παρόχου σύνδεσής σας | Όχι: υπογράφει ο πάροχος | Λειτουργεί |
| Δελτίο διακομιστή μιας χρήσης | Όχι, ούτε επαναχρησιμοποιείται | Το καλύτερο |
Ενεργοποιήστε στη σύνδεση «απαιτείται αναγνωρισμένος συνομιλητής» και σε ανώνυμη κλήση τα εργαλεία σας δεν θα σηκωθούν καθόλου — ο πράκτορας δεν θα μιλήσει για ξένο λογαριασμό ακόμη κι αν αργότερα εμφανιστεί σφάλμα στον κώδικά σας.
Μια κλήση χωρίς token δεν αποκτά καμία πρόσβαση σε λογαριασμό. Αυτή είναι η προεπιλογή, όχι επιλογή.
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 | Μέγεθος σε pixel· σε στενές οθόνες μικραίνει μόνο του | 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 | Το token του συνδεδεμένου χρήστη | — |
Events για τον δικό σας κώδικα
Κανένα από τα δύο events δεν μεταφέρει δεδομένα: και τα δύο είναι απλό Event, οπότε παίρνετε το γεγονός και τη στιγμή. Περιεχόμενο συνομιλίας δεν υπάρχει σε κανένα από τα δύο — απομαγνητοφωνήσεις και ηχογραφήσεις ζουν στον πίνακα και στο API.
Το κανάλι είναι κρυπτογραφημένο
Αιτήματα ελέγχου μέσω HTTPS, ήχος μέσω WSS και SRTP. Χωρίς HTTPS ο περιηγητής ούτως ή άλλως δεν δίνει μικρόφωνο.
Ο πράκτορας ελέγχεται
Κλήση σηκώνεται μόνο για αναγνωριστικό πράκτορα που υπάρχει.
Η πρόσβαση έχει όριο ρυθμού
Η παραχώρηση πρόσβασης στη συνομιλία περιορίζεται, ενάντια σε brute force και κατάχρηση.
Το token συνεδρίας δεν κατακάθεται
Διαβάζεται εκ νέου σε κάθε κλήση και δεν γράφεται ποτέ στη βάση μας.
Η λίστα επιτρεπόμενων domain δεν είναι όριο ασφαλείας
Αυτός ο έλεγχος τρέχει στον περιηγητή. Είναι χρήσιμος ώστε το widget να μην ξεκινήσει κατά λάθος σε αντίγραφο της σελίδας σας, αλλά δεν εμποδίζει κανέναν να καλέσει την υπηρεσία απευθείας. Θεωρήστε δημόσιο ό,τι θα έλεγε ο πράκτορας σε ανώνυμο συνομιλητή, και προστατέψτε τα υπόλοιπα με token συνεδρίας.
Τι μένει και πού
Στον περιηγητή
Δύο εγγραφές στο localStorage: ένας δείκτης επισκέπτη και το γεγονός της συγκατάθεσης
Ήχος
Σε ροή. Ηχογραφείται μόνο αν η ηχογράφηση είναι ενεργή για τον πράκτορα
Απομαγνητοφώνηση
Αποθηκεύεται μαζί με την ηχογράφηση, διαβάζεται στον πίνακα
Τοποθεσία
Επεξεργασία και αποθήκευση στην ΕΕ
Χωρίς HTTPS δεν υπάρχει μικρόφωνο
Δοκιμάστε σε localhost ή ασφαλές domain
Το Safari θέλει χειρονομία πριν τον ήχο
Η κλήση πρέπει να ξεκινά από κλικ
Blockers και proxy μπορεί να κόψουν το WebRTC
Κρατήστε τη φόρμα επανάκλησης ως εναλλακτική
Σε ιδιωτικό παράθυρο δεν μένει δείκτης
Κάθε συνομιλία μοιάζει με την πρώτη — είναι φυσιολογικό
Το React κάτω από 19 δεν περνά αντικείμενα ως χαρακτηριστικά
Τυλίξτε με @lit/react
Το server rendering σπάει με απευθείας import
Δυναμικό import με ssr: false
Ξεκινήστε δωρεάν σε 60 δευτερόλεπτα. Χωρίς πιστωτική κάρτα. Ακυρώστε οποτεδήποτε. Απλώς το τηλέφωνο να απαντά μόνο του.