@helpai/elements 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -12
- package/configurator.mjs +1 -1
- package/docs/README.md +183 -0
- package/docs/analytics.md +551 -0
- package/docs/api-reference.md +637 -0
- package/docs/browser-support.md +302 -0
- package/docs/configuration.md +1014 -0
- package/docs/events-and-hooks.md +483 -0
- package/docs/faq.md +435 -0
- package/docs/frameworks.md +643 -0
- package/docs/installation.md +626 -0
- package/docs/options.md +323 -0
- package/docs/quickstart.md +229 -0
- package/docs/recipes.md +725 -0
- package/docs/security-privacy.md +636 -0
- package/docs/theming.md +570 -0
- package/docs/troubleshooting.md +523 -0
- package/elements-web-component.esm.js +39 -39
- package/elements-web-component.esm.js.map +4 -4
- package/elements.cjs.js +39 -39
- package/elements.cjs.js.map +4 -4
- package/elements.esm.js +39 -39
- package/elements.esm.js.map +4 -4
- package/elements.js +39 -39
- package/elements.js.map +4 -4
- package/index.d.ts +107 -3
- package/index.mjs +13 -13
- package/jsx-react.d.ts +15 -0
- package/jsx-svelte.d.ts +14 -0
- package/jsx-vue.d.ts +14 -0
- package/llms-full.txt +7954 -0
- package/llms.txt +37 -0
- package/loader.js +3 -3
- package/loader.min.js +1 -1
- package/package.json +36 -1
- package/react.d.ts +3575 -0
- package/react.mjs +62 -0
- package/schema.d.ts +56 -0
- package/schema.json +24 -2
- package/schema.mjs +1 -1
- package/web-component.d.ts +1583 -17
- package/web-component.mjs +14 -14
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
# Configuration reference
|
|
2
|
+
|
|
3
|
+
Every option you can pass to `init(...)` / `mount(...)` / set via
|
|
4
|
+
`data-*` attributes / set via web-component attributes.
|
|
5
|
+
|
|
6
|
+
Group-by-group walkthrough with realistic examples. For an at-a-glance
|
|
7
|
+
lookup, see [API reference](./api-reference.md).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## How options arrive
|
|
12
|
+
|
|
13
|
+
Options resolve in this order (highest priority wins):
|
|
14
|
+
|
|
15
|
+
1. **Options you pass to `init()`** — always win.
|
|
16
|
+
2. **Server-pushed settings** — your Help AI dashboard settings for this
|
|
17
|
+
deployment. Fill in anything you left unset.
|
|
18
|
+
3. **Built-in defaults** — sensible fallbacks shipped with the widget.
|
|
19
|
+
|
|
20
|
+
This means you can:
|
|
21
|
+
|
|
22
|
+
- Hardcode `publicKey` in code, tune the rest from the dashboard.
|
|
23
|
+
- Ship all config in `init()` and ignore the dashboard.
|
|
24
|
+
- Override a specific field (e.g., force `theme: "dark"`) while letting
|
|
25
|
+
the dashboard control everything else.
|
|
26
|
+
|
|
27
|
+
### Canonical option shape
|
|
28
|
+
|
|
29
|
+
Embed identity, routing, context, endpoint overrides, storage, and callbacks stay
|
|
30
|
+
at the top level. Serializable widget settings are grouped by ownership:
|
|
31
|
+
|
|
32
|
+
| Section | Fields described in this guide |
|
|
33
|
+
| -------------- | ------------------------------------------------------------------------------------ |
|
|
34
|
+
| `presentation` | `mode`, `iframe`, `position`, `size` |
|
|
35
|
+
| `behavior` | `startMinimized`, `responseMode`, transcript visibility, feedback, suggestion policy |
|
|
36
|
+
| `header` | `actions` |
|
|
37
|
+
| `composer` | `sendButton`, `attachments` |
|
|
38
|
+
| `feedback` | `sound`, `haptics` |
|
|
39
|
+
| `i18n` | `locale`, `defaultLocale`, `availableLocales`, `strings` |
|
|
40
|
+
| `footer` | `composerDisclaimer`, `poweredBy` |
|
|
41
|
+
|
|
42
|
+
The flat HTML attributes remain intentionally short (`data-mode`, `data-actions`,
|
|
43
|
+
and so on); the attribute parser maps them into this nested JavaScript shape.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Required
|
|
48
|
+
|
|
49
|
+
### `publicKey`
|
|
50
|
+
|
|
51
|
+
`string` · required for authenticated browser deployments
|
|
52
|
+
|
|
53
|
+
Your deployment's public key from the Help AI dashboard. Looks like
|
|
54
|
+
`agent_pk_...`.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
init({ publicKey: 'agent_pk_abc123...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The public key authorizes the public widget surface. Pair it with that same
|
|
61
|
+
deployment's `aiAgentDeploymentId`; the backend requires both values to match.
|
|
62
|
+
Intentional guest mode omits the key but still supplies the deployment ID.
|
|
63
|
+
There is no per-end-user token — see
|
|
64
|
+
[`userContext`](#usercontext) below.
|
|
65
|
+
|
|
66
|
+
### `aiAgentDeploymentId`
|
|
67
|
+
|
|
68
|
+
`string` · required for browser deployments
|
|
69
|
+
|
|
70
|
+
The deployment UUID from the same Dashboard → Deployments install panel as the
|
|
71
|
+
public key. It selects the exact agent/configuration and acts as a
|
|
72
|
+
defense-in-depth match for authenticated mounts. Send it on every browser
|
|
73
|
+
mount; also send the public key unless guest mode is intentional.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Identity + routing
|
|
78
|
+
|
|
79
|
+
### `widgetId`
|
|
80
|
+
|
|
81
|
+
`string` · default: `"default"`
|
|
82
|
+
|
|
83
|
+
A name you pick for this widget instance. Matters when you run more
|
|
84
|
+
than one widget on a page — each widgetId gets its own chat thread,
|
|
85
|
+
settings, and storage.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
init({ widgetId: 'support', publicKey: SUPPORT_KEY, aiAgentDeploymentId: SUPPORT_DEPLOYMENT_ID });
|
|
89
|
+
init({ widgetId: 'sales', publicKey: SALES_KEY, aiAgentDeploymentId: SALES_DEPLOYMENT_ID });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
See [Recipes § Multiple widgets](./recipes.md#multiple-widgets-on-one-page).
|
|
93
|
+
|
|
94
|
+
### `baseUrl`
|
|
95
|
+
|
|
96
|
+
`string` · default: `https://help.ai`
|
|
97
|
+
|
|
98
|
+
The MAIN site origin the widget calls — the API modules derive from it
|
|
99
|
+
(agent API at `${baseUrl}/api/agent`, data API at `${baseUrl}/api/data`).
|
|
100
|
+
Override if you use:
|
|
101
|
+
|
|
102
|
+
- A custom domain (`your-company.com`).
|
|
103
|
+
- A local dev backend.
|
|
104
|
+
|
|
105
|
+
### `userToken`
|
|
106
|
+
|
|
107
|
+
`string | () => string | Promise<string>` · default: none (anonymous)
|
|
108
|
+
|
|
109
|
+
Signs the visitor in as your logged-in user: a JWT your server signs with the deployment's secret key, or a function
|
|
110
|
+
returning a fresh one. Their conversations then follow them to any device, and the AI gets their name and email as
|
|
111
|
+
verified facts. Runtime-only: not an attribute or `data-*` option, so the token stays out of the page's markup. See
|
|
112
|
+
[Security → Verified users](./security-privacy.md#verified-users-usertoken) for signing it.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
init({ publicKey: 'agent_pk_...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000', userToken: () => fetch('/api/help-ai-token').then((res) => res.text()) });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `userContext`
|
|
119
|
+
|
|
120
|
+
`object | undefined`
|
|
121
|
+
|
|
122
|
+
Host-asserted information about the end user, passed to the AI as
|
|
123
|
+
**context only — never an authentication credential.** The backend does
|
|
124
|
+
not register or authenticate these users. Optional — omit it for
|
|
125
|
+
anonymous chats. Recommended for logged-in users so the AI has context.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
init({
|
|
129
|
+
publicKey: 'agent_pk_...',
|
|
130
|
+
aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000',
|
|
131
|
+
userContext: {
|
|
132
|
+
id: 'user_42', // your user id
|
|
133
|
+
email: 'jane@help.ai',
|
|
134
|
+
name: 'Jane Doe',
|
|
135
|
+
plan: 'pro',
|
|
136
|
+
role: 'admin',
|
|
137
|
+
phone: '+15551234567',
|
|
138
|
+
seats: 12, // arbitrary custom scalar field
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A flat object. Recognised fields (all optional):
|
|
144
|
+
|
|
145
|
+
- `id` — your user's stable identifier.
|
|
146
|
+
- `email` — for personalization.
|
|
147
|
+
- `name` — for personalization (also drives the Home tab greeting,
|
|
148
|
+
e.g. "Hi Jane 👋").
|
|
149
|
+
- `plan` — subscription / pricing tier (e.g. `"pro"`).
|
|
150
|
+
- `role` — the user's role on your site (e.g. `"admin"`).
|
|
151
|
+
- `phone` — phone number.
|
|
152
|
+
|
|
153
|
+
Plus any arbitrary custom scalar fields (`string | number | boolean`) —
|
|
154
|
+
account age, feature flags, etc.
|
|
155
|
+
|
|
156
|
+
Because this is untrusted external input, it is sanitized client-side
|
|
157
|
+
before it leaves the page: non-scalar values are dropped, reserved keys
|
|
158
|
+
(`visitorId`, `conversationId`, `conversationId`, `jwt`, `token`, `authorization`,
|
|
159
|
+
`password`) are stripped, a malformed `email` is dropped, strings are
|
|
160
|
+
trimmed and capped at 1024 chars, max 50 keys. Sanitization warns to the
|
|
161
|
+
debug log and never throws.
|
|
162
|
+
|
|
163
|
+
> **This is YOUR site's logged-in user — not the widget's own visitor.**
|
|
164
|
+
> The widget independently mints and persists an anonymous
|
|
165
|
+
> `visitorId` / `conversationId` for the device; `userContext` never overrides
|
|
166
|
+
> those. Setting `userContext` simply attaches your context to the same
|
|
167
|
+
> visitor.
|
|
168
|
+
|
|
169
|
+
### `pageContext`
|
|
170
|
+
|
|
171
|
+
`object | undefined`
|
|
172
|
+
|
|
173
|
+
Host-asserted context about the page/visit (separate from `userContext`),
|
|
174
|
+
so the AI can adapt to where the visitor is — a pricing visitor is not a
|
|
175
|
+
docs reader is not a help-desk ticket.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
init({
|
|
179
|
+
publicKey: 'agent_pk_...',
|
|
180
|
+
aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000',
|
|
181
|
+
pageContext: {
|
|
182
|
+
area: 'pricing', // host-defined surface
|
|
183
|
+
path: '/pricing/enterprise',
|
|
184
|
+
title: 'Enterprise plans',
|
|
185
|
+
// url auto-filled from location.href when omitted
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Recognised fields (all optional):
|
|
191
|
+
|
|
192
|
+
- `area` — host-defined surface the visitor is on. Free-form string;
|
|
193
|
+
suggested values: `home`, `docs`, `help-desk`, `support`, `pricing`,
|
|
194
|
+
`checkout`, `orders`, `account`, `blog` (any string accepted).
|
|
195
|
+
- `url` — full page URL. Auto-filled from the host page's
|
|
196
|
+
`location.href` when omitted.
|
|
197
|
+
- `path` — page path, e.g. `/pricing/enterprise`.
|
|
198
|
+
- `title` — page title.
|
|
199
|
+
|
|
200
|
+
Plus arbitrary custom scalar fields. Sanitized client-side with the same
|
|
201
|
+
rules as `userContext`.
|
|
202
|
+
|
|
203
|
+
> **On the wire:** `userContext` + `pageContext` are bundled into one
|
|
204
|
+
> `context` field — `base64url(JSON.stringify({ user, page }))` — sent on
|
|
205
|
+
> **every** request (body for POSTs, `?context=` for GETs), so the backend
|
|
206
|
+
> always has the visitor's latest context as they navigate. base64url is
|
|
207
|
+
> encoding, **not encryption** (the transport is already TLS); it just keeps
|
|
208
|
+
> PII out of plaintext access logs / the devtools network tab.
|
|
209
|
+
|
|
210
|
+
### Setting context after login / on navigation
|
|
211
|
+
|
|
212
|
+
In a typical SPA the visitor is anonymous on first load and logs in
|
|
213
|
+
later. Update context dynamically via the instance handle. The new context
|
|
214
|
+
rides the **next** request automatically (no reconnect) — and a
|
|
215
|
+
`setUserContext` identity change additionally re-personalizes the greeting,
|
|
216
|
+
keeping the **same** visitor/conversation so the conversation is continuous:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
const widget = HelpAI.init({ publicKey: 'agent_pk_...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
|
|
220
|
+
|
|
221
|
+
// after your app authenticates the user:
|
|
222
|
+
widget.setUserContext({
|
|
223
|
+
id: 'user_42',
|
|
224
|
+
email: 'jane@help.ai',
|
|
225
|
+
name: 'Jane Doe',
|
|
226
|
+
plan: 'pro',
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// on SPA navigation:
|
|
230
|
+
widget.setPageContext({ area: 'checkout', path: '/checkout' });
|
|
231
|
+
|
|
232
|
+
// on logout:
|
|
233
|
+
widget.clearUserContext();
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
You can also seed context at load time via `data-user-*` /
|
|
237
|
+
`data-page-*` tag attributes — see [Installation](./installation.md) and
|
|
238
|
+
the framework recipes in [`frameworks.md`](./frameworks.md).
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Rendering
|
|
243
|
+
|
|
244
|
+
### `presentation.mode`
|
|
245
|
+
|
|
246
|
+
`"floating" | "inline" | "standalone" | "modal" | "drawer" | "page"` · default: `"floating"`
|
|
247
|
+
|
|
248
|
+
How the widget renders on the page.
|
|
249
|
+
|
|
250
|
+
- **`"floating"`** (default) — floating chat bubble in a corner.
|
|
251
|
+
Clicking opens the panel. Best for marketing sites, support chat.
|
|
252
|
+
- **`"inline"`** — a block element that fills its parent container. No
|
|
253
|
+
bubble, no popover. Best for in-app assistants, sidebar chats.
|
|
254
|
+
- **`"standalone"`** — full viewport. The dedicated chat tab; renders
|
|
255
|
+
the full module surface (no sidebar).
|
|
256
|
+
- **`"modal"`** — a centered dialog over a backdrop. The host opens it
|
|
257
|
+
(no launcher); call `open()` from your own trigger.
|
|
258
|
+
- **`"drawer"`** — docks to a screen edge; the launcher becomes a
|
|
259
|
+
vertical edge tab.
|
|
260
|
+
- **`"page"`** — full-viewport split: a conversation **sidebar** plus
|
|
261
|
+
the module surface in the main area.
|
|
262
|
+
|
|
263
|
+
`target` controls where every mode's shadow host is inserted. It is most
|
|
264
|
+
important for `inline` / `standalone` / `page`; overlay modes remain
|
|
265
|
+
viewport-positioned even when their host is inserted under a custom target.
|
|
266
|
+
See `../features/modes.md` for the full behavior matrix.
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
init({ presentation: { mode: 'inline' }, target: document.getElementById('chat')! });
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### `target`
|
|
273
|
+
|
|
274
|
+
`HTMLElement | undefined`
|
|
275
|
+
|
|
276
|
+
Where to insert the widget's shadow host. Recommended for `inline` /
|
|
277
|
+
`standalone` / `page` modes. Floating, drawer, and modal hosts also use
|
|
278
|
+
it, while their panels remain viewport-positioned. Defaults to `<body>`.
|
|
279
|
+
|
|
280
|
+
### `presentation.iframe`
|
|
281
|
+
|
|
282
|
+
`boolean` · default: `false`
|
|
283
|
+
|
|
284
|
+
Wrap the widget in a same-origin iframe for strict JS + CSS isolation.
|
|
285
|
+
Useful when the host page has aggressive global scripts that mangle
|
|
286
|
+
`window.*` or inject hostile CSS.
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
init({ presentation: { mode: 'floating', iframe: true }, publicKey: 'agent_pk_...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Composes with any `mode`. Default is `false` — shadow DOM alone is
|
|
293
|
+
enough for 95% of deployments. [Security § Iframe isolation](./security-privacy.md#iframe-isolation).
|
|
294
|
+
|
|
295
|
+
### `presentation.position`
|
|
296
|
+
|
|
297
|
+
`"bottom-right" | "bottom-left" | "top-right" | "top-left"` · default: `"bottom-right"`
|
|
298
|
+
|
|
299
|
+
Launcher corner. Only applies in `floating` mode.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
init({ presentation: { position: 'bottom-left' } });
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### `behavior.startMinimized`
|
|
306
|
+
|
|
307
|
+
`boolean` · default: `true`
|
|
308
|
+
|
|
309
|
+
Whether the floating panel starts closed (showing only the launcher).
|
|
310
|
+
Only applies in `floating` mode.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
// Start open on every fresh page load
|
|
314
|
+
init({ behavior: { startMinimized: false } });
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
User's last open/close choice is remembered per device — so even with
|
|
318
|
+
`behavior.startMinimized: false`, closing the widget and reloading keeps it
|
|
319
|
+
closed.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Theming
|
|
324
|
+
|
|
325
|
+
### `theme`
|
|
326
|
+
|
|
327
|
+
`"auto" | "light" | "dark" | ThemeOverrides` · default: `"auto"`
|
|
328
|
+
|
|
329
|
+
Three forms:
|
|
330
|
+
|
|
331
|
+
**1. Preset mode:**
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
init({ theme: 'auto' }); // follows OS prefers-color-scheme
|
|
335
|
+
init({ theme: 'light' }); // force light
|
|
336
|
+
init({ theme: 'dark' }); // force dark
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**2. Color overrides (stays in auto mode):**
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
init({
|
|
343
|
+
theme: {
|
|
344
|
+
accent: '#ff3e00', // primary brand color
|
|
345
|
+
accentText: '#ffffff', // text on the accent color
|
|
346
|
+
radius: '12px', // corner rounding
|
|
347
|
+
fontFamily: 'Inter, system-ui, sans-serif',
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**3. Both at once** — not supported in one call, but users can pick a
|
|
353
|
+
mode via the header picker regardless of palette overrides.
|
|
354
|
+
|
|
355
|
+
End users can flip the theme via the header picker. Their choice is
|
|
356
|
+
persisted per device + synced cross-device.
|
|
357
|
+
|
|
358
|
+
Full customization guide: [Theming](./theming.md).
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Sizing
|
|
363
|
+
|
|
364
|
+
### `presentation.size`
|
|
365
|
+
|
|
366
|
+
`SizeOptions | undefined`
|
|
367
|
+
|
|
368
|
+
Control the panel's dimensions across three tiers:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
init({
|
|
372
|
+
presentation: {
|
|
373
|
+
size: {
|
|
374
|
+
// Normal tier (default panel size)
|
|
375
|
+
width: '420px',
|
|
376
|
+
height: '640px',
|
|
377
|
+
|
|
378
|
+
// Expanded tier (when user clicks the ⤢ button)
|
|
379
|
+
expanded: { width: '80vw', height: '80vh' },
|
|
380
|
+
|
|
381
|
+
// User drag-resize
|
|
382
|
+
resize: {
|
|
383
|
+
enabled: true,
|
|
384
|
+
minWidth: '360px',
|
|
385
|
+
maxWidth: '900px',
|
|
386
|
+
minHeight: '480px',
|
|
387
|
+
maxHeight: '90vh',
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
// First-render pick
|
|
391
|
+
initialSize: 'auto', // auto / normal / expanded / fullscreen
|
|
392
|
+
autoSizeBreakpoint: 900, // "auto" → expanded if viewport ≥ 900px
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Size tiers
|
|
399
|
+
|
|
400
|
+
- **Normal** — `width`/`height` or mode-specific CSS defaults.
|
|
401
|
+
- **Expanded** — ⤢ header button, configurable dimensions.
|
|
402
|
+
- **Fullscreen** — ⛶ header button, always 100vw × 100vh.
|
|
403
|
+
|
|
404
|
+
### Drag-resize
|
|
405
|
+
|
|
406
|
+
`presentation.size.resize.enabled: true` shows a corner grip on the panel. Users drag
|
|
407
|
+
it to resize; dimensions persist per device + sync cross-device.
|
|
408
|
+
`presentation.size.resize.minWidth` etc. clamp the drag range.
|
|
409
|
+
|
|
410
|
+
### `presentation.size.initialSize`
|
|
411
|
+
|
|
412
|
+
Which tier to open at on the user's **first** mount. After that, user
|
|
413
|
+
interactions own the size.
|
|
414
|
+
|
|
415
|
+
- `"normal"` — force the normal tier.
|
|
416
|
+
- `"expanded"` — open roomy.
|
|
417
|
+
- `"fullscreen"` — open full-screen.
|
|
418
|
+
- `"auto"` (default) — `fullscreen` below 640 px, `normal` from 640 px
|
|
419
|
+
to `autoSizeBreakpoint`, then `expanded` on wider viewports.
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Launcher
|
|
424
|
+
|
|
425
|
+
### `launcher`
|
|
426
|
+
|
|
427
|
+
`LauncherOptions | undefined` · only applies in `presentation.mode: "floating"`
|
|
428
|
+
|
|
429
|
+
The FAB and its nudges.
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
init({
|
|
433
|
+
launcher: {
|
|
434
|
+
variant: 'pill', // circle / pill / bar / minimal
|
|
435
|
+
size: 'md', // sm / md / lg
|
|
436
|
+
label: 'Ask us anything',
|
|
437
|
+
icon: 'chat', // chat / message / bubbles / spark / wand / bot / help / lifebuoy / headset / search / sparkles / concierge / wave / raw SVG
|
|
438
|
+
hidden: false, // true = no FAB
|
|
439
|
+
|
|
440
|
+
// Unified pre-engagement bubble (optional). Same component
|
|
441
|
+
// for greetings, nudges, onboarding tips — pick the
|
|
442
|
+
// visuals + dismissal lifecycle for the use case.
|
|
443
|
+
callout: {
|
|
444
|
+
text: '👋 Hi! Need any help?',
|
|
445
|
+
shape: 'pill', // pill / bubble / callout
|
|
446
|
+
position: 'auto', // auto / horizontal / vertical
|
|
447
|
+
delayMs: 1200,
|
|
448
|
+
autoDismissMs: 0,
|
|
449
|
+
animated: true,
|
|
450
|
+
persistent: true, // true (default) = re-show on every visit
|
|
451
|
+
// false = dismiss-once, hidden across visits
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Variants + callout fully explained in
|
|
458
|
+
[Theming § Launcher](./theming.md#launcher).
|
|
459
|
+
|
|
460
|
+
### `launcher.hidden`
|
|
461
|
+
|
|
462
|
+
Hide the launcher FAB entirely. Use when you want to trigger the widget
|
|
463
|
+
from your own UI:
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
const chat = init({ launcher: { hidden: true } });
|
|
467
|
+
document.getElementById('my-custom-chat-button').onclick = () => chat.open();
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Header actions
|
|
473
|
+
|
|
474
|
+
### `header.actions`
|
|
475
|
+
|
|
476
|
+
`ActionName[]` · per-mode defaults
|
|
477
|
+
|
|
478
|
+
The list is a flat enabled set. `history` and `close` render as dedicated
|
|
479
|
+
buttons; the rest live in the ⋯ menu in a fixed internal order. Unlisted actions
|
|
480
|
+
are hidden, so input order does not choose placement.
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
init({
|
|
484
|
+
header: {
|
|
485
|
+
actions: ['theme', 'textSize', 'history', 'close', 'clear', 'fullscreen', 'popOut', 'sound', 'language'],
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Available actions:
|
|
491
|
+
|
|
492
|
+
| Name | What it does |
|
|
493
|
+
| ------------ | --------------------------------------------------------------------------- |
|
|
494
|
+
| `clear` | Start a new conversation (trash icon) |
|
|
495
|
+
| `expand` | Toggle normal ↔ expanded tier (⤢) |
|
|
496
|
+
| `fullscreen` | Toggle normal ↔ fullscreen (⛶) |
|
|
497
|
+
| `popOut` | Open the active conversation in `behavior.popOutUrl` |
|
|
498
|
+
| `sound` | Toggle notification sounds (switch-style) |
|
|
499
|
+
| `history` | Toggle the chat history pane |
|
|
500
|
+
| `language` | Open the language picker (renders as globe icon or inline rows in overflow) |
|
|
501
|
+
| `theme` | Open the theme picker (auto/light/dark) |
|
|
502
|
+
| `textSize` | Cycle the conversation text size |
|
|
503
|
+
| `close` | Close the panel (X) |
|
|
504
|
+
|
|
505
|
+
`popOut` renders only when `behavior.popOutUrl` is configured. The new tab
|
|
506
|
+
receives `chat`, `widgetId`, `public_key` (when present), and
|
|
507
|
+
`ai_agent_deployment_id` query parameters so it can resume the same thread.
|
|
508
|
+
|
|
509
|
+
Per-mode defaults and visibility rules are maintained in
|
|
510
|
+
Header actions.
|
|
511
|
+
|
|
512
|
+
> `header.actions` is always a complete replacement for the per-mode default;
|
|
513
|
+
> arrays are never merged. An explicit client list wins over the server list.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Composer
|
|
518
|
+
|
|
519
|
+
### `composer.sendButton`
|
|
520
|
+
|
|
521
|
+
Send-button chrome.
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
init({
|
|
525
|
+
composer: {
|
|
526
|
+
sendButton: {
|
|
527
|
+
shape: 'pill', // circle / square / pill
|
|
528
|
+
icon: 'arrowUp', // arrowUp / paperPlane / send / chevron
|
|
529
|
+
variant: 'filled', // filled / outline / ghost
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
3 shapes × 4 icons × 3 variants = 36 combinations. Pick what matches
|
|
536
|
+
your brand.
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## Footer
|
|
541
|
+
|
|
542
|
+
### `footer.composerDisclaimer`
|
|
543
|
+
|
|
544
|
+
Small grey text below the textarea. Classic "AI can make mistakes"
|
|
545
|
+
pattern.
|
|
546
|
+
|
|
547
|
+
```ts
|
|
548
|
+
init({ footer: { composerDisclaimer: 'AI can make mistakes. Check important info.' } });
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Empty / omitted = no disclaimer shown.
|
|
552
|
+
|
|
553
|
+
### `footer.poweredBy`
|
|
554
|
+
|
|
555
|
+
Attribution row below the disclaimer. Defaults to the active
|
|
556
|
+
brand: `Powered by <BRAND.displayName>` linking to `BRAND.siteUrl`
|
|
557
|
+
(e.g. `Powered by Help AI` → `https://help.ai` for `@helpai/elements`).
|
|
558
|
+
Override fields per-deployment when you want different copy or to add a logo;
|
|
559
|
+
omit fields to inherit the brand defaults.
|
|
560
|
+
|
|
561
|
+
```ts
|
|
562
|
+
init({
|
|
563
|
+
footer: {
|
|
564
|
+
poweredBy: {
|
|
565
|
+
text: 'Powered by Acme', // overrides brand default
|
|
566
|
+
href: 'https://acme.com', // overrides brand default
|
|
567
|
+
logoUrl: 'https://yoursite.com/helpai-logo.svg',
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
To opt out of the attribution entirely, pass an empty `text` (and no
|
|
574
|
+
logo): `footer: { poweredBy: { text: "" } }` collapses the element.
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Attachments + features
|
|
579
|
+
|
|
580
|
+
### `composer.attachments`
|
|
581
|
+
|
|
582
|
+
Client-side limits. Server enforces its own — these are to give users
|
|
583
|
+
fast feedback before upload.
|
|
584
|
+
|
|
585
|
+
```ts
|
|
586
|
+
init({
|
|
587
|
+
composer: {
|
|
588
|
+
attachments: {
|
|
589
|
+
maxBytes: 10 * 1024 * 1024, // 10 MB, default
|
|
590
|
+
maxCount: 10, // default
|
|
591
|
+
accept: 'image/*,application/pdf', // comma-separated MIME globs
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### `features.disableFileUpload`
|
|
598
|
+
|
|
599
|
+
`boolean` · default: `false`
|
|
600
|
+
|
|
601
|
+
Turn OFF file uploads — hide the paperclip button and reject drag-drop / paste.
|
|
602
|
+
Unset/`false` = enabled (default); set `true` to disable attachments entirely.
|
|
603
|
+
|
|
604
|
+
### `features.enableOperatorRequest`
|
|
605
|
+
|
|
606
|
+
`boolean` · default: `false`
|
|
607
|
+
|
|
608
|
+
Opt IN **operator control** — exposes a "Talk to a person" control in the
|
|
609
|
+
composer and the connected-to-operator states (a status banner while the conversation
|
|
610
|
+
is routed to the operator, and operator reply bubbles). This is a **positive opt-in**
|
|
611
|
+
for a new capability (unlike the `disable*` flags): unset/`false` = off (default), `true` = on.
|
|
612
|
+
|
|
613
|
+
Every deployment holds one long-lived `GET /pai/stream-resume`; this flag does not gate
|
|
614
|
+
the stream. It only exposes the visitor's `request-operator` control. AI replies, operator
|
|
615
|
+
replies, and control-state changes continue to arrive on the resume channel and deduplicate
|
|
616
|
+
by message id whether the request control is visible or not. The composer stays open while
|
|
617
|
+
operator-routed so the visitor can keep typing. Operator requests require backend support.
|
|
618
|
+
|
|
619
|
+
```ts
|
|
620
|
+
init({ features: { enableOperatorRequest: true } });
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### `features.voice`
|
|
624
|
+
|
|
625
|
+
`"local" | "server" | "disabled"` · default: `"local"`
|
|
626
|
+
|
|
627
|
+
How voice input works:
|
|
628
|
+
|
|
629
|
+
- `"local"` — uses the browser's Web Speech API. Zero upload, free,
|
|
630
|
+
quality varies by browser.
|
|
631
|
+
- `"server"` — records audio via `MediaRecorder`, uploads to
|
|
632
|
+
`/pai/transcribe-audio`, receives text. Consistent quality, uses your
|
|
633
|
+
upload quota.
|
|
634
|
+
- `"disabled"` — hides the mic button entirely.
|
|
635
|
+
|
|
636
|
+
## Messenger modules
|
|
637
|
+
|
|
638
|
+
### `modules`
|
|
639
|
+
|
|
640
|
+
Turns the panel into an Intercom-style **messenger** with a bottom tab
|
|
641
|
+
bar. `modules` is an **ordered array of up to 4 tabs**; each tab picks a UI
|
|
642
|
+
`layout`, the content collections it shows (`contentCollectionIds`), and a
|
|
643
|
+
translatable `label`. The surface renders in
|
|
644
|
+
**every** mode (in `page` mode it fills the main area beside the sidebar). With nothing set, just the **chat** shows —
|
|
645
|
+
today's single-chat widget, no tab bar.
|
|
646
|
+
|
|
647
|
+
```ts
|
|
648
|
+
init({
|
|
649
|
+
publicKey: 'agent_pk_...',
|
|
650
|
+
aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000',
|
|
651
|
+
modules: [
|
|
652
|
+
{ label: 'tabHome', layout: 'home', contentCollectionIds: [GUIDES_ID], greetingText: 'Hi {name} 👋', brandName: 'Acme' }, // "Popular articles" = the collections' pinned items
|
|
653
|
+
{ label: 'tabConversations', layout: 'chat' }, // the conversation
|
|
654
|
+
{ id: 'help', label: 'tabHelp', layout: 'help', contentCollectionIds: [GUIDES_ID, BILLING_ID] },
|
|
655
|
+
{ id: 'news', label: 'tabNews', layout: 'news', contentCollectionIds: [NEWS_ID] },
|
|
656
|
+
],
|
|
657
|
+
});
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
| Field | Required | Meaning |
|
|
661
|
+
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------- |
|
|
662
|
+
| `label` | yes | Tab label — an i18n key (e.g. `tabHome`) or literal; translated via `i18n.strings`. |
|
|
663
|
+
| `layout` | yes | `chat` (the conversation), `home`, `help`, or `news`. |
|
|
664
|
+
| `contentCollectionIds` | no | Content collection ids (UUIDv7, max 20) this tab shows, in order; Help sections follow this order. |
|
|
665
|
+
| `id` | no | Stable id for nav + last-tab memory (defaults to the first `contentCollectionIds`, else `layout`). |
|
|
666
|
+
| home extras | no | `greetingText` (supports `{name}`), `hideSearchBar`, `brandName`, … (`home` layout only). |
|
|
667
|
+
|
|
668
|
+
- **Order is the array order** (left → right). One tab → no tab bar; zero
|
|
669
|
+
tabs (`[]`) → an empty state.
|
|
670
|
+
- The first tab is the first-open default; the widget then remembers the
|
|
671
|
+
user's last tab (per device).
|
|
672
|
+
- The deployment can also push `modules` from the dashboard via the
|
|
673
|
+
handshake (`config.modules`); your `init({ modules })` wins (replace-whole).
|
|
674
|
+
|
|
675
|
+
Each tab's content is fetched lazily from `GET /content` (data-api) on first open.
|
|
676
|
+
Full behavior + wire contract:
|
|
677
|
+
`features/messenger-modules.md`.
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
## Response mode
|
|
682
|
+
|
|
683
|
+
### `behavior.responseMode`
|
|
684
|
+
|
|
685
|
+
`"streaming" | "buffered"` · default: `"streaming"`
|
|
686
|
+
|
|
687
|
+
- `"streaming"` — paint replies token-by-token as they arrive. Default.
|
|
688
|
+
- `"buffered"` — show a neutral loading indicator until the reply
|
|
689
|
+
finishes, then render it whole. The backend always streams; this is a
|
|
690
|
+
client-rendering choice. Useful when token-by-token feels stuttery.
|
|
691
|
+
|
|
692
|
+
### `behavior.disableMessageFeedback`
|
|
693
|
+
|
|
694
|
+
`boolean` · default: `false`
|
|
695
|
+
|
|
696
|
+
Turn OFF the 👍/👎 feedback controls under each rateable reply (AI or operator) — unset/`false` shows them (default). While
|
|
697
|
+
shown, a choice fires the [`messageFeedback`](./events-and-hooks.md) event and is sent to the backend.
|
|
698
|
+
|
|
699
|
+
```ts
|
|
700
|
+
init({ behavior: { disableMessageFeedback: true } }); // hide the controls
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### `behavior.autoSendSuggestions`
|
|
704
|
+
|
|
705
|
+
`boolean` · default: `false`
|
|
706
|
+
|
|
707
|
+
Controls what tapping a suggestion chip does. Unset/`false` (default) **fills the composer** with the chip's
|
|
708
|
+
text so the visitor can edit before sending; `true` **sends** it immediately.
|
|
709
|
+
|
|
710
|
+
```ts
|
|
711
|
+
init({ behavior: { autoSendSuggestions: true } }); // send on tap instead of filling
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## Internationalization
|
|
717
|
+
|
|
718
|
+
### `i18n.locale`
|
|
719
|
+
|
|
720
|
+
`string | undefined`
|
|
721
|
+
|
|
722
|
+
Force a specific BCP-47 locale, overriding the visitor's browser
|
|
723
|
+
setting.
|
|
724
|
+
|
|
725
|
+
```ts
|
|
726
|
+
init({ i18n: { locale: 'fr-CA' } });
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### `i18n.defaultLocale`
|
|
730
|
+
|
|
731
|
+
`"auto" | string` · default: `"auto"`
|
|
732
|
+
|
|
733
|
+
Initial / fallback locale when the user hasn't picked one.
|
|
734
|
+
|
|
735
|
+
- `"auto"` — sniff `navigator.language`.
|
|
736
|
+
- Explicit tag (e.g., `"es-MX"`) — force that one.
|
|
737
|
+
|
|
738
|
+
### `i18n.availableLocales`
|
|
739
|
+
|
|
740
|
+
`string[]` · optional
|
|
741
|
+
|
|
742
|
+
Locales offered in the language switcher. Omission defaults to the active
|
|
743
|
+
locale as a one-entry list; zero or one entries hide the picker.
|
|
744
|
+
|
|
745
|
+
```ts
|
|
746
|
+
init({
|
|
747
|
+
i18n: {
|
|
748
|
+
availableLocales: ['en', 'fr', 'es', 'de'],
|
|
749
|
+
defaultLocale: 'auto',
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### `i18n.strings`
|
|
755
|
+
|
|
756
|
+
Per-key overrides for any UI string. Two shapes:
|
|
757
|
+
|
|
758
|
+
**Flat** (applies to active locale):
|
|
759
|
+
|
|
760
|
+
```ts
|
|
761
|
+
init({
|
|
762
|
+
i18n: {
|
|
763
|
+
strings: {
|
|
764
|
+
send: 'Go',
|
|
765
|
+
composerPlaceholder: 'Ask me anything…',
|
|
766
|
+
newConversation: 'Start fresh',
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
**Nested** (per-locale):
|
|
773
|
+
|
|
774
|
+
```ts
|
|
775
|
+
init({
|
|
776
|
+
i18n: {
|
|
777
|
+
strings: {
|
|
778
|
+
en: { send: 'Go' },
|
|
779
|
+
fr: { send: 'Envoyer' },
|
|
780
|
+
es: { send: 'Enviar' },
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
});
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
Full key list: [i18n](./theming.md#internationalization) or
|
|
787
|
+
`src/i18n/strings.ts` in the source.
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
791
|
+
## Sound + haptics
|
|
792
|
+
|
|
793
|
+
### `feedback.sound`
|
|
794
|
+
|
|
795
|
+
Notification sounds.
|
|
796
|
+
|
|
797
|
+
```ts
|
|
798
|
+
init({
|
|
799
|
+
feedback: {
|
|
800
|
+
sound: {
|
|
801
|
+
enabled: true, // default
|
|
802
|
+
volume: 0.3, // 0..1, default 0.3
|
|
803
|
+
events: {
|
|
804
|
+
messageReceived: true, // built-in synth tone
|
|
805
|
+
messageSent: true,
|
|
806
|
+
error: 'https://cdn.your-site.com/error.mp3', // custom audio file
|
|
807
|
+
voiceStart: false, // silence this one
|
|
808
|
+
voiceStop: false,
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
});
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
Per-event values:
|
|
816
|
+
|
|
817
|
+
- `true` — use the built-in synthesized tone.
|
|
818
|
+
- `false` — silence this event.
|
|
819
|
+
- `"url"` — URL to a custom audio file (decoded once + cached).
|
|
820
|
+
|
|
821
|
+
End users can mute via the sound action in the header. Their choice
|
|
822
|
+
is remembered.
|
|
823
|
+
|
|
824
|
+
### `feedback.haptics`
|
|
825
|
+
|
|
826
|
+
Tactile feedback on touch devices.
|
|
827
|
+
|
|
828
|
+
```ts
|
|
829
|
+
init({
|
|
830
|
+
feedback: {
|
|
831
|
+
haptics: {
|
|
832
|
+
enabled: true, // default: true on touch devices
|
|
833
|
+
events: {
|
|
834
|
+
messageSent: [6], // single 6ms tap
|
|
835
|
+
error: [10, 50, 10], // two taps with a 50ms pause
|
|
836
|
+
},
|
|
837
|
+
},
|
|
838
|
+
},
|
|
839
|
+
});
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
Per-event values:
|
|
843
|
+
|
|
844
|
+
- `true` — default pattern.
|
|
845
|
+
- `false` — silence.
|
|
846
|
+
- number — single vibration of N ms.
|
|
847
|
+
- number[] — alternating `[vibrate, pause, vibrate, …]`.
|
|
848
|
+
|
|
849
|
+
iOS: WebKit only supports a single tick; arrays are flattened.
|
|
850
|
+
|
|
851
|
+
---
|
|
852
|
+
|
|
853
|
+
## Callbacks (shortcut for events)
|
|
854
|
+
|
|
855
|
+
Quick inline callbacks for common events. Same as calling `.on(...)`
|
|
856
|
+
on the returned instance.
|
|
857
|
+
|
|
858
|
+
```ts
|
|
859
|
+
init({
|
|
860
|
+
publicKey: 'agent_pk_...',
|
|
861
|
+
aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000',
|
|
862
|
+
onMessage: (msg) => console.log(msg),
|
|
863
|
+
onOpen: () => console.log('opened'),
|
|
864
|
+
onClose: () => console.log('closed'),
|
|
865
|
+
onError: (err) => Sentry.captureException(err),
|
|
866
|
+
});
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
For richer event handling, see [Events & hooks](./events-and-hooks.md).
|
|
870
|
+
|
|
871
|
+
---
|
|
872
|
+
|
|
873
|
+
## Storage
|
|
874
|
+
|
|
875
|
+
### `storage`
|
|
876
|
+
|
|
877
|
+
`ChatWidgetOptions["storage"]`
|
|
878
|
+
|
|
879
|
+
Custom synchronous storage adapter. Default is `localStorage`. Swap for:
|
|
880
|
+
|
|
881
|
+
- `sessionStorage`.
|
|
882
|
+
- Encrypted wrapper.
|
|
883
|
+
- In-memory shim (for environments without `localStorage`).
|
|
884
|
+
|
|
885
|
+
```ts
|
|
886
|
+
import type { ChatWidgetOptions } from '@helpai/elements';
|
|
887
|
+
|
|
888
|
+
type StorageAdapter = NonNullable<ChatWidgetOptions['storage']>;
|
|
889
|
+
|
|
890
|
+
const myStorage: StorageAdapter = {
|
|
891
|
+
get: (key) => sessionStorage.getItem(key),
|
|
892
|
+
set: (key, value) => sessionStorage.setItem(key, value),
|
|
893
|
+
remove: (key) => sessionStorage.removeItem(key),
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
init({ publicKey: 'agent_pk_...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000', storage: myStorage });
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
The adapter also receives `draft.<conversationId>` keys containing exact
|
|
900
|
+
unsent textarea text and owns their storage, retention, and privacy behavior.
|
|
901
|
+
The widget itself does not include drafts in Help AI network requests before
|
|
902
|
+
Send. Use an encrypted adapter when same-origin plaintext storage is not
|
|
903
|
+
acceptable; an in-memory adapter disables reload restoration.
|
|
904
|
+
|
|
905
|
+
Full adapter contract: [Security & privacy § custom storage](./security-privacy.md#custom-storage).
|
|
906
|
+
|
|
907
|
+
---
|
|
908
|
+
|
|
909
|
+
## Runtime changes
|
|
910
|
+
|
|
911
|
+
You can change most options after mount via `chat.update(...)`:
|
|
912
|
+
|
|
913
|
+
```ts
|
|
914
|
+
const chat = init({ publicKey: 'agent_pk_...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
|
|
915
|
+
|
|
916
|
+
// Later — flip to dark mode
|
|
917
|
+
chat.update({ theme: 'dark' });
|
|
918
|
+
|
|
919
|
+
// Or change the launcher label
|
|
920
|
+
chat.update({ launcher: { variant: 'pill', label: 'New message!' } });
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
`update()` is a **shallow merge** at the top level. Nested objects are
|
|
924
|
+
replaced, not merged:
|
|
925
|
+
|
|
926
|
+
```ts
|
|
927
|
+
chat.update({ presentation: { size: { expanded: { width: '800px' } } } });
|
|
928
|
+
// This replaces the whole `presentation` section — mode, iframe, and the other size fields are gone!
|
|
929
|
+
|
|
930
|
+
// Keep the current section in your application state and spread each nested level you preserve:
|
|
931
|
+
chat.update({
|
|
932
|
+
presentation: {
|
|
933
|
+
...currentPresentation,
|
|
934
|
+
size: {
|
|
935
|
+
...currentPresentation.size,
|
|
936
|
+
expanded: { width: '800px' },
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
});
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
The `publicKey`, `widgetId`, `presentation.mode`, and `presentation.iframe` can't change at runtime
|
|
943
|
+
— they require a full remount (`destroy()` + `mount()`).
|
|
944
|
+
|
|
945
|
+
---
|
|
946
|
+
|
|
947
|
+
## Putting it all together — full example
|
|
948
|
+
|
|
949
|
+
```ts
|
|
950
|
+
import { init } from '@helpai/elements';
|
|
951
|
+
|
|
952
|
+
const chat = init({
|
|
953
|
+
// Identity
|
|
954
|
+
publicKey: process.env.NEXT_PUBLIC_HELPAI_KEY!,
|
|
955
|
+
aiAgentDeploymentId: process.env.NEXT_PUBLIC_HELPAI_DEPLOYMENT_ID!,
|
|
956
|
+
widgetId: 'support',
|
|
957
|
+
userContext: { id: currentUser.id, email: currentUser.email, name: currentUser.name },
|
|
958
|
+
pageContext: { area: 'support', path: location.pathname },
|
|
959
|
+
|
|
960
|
+
// Presentation + behavior
|
|
961
|
+
presentation: {
|
|
962
|
+
mode: 'floating',
|
|
963
|
+
position: 'bottom-right',
|
|
964
|
+
size: {
|
|
965
|
+
expanded: { width: '720px', height: '85vh' },
|
|
966
|
+
resize: { enabled: true, minWidth: '360px', maxWidth: '900px' },
|
|
967
|
+
initialSize: 'auto',
|
|
968
|
+
autoSizeBreakpoint: 1024,
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
behavior: { startMinimized: true },
|
|
972
|
+
theme: { accent: '#ff3e00', radius: '12px' },
|
|
973
|
+
|
|
974
|
+
// Launcher
|
|
975
|
+
launcher: {
|
|
976
|
+
variant: 'pill',
|
|
977
|
+
label: 'Ask us anything',
|
|
978
|
+
icon: 'chat',
|
|
979
|
+
callout: { text: 'Hey there 👋', delayMs: 2000 },
|
|
980
|
+
},
|
|
981
|
+
|
|
982
|
+
// i18n
|
|
983
|
+
i18n: {
|
|
984
|
+
availableLocales: ['en', 'fr', 'es'],
|
|
985
|
+
defaultLocale: 'auto',
|
|
986
|
+
strings: {
|
|
987
|
+
en: { composerPlaceholder: 'Ask about anything…' },
|
|
988
|
+
fr: { composerPlaceholder: 'Demandez-moi…' },
|
|
989
|
+
},
|
|
990
|
+
},
|
|
991
|
+
|
|
992
|
+
// Feedback
|
|
993
|
+
feedback: { sound: { enabled: true, volume: 0.25 } },
|
|
994
|
+
|
|
995
|
+
// Footer
|
|
996
|
+
footer: {
|
|
997
|
+
composerDisclaimer: 'AI-generated answers may contain mistakes.',
|
|
998
|
+
poweredBy: { text: 'Powered by Help AI', href: 'https://help.ai' },
|
|
999
|
+
},
|
|
1000
|
+
|
|
1001
|
+
// Events
|
|
1002
|
+
onMessage: (m) => analytics.track('chat_message', m),
|
|
1003
|
+
onError: (err) => Sentry.captureException(err),
|
|
1004
|
+
});
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
## Not covered here
|
|
1010
|
+
|
|
1011
|
+
- **Events** → [Events & hooks](./events-and-hooks.md)
|
|
1012
|
+
- **Theming deep-dive** → [Theming](./theming.md)
|
|
1013
|
+
- **Framework-specific wiring** → [Frameworks](./frameworks.md)
|
|
1014
|
+
- **Backend API** → Backend contract (internal)
|