JavaScript API
The embedded widget exposes one global on the host page, window.VAWaveWidget. This page covers the
methods you can call on it, the state you can read, and the three events it emits. If you have not
embedded the widget yet, start with Embedding the widget.
Getting a reference to the widget
The embed script creates the widget instance and assigns it to window.VAWaveWidget. Two things are
worth knowing before you call anything:
- Until the instance exists,
window.VAWaveWidgetmay hold the widget class instead of the live instance. Check for the method you are about to call rather than checking that the global is set. - The widget fetches its settings from the server before it renders.
isReadyflips totrueonly after that succeeds and the DOM is in place. If authentication fails, nothing renders andisReadystaysfalseforever.
function onWidgetReady(callback) {
const tick = () => {
const w = window.VAWaveWidget;
if (w && w.isReady === true && typeof w.activateChat === 'function') {
callback(w);
} else {
setTimeout(tick, 200);
}
};
tick();
}
onWidgetReady((widget) => {
console.log('Widget ready, session:', widget.sessionId);
});
Method summary
| Method | Returns | What it does |
|---|---|---|
activateChat() |
— | Opens the chat panel and focuses the input |
activateVoice(options?) |
Promise |
Starts a voice session |
toggle() |
— | The orb's own click behaviour: opens chat when idle, closes chat or stops voice when active |
closeChat() |
— | Leaves chat and returns the widget to its idle state |
stop() |
— | Ends the voice session and returns the widget to its idle state |
start() |
Promise |
Nothing. Empty method kept for backwards compatibility |
processMessage(text, textMode?) |
Promise |
Sends text to the assistant as if the visitor had said it |
sendChatMessage() |
Promise |
Sends whatever is currently typed in the input. Takes no argument |
resetChat() |
Promise |
Ends the conversation on the server and starts a fresh one |
minimize() |
— | Collapses the widget |
expand() |
— | Restores a minimized widget |
toggleMinimize() |
— | Calls minimize() or expand() depending on current state |
expandToFullscreen(mode) |
— | Expands the Minimal presentation. mode is 'chat', 'voice' or 'idle' |
collapseFromFullscreen() |
— | Collapses the Minimal presentation back to its ambient pill |
destroy() |
— | Tears the widget down completely and removes it from the page |
registerTool(name, def) |
boolean |
Registers a page tool the assistant may call |
registerTools(map) |
number |
Registers several page tools at once, returns how many were accepted |
unregisterTool(name) |
boolean |
Removes a previously registered page tool |
setContext(state) |
— | Publishes ambient page state to the assistant |
Anything else you find on the object is internal. It can change without notice.
Sending messages
processMessage(text, textMode)
This is the method to use when your page needs to say something on the visitor's behalf.
window.VAWaveWidget.activateChat();
window.VAWaveWidget.processMessage('What are your opening hours?', true);
text— the message content.textMode— passtrueto treat it as a typed message. Defaults tofalse, in which case the reply is spoken aloud whenever a voice session is running.
Warning
The message is discarded if the widget is idle. Messages are only processed when the widget is
in chat or voice mode, or when the Minimal presentation is expanded. Call activateChat() (or
activateVoice()) first, as in the example above.
Other behaviour worth knowing: messages are queued, so calling processMessage() twice in a row
runs both turns in order. If the conversation ended because the account ran out of credits, the
call is refused and an error flash is shown instead.
sendChatMessage()
sendChatMessage() takes no argument. It reads the current value of the chat input, clears it,
and sends it. This is what the Enter key and the mic button call. Passing a string to it does
nothing — the string is ignored and the input value is sent instead.
Use processMessage(text) to send text programmatically.
The method also ignores calls that arrive within 100 ms of the previous send, and calls made while the assistant is still answering.
Opening and closing
activateChat()
Opens chat. On the Minimal presentation it expands the whole surface rather than showing a corner panel. If a voice session is running it is stopped first — chat and voice are mutually exclusive outside the Minimal surface.
activateVoice(options)
Starts a voice session and resolves when the connection attempt finishes.
await window.VAWaveWidget.activateVoice({ greet: false });
options.greet— passfalseto suppress the spoken greeting,trueto force it. Omitted, the assistant greets unless a conversation is already visible on screen.
Call it from inside a real user gesture (a click or tap handler). Browsers only unlock audio playback and microphone access during a gesture, so a voice session started from a timer or on page load will not produce sound. If voice is turned off for the assistant, the call returns immediately and does nothing.
closeChat()
Returns the widget to idle: stops audio playback and capture, aborts the in-flight request, clears the input, and closes the mobile takeover. Conversation history is preserved, so reopening chat shows the same thread.
Warning
closeChat() does not collapse the Minimal presentation. On a Minimal widget it leaves the
expanded surface in place with no chat in it. Call collapseFromFullscreen() instead — that
method closes chat, stops voice, and returns the surface to its ambient pill.
stop()
Ends the voice session — closes the voice connection, releases the microphone, stops playback and clears the message queue — and puts the widget back in its idle state. Display history is kept.
start()
An empty method. It exists only so older host-page code that calls it does not break. It does not
start voice; use activateVoice().
minimize(), expand(), toggleMinimize()
minimize() closes chat or voice first, then collapses the widget. expand() restores it.
Warning
The widget provides no on-screen control to expand a minimized widget. If you call
minimize(), your page must provide its own control that calls expand(), or the visitor is
left with no way back. The minimized flag is also cleared on the next page load, so the
collapsed state does not persist across navigation.
resetChat()
Ends the current conversation on the server, generates a new session, clears both the on-screen history and the history sent to the model, and shows the welcome message again. This is what the New conversation control in the widget does.
destroy()
Stops everything, removes the widget's DOM and injected styles, releases the microphone, and closes
the audio context. There is no matching re-create call — after destroy() the widget stays gone
until the page reloads.
Page tools
registerTool(), registerTools(), unregisterTool() and setContext() let your page expose
functions the assistant can call and keep the assistant aware of what is on screen. They have their
own page: Page tools.
State you can read
These properties are readable at any time. Treat them as read-only — assigning to them does not update the UI, and the widget overwrites them on its next state change.
| Property | Type | Meaning |
|---|---|---|
isReady |
boolean | true once settings loaded and the widget rendered |
mode |
string | 'hover' (idle), 'chat' or 'voice' |
state |
string | 'idle', 'loading', 'listening', 'processing', 'speaking' or 'error' |
displayMode |
string | 'widget' for the orb presentation, 'fullscreen' for Minimal |
fsExpanded |
boolean | Whether the Minimal surface is expanded |
minimized |
boolean | Whether the widget is collapsed |
isProcessing |
boolean | An assistant turn is in flight |
isSpeaking |
boolean | The assistant is currently speaking |
conversationEnded |
boolean | The conversation is closed |
endReason |
string / null | Why it closed, e.g. 'insufficient_credits' |
sessionId |
string / null | Current conversation session |
visitorId |
string | Stable per-browser visitor identifier, stored in localStorage |
siteId |
string | The assistant this embed is bound to |
botName |
string | The assistant's display name |
voiceEnabled |
boolean | Whether voice is available on this assistant |
ttsEnabled |
boolean | Whether the assistant may speak |
displayHistory |
array | Messages currently rendered in the widget |
conversationHistory |
array | { role, content } turns sent to the model |
Note
displayMode: 'fullscreen' is the internal name for the Minimal presentation. It is not a
browser fullscreen mode.
Observing the widget from the page
The widget keeps its container's class list in sync with its state, which is the most reliable way
to react to changes. The container is a div.va-container appended to document.body; its id is
generated at runtime, so select it by class.
| Class | Present when |
|---|---|
va-loading / va-ready |
Before / after the widget finishes initializing |
chat-mode |
Chat is open |
voice-mode |
A voice session is running |
minimized |
The widget is collapsed |
fs-collapsed |
Minimal presentation, ambient pill |
fullscreen-mode |
Minimal presentation, expanded |
const container = document.querySelector('.va-container');
new MutationObserver(() => {
console.log('chat open:', container.classList.contains('chat-mode'));
}).observe(container, { attributes: true, attributeFilter: ['class'] });
Events
The widget emits three things. That is the complete list.
va-balance-update
A CustomEvent dispatched on window when a reply carries a remaining credit balance for the
visitor.
window.addEventListener('va-balance-update', (e) => {
console.log('Balance:', e.detail.balance);
});
This only happens when the assistant is wired to an external usage webhook that returns a balance. Ordinary replies do not carry one, so most sites never see this event.
va-voice-unavailable
A CustomEvent dispatched on window when speech synthesis is refused because the account is out
of credits. The widget disables its own speech for the rest of the page session when this fires.
window.addEventListener('va-voice-unavailable', (e) => {
// e.detail.reason === 'insufficient_credits'
showNoticeToUser('Voice is unavailable right now.');
});
va-widget-state (postMessage)
When the widget runs inside an iframe, it posts a message to the parent window every time its container state changes:
{ "type": "va-widget-state", "expanded": true }
expanded is true while chat or voice is active. The message is posted with the widget page's own
origin as the target origin, so only a same-origin parent receives it. If your parent page is on
a different domain than the iframe, nothing arrives.
window.addEventListener('message', (e) => {
if (e.origin !== window.location.origin) return;
if (e.data && e.data.type === 'va-widget-state') {
resizeIframe(e.data.expanded);
}
});
What the widget does not have
- No callback registry. There is no
on(),off(),addEventListener()oronMessagehook onwindow.VAWaveWidget. You cannot subscribe to messages, turns, or tool calls from the host page. - No inbound
postMessagelistener. The widget never listens for messages from a parent frame, so you cannot control it from an outer page by posting to the iframe. Control it by calling methods on the global inside the same document.
To observe more than the three events above, you have two options:
- In the browser — watch the DOM, as shown in Observing the widget from the page.
- On your server — use Actions. Hiroi sends signed HTTPS requests to your endpoint on conversation, message, form, escalation and contact events, which is the supported way to react to what happens in a conversation.