@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,323 @@
1
+ # Configuration — `ChatWidgetOptions`
2
+
3
+ The canonical surface lives in `src/schema/widget.ts` and
4
+ `src/schema/deployment.ts` as Zod schemas. Every field carries a
5
+ `.describe(...)` string and (where useful) `.meta({ examples })` —
6
+ this page is a flat reference auto-generated from those annotations.
7
+
8
+ When the schema and this doc disagree, **the schema wins**. Run
9
+ `bun run gen:docs` after editing a schema to refresh the tables; CI
10
+ fails if this file is out of sync.
11
+
12
+ For longer-form prose on what each subsystem does, see the
13
+ `features/` directory:
14
+
15
+ - Theming
16
+ - Sizing
17
+ - Launcher
18
+ - Header actions
19
+ - Composer
20
+ - Feedback
21
+ - i18n
22
+ - Modes
23
+
24
+ ---
25
+
26
+ ## Conventions
27
+
28
+ - Every field is optional unless its row says otherwise.
29
+ - "Default" is what the runtime applies when the field is absent.
30
+ `—` means there's no schema-level default; the resolver falls
31
+ through to runtime behaviour or leaves the field undefined.
32
+ - Field names use the schema (camelCase) form. The web-component +
33
+ `<script data-*>` boot accept the kebab-case equivalent (e.g.
34
+ `data-launcher-variant` ↔ `launcher.variant`).
35
+ - "Server-pushable" fields can arrive in `HandshakeResponse.config`;
36
+ see [§ Server settings](#server-settings).
37
+
38
+ ---
39
+
40
+ <!-- AUTO-GENERATED:CONFIG-REFERENCE START -->
41
+
42
+ <!-- This block is generated by `scripts/gen-config-docs.ts` from the Zod
43
+ schemas in `src/schema/widget.ts` + `src/schema/deployment.ts`. Edit
44
+ those schemas (`.describe(...)` + `.meta({ examples })`) and run
45
+ `bun run gen:docs` to regenerate. CI fails when this block drifts. -->
46
+
47
+ ### Connection (`ConnectionConfig`)
48
+
49
+ Where the widget talks to and how it authenticates. Owned by the embedding developer (typically baked into the page template / env var) — distinct from `userPrefs`, which is the dashboard-pushable UX surface.
50
+
51
+ | Field | Type | Default | Description |
52
+ | --------------------- | ----------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
53
+ | `widgetId` | `string` | `"default"` | Stable per-instance id used to namespace localStorage keys. Set this when mounting more than one widget on the same page so their conversation state doesn't collide. |
54
+ | `publicKey?` | `string` | — | Deployment public key (`agent_pk_…`) issued from the dashboard. Sent on every API call as the `x-public-key` header when present. |
55
+ | `aiAgentDeploymentId` | `string` | — | Required deployment UUID from the dashboard. Picks which agent / settings bundle answers this mount. Sent as `x-ai-agent-deployment-id`. |
56
+ | `baseUrl?` | `string` | — | MAIN site origin (e.g. `https://help.ai`) — the API modules derive from it: agent API at `${baseUrl}/api/agent`, data API at `${baseUrl}/api/data`. Set this to point at your staging / self-hosted / custom backend. Default: the brand's `defaultBaseUrl` baked at build time. |
57
+ | `agentApiBaseUrl?` | `string` | — | Full agent-API base — set it to reach a STANDALONE `module-agent-api` directly (local module on its own port, e.g. `http://localhost:3069`; routes attach as-is: `/pai/handshake`). Precedence: this option > derived from the main origin (`${baseUrl}/api/agent`). Not server-pushable. |
58
+ | `dataApiBaseUrl?` | `string` | — | Full data-API base (e.g. `https://help.ai/api/data`, or a standalone local module like `http://localhost:3071`) — the service serving content, form definitions (`GET /pai/bootstrap`), and form submissions at root-level paths. Precedence: this option > server-pushed `config.dataApiBaseUrl` > derived from the main origin (`${baseUrl}/api/data`). |
59
+ | `userContext?` | object | — | Host-asserted end-user context (informational, untrusted — not authentication). Recognised `id` / `email` / `name` / `plan` / `role` / `phone` plus any extra custom scalar fields forwarded to the agent. |
60
+ | `pageContext?` | object | — | Host-asserted page/visit context (informational, untrusted). Where the visitor is + the surface (`area`) they're using, plus any custom scalar fields, forwarded to the agent. |
61
+ | `endpoints?` | object | — | HTTP endpoint overrides. `upload` / `transcribe` paths are appended to the agent base (`${baseUrl}/api/agent`), `submitForm` to the data base (`dataApiBaseUrl`); absolute URLs are accepted too. |
62
+ | `debug?` | `boolean` \| `"off"` \| `"error"` \| `"warn"` \| `"info"` \| `"debug"` \| `"trace"` | — | Console log verbosity. `false` / `"off"` silences everything (default in production). `true` → `"debug"`. Levels `error` / `warn` / `info` / `debug` / `trace` gate output additively. Use `"trace"` to capture per-chunk SSE frames + every state transition. Also toggleable at runtime via `chat.setDebug(level)` or `?<brand>-debug=trace` in the page URL — the URL beats this option, this option beats the persisted `localStorage` value. |
63
+
64
+ **Examples:**
65
+
66
+ ```json
67
+ [
68
+ {
69
+ "publicKey": "agent_pk_demo",
70
+ "aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001"
71
+ },
72
+ {
73
+ "publicKey": "agent_pk_demo",
74
+ "aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001",
75
+ "widgetId": "support"
76
+ },
77
+ {
78
+ "publicKey": "agent_pk_demo",
79
+ "aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001",
80
+ "baseUrl": "https://api.acme.com"
81
+ }
82
+ ]
83
+ ```
84
+
85
+ ---
86
+
87
+ ### Deployment widget settings (`WidgetSettings`)
88
+
89
+ Portal-configurable widget settings — thirteen sections, one per dashboard tab. The backend pushes this shape on `/pai/handshake` under `config`, except `forms`, which the data API serves separately.
90
+
91
+ | Field | Type | Default | Description |
92
+ | --------------- | ------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
+ | `presentation?` | object | — | Layout fundamentals — mode, anchor corner, iframe isolation, panel size + drawer inset. |
94
+ | `behavior?` | object | — | Conversation behaviour — lifecycle, streaming, and the transcript surface (scroll, feedback). |
95
+ | `theme` | `"auto"` \| `"light"` \| `"dark"` \| `"host"` \| object | `"auto"` | Theme preference (`auto` follows OS) or a tokens override object (`accent`, `radius`, `fontFamily`). |
96
+ | `launcher?` | object | — | Floating-launcher (FAB) appearance and behaviour. The FAB is the round button in the corner that opens the chat panel. Floating mode only. |
97
+ | `header?` | object | — | Panel-header configuration. |
98
+ | `composer?` | object | — | Composer chrome — send-button styling + attachment limits. |
99
+ | `feedback?` | object | — | Audible + tactile feedback. |
100
+ | `i18n?` | object | — | Locale + i18n overrides. |
101
+ | `footer?` | object | — | Footer — attribution + composer disclaimer. |
102
+ | `features?` | object | — | Capability flags — file upload, voice mode, HITL, and operator requests. |
103
+ | `forms?` | object[] | — | Event-driven forms — an ordered list (order = priority). Each binds to trigger event(s), gates the chat until submit/skip, dedupes by frequency, and records via `endpoints.submitForm`. Generic across verticals. |
104
+ | `modules?` | object[] | — | Messenger tabs — an ordered list of up to 4 tabs, each picking a `layout` + the content collections it shows (`contentCollectionIds`) + a translatable `label`. One tab → no tab bar (just that content); zero → an empty state. Floating / drawer / modal only. |
105
+ | `tracking?` | object | — | Anonymous usage tracking for the widget owner — GA-style pixel events (open, send, form submit, …) with page / device / locale dimensions. Never includes message or form content; the only visitor identifier is the widget's anonymous visitorId. |
106
+
107
+ **Examples:**
108
+
109
+ ```json
110
+ [
111
+ {
112
+ "presentation": {
113
+ "mode": "floating",
114
+ "position": "bottom-right"
115
+ },
116
+ "theme": "auto"
117
+ },
118
+ {
119
+ "presentation": {
120
+ "mode": "floating"
121
+ },
122
+ "theme": {
123
+ "accent": "#7c3aed",
124
+ "radius": "12px"
125
+ },
126
+ "launcher": {
127
+ "variant": "pill",
128
+ "label": "Ask AI",
129
+ "callout": {
130
+ "text": "👋 Need help?"
131
+ }
132
+ }
133
+ },
134
+ {
135
+ "presentation": {
136
+ "mode": "inline"
137
+ },
138
+ "behavior": {
139
+ "responseMode": "streaming"
140
+ },
141
+ "features": {
142
+ "voice": "local"
143
+ }
144
+ }
145
+ ]
146
+ ```
147
+
148
+ ---
149
+
150
+ ### Full widget config (`WidgetConfig`)
151
+
152
+ Full tooling config — connection (where + auth) + widget settings (look + behaviour) + page-mode handshake extras. The production widget does not import this Zod schema. Mode-specific constraints (e.g. `position` only meaningful in `floating`) are NOT enforced here so the same object can flow across mode changes.
153
+
154
+ | Field | Type | Default | Description |
155
+ | --------------------- | ----------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
156
+ | `widgetId` | `string` | `"default"` | Stable per-instance id used to namespace localStorage keys. Set this when mounting more than one widget on the same page so their conversation state doesn't collide. |
157
+ | `publicKey?` | `string` | — | Deployment public key (`agent_pk_…`) issued from the dashboard. Sent on every API call as the `x-public-key` header when present. |
158
+ | `aiAgentDeploymentId` | `string` | — | Required deployment UUID from the dashboard. Picks which agent / settings bundle answers this mount. Sent as `x-ai-agent-deployment-id`. |
159
+ | `baseUrl?` | `string` | — | MAIN site origin (e.g. `https://help.ai`) — the API modules derive from it: agent API at `${baseUrl}/api/agent`, data API at `${baseUrl}/api/data`. Set this to point at your staging / self-hosted / custom backend. Default: the brand's `defaultBaseUrl` baked at build time. |
160
+ | `agentApiBaseUrl?` | `string` | — | Full agent-API base — set it to reach a STANDALONE `module-agent-api` directly (local module on its own port, e.g. `http://localhost:3069`; routes attach as-is: `/pai/handshake`). Precedence: this option > derived from the main origin (`${baseUrl}/api/agent`). Not server-pushable. |
161
+ | `dataApiBaseUrl?` | `string` | — | Full data-API base (e.g. `https://help.ai/api/data`, or a standalone local module like `http://localhost:3071`) — the service serving content, form definitions (`GET /pai/bootstrap`), and form submissions at root-level paths. Precedence: this option > server-pushed `config.dataApiBaseUrl` > derived from the main origin (`${baseUrl}/api/data`). |
162
+ | `userContext?` | object | — | Host-asserted end-user context (informational, untrusted — not authentication). Recognised `id` / `email` / `name` / `plan` / `role` / `phone` plus any extra custom scalar fields forwarded to the agent. |
163
+ | `pageContext?` | object | — | Host-asserted page/visit context (informational, untrusted). Where the visitor is + the surface (`area`) they're using, plus any custom scalar fields, forwarded to the agent. |
164
+ | `endpoints?` | object | — | HTTP endpoint overrides. `upload` / `transcribe` paths are appended to the agent base (`${baseUrl}/api/agent`), `submitForm` to the data base (`dataApiBaseUrl`); absolute URLs are accepted too. |
165
+ | `debug?` | `boolean` \| `"off"` \| `"error"` \| `"warn"` \| `"info"` \| `"debug"` \| `"trace"` | — | Console log verbosity. `false` / `"off"` silences everything (default in production). `true` → `"debug"`. Levels `error` / `warn` / `info` / `debug` / `trace` gate output additively. Use `"trace"` to capture per-chunk SSE frames + every state transition. Also toggleable at runtime via `chat.setDebug(level)` or `?<brand>-debug=trace` in the page URL — the URL beats this option, this option beats the persisted `localStorage` value. |
166
+ | `presentation?` | object | — | Layout fundamentals — mode, anchor corner, iframe isolation, panel size + drawer inset. |
167
+ | `behavior?` | object | — | Conversation behaviour — lifecycle, streaming, and the transcript surface (scroll, feedback). |
168
+ | `theme` | `"auto"` \| `"light"` \| `"dark"` \| `"host"` \| object | `"auto"` | Theme preference (`auto` follows OS) or a tokens override object (`accent`, `radius`, `fontFamily`). |
169
+ | `launcher?` | object | — | Floating-launcher (FAB) appearance and behaviour. The FAB is the round button in the corner that opens the chat panel. Floating mode only. |
170
+ | `header?` | object | — | Panel-header configuration. |
171
+ | `composer?` | object | — | Composer chrome — send-button styling + attachment limits. |
172
+ | `feedback?` | object | — | Audible + tactile feedback. |
173
+ | `i18n?` | object | — | Locale + i18n overrides. |
174
+ | `footer?` | object | — | Footer — attribution + composer disclaimer. |
175
+ | `features?` | object | — | Capability flags — file upload, voice mode, HITL, and operator requests. |
176
+ | `forms?` | object[] | — | Event-driven forms — an ordered list (order = priority). Each binds to trigger event(s), gates the chat until submit/skip, dedupes by frequency, and records via `endpoints.submitForm`. Generic across verticals. |
177
+ | `modules?` | object[] | — | Messenger tabs — an ordered list of up to 4 tabs, each picking a `layout` + the content collections it shows (`contentCollectionIds`) + a translatable `label`. One tab → no tab bar (just that content); zero → an empty state. Floating / drawer / modal only. |
178
+ | `tracking?` | object | — | Anonymous usage tracking for the widget owner — GA-style pixel events (open, send, form submit, …) with page / device / locale dimensions. Never includes message or form content; the only visitor identifier is the widget's anonymous visitorId. |
179
+ | `site?` | — | — | Site config — applies in `mode: "page"`. Comes from the handshake response, not user-edited. |
180
+ | `blocks?` | — | — | Blocks (nav + link cards) — applies in `mode: "page"`. Comes from the handshake response. |
181
+
182
+ **Examples:**
183
+
184
+ ```json
185
+ [
186
+ {
187
+ "publicKey": "agent_pk_demo",
188
+ "aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001",
189
+ "presentation": {
190
+ "mode": "floating"
191
+ },
192
+ "theme": "auto"
193
+ },
194
+ {
195
+ "publicKey": "agent_pk_demo",
196
+ "aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001",
197
+ "presentation": {
198
+ "mode": "modal",
199
+ "size": {
200
+ "width": "720px",
201
+ "height": "640px"
202
+ }
203
+ }
204
+ }
205
+ ]
206
+ ```
207
+
208
+ ---
209
+
210
+ ### Handshake response (`HandshakeResponse`)
211
+
212
+ | Field | Type | Default | Description |
213
+ | ---------------- | -------- | ------- | ----------- |
214
+ | `deployment` | object | — | |
215
+ | `agent` | object | — | |
216
+ | `config?` | object | — | |
217
+ | `site?` | object | — | |
218
+ | `blocks?` | object | — | |
219
+ | `welcome?` | object | — | |
220
+ | `visitorId` | `string` | — | |
221
+ | `conversationId` | `string` | — | |
222
+ | `userPrefs?` | object | — | |
223
+ | `rebind?` | object | — | |
224
+
225
+ <!-- AUTO-GENERATED:CONFIG-REFERENCE END -->
226
+
227
+ ---
228
+
229
+ ## Storage
230
+
231
+ | Option | Type | Default | Server-push |
232
+ | --------- | --------------- | ----------------------- | ----------- |
233
+ | `storage` | `ClientStorage` | `LocalStorageAdapter()` | no |
234
+
235
+ ```ts
236
+ interface ClientStorage {
237
+ get(key: string): string | null;
238
+ set(key: string, value: string): void;
239
+ remove(key: string): void;
240
+ }
241
+ ```
242
+
243
+ Adapter methods are synchronous. Swap the default `localStorage` adapter for
244
+ `sessionStorage`, an encrypted wrapper, or an in-memory shim (tests, SSR). The
245
+ adapter receives dynamic `draft.<conversationId>` keys whose values are exact
246
+ unsent textarea text and owns their storage, retention, and privacy behavior.
247
+
248
+ `storage` and the function-typed `onMessage` / `onOpen` / `onClose` /
249
+ `onError` / `target` callbacks are runtime-only and not on the schema
250
+ (they can't be JSON-serialised).
251
+
252
+ ---
253
+
254
+ ## Callbacks
255
+
256
+ | Option | Type |
257
+ | ----------- | ------------------------------- |
258
+ | `onMessage` | `(msg: { role, text }) => void` |
259
+ | `onOpen` | `() => void` |
260
+ | `onClose` | `() => void` |
261
+ | `onError` | `(err: unknown) => void` |
262
+
263
+ Short-hand for `instance.on(...)` — useful for one-off integrations
264
+ that don't want to keep a handle.
265
+
266
+ ---
267
+
268
+ ## Resolved options (internal)
269
+
270
+ After `resolveOptions(raw)`, every field is either a default or a
271
+ user-supplied value. Internal modules always depend on the
272
+ `ResolvedOptions` shape. Key differences from the input:
273
+
274
+ - `themeMode` (ThemePreference) and `themeOverrides` (ThemeOverrides)
275
+ are **split** from the single `theme` input.
276
+ - `launcher.callout` is either a `Required<...>` object or `null`
277
+ (not undefined).
278
+ - `poweredBy` is either `PoweredByOption` or `null`.
279
+ - `strings` is fully populated `Record<StringKey, string>` (no
280
+ missing keys).
281
+ - `stringsOverride` is preserved separately so `App` can re-resolve
282
+ into a new locale at runtime.
283
+ - `locale` is a concrete BCP-47 tag (`"auto"` already resolved).
284
+
285
+ Full shape in `src/core/config/types.ts` `ResolvedOptions`.
286
+
287
+ ---
288
+
289
+ ## Server settings
290
+
291
+ `ServerConfig` (the handshake `config` field) is a subset of
292
+ `ChatWidgetOptions`. Server can push anything that ships under the
293
+ schemas above; callbacks (`onMessage` etc.) and identity fields
294
+ (`publicKey`) are deliberately excluded.
295
+
296
+ Merge policy (`src/core/config/merge.ts` `mergeServerConfig`):
297
+
298
+ - **Scalars** — user wins per-field. If the user didn't set it,
299
+ server's value applies.
300
+ - **Objects** — leaf merge. Server provides defaults; any user-set
301
+ leaf wins.
302
+ - **`header.actions`** — replace-whole list; server and user arrays are never concatenated.
303
+ - **`i18n.strings`** — locale-aware merge; flat-form overrides apply to
304
+ the active locale only, nested-form overrides apply per-locale.
305
+
306
+ ---
307
+
308
+ ## Runtime safety
309
+
310
+ The IIFE auto-boot bundle does NOT run Zod at runtime — pulling Zod
311
+ in would push the bundle ~70 KB gz over budget. Instead, every
312
+ field flows through `resolveOptions` in `src/core/config/resolve.ts`,
313
+ which uses `?? default` fallbacks for every option. A bad value
314
+ (wrong enum, malformed length, etc.) flows through and the
315
+ downstream consumer either uses the value as-is or substitutes a
316
+ sane default — the widget always mounts.
317
+
318
+ For strict validation (the dashboard / dev configurator), import
319
+ schemas directly from the `@helpai/elements/schema` package subpath
320
+ and call `.safeParse()`. That path DOES include Zod; it's only
321
+ loaded in admin contexts, not on every customer page.
322
+
323
+ Pass `footer: { poweredBy: { text: "" } }` to opt out of the brand attribution.
@@ -0,0 +1,229 @@
1
+ # Quickstart
2
+
3
+ From zero to working chat widget in under 5 minutes.
4
+
5
+ ---
6
+
7
+ ## 1. Get your browser deployment credentials
8
+
9
+ 1. Sign up at [help.ai](https://help.ai) if you haven't.
10
+ 2. Go to **Dashboard → Deployments**.
11
+ 3. Click **New deployment** — give it a name (e.g. "Marketing site").
12
+ 4. Copy the public key (`agent_pk_abc123...`) and deployment UUID from the
13
+ generated install snippet.
14
+
15
+ > The dashboard generates a copy-paste snippet for you — feel free to use
16
+ > that directly. The rest of this guide assumes you want to know what
17
+ > each piece does.
18
+
19
+ ---
20
+
21
+ ## 2. Pick your install path
22
+
23
+ The widget works four ways. Pick whichever matches your site:
24
+
25
+ - **Static HTML** (marketing site, WordPress, landing page) → [path A](#path-a--static-html).
26
+ - **Async-loading site** (uses Google Tag Manager, Segment, etc.) → [path B](#path-b--async-loader).
27
+ - **React / Next.js / Remix** → the React kit in [path C](#path-c--npm).
28
+ - **Vue / Svelte / Nuxt** (any framework with a build step) → [path C](#path-c--npm).
29
+ - **Plain HTML but prefer a declarative tag** → [path D](#path-d--web-component).
30
+
31
+ All four paths land on the same widget with the same behavior. Mix and
32
+ match as needed.
33
+
34
+ ---
35
+
36
+ ## Path A — static HTML
37
+
38
+ Paste this into your `<head>`:
39
+
40
+ ```html
41
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" data-public-key="agent_pk_abc123..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" defer></script>
42
+ ```
43
+
44
+ That's it. Reload your page and look at the bottom-right corner.
45
+
46
+ **What `defer` does** — the script downloads in the background and runs
47
+ after the HTML parses. Zero impact on first contentful paint. Always
48
+ use `defer`.
49
+
50
+ **Available attributes** — every config option is available as a
51
+ kebab-case `data-*` attribute. Common ones:
52
+
53
+ ```html
54
+ <script
55
+ src="https://cdn.help.ai/sdk/js/elements/latest/elements.js"
56
+ data-public-key="agent_pk_..."
57
+ data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"
58
+ data-theme="dark"
59
+ data-position="bottom-left"
60
+ data-launcher-variant="pill"
61
+ data-launcher-label="Ask AI"
62
+ defer
63
+ ></script>
64
+ ```
65
+
66
+ Full list: [Installation § script data attributes](./installation.md#path-a--script-data--auto-boot).
67
+
68
+ ---
69
+
70
+ ## Path B — async loader
71
+
72
+ If your site already uses a tag-manager-style loader pattern (Google
73
+ Analytics, Segment, Intercom, etc.), you'll recognize this shape:
74
+
75
+ ```html
76
+ <script>
77
+ (function (w, d) {
78
+ w.HelpAI =
79
+ w.HelpAI ||
80
+ function () {
81
+ (w.HelpAI.q = w.HelpAI.q || []).push(arguments);
82
+ };
83
+ var s = d.createElement('script');
84
+ s.async = 1;
85
+ s.src = 'https://cdn.help.ai/sdk/js/elements/latest/elements.js';
86
+ d.head.appendChild(s);
87
+ })(window, document);
88
+
89
+ HelpAI('init', { publicKey: 'agent_pk_abc123...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
90
+ </script>
91
+ ```
92
+
93
+ **What this does:**
94
+
95
+ 1. Defines `window.HelpAI` as a queue until the real bundle loads.
96
+ 2. Kicks off the async script download.
97
+ 3. Queues an `init` call — it'll run as soon as the bundle is ready.
98
+
99
+ You can stack more queued calls:
100
+
101
+ ```js
102
+ HelpAI('init', { publicKey: 'agent_pk_...', aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000' });
103
+ HelpAI('open'); // auto-opens the panel once init finishes
104
+ ```
105
+
106
+ Same behavior as path A, just split into two script tags so you can
107
+ load asynchronously.
108
+
109
+ ---
110
+
111
+ ## Path C — NPM
112
+
113
+ For sites with a build step (Next.js, Vite, Remix, Nuxt, SvelteKit,
114
+ Astro…):
115
+
116
+ ```bash
117
+ npm install @helpai/elements
118
+ # or: yarn add @helpai/elements
119
+ # or: pnpm add @helpai/elements
120
+ # or: bun add @helpai/elements
121
+ ```
122
+
123
+ ### React, Next.js, Remix
124
+
125
+ The React kit renders on the server safely and is already `'use client'`; put it in the root layout:
126
+
127
+ ```tsx
128
+ // app/layout.tsx
129
+ import { HelpAI } from '@helpai/elements/react';
130
+
131
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
132
+ return (
133
+ <html lang='en'>
134
+ <body>
135
+ {children}
136
+ <HelpAI publicKey={process.env.NEXT_PUBLIC_HELPAI_KEY!} aiAgentDeploymentId={process.env.NEXT_PUBLIC_HELPAI_DEPLOYMENT_ID!} />
137
+ </body>
138
+ </html>
139
+ );
140
+ }
141
+ ```
142
+
143
+ `useHelpAI()` in any component returns `{ state, ask, open, close, reset }`.
144
+
145
+ ### Vue / Svelte / Angular / any bundler
146
+
147
+ Call `init(...)` once on mount and `destroy()` on unmount, or use the `<web-ai-chat>` element (path D).
148
+ Full framework-by-framework walkthrough: [Framework integrations](./frameworks.md).
149
+
150
+ ---
151
+
152
+ ## Path D — web component
153
+
154
+ Declarative tag that works in any framework or plain HTML:
155
+
156
+ ```html
157
+ <!-- Load once, anywhere in the page -->
158
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" defer></script>
159
+
160
+ <!-- Use the element anywhere -->
161
+ <web-ai-chat public-key="agent_pk_abc123..." ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" mode="inline" theme="auto"></web-ai-chat>
162
+ ```
163
+
164
+ Attributes mirror the options. Changing an attribute at runtime
165
+ re-renders the widget with the new value.
166
+
167
+ Web components shine for:
168
+
169
+ - In-app help centers (`mode="inline"` inside a sidebar).
170
+ - CMS-built pages where you can paste HTML but not run JS.
171
+ - Declarative "just put the widget here" integrations.
172
+
173
+ ---
174
+
175
+ ## 3. Verify it's working
176
+
177
+ Open your page and look at the bottom-right. You should see a small
178
+ round chat bubble with an accent color.
179
+
180
+ - **Click it** → a panel opens with a greeting from the assistant.
181
+ - **Type a message** → the assistant replies token-by-token.
182
+ - **Close the panel** → the bubble returns.
183
+ - **Reload the page** → your chat history stays intact.
184
+
185
+ Something not showing up? → [Troubleshooting](./troubleshooting.md).
186
+
187
+ ---
188
+
189
+ ## 4. Common next steps
190
+
191
+ - **Match your brand** — [Theming](./theming.md) has every color,
192
+ radius, and font knob.
193
+ - **Change the launcher label / icon** — [`launcher` options](./configuration.md#launcher).
194
+ - **Know who the visitor is** — pass `userContext: { id, email, name }`
195
+ (and `pageContext: { area }`) for personalization.
196
+ - **Forward chat events to analytics** — [Events & hooks](./events-and-hooks.md).
197
+ - **Add a second widget** (one for sales, one for support) — [Recipes
198
+ § Multiple widgets](./recipes.md#multiple-widgets-on-one-page).
199
+
200
+ ---
201
+
202
+ ## TypeScript users
203
+
204
+ Types ship with the NPM package — no separate `@types` install needed:
205
+
206
+ ```ts
207
+ import { init, mount, destroy, hook, type ChatWidgetOptions, type ChatWidgetInstance, type EventMap, type EventName } from '@helpai/elements';
208
+ ```
209
+
210
+ Every option is autocomplete-friendly. Hover a field in your IDE to see
211
+ its doc.
212
+
213
+ ---
214
+
215
+ ## Environment variables
216
+
217
+ For server-rendered apps, expose the public key and deployment ID through the
218
+ framework's public environment variables. The values are designed for browser
219
+ installation; the backend accepts the authenticated pair only when both match
220
+ the same deployment:
221
+
222
+ | Framework | Env var prefix |
223
+ | ----------------------- | ---------------------- |
224
+ | Next.js | `NEXT_PUBLIC_*` |
225
+ | Vite / SvelteKit / Nuxt | `VITE_*` or `PUBLIC_*` |
226
+ | Remix | read from `window.ENV` |
227
+ | Create React App | `REACT_APP_*` |
228
+
229
+ Full security notes: [Security & privacy](./security-privacy.md).