@helpai/elements 1.8.0 → 1.9.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 +622 -0
- package/docs/browser-support.md +302 -0
- package/docs/configuration.md +1001 -0
- package/docs/events-and-hooks.md +483 -0
- package/docs/faq.md +435 -0
- package/docs/frameworks.md +640 -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 +575 -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 +84 -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 +7862 -0
- package/llms.txt +36 -0
- package/loader.js +3 -3
- package/loader.min.js +1 -1
- package/package.json +36 -1
- package/react.d.ts +3552 -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 +1561 -17
- package/web-component.mjs +14 -14
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# Events & lifecycle hooks
|
|
2
|
+
|
|
3
|
+
Listen for chat interactions, forward them to analytics, trigger
|
|
4
|
+
custom UX.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Two mechanisms
|
|
9
|
+
|
|
10
|
+
| Mechanism | Scope | When to use |
|
|
11
|
+
| ----------------------------------------------------- | ------------------------------------------ | ---------------------------------------------- |
|
|
12
|
+
| **Instance events** — `chat.on(event, fn)` | One widget you hold a reference to | Custom UX wired to a specific widget |
|
|
13
|
+
| **Global hooks** — `HelpAI.hook(widgetId, event, fn)` | Any widget on the page (by id or wildcard) | Analytics, error reporting, cross-widget sinks |
|
|
14
|
+
|
|
15
|
+
Both deliver the same event stream. Both are safe to mix.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Available events
|
|
20
|
+
|
|
21
|
+
| Event | Fires on | Payload |
|
|
22
|
+
| ----------------- | ------------------------------------------------- | ------------------------------------------------------ |
|
|
23
|
+
| `open` | Panel opens (floating mode) | `void` |
|
|
24
|
+
| `close` | Panel closes | `void` |
|
|
25
|
+
| `message` | User sends a message + assistant finishes a reply | `{ role: "user" \| "assistant"; text: string }` |
|
|
26
|
+
| `error` | Transport error or mid-stream error | `unknown` (error object or message) |
|
|
27
|
+
| `expand` | Expanded tier toggled (⤢) | `boolean` (new state) |
|
|
28
|
+
| `fullscreen` | Fullscreen tier toggled (⛶) | `boolean` (new state) |
|
|
29
|
+
| `handshake` | Widget finishes booting (once per mount) | `HandshakeResponse` (dashboard config) |
|
|
30
|
+
| `localeChange` | User picks a different language | `string` (new BCP-47 tag) |
|
|
31
|
+
| `messageFeedback` | User rates a reply 👍/👎 (or clears it) | `{ messageId: string; value: "up" \| "down" \| null }` |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Instance events
|
|
36
|
+
|
|
37
|
+
If you hold the `ChatWidgetInstance` returned from `init()` / `mount()`,
|
|
38
|
+
subscribe with `.on()`:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const chat = init({ publicKey: 'agent_pk_...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
|
|
42
|
+
|
|
43
|
+
const off = chat.on('message', (msg) => {
|
|
44
|
+
console.log(`[${msg.role}] ${msg.text}`);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Later — unsubscribe
|
|
48
|
+
off();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`.on()` returns an unsubscribe function. Call it when you don't need
|
|
52
|
+
the listener anymore (e.g., component unmount).
|
|
53
|
+
|
|
54
|
+
### Inline callbacks
|
|
55
|
+
|
|
56
|
+
For one-off handlers, pass them directly to `init()`:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
init({
|
|
60
|
+
publicKey: 'agent_pk_...',
|
|
61
|
+
aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000',
|
|
62
|
+
onMessage: (m) => analytics.track('chat_msg', m),
|
|
63
|
+
onOpen: () => analytics.track('chat_open'),
|
|
64
|
+
onClose: () => analytics.track('chat_close'),
|
|
65
|
+
onError: (err) => Sentry.captureException(err),
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Equivalent to calling `.on()` immediately after mount but cleaner for
|
|
70
|
+
simple cases.
|
|
71
|
+
|
|
72
|
+
### Typed payloads
|
|
73
|
+
|
|
74
|
+
Events are typed. In TypeScript, the payload is narrowed to the
|
|
75
|
+
correct shape:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import type { EventMap } from '@helpai/elements';
|
|
79
|
+
|
|
80
|
+
chat.on('message', (m) => {
|
|
81
|
+
// m is typed as { role: string; text: string }
|
|
82
|
+
console.log(m.role, m.text);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
chat.on('expand', (isExpanded) => {
|
|
86
|
+
// isExpanded is typed as boolean
|
|
87
|
+
if (isExpanded) console.log('expanded');
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Global lifecycle hooks
|
|
94
|
+
|
|
95
|
+
Different problem: you want to listen to events from **any** widget on
|
|
96
|
+
the page, possibly before the widget even loads.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { hook } from '@helpai/elements';
|
|
100
|
+
|
|
101
|
+
// Fire for every event on every widget
|
|
102
|
+
hook('*', '*', (instance, event, data) => {
|
|
103
|
+
analytics.track(`chat.${event}`, {
|
|
104
|
+
widgetId: instance.widgetId,
|
|
105
|
+
data,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Fire only for 'message' on the "support" widget
|
|
110
|
+
hook('support', 'message', (instance, _event, data) => {
|
|
111
|
+
logSupportMessage(data);
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Wildcards (`"*"`) match any widget id or event name. Returns an
|
|
116
|
+
unsubscribe function.
|
|
117
|
+
|
|
118
|
+
### Differences from `.on()`
|
|
119
|
+
|
|
120
|
+
| | `chat.on(event, fn)` | `hook(widgetId, event, fn)` |
|
|
121
|
+
| ------------------------- | -------------------- | ------------------------------------ |
|
|
122
|
+
| Scope | One widget | Any matching widget(s) |
|
|
123
|
+
| Callback signature | `(payload) => void` | `(instance, event, payload) => void` |
|
|
124
|
+
| Needs the instance handle | Yes | No — works with zero-JS embeds |
|
|
125
|
+
| Survives `destroy()` | No | Yes — registry is page-scoped |
|
|
126
|
+
| Pre-boot queueable | No | Yes |
|
|
127
|
+
|
|
128
|
+
### Pre-boot hooks
|
|
129
|
+
|
|
130
|
+
If you use the async loader snippet, you can register hooks **before
|
|
131
|
+
the bundle arrives**. Push tuples onto `window.HelpAIHooks`:
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<script>
|
|
135
|
+
window.HelpAIHooks = window.HelpAIHooks || [];
|
|
136
|
+
|
|
137
|
+
window.HelpAIHooks.push([
|
|
138
|
+
'*',
|
|
139
|
+
'*',
|
|
140
|
+
(inst, event, data) => {
|
|
141
|
+
analytics.track(`chat.${event}`, { widgetId: inst.widgetId, data });
|
|
142
|
+
},
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
window.HelpAIHooks.push([
|
|
146
|
+
'support',
|
|
147
|
+
'open',
|
|
148
|
+
(inst) => {
|
|
149
|
+
mixpanel.track('support_opened');
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
</script>
|
|
153
|
+
<!-- Widget loads below; hooks fire on its first event -->
|
|
154
|
+
<script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" defer></script>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Once the bundle loads, `window.HelpAIHooks` stays as a "live facade"
|
|
158
|
+
— you can keep pushing to it from lazily-loaded scripts and entries
|
|
159
|
+
register eagerly.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Event catalog — detailed
|
|
164
|
+
|
|
165
|
+
### `open`
|
|
166
|
+
|
|
167
|
+
Fires when the floating panel opens. Does NOT fire for inline /
|
|
168
|
+
standalone widgets (they're "always open").
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
chat.on('open', () => {
|
|
172
|
+
document.body.classList.add('chat-open');
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### `close`
|
|
177
|
+
|
|
178
|
+
Fires when the panel closes. In inline + fullscreen, fires `fullscreen:
|
|
179
|
+
false` instead (the Escape routes to "exit fullscreen").
|
|
180
|
+
|
|
181
|
+
### `message`
|
|
182
|
+
|
|
183
|
+
Fires **twice per user turn**:
|
|
184
|
+
|
|
185
|
+
1. On user send — `role: "user"`. Fires before the network round-trip.
|
|
186
|
+
2. On assistant finish — `role: "assistant"`. Fires when the final
|
|
187
|
+
`finish` frame arrives (stream complete).
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
chat.on('message', (m) => {
|
|
191
|
+
if (m.role === 'user') {
|
|
192
|
+
analytics.track('message_sent', { chars: m.text.length });
|
|
193
|
+
} else {
|
|
194
|
+
analytics.track('message_received', { chars: m.text.length });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`text` is the **final assembled text** — not per-token. If you need
|
|
200
|
+
token-by-token, subscribe to signals directly (advanced).
|
|
201
|
+
|
|
202
|
+
### `error`
|
|
203
|
+
|
|
204
|
+
Fires on transport errors, mid-stream errors, or reducer errors.
|
|
205
|
+
Payload is `unknown` — test its shape:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
chat.on('error', (err) => {
|
|
209
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
210
|
+
console.error('Widget error:', message);
|
|
211
|
+
Sentry.captureException(err);
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The widget catches errors in listeners themselves — a throw inside
|
|
216
|
+
your handler won't break the widget or stop other listeners.
|
|
217
|
+
|
|
218
|
+
### `expand`
|
|
219
|
+
|
|
220
|
+
Fires when the user toggles the ⤢ expanded tier. Payload is the new
|
|
221
|
+
state (`true` = entering expanded, `false` = leaving).
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
chat.on('expand', (isExpanded) => {
|
|
225
|
+
if (isExpanded) trackConversationSignal('expanded-tier');
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Does NOT fire when the server-pushed `initialSize` auto-applies on
|
|
230
|
+
first mount.
|
|
231
|
+
|
|
232
|
+
### `fullscreen`
|
|
233
|
+
|
|
234
|
+
Similar to `expand` but for the ⛶ fullscreen tier. Payload is the new
|
|
235
|
+
state.
|
|
236
|
+
|
|
237
|
+
### `handshake`
|
|
238
|
+
|
|
239
|
+
Fires **once per mount** — after the widget finishes booting
|
|
240
|
+
(`/pai/handshake` resolves). Payload is the full bootstrap response
|
|
241
|
+
from the backend:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
chat.on('handshake', (res) => {
|
|
245
|
+
console.log('agent:', res.agent?.name);
|
|
246
|
+
console.log('welcome messages:', res.welcome?.messages);
|
|
247
|
+
console.log('server-pushed widget config:', res.config);
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Does NOT fire on "new conversation" re-connects. Only the very first
|
|
252
|
+
bootstrap.
|
|
253
|
+
|
|
254
|
+
### `conversationRebound`
|
|
255
|
+
|
|
256
|
+
The server echoes the client-owned `visitorId`. If it replaces a client-minted
|
|
257
|
+
`conversationId` during the handshake or first-message acknowledgement, the
|
|
258
|
+
widget persists the replacement before firing this event.
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
chat.on('conversationRebound', ({ previous, current }) => {
|
|
262
|
+
analytics.track('conversation-rebound', { previous, current });
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The common handshake path echoes the requested conversation id and does not
|
|
267
|
+
fire this event.
|
|
268
|
+
|
|
269
|
+
### `localeChange`
|
|
270
|
+
|
|
271
|
+
Fires when the user picks a different language from the header
|
|
272
|
+
picker. Payload is the new BCP-47 tag.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
chat.on('localeChange', (locale) => {
|
|
276
|
+
// Sync the rest of the site's language
|
|
277
|
+
document.documentElement.lang = locale;
|
|
278
|
+
fetch('/api/user/preferences', {
|
|
279
|
+
method: 'POST',
|
|
280
|
+
body: JSON.stringify({ locale }),
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Common patterns
|
|
288
|
+
|
|
289
|
+
### Send every event to Google Analytics 4
|
|
290
|
+
|
|
291
|
+
```html
|
|
292
|
+
<script>
|
|
293
|
+
window.HelpAIHooks = window.HelpAIHooks || [];
|
|
294
|
+
window.HelpAIHooks.push([
|
|
295
|
+
'*',
|
|
296
|
+
'*',
|
|
297
|
+
(inst, event, data) => {
|
|
298
|
+
gtag('event', `chat_${event}`, {
|
|
299
|
+
widget_id: inst.widgetId,
|
|
300
|
+
payload: JSON.stringify(data).slice(0, 100),
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
]);
|
|
304
|
+
</script>
|
|
305
|
+
<script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" defer></script>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Forward to Segment
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { hook } from '@helpai/elements';
|
|
312
|
+
|
|
313
|
+
hook('*', '*', (inst, event, data) => {
|
|
314
|
+
window.analytics.track(`Chat ${event}`, {
|
|
315
|
+
widgetId: inst.widgetId,
|
|
316
|
+
...(typeof data === 'object' && data !== null ? data : {}),
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Capture errors in Sentry
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import { hook } from '@helpai/elements';
|
|
325
|
+
import * as Sentry from '@sentry/browser';
|
|
326
|
+
|
|
327
|
+
hook('*', 'error', (inst, _event, err) => {
|
|
328
|
+
Sentry.captureException(err, {
|
|
329
|
+
tags: { widget: inst.widgetId },
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Auto-close on inactivity
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
const chat = init({ publicKey: 'agent_pk_...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
|
|
338
|
+
|
|
339
|
+
let timer;
|
|
340
|
+
|
|
341
|
+
chat.on('open', () => {
|
|
342
|
+
clearTimeout(timer);
|
|
343
|
+
timer = setTimeout(() => chat.close(), 5 * 60 * 1000); // 5 min
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
chat.on('message', () => {
|
|
347
|
+
clearTimeout(timer);
|
|
348
|
+
timer = setTimeout(() => chat.close(), 5 * 60 * 1000);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
chat.on('close', () => clearTimeout(timer));
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Notify an intranet app when a visitor starts chatting
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
chat.on('message', (m) => {
|
|
358
|
+
if (m.role === 'user') {
|
|
359
|
+
fetch('/api/internal/chat-alerts', {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
body: JSON.stringify({
|
|
362
|
+
firstMessage: m.text,
|
|
363
|
+
userId: currentUser.id,
|
|
364
|
+
at: Date.now(),
|
|
365
|
+
}),
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Custom pulse on launcher when a new conversation is ready
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
const chat = init({ publicKey: 'agent_pk_...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
|
|
375
|
+
|
|
376
|
+
chat.on('handshake', (res) => {
|
|
377
|
+
if (res.welcome?.messages?.length > 0) {
|
|
378
|
+
// Add a CSS class to your custom badge
|
|
379
|
+
document.querySelector('#chat-nudge').classList.add('has-new');
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### React hook for message tracking
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import { getInstance } from '@helpai/elements';
|
|
388
|
+
import { useEffect } from 'react';
|
|
389
|
+
|
|
390
|
+
export function useChatMessages(onMessage: (m: { role: string; text: string }) => void) {
|
|
391
|
+
useEffect(() => {
|
|
392
|
+
const chat = getInstance();
|
|
393
|
+
if (!chat) return;
|
|
394
|
+
return chat.on('message', onMessage);
|
|
395
|
+
}, [onMessage]);
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Instrument user's message lengths
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
hook('*', 'message', (inst, _event, m) => {
|
|
403
|
+
if (m.role !== 'user') return;
|
|
404
|
+
const bucket = m.text.length < 20 ? 'short' : m.text.length < 100 ? 'medium' : 'long';
|
|
405
|
+
analytics.track('chat_message_length', { widgetId: inst.widgetId, bucket });
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Ordering guarantees
|
|
412
|
+
|
|
413
|
+
For a single event fired on a widget:
|
|
414
|
+
|
|
415
|
+
1. Per-instance `.on()` listeners fire in registration order.
|
|
416
|
+
2. Global `hook()` listeners fire in registration order.
|
|
417
|
+
3. Errors in one listener don't break the others.
|
|
418
|
+
|
|
419
|
+
Per-event fire order in a typical turn:
|
|
420
|
+
|
|
421
|
+
```
|
|
422
|
+
User types "hi", hits Enter
|
|
423
|
+
│
|
|
424
|
+
├─ message (role: user, text: "hi") ← fires immediately, before network
|
|
425
|
+
│
|
|
426
|
+
│ [network round-trip, streaming begins]
|
|
427
|
+
│
|
|
428
|
+
└─ message (role: assistant, text: "...") ← fires on stream finish
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Unsubscribe hygiene
|
|
434
|
+
|
|
435
|
+
Every subscription returns an `off()` function. Store and call them
|
|
436
|
+
on cleanup to avoid memory leaks:
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
const offs = [chat.on('open', onOpen), chat.on('message', onMessage), chat.on('error', onError)];
|
|
440
|
+
|
|
441
|
+
// Cleanup (e.g., React component unmount)
|
|
442
|
+
for (const off of offs) off();
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
React pattern:
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
const chat = init({ publicKey, aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
|
|
450
|
+
const offs = [chat.on('message', onMessage), chat.on('error', onError)];
|
|
451
|
+
return () => {
|
|
452
|
+
for (const off of offs) off();
|
|
453
|
+
chat.destroy();
|
|
454
|
+
};
|
|
455
|
+
}, [publicKey]);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
`chat.destroy()` also clears all instance subscriptions — so the
|
|
459
|
+
explicit `off()` is belt-and-suspenders. Either way works.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## When listeners don't fire
|
|
464
|
+
|
|
465
|
+
| Symptom | Likely cause |
|
|
466
|
+
| ----------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
467
|
+
| `open` never fires | Widget is in inline/standalone mode — it's "always open" |
|
|
468
|
+
| `handshake` never fires | Network error on `/pai/handshake`. `error` fires instead |
|
|
469
|
+
| `message` fires twice per user send | Correct — user first, then assistant reply |
|
|
470
|
+
| No events at all | `.on()` called before the instance was fully constructed. Use `init()`'s return value or a pre-boot hook |
|
|
471
|
+
| Pre-boot hook never fires | Widget never mounted — check the public-key/deployment-id pair |
|
|
472
|
+
|
|
473
|
+
[Troubleshooting](./troubleshooting.md) has the full diagnostic
|
|
474
|
+
checklist.
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Next
|
|
479
|
+
|
|
480
|
+
- [Analytics integration](./analytics.md) — GA4, Mixpanel, Segment, Amplitude, PostHog, Sentry, multi-provider
|
|
481
|
+
- [Recipes](./recipes.md) — copy-paste scenarios
|
|
482
|
+
- [API reference § events](./api-reference.md#events) — compact lookup
|
|
483
|
+
- [Configuration § callbacks](./configuration.md#callbacks-shortcut-for-events)
|