docs: web widget

This commit is contained in:
Sabiha Khan 2026-05-06 20:40:10 +05:30
parent 38b6c9e422
commit 24408fc401
8 changed files with 147 additions and 25 deletions

View file

@ -27,48 +27,138 @@ Step 4: Copy the generated embed code and paste it into your web page to test yo
| Mode | What it renders | When to use | | Mode | What it renders | When to use |
| --------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | --------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Floating Widget** | A circular call button anchored to a corner of the page. | You want a turn-key chat-bubble experience that doesn't disturb your existing layout. | | **Floating Widget** | A pill-shaped CTA button anchored to a corner of the page. | You want a turn-key chat-bubble experience that doesn't disturb your existing layout. |
| **Inline Component** | A panel rendered inside a `<div id="dograh-inline-container">` that you place in your page. | You want the agent embedded in a specific section (landing-page hero, support tab, etc.). | | **Inline Component** | A panel rendered inside a `<div id="dograh-inline-container">` that you place in your page. | You want the agent embedded in a specific section (landing-page hero, support tab, etc.). |
| **Headless** | No UI. Only the audio pipeline plus a JavaScript API on `window.DograhWidget`. | You want full control over the UI — your own buttons, design system, framework state, animations. | | **Headless** | No UI. Only the audio pipeline plus a JavaScript API on `window.DograhWidget`. | You want full control over the UI — your own buttons, design system, framework state, animations. |
## Headless mode ## Prerequisites
These apply to all three modes:
- Serve your page over **HTTPS** or from `http://localhost`. Browsers refuse microphone access on plain HTTP origins or `file://`.
- If you set **Allowed Domains** in the dashboard, include your test origin (e.g. `localhost`) — otherwise the widget's config and signaling requests are rejected. Leave the list empty to allow all domains.
- The embed snippet you copy from the dashboard is a single `<script>` tag that loads `dograh-widget.js` **asynchronously**. The widget auto-initializes once it loads and exposes `window.DograhWidget`. Code that registers callbacks must wait for the widget to be available.
## Floating Widget
![Floating widget shown in the corner of a host page](../images/floating-widget-example.png)
Renders a pill-shaped button (microphone icon + text) anchored to a corner of the page. Clicking it starts a call; clicking again ends it. The button auto-updates its label and color across the call lifecycle: configured text → "Connecting…" → "End Call" → "Retry" on failure.
Configure **Button Text**, **Button Color**, and **Position** (top/bottom + left/right) from the dashboard.
The host page writes no JavaScript — pasting the embed snippet is the entire integration. If you want to subscribe to call lifecycle events (e.g. analytics), see [Lifecycle callbacks](#lifecycle-callbacks-all-modes) below
## Inline Component
![Inline widget rendered inside a page section](../images/inline-widget-example.png)
Renders a panel (status icon + status text + CTA button) inside a `<div>` you place in your page. Status changes update the panel in place.
Configure **Button Text**, **Button Color**, and **Call to Action Text** from the dashboard.
### Plain HTML
Place a container `<div>` where you want the widget to render. The widget auto-attaches to it.
```html
<!-- Paste the dograh embed snippet from the dashboard somewhere on the page -->
<div id="dograh-inline-container"></div>
```
### React
Because React mounts after the widget script may have already loaded, integrate via `initInline` on first mount and `refresh` on remount. Poll for `window.DograhWidget` to handle the async script load.
```tsx
import { useEffect } from 'react';
declare global {
interface Window {
DograhWidget?: {
initInline: (options: { container: HTMLElement }) => void;
refresh: () => void;
getState: () => { isInitialized: boolean };
};
}
}
export function Assistant() {
useEffect(() => {
let retries = 0;
const tryInit = () => {
const container = document.getElementById('dograh-inline-container');
if (window.DograhWidget && container) {
const { isInitialized } = window.DograhWidget.getState();
if (isInitialized) window.DograhWidget.refresh();
else window.DograhWidget.initInline({ container });
} else if (retries++ < 50) {
setTimeout(tryInit, 100);
}
};
tryInit();
}, []);
return <div id="dograh-inline-container" />;
}
```
## Headless Mode
![Headless widget driven by host-page UI](../images/headless-widget-example.png)
In Headless mode the widget injects no UI of its own. You render whatever buttons, banners, or in-call indicators you want, and call the JavaScript API to start and end calls. In Headless mode the widget injects no UI of its own. You render whatever buttons, banners, or in-call indicators you want, and call the JavaScript API to start and end calls.
### JavaScript API ### JavaScript API
| Method / Callback | Description | | Method / Callback | Description |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `window.DograhWidget.start()` | Begin a voice call. Must be called from inside a user-gesture handler (e.g. `click`) so the browser grants microphone access. | | `window.DograhWidget.start()` | Begin a voice call. Must be called from inside a user-gesture handler (e.g. `click`) so the browser grants microphone access. |
| `window.DograhWidget.end()` | End the active call. | | `window.DograhWidget.end()` | End the active call. |
| `window.DograhWidget.onStatusChange(cb)` | Fires on every status change. Values: `idle`, `connecting`, `connected`, `failed`. | | `window.DograhWidget.onCallStart(cb)` | Fires when `start()` is invoked (status `connecting`). No payload. |
| `window.DograhWidget.onCallStart(cb)` | Fires once the call is connected. | | `window.DograhWidget.onCallConnected(cb)` | Fires when the WebRTC connection is established. Payload: `{ agentId, workflowRunId, token }`. |
| `window.DograhWidget.onCallEnd(cb)` | Fires when the call ends. | | `window.DograhWidget.onCallDisconnected(cb)` | Fires only if the call had connected, when teardown runs. Payload: `{ agentId, workflowRunId, token, durationSeconds }`. |
| `window.DograhWidget.onError(cb)` | Fires on any error (mic permission denied, server error, etc.). | | `window.DograhWidget.onCallEnd(cb)` | Fires whenever the call session is torn down (including failed-to-connect attempts). No payload. |
| `window.DograhWidget.getState()` | Returns the current widget state, including `connectionStatus`. | | `window.DograhWidget.onStatusChange(cb)` | Fires on every status change. Callback receives `(status, text, subtext)`. Status values: `idle`, `connecting`, `connected`, `failed`. |
| `window.DograhWidget.onError(cb)` | Fires on errors (mic permission denied, server error, etc.). Callback receives an `Error` object. |
### Recommended pattern All `on*` setters are single-listener — calling the same one again replaces the previous handler.
Mirror the call status into a state variable that you own, then render whatever UI you like from it. <Note>
**About timing.** The widget script loads asynchronously, so `window.DograhWidget` may not exist at the moment your inline `<script>` first runs. The examples below assume `window.DograhWidget` is already available when registration runs. To guarantee that:
#### Vanilla JS - **Vanilla JS:** wrap your registration code in `window.addEventListener('load', () => { /* register here */ })`.
- **React:** inside `useEffect`, register immediately if `document.readyState === 'complete'`, otherwise add a one-time `window.load` listener that registers on fire.
- **Click handlers** that call `start()` / `end()` don't need a guard — by the time a user clicks, the widget has long since loaded.
</Note>
### Vanilla JS
```html ```html
<button id="talk-btn">Talk to AI</button> <button id="talk-btn">Talk to AI</button>
<script> <script>
let callStatus = 'idle'; let callStatus = 'idle';
const btn = document.getElementById('talk-btn');
window.DograhWidget?.onStatusChange((status) => { function render() {
callStatus = status; btn.textContent =
document.getElementById('talk-btn').textContent = callStatus === 'connected' ? 'End Call'
status === 'connected' ? 'End Call' : callStatus === 'connecting' ? 'Connecting…'
: status === 'connecting' ? 'Connecting…' : callStatus === 'failed' ? 'Retry'
: status === 'failed' ? 'Retry'
: 'Talk to AI'; : 'Talk to AI';
}
window.DograhWidget.onStatusChange((status) => {
callStatus = status;
render();
}); });
document.getElementById('talk-btn').addEventListener('click', () => { window.DograhWidget.onError((err) => {
console.error('Dograh error:', err.message);
});
btn.addEventListener('click', () => {
if (callStatus === 'connected' || callStatus === 'connecting') { if (callStatus === 'connected' || callStatus === 'connecting') {
window.DograhWidget.end(); window.DograhWidget.end();
} else { } else {
@ -78,14 +168,30 @@ Mirror the call status into a state variable that you own, then render whatever
</script> </script>
``` ```
#### React ### React + TypeScript
```tsx ```tsx
function TalkButton() { import { useEffect, useState } from 'react';
const [status, setStatus] = useState('idle');
type CallStatus = 'idle' | 'connecting' | 'connected' | 'failed';
declare global {
interface Window {
DograhWidget: {
start: () => void;
end: () => void;
onStatusChange: (cb: (status: CallStatus, text?: string, subtext?: string) => void) => void;
onError: (cb: (err: Error) => void) => void;
};
}
}
export function TalkButton() {
const [status, setStatus] = useState<CallStatus>('idle');
useEffect(() => { useEffect(() => {
window.DograhWidget?.onStatusChange(setStatus); window.DograhWidget.onStatusChange((s) => setStatus(s));
window.DograhWidget.onError((err) => console.error('Dograh error:', err.message));
}, []); }, []);
const isLive = status === 'connected' || status === 'connecting'; const isLive = status === 'connected' || status === 'connecting';
@ -102,3 +208,19 @@ function TalkButton() {
<Note> <Note>
`start()` must run inside a real user-gesture handler (`click`, `touchend`, etc.). Browsers refuse to grant microphone access to scripts that request it outside of one — calling `start()` from a `setTimeout` or on page load will fail with a permission error. `start()` must run inside a real user-gesture handler (`click`, `touchend`, etc.). Browsers refuse to grant microphone access to scripts that request it outside of one — calling `start()` from a `setTimeout` or on page load will fail with a permission error.
</Note> </Note>
## Lifecycle callbacks (all modes)
The `on*` callbacks in the [Headless JavaScript API](#javascript-api) work in **all three embed modes**, not just Headless. Use them for analytics or to trigger UI in the host page even when the widget is rendering its own UI (Floating or Inline).
```js
window.DograhWidget.onCallConnected(({ agentId, workflowRunId }) => {
analytics.track('voice_call_started', { agentId, workflowRunId });
});
window.DograhWidget.onCallDisconnected(({ workflowRunId, durationSeconds }) => {
analytics.track('voice_call_ended', { workflowRunId, durationSeconds });
});
```
`onCallConnected` and `onCallDisconnected` only fire when the call actually establishes a media connection — failed-to-connect attempts (e.g. denied mic, network failure) don't trigger them, so analytics stay clean.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 333 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 284 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 465 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 237 KiB

Before After
Before After