@helpai/elements 1.7.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.
@@ -0,0 +1,622 @@
1
+ # API reference
2
+
3
+ Compact lookup for the SDK's exported functions, types, and events.
4
+ For explanations + examples, see the [Configuration](./configuration.md)
5
+ and [Events & hooks](./events-and-hooks.md) guides.
6
+
7
+ ---
8
+
9
+ ## Exports
10
+
11
+ ```ts
12
+ import {
13
+ // Functions
14
+ init, // Mount (or re-config) a widget
15
+ mount, // Mount a new widget (non-idempotent)
16
+ destroy, // Unmount one or all widgets
17
+ hook, // Register a global lifecycle hook
18
+ getInstance, // Look up a live instance
19
+ getAllInstances, // Get every live instance
20
+
21
+ // Constants
22
+ version, // Semver string
23
+ brand, // { id, displayName, cssPrefix, cdnUrl, ... }
24
+ HOOK_WILDCARD, // "*" — wildcard for hook() arguments
25
+
26
+ // Types
27
+ type ChatWidgetOptions,
28
+ type ChatWidgetInstance,
29
+ type ResolvedOptions,
30
+ type EventMap,
31
+ type EventName,
32
+ type HookListener,
33
+ type LogEntry,
34
+ type LogLevel,
35
+ } from '@helpai/elements';
36
+ ```
37
+
38
+ Web component sub-entry (auto-registers the custom element):
39
+
40
+ ```ts
41
+ import '@helpai/elements/web-component';
42
+ ```
43
+
44
+ React kit (React 18+; everything above plus the component and the hook, so import it all from this one path):
45
+
46
+ ```ts
47
+ import {
48
+ HelpAI, // <HelpAI {...options} /> mounts the widget, re-configures it on prop changes, removes it on unmount
49
+ useHelpAI, // useHelpAI(widgetId?) → { state, ask, open, close, reset }, re-rendering on state changes
50
+ type HelpAIProps,
51
+ type HelpAIControls,
52
+ } from '@helpai/elements/react';
53
+ ```
54
+
55
+ See [Frameworks → React](./frameworks.md#react).
56
+
57
+ ---
58
+
59
+ ## Functions
60
+
61
+ ### `init(opts): ChatWidgetInstance`
62
+
63
+ Mount a widget (or re-config an existing one).
64
+
65
+ ```ts
66
+ function init(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance;
67
+ ```
68
+
69
+ Idempotent — if an instance with the same `widgetId` exists, its
70
+ options are merged (via `update`) and the existing handle is
71
+ returned.
72
+
73
+ ### `mount(opts): ChatWidgetInstance`
74
+
75
+ Force a fresh mount. If an instance exists at the same `widgetId`,
76
+ it's destroyed first.
77
+
78
+ ```ts
79
+ function mount(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance;
80
+ ```
81
+
82
+ Prefer `init()` — safer for SPA route changes.
83
+
84
+ ### `destroy(widgetId?)`
85
+
86
+ Unmount one or all widgets.
87
+
88
+ ```ts
89
+ function destroy(widgetId?: string): void;
90
+ ```
91
+
92
+ - With `widgetId` — destroys that one.
93
+ - Without — destroys every widget on the page.
94
+
95
+ ### `hook(widgetId, event, listener): () => void`
96
+
97
+ Register a global lifecycle hook. Fires for any widget matching
98
+ `widgetId` + `event`. Wildcards (`"*"`) match anything.
99
+
100
+ ```ts
101
+ function hook(widgetId: string, event: string, listener: HookListener): () => void;
102
+ ```
103
+
104
+ Returns an unsubscribe function.
105
+
106
+ ### `getInstance(widgetId?): ChatWidgetInstance | null`
107
+
108
+ Look up a live instance by id.
109
+
110
+ ```ts
111
+ function getInstance(widgetId?: string): ChatWidgetInstance | null;
112
+ ```
113
+
114
+ `widgetId` defaults to `"default"`. Returns `null` if nothing is
115
+ mounted.
116
+
117
+ ### `getAllInstances(): ChatWidgetInstance[]`
118
+
119
+ Returns every live widget instance on the page.
120
+
121
+ ---
122
+
123
+ ## `ChatWidgetInstance`
124
+
125
+ ```ts
126
+ interface ChatWidgetInstance {
127
+ readonly widgetId: string;
128
+
129
+ // Panel control
130
+ open(target?: OpenTarget): void; // with a target: that tab, or one Help article / News post in it
131
+ close(): void;
132
+ expand(): void; // toggle normal ↔ expanded
133
+ openForm(id: string): void;
134
+ ask(text: string, options?: { send?: boolean }): void; // open the chat with `text` in the composer ({ send: true } sends it)
135
+
136
+ // State
137
+ readonly state: WidgetState; // { open, ready, unread, conversationId? }, live
138
+ readonly ready: Promise<void>; // resolves once connected
139
+
140
+ // Lifecycle
141
+ reset(): void; // start over as a new anonymous visitor (on logout)
142
+ destroy(): void;
143
+
144
+ // Events
145
+ on<E extends EventName>(name: E, fn: (payload: EventMap[E]) => void): () => void; // returns unsubscribe fn
146
+
147
+ // Re-config
148
+ update(patch: Partial<ChatWidgetOptions>): void;
149
+
150
+ // Host-asserted context (informational, not auth; re-handshakes, same visitor/conversation)
151
+ setUserContext(ctx: Partial<NonNullable<ChatWidgetOptions['userContext']>>): void; // merges into the current user context
152
+ clearUserContext(): void;
153
+ setPageContext(ctx: Partial<NonNullable<ChatWidgetOptions['pageContext']>>): void; // merges into the current page context
154
+ clearPageContext(): void;
155
+
156
+ // Runtime diagnostics (module-global across widget instances)
157
+ setDebug(level: LogLevel | boolean | number | string): void;
158
+ readonly debugLevel: () => LogLevel;
159
+ onLog(sink: ((entry: LogEntry) => void) | null): void;
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## `ChatWidgetOptions`
166
+
167
+ The generated, schema-backed field reference is
168
+ [`docs/api/configuration.md`](./options.md); it is authoritative.
169
+ The public input keeps embed identity, context, endpoint overrides, storage,
170
+ and callbacks at the top level. Serializable widget settings use focused
171
+ nested sections. `mount()` and `init()` require `aiAgentDeploymentId`; the
172
+ deployment `publicKey` remains optional for intentional guest mode.
173
+
174
+ ```ts
175
+ init({
176
+ publicKey: 'agent_pk_...',
177
+ aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000',
178
+ presentation: {
179
+ mode: 'inline',
180
+ position: 'bottom-right',
181
+ iframe: false,
182
+ size: { width: '420px', height: '640px' },
183
+ },
184
+ behavior: {
185
+ startMinimized: false,
186
+ responseMode: 'streaming',
187
+ disableMessageFeedback: false,
188
+ autoSendSuggestions: false,
189
+ popOutUrl: 'https://chat.example.com',
190
+ },
191
+ theme: 'auto',
192
+ launcher: { variant: 'pill', label: 'Ask AI' },
193
+ header: { actions: ['theme', 'textSize', 'popOut', 'history', 'close'] },
194
+ composer: {
195
+ sendButton: { shape: 'circle', icon: 'arrowUp', variant: 'filled' },
196
+ attachments: { maxCount: 3, accept: 'image/*,application/pdf' },
197
+ },
198
+ feedback: { sound: { enabled: true }, haptics: { enabled: true } },
199
+ i18n: { locale: 'en', availableLocales: ['en', 'fr'] },
200
+ footer: {
201
+ composerDisclaimer: 'AI can make mistakes.',
202
+ poweredBy: { text: 'Powered by Help AI', href: 'https://help.ai' },
203
+ },
204
+ });
205
+ ```
206
+
207
+ The complete type also exposes `features`, `forms`, `modules`, and `tracking`.
208
+ See [Configuration](./configuration.md) for behavior and examples.
209
+
210
+ ### `ActionName`
211
+
212
+ ```ts
213
+ type ActionName = 'clear' | 'expand' | 'fullscreen' | 'popOut' | 'sound' | 'history' | 'language' | 'theme' | 'textSize' | 'close';
214
+ ```
215
+
216
+ ### `FeedbackEvent`
217
+
218
+ ```ts
219
+ type FeedbackEvent = 'messageReceived' | 'messageSent' | 'error' | 'voiceStart' | 'voiceStop';
220
+ ```
221
+
222
+ ### `StringsOverride`
223
+
224
+ ```ts
225
+ type LocaleStrings = Partial<Record<StringKey, string>>;
226
+
227
+ type StringsOverride =
228
+ | LocaleStrings // flat (applies to active locale)
229
+ | Partial<Record<string, LocaleStrings>>; // nested (per-locale)
230
+ ```
231
+
232
+ `StringKey` includes: `attach`, `composerPlaceholder`, `send`, `stop`,
233
+ `close`, `fullscreen`, `expand`, `history`, `language`, `theme`,
234
+ `themeAuto`, `themeLight`, `themeDark`, ... (full list in
235
+ `src/i18n/strings.ts`).
236
+
237
+ ### Storage adapter
238
+
239
+ All adapter methods are synchronous.
240
+
241
+ ```ts
242
+ type StorageAdapter = NonNullable<ChatWidgetOptions['storage']>;
243
+
244
+ interface StorageAdapterShape {
245
+ get(key: string): string | null;
246
+ set(key: string, value: string): void;
247
+ remove(key: string): void;
248
+ }
249
+ ```
250
+
251
+ ### `open(target?)`
252
+
253
+ ```ts
254
+ interface OpenTarget {
255
+ to: 'home' | 'chats' | 'help' | 'news';
256
+ id?: string; // a Help article or News post (its content id)
257
+ }
258
+ ```
259
+
260
+ Opens the panel; with a target it also shows that tab at its root, or the article / post in it. Back from the post
261
+ lands on the tab's list. A tab the widget doesn't have (or an unknown target) just opens the panel, with a `warn` log.
262
+ A target sent before the widget has connected waits until the deployment's tabs are known. For a site's own buttons:
263
+
264
+ ```ts
265
+ document.querySelector('#whats-new')?.addEventListener('click', () => chat.open({ to: 'news' }));
266
+ document.querySelector('#help')?.addEventListener('click', () => chat.open({ to: 'help' }));
267
+ chat.open({ to: 'news', id: '019a0b6c-7e2a-7000-9d3e-4a5b6c7d8e01' }); // one post
268
+ ```
269
+
270
+ ### `ask(text, options?)`
271
+
272
+ ```ts
273
+ ask(text: string, options?: { send?: boolean }): void;
274
+ ```
275
+
276
+ Opens the chat with `text` in the composer, focused, for the visitor to edit and send — the way a suggestion chip
277
+ does by default. With `{ send: true }` it goes out right away as the visitor's message in their current conversation
278
+ instead. Use it for a site's own "Ask about pricing" buttons:
279
+
280
+ ```ts
281
+ document.querySelector('#ask-pricing')?.addEventListener('click', () => chat.ask('How much does the Pro plan cost?'));
282
+ document.querySelector('#ask-now')?.addEventListener('click', () => chat.ask('Talk me through the Pro plan', { send: true }));
283
+ ```
284
+
285
+ The text waits until the chat can take it, so a button works whenever it's clicked: before the widget has connected
286
+ or while a pre-chat form is waiting for an answer; a send also waits for a reply still arriving. A newer `ask`
287
+ replaces one that is still waiting. Text is trimmed; an empty string is ignored with a `warn` log. If a send is
288
+ refused (for example the visitor is filling an inline form), the text is left in the composer for the visitor to send.
289
+
290
+ A send speaks for the visitor, so call it from something the visitor did (a click), not on page load.
291
+
292
+ Without script, mark any element on the page with `data-helpai-ask`; a click asks the default widget. An empty
293
+ value asks the element's own text, and `data-helpai-send` on the same element sends it instead of filling the composer:
294
+
295
+ ```html
296
+ <button data-helpai-ask="How do I book a session?">Book a session</button> <button data-helpai-ask data-helpai-send>Do you offer gift cards?</button>
297
+ ```
298
+
299
+ ---
300
+
301
+ ### `state` and `ready`
302
+
303
+ ```ts
304
+ interface WidgetState {
305
+ open: boolean; // the panel is showing (always true for inline / page / standalone)
306
+ ready: boolean; // connected: commands act at once
307
+ unread: number; // conversations with replies the visitor hasn't read
308
+ conversationId?: string; // the visitor's current conversation
309
+ }
310
+ ```
311
+
312
+ `chat.state` is the widget's state right now; the `state` event delivers every change and `unread` fires when the
313
+ count changes. `chat.ready` resolves once the widget has connected, so code that needs the widget can `await` it
314
+ instead of listening for `handshake`. The unread count is known once the widget has been opened in the page load (a
315
+ closed widget makes no request beyond its handshake) and stays live from then on.
316
+
317
+ ```ts
318
+ await chat.ready;
319
+ const badge = document.querySelector('#help-badge');
320
+ function showUnread(count: number) {
321
+ badge.textContent = String(count);
322
+ badge.hidden = count === 0;
323
+ }
324
+ showUnread(chat.state.unread);
325
+ chat.on('unread', showUnread);
326
+ ```
327
+
328
+ ### `reset()`
329
+
330
+ Starts over as a new anonymous visitor. Call it when your user logs out on a shared computer: it forgets the stored
331
+ visitor, their current conversation (draft and queued messages included), preferences and the user context set with
332
+ `setUserContext`, then reconnects as a first visit. Listeners added with `on()` stay, and the handle keeps working.
333
+ On `<web-ai-chat>`, also remove a `user-context` attribute you set.
334
+
335
+ ---
336
+
337
+ ## Events
338
+
339
+ ### `EventMap`
340
+
341
+ ```ts
342
+ interface EventMap {
343
+ open: void;
344
+ close: void;
345
+ message: { role: string; text: string };
346
+ error: unknown;
347
+ expand: boolean; // new state
348
+ fullscreen: boolean; // new state
349
+ handshake: HandshakeResponse; // backend's bootstrap response
350
+ localeChange: string; // new BCP-47 tag
351
+ state: WidgetState; // open / ready / unread / conversation changed
352
+ unread: number; // the unread conversation count changed
353
+ // … and more: see `EventMap` in the package types
354
+ }
355
+
356
+ type EventName = keyof EventMap;
357
+ ```
358
+
359
+ ### `HookListener`
360
+
361
+ Unified signature for global hooks:
362
+
363
+ ```ts
364
+ type HookListener = (instance: ChatWidgetInstance, event: EventName, data: unknown) => void;
365
+ ```
366
+
367
+ Within a handler, narrow `data` by event name:
368
+
369
+ ```ts
370
+ hook('*', '*', (inst, event, data) => {
371
+ switch (event) {
372
+ case 'message': {
373
+ const m = data as EventMap['message'];
374
+ break;
375
+ }
376
+ case 'expand':
377
+ case 'fullscreen': {
378
+ const flag = data as boolean;
379
+ break;
380
+ }
381
+ }
382
+ });
383
+ ```
384
+
385
+ ---
386
+
387
+ ## Auto-boot via script tag
388
+
389
+ The auto-boot parser exposes the documented flat `data-*` subset.
390
+
391
+ ```html
392
+ <script
393
+ src="https://cdn.help.ai/sdk/js/elements/latest/elements.js"
394
+ data-public-key="agent_pk_..."
395
+ data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"
396
+ data-mode="floating"
397
+ data-theme="auto"
398
+ data-position="bottom-right"
399
+ data-launcher-variant="pill"
400
+ data-launcher-label="Ask us"
401
+ data-launcher-icon="spark"
402
+ data-response-mode="streaming"
403
+ data-features-disable-file-upload="true"
404
+ data-features-voice="local"
405
+ data-user-id="user_42"
406
+ data-user-name="Jane Doe"
407
+ data-page-area="support"
408
+ defer
409
+ ></script>
410
+ ```
411
+
412
+ Full attribute list: [Installation § script data attributes](./installation.md#attributes).
413
+
414
+ `data-no-autoboot` — loads the bundle but doesn't auto-mount. For
415
+ deferred init via JS.
416
+
417
+ ---
418
+
419
+ ## Global `window.HelpAI` API
420
+
421
+ After the IIFE boots:
422
+
423
+ ```ts
424
+ // Direct methods
425
+ window.HelpAI.init(opts)
426
+ window.HelpAI.mount(opts)
427
+ window.HelpAI.destroy(widgetId?)
428
+ window.HelpAI.getInstance(widgetId?)
429
+ window.HelpAI.getAllInstances()
430
+ window.HelpAI.hook(widgetId, event, fn)
431
+
432
+ // Shortcuts for the default widget
433
+ window.HelpAI.open() // or .open({ to: 'news', id? })
434
+ window.HelpAI.ask(text, { send }) // send defaults to false: fill the composer
435
+ await window.HelpAI.ready() // the default widget, once connected (also when it mounts later)
436
+ window.HelpAI.reset() // a new anonymous visitor (on logout)
437
+ window.HelpAI.close()
438
+ window.HelpAI.expand()
439
+ window.HelpAI.on(event, fn)
440
+ window.HelpAI.update(patch)
441
+
442
+ // Metadata
443
+ window.HelpAI.version // semver string
444
+ window.HelpAI.brand // { id, displayName, cssPrefix, cdnUrl, ... }
445
+
446
+ // Callable dispatch (for queued calls)
447
+ window.HelpAI('init', opts)
448
+ window.HelpAI('open')
449
+ window.HelpAI('open', { to: 'help' })
450
+ window.HelpAI('ask', 'How do I book?') // or ('ask', 'How do I book?', { send: true })
451
+ window.HelpAI('ready', (chat) => chat.on('unread', showUnread)) // queue-able before the bundle loads
452
+ window.HelpAI('reset')
453
+ window.HelpAI('hook', '*', 'message', fn)
454
+ ```
455
+
456
+ ---
457
+
458
+ ## Pre-boot queues
459
+
460
+ Used with the async loader snippet — write calls before the bundle
461
+ arrives:
462
+
463
+ ```html
464
+ <script>
465
+ // Command queue
466
+ window.HelpAI =
467
+ window.HelpAI ||
468
+ function () {
469
+ (window.HelpAI.q = window.HelpAI.q || []).push(arguments);
470
+ };
471
+
472
+ // Hook queue — array of [widgetId, event, fn] tuples or { widgetId, event, listener } objects
473
+ window.HelpAIHooks = window.HelpAIHooks || [];
474
+ </script>
475
+ ```
476
+
477
+ The IIFE drains both on boot.
478
+
479
+ ---
480
+
481
+ ## Web component
482
+
483
+ Custom element tag name: `<web-ai-chat>`.
484
+
485
+ The Web Component exposes the same curated parser fields without the
486
+ script dataset's outer `data-` prefix:
487
+
488
+ ```html
489
+ <web-ai-chat
490
+ public-key="agent_pk_..."
491
+ ai-agent-deployment-id="..."
492
+ mode="inline"
493
+ theme="auto"
494
+ position="bottom-right"
495
+ launcher-variant="pill"
496
+ launcher-label="Ask us"
497
+ launcher-icon="chat"
498
+ actions="theme,history,close"
499
+ height="500px"
500
+ ></web-ai-chat>
501
+ ```
502
+
503
+ Changing a reactive attribute re-resolves the live widget. `iframe`
504
+ and `start-minimized` are mount-time-only.
505
+
506
+ The element has the instance's commands, state and events, so a page drives it directly:
507
+
508
+ ```js
509
+ const chat = document.querySelector('web-ai-chat');
510
+ chat.open(); // or chat.open({ to: 'help' })
511
+ chat.ask('How do I book a session?');
512
+ chat.close();
513
+ chat.expand();
514
+ chat.openForm('lead');
515
+ await chat.ready;
516
+ chat.on('unread', (count) => console.log(count, chat.state));
517
+ chat.reset(); // on logout
518
+ ```
519
+
520
+ `on()` subscriptions end when the element leaves the page. Importing `@helpai/elements/web-component` types
521
+ `document.querySelector('web-ai-chat')` as `WebAiChatElement` in TypeScript; the
522
+ [JSX typings](#jsx-typings) do the same for React, Vue and Svelte markup.
523
+
524
+ ### No-script triggers
525
+
526
+ Mark any element on the page and a click drives the default widget, with no script. A link keeps working without the
527
+ widget; with it, the click opens the widget instead of following the link.
528
+
529
+ | Attribute | Click does |
530
+ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
531
+ | `data-helpai-open` | Opens the panel; a value opens a tab (`home`, `chats`, `help`, `news`) or one article or post (`help/<id>`) |
532
+ | `data-helpai-ask="<text>"` | Opens the chat with the text in the composer (empty value: the element's own text); add `data-helpai-send` to send it |
533
+ | `data-helpai-form="<form id>"` | Opens a form that has a `manual` trigger |
534
+ | `data-helpai-close` | Closes the panel |
535
+
536
+ ```html
537
+ <a href="/help" data-helpai-open="help">Help</a>
538
+ <a href="/support/billing" data-helpai-open="help/019a0b6c-7e2a-7000-9d3e-4a5b6c7d8e01">Billing questions</a>
539
+ <button data-helpai-ask>Which plan fits a team of 10?</button>
540
+ <button data-helpai-form="book-demo">Book a demo</button>
541
+ ```
542
+
543
+ ### Deep links
544
+
545
+ A page address can open the default widget on arrival — for support emails, docs and QR codes. Either parameter can
546
+ sit in the query or the hash. The widget acts once and drops its parameters from the address (the page's own
547
+ parameters and anchors stay), so a reload or a copied link doesn't repeat it. A linked question only fills the
548
+ composer; it is never sent for the visitor.
549
+
550
+ | Address | Opens |
551
+ | -------------------------------------- | ---------------------------------------------------------- |
552
+ | `?helpai=open` | The panel |
553
+ | `?helpai=help/<article id>` | Help at that article (any target `data-helpai-open` takes) |
554
+ | `#helpai-ask=Can+I+export+invoices%3F` | The chat with the question in the composer |
555
+
556
+ ### JSX typings
557
+
558
+ Types-only subpaths register `<web-ai-chat>` with its attributes in each framework's JSX, so attributes autocomplete,
559
+ `ai-agent-deployment-id` is required, and a ref or `bind:this` is the typed element:
560
+
561
+ ```ts
562
+ import type {} from '@helpai/elements/jsx/react'; // React 19
563
+ import type {} from '@helpai/elements/jsx/svelte'; // Svelte 5, e.g. in src/app.d.ts
564
+ import type {} from '@helpai/elements/jsx/vue'; // Vue 3 (also set compilerOptions.isCustomElement for the tag)
565
+ ```
566
+
567
+ ---
568
+
569
+ ## Brand constants
570
+
571
+ `window.HelpAI.brand` (or `import { brand } from '@helpai/elements'`):
572
+
573
+ ```ts
574
+ interface Brand {
575
+ id: string; // e.g. "help-ai"
576
+ name: string; // NPM package name, e.g. "@helpai/elements"
577
+ global: string; // window global, e.g. "HelpAI"
578
+ cssPrefix: string; // "helpai" — used for all CSS classes + custom properties
579
+ displayName: string; // "Help AI" — user-visible brand name
580
+ cdnUrl: string; // "https://cdn.help.ai/sdk/js/elements/latest/elements.js"
581
+ defaultBaseUrl: string; // "https://help.ai" — main origin; modules at /api/agent + /api/data
582
+ defaultTheme: {
583
+ accent: string;
584
+ accentText: string;
585
+ radius: string;
586
+ fontFamily: string;
587
+ };
588
+ }
589
+ ```
590
+
591
+ Read-only. Useful for:
592
+
593
+ - "Open in full Help AI dashboard" links.
594
+ - "Download the mobile app" banners.
595
+ - Dynamic theme fallbacks.
596
+
597
+ ---
598
+
599
+ ## Versioning
600
+
601
+ Semver. `version` export matches the NPM package.
602
+
603
+ - **Patch** (1.2.0 → 1.2.1): bug fixes, perf, docs. Auto-upgrade safe.
604
+ - **Minor** (1.2.0 → 1.3.0): new options, new events, backward-compatible. Auto-upgrade safe.
605
+ - **Major** (1.x.x → 2.0.0): breaking changes. Review the changelog.
606
+
607
+ Pin a specific version with the CDN URL:
608
+
609
+ ```html
610
+ <script src="https://cdn.help.ai/v1.2.0/elements.js" defer></script>
611
+ ```
612
+
613
+ Or use `/v1/` for the latest 1.x.x (auto-patches, no major breaks).
614
+
615
+ ---
616
+
617
+ ## See also
618
+
619
+ - [Configuration](./configuration.md) — options with examples + context.
620
+ - [Events & hooks](./events-and-hooks.md) — event handling deep-dive.
621
+ - [Recipes](./recipes.md) — copy-paste scenarios.
622
+ - [Troubleshooting](./troubleshooting.md) — when things go wrong.