@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,640 @@
1
+ # Framework integrations
2
+
3
+ Copy-paste recipes for every major frontend framework + CMS.
4
+
5
+ ---
6
+
7
+ ## Contents
8
+
9
+ - [Next.js](#nextjs)
10
+ - [React (Vite / CRA)](#react)
11
+ - [Vue 3](#vue-3)
12
+ - [Nuxt 3](#nuxt-3)
13
+ - [Svelte / SvelteKit](#svelte--sveltekit)
14
+ - [Astro](#astro)
15
+ - [Remix](#remix)
16
+ - [SolidJS](#solidjs)
17
+ - [Angular](#angular)
18
+ - [WordPress](#wordpress)
19
+ - [Shopify](#shopify)
20
+ - [Webflow](#webflow)
21
+ - [Squarespace / Wix](#squarespace--wix)
22
+
23
+ ---
24
+
25
+ ## Next.js
26
+
27
+ The React kit, `@helpai/elements/react`, is server-render safe and already marked `'use client'`, so it drops straight
28
+ into a layout. Nothing touches the page until it runs in the browser.
29
+
30
+ **App Router** (13+):
31
+
32
+ ```tsx
33
+ // app/layout.tsx
34
+ import { HelpAI } from '@helpai/elements/react';
35
+
36
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
37
+ return (
38
+ <html lang='en'>
39
+ <body>
40
+ {children}
41
+ <HelpAI publicKey={process.env.NEXT_PUBLIC_HELPAI_KEY!} aiAgentDeploymentId={process.env.NEXT_PUBLIC_HELPAI_DEPLOYMENT_ID!} theme='host' />
42
+ </body>
43
+ </html>
44
+ );
45
+ }
46
+ ```
47
+
48
+ ```bash
49
+ # .env.local
50
+ NEXT_PUBLIC_HELPAI_KEY=agent_pk_abc123...
51
+ NEXT_PUBLIC_HELPAI_DEPLOYMENT_ID=01900000-0000-7000-8000-000000000000
52
+ ```
53
+
54
+ A server component can only pass serializable props, so callbacks (`onOpen`, `onMessage`, …) go on a `<HelpAI />`
55
+ rendered from a `'use client'` component of your own. `useHelpAI()` (below) works in any client component.
56
+
57
+ **Pages Router**: render `<HelpAI />` in `pages/_app.tsx` the same way; no `dynamic(..., { ssr: false })` needed.
58
+
59
+ ---
60
+
61
+ ## React
62
+
63
+ The React kit (React 18+) mounts the widget with `<HelpAI />` and drives it from any component with `useHelpAI()`:
64
+
65
+ ```tsx
66
+ import { HelpAI, useHelpAI } from '@helpai/elements/react';
67
+
68
+ export function Root() {
69
+ return (
70
+ <>
71
+ <main>{/* your app */}</main>
72
+ <HelpAI publicKey={import.meta.env.VITE_HELPAI_KEY} aiAgentDeploymentId={import.meta.env.VITE_HELPAI_DEPLOYMENT_ID} theme='host' />
73
+ </>
74
+ );
75
+ }
76
+
77
+ function PricingHelp() {
78
+ const { ask, open, state } = useHelpAI();
79
+ return (
80
+ <>
81
+ <button onClick={() => ask('Which plan fits a team of 10?')}>Ask about plans</button>
82
+ <button onClick={() => open({ to: 'help' })}>Help center {state.unread > 0 && `(${state.unread})`}</button>
83
+ </>
84
+ );
85
+ }
86
+ ```
87
+
88
+ - **`<HelpAI {...options} />`** takes every [widget option](./configuration.md) as a prop; `aiAgentDeploymentId` is
89
+ required. It renders nothing itself: the widget mounts once the page is in the browser, a prop change re-configures
90
+ the live widget without a remount, and unmounting removes it. Callback props (`onOpen`, `onMessage`, …) can be new
91
+ functions on every render; the widget always calls the latest one.
92
+ - **`useHelpAI(widgetId?)`** returns `{ state, ask, open, close, reset }`. `state` is
93
+ [`{ open, ready, unread, conversationId? }`](./api-reference.md#state-and-ready) and re-renders the component when it changes;
94
+ the server render sees the initial state. The commands are stable across renders and do nothing until the widget
95
+ has mounted (`state.ready`). `ask(text)` fills the composer; `ask(text, { send: true })` sends it. Call `reset()`
96
+ when your user logs out.
97
+ - **One import path.** The kit carries the whole widget API (`getInstance`, `hook`, the types), so import everything
98
+ from `@helpai/elements/react`; the package's other entries are separate copies with their own widgets.
99
+ - **Two widgets** on one page: give each `<HelpAI widgetId='support' … />` and read it with `useHelpAI('support')`.
100
+
101
+ The kit bundles the widget and imports only React from your app, so you don't need Preact installed.
102
+
103
+ ### React 19 — use the web component directly
104
+
105
+ React 19+ supports custom elements natively:
106
+
107
+ ```tsx
108
+ // No JS bundle import needed — add a <script> tag or use the web-component sub-entry
109
+ import '@helpai/elements/web-component';
110
+
111
+ function Chat() {
112
+ return <web-ai-chat public-key={import.meta.env.VITE_HELPAI_KEY} ai-agent-deployment-id={import.meta.env.VITE_HELPAI_DEPLOYMENT_ID} theme='auto' />;
113
+ }
114
+ ```
115
+
116
+ ### TypeScript typings
117
+
118
+ `import type {} from '@helpai/elements/jsx/react';` once (in your entry file or a `.d.ts`) registers `<web-ai-chat>`
119
+ in React 19's JSX: every attribute the element reads autocompletes, `ai-agent-deployment-id` is required, and a `ref`
120
+ is the typed element with `ask`, `open`, `state`, `ready` and `reset`. Vue and Svelte have `@helpai/elements/jsx/vue`
121
+ and `@helpai/elements/jsx/svelte` ([JSX typings](./api-reference.md#jsx-typings)).
122
+
123
+ ```tsx
124
+ import { useRef } from 'react';
125
+
126
+ import type {} from '@helpai/elements/jsx/react';
127
+
128
+ function Chat() {
129
+ const chat = useRef<HTMLElementTagNameMap['web-ai-chat']>(null);
130
+ return (
131
+ <>
132
+ <button onClick={() => chat.current?.ask('Which plan fits a team of 10?')}>Ask about plans</button>
133
+ <web-ai-chat ref={chat} public-key='agent_pk_...' ai-agent-deployment-id='...' theme='host' />
134
+ </>
135
+ );
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Vue 3
142
+
143
+ Composition API:
144
+
145
+ ```vue
146
+ <!-- components/Chat.vue -->
147
+ <script setup lang="ts">
148
+ import { onMounted, onBeforeUnmount } from 'vue';
149
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
150
+
151
+ let instance: ChatWidgetInstance | null = null;
152
+
153
+ onMounted(() => {
154
+ instance = init({
155
+ publicKey: import.meta.env.VITE_HELPAI_KEY,
156
+ aiAgentDeploymentId: import.meta.env.VITE_HELPAI_DEPLOYMENT_ID,
157
+ });
158
+ });
159
+
160
+ onBeforeUnmount(() => {
161
+ instance?.destroy();
162
+ });
163
+ </script>
164
+
165
+ <template>
166
+ <!-- Nothing to render — the widget mounts its own DOM -->
167
+ </template>
168
+ ```
169
+
170
+ ```vue
171
+ <!-- App.vue -->
172
+ <script setup lang="ts">
173
+ import Chat from './components/Chat.vue';
174
+ </script>
175
+
176
+ <template>
177
+ <main>
178
+ <!-- your app -->
179
+ </main>
180
+ <Chat />
181
+ </template>
182
+ ```
183
+
184
+ ### Web component approach
185
+
186
+ Vue 3 supports custom elements natively. Register the web-component
187
+ sub-entry, then use the tag:
188
+
189
+ ```ts
190
+ // main.ts
191
+ import { createApp } from 'vue';
192
+ import '@helpai/elements/web-component';
193
+
194
+ import App from './App.vue';
195
+
196
+ const app = createApp(App);
197
+ // Tell Vue to treat `web-ai-chat` as a custom element (not a Vue component)
198
+ app.config.compilerOptions.isCustomElement = (tag) => tag === 'web-ai-chat';
199
+ app.mount('#app');
200
+ ```
201
+
202
+ ```vue
203
+ <template>
204
+ <web-ai-chat public-key="agent_pk_..." ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" theme="auto" />
205
+ </template>
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Nuxt 3
211
+
212
+ ```ts
213
+ // plugins/web-ai-chat.client.ts
214
+ import { init } from '@helpai/elements';
215
+
216
+ export default defineNuxtPlugin((nuxtApp) => {
217
+ const chat = init({
218
+ publicKey: useRuntimeConfig().public.helpaiKey,
219
+ aiAgentDeploymentId: useRuntimeConfig().public.helpaiDeploymentId,
220
+ });
221
+
222
+ nuxtApp.hook('app:beforeMount', () => {
223
+ // Widget already mounted by init(). This hook is just a reference.
224
+ });
225
+
226
+ // Clean up on app tear-down (rarely triggered in SPA navigation)
227
+ nuxtApp.hook('app:suspense:resolve', () => {
228
+ // No-op — widget stays across navigations
229
+ });
230
+
231
+ return {
232
+ provide: {
233
+ chat,
234
+ },
235
+ };
236
+ });
237
+ ```
238
+
239
+ ```ts
240
+ // nuxt.config.ts
241
+ export default defineNuxtConfig({
242
+ runtimeConfig: {
243
+ public: {
244
+ helpaiKey: process.env.NUXT_PUBLIC_HELPAI_KEY,
245
+ helpaiDeploymentId: process.env.NUXT_PUBLIC_HELPAI_DEPLOYMENT_ID,
246
+ },
247
+ },
248
+ });
249
+ ```
250
+
251
+ Note the `.client.ts` suffix — ensures the plugin only runs in the
252
+ browser.
253
+
254
+ ---
255
+
256
+ ## Svelte / SvelteKit
257
+
258
+ **SvelteKit**:
259
+
260
+ ```svelte
261
+ <!-- src/routes/+layout.svelte -->
262
+ <script lang="ts">
263
+ import { onMount, onDestroy } from 'svelte';
264
+ import { browser } from '$app/environment';
265
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
266
+ import { PUBLIC_HELPAI_DEPLOYMENT_ID, PUBLIC_HELPAI_KEY } from '$env/static/public';
267
+
268
+ let instance: ChatWidgetInstance | null = null;
269
+
270
+ onMount(() => {
271
+ if (!browser) return;
272
+ instance = init({ publicKey: PUBLIC_HELPAI_KEY, aiAgentDeploymentId: PUBLIC_HELPAI_DEPLOYMENT_ID });
273
+ });
274
+
275
+ onDestroy(() => {
276
+ instance?.destroy();
277
+ });
278
+ </script>
279
+
280
+ <slot />
281
+ ```
282
+
283
+ **Plain Svelte** (no SvelteKit):
284
+
285
+ ```svelte
286
+ <!-- App.svelte -->
287
+ <script lang="ts">
288
+ import { onMount, onDestroy } from 'svelte';
289
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
290
+
291
+ let instance: ChatWidgetInstance | null = null;
292
+
293
+ onMount(() => {
294
+ instance = init({ publicKey: import.meta.env.VITE_HELPAI_KEY, aiAgentDeploymentId: import.meta.env.VITE_HELPAI_DEPLOYMENT_ID });
295
+ });
296
+
297
+ onDestroy(() => {
298
+ instance?.destroy();
299
+ });
300
+ </script>
301
+
302
+ <main>
303
+ <!-- your app -->
304
+ </main>
305
+ ```
306
+
307
+ ### Web component in Svelte
308
+
309
+ Svelte handles custom elements natively:
310
+
311
+ ```svelte
312
+ <script lang="ts">
313
+ import '@helpai/elements/web-component';
314
+ </script>
315
+
316
+ <web-ai-chat public-key="agent_pk_..." ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" theme="auto" />
317
+ ```
318
+
319
+ ---
320
+
321
+ ## Astro
322
+
323
+ Astro is SSR-first; wrap the widget in a client directive:
324
+
325
+ ```astro
326
+ ---
327
+ // src/components/Chat.astro
328
+ const publicKey = import.meta.env.PUBLIC_HELPAI_KEY;
329
+ const aiAgentDeploymentId = import.meta.env.PUBLIC_HELPAI_DEPLOYMENT_ID;
330
+ ---
331
+
332
+ <script define:vars={{ publicKey, aiAgentDeploymentId }}>
333
+ import('@helpai/elements').then(({ init }) => {
334
+ init({ publicKey, aiAgentDeploymentId });
335
+ });
336
+ </script>
337
+ ```
338
+
339
+ ```astro
340
+ ---
341
+ // src/layouts/Layout.astro
342
+ import Chat from '../components/Chat.astro';
343
+ ---
344
+ <html>
345
+ <body>
346
+ <slot />
347
+ <Chat />
348
+ </body>
349
+ </html>
350
+ ```
351
+
352
+ Or use the web component (even simpler — no JS needed):
353
+
354
+ ```astro
355
+ ---
356
+ import '@helpai/elements/web-component';
357
+ ---
358
+
359
+ <web-ai-chat public-key={Astro.env.PUBLIC_HELPAI_KEY} ai-agent-deployment-id={Astro.env.PUBLIC_HELPAI_DEPLOYMENT_ID} />
360
+ ```
361
+
362
+ ---
363
+
364
+ ## Remix
365
+
366
+ Remix (and React Router 7) render on the server, which the [React kit](#react) handles: pass the keys from the loader.
367
+
368
+ ```tsx
369
+ // app/root.tsx
370
+ import { HelpAI } from '@helpai/elements/react';
371
+ import { Outlet, useLoaderData } from '@remix-run/react';
372
+
373
+ export async function loader() {
374
+ return {
375
+ helpaiKey: process.env.HELPAI_PUBLIC_KEY ?? '',
376
+ helpaiDeploymentId: process.env.HELPAI_DEPLOYMENT_ID ?? '',
377
+ };
378
+ }
379
+
380
+ export default function App() {
381
+ const { helpaiKey, helpaiDeploymentId } = useLoaderData<typeof loader>();
382
+
383
+ return (
384
+ <html>
385
+ <body>
386
+ <Outlet />
387
+ {helpaiKey && helpaiDeploymentId && <HelpAI publicKey={helpaiKey} aiAgentDeploymentId={helpaiDeploymentId} />}
388
+ </body>
389
+ </html>
390
+ );
391
+ }
392
+ ```
393
+
394
+ ---
395
+
396
+ ## SolidJS
397
+
398
+ ```tsx
399
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
400
+ import { onMount, onCleanup } from 'solid-js';
401
+
402
+ export function Chat() {
403
+ let instance: ChatWidgetInstance | null = null;
404
+
405
+ onMount(() => {
406
+ instance = init({ publicKey: import.meta.env.VITE_HELPAI_KEY, aiAgentDeploymentId: import.meta.env.VITE_HELPAI_DEPLOYMENT_ID });
407
+ });
408
+
409
+ onCleanup(() => {
410
+ instance?.destroy();
411
+ });
412
+
413
+ return null;
414
+ }
415
+ ```
416
+
417
+ Solid also supports custom elements natively:
418
+
419
+ ```tsx
420
+ import '@helpai/elements/web-component';
421
+
422
+ export default function App() {
423
+ return <web-ai-chat public-key='agent_pk_...' ai-agent-deployment-id='01900000-0000-7000-8000-000000000000' />;
424
+ }
425
+ ```
426
+
427
+ ---
428
+
429
+ ## Angular
430
+
431
+ Angular needs `CUSTOM_ELEMENTS_SCHEMA` to accept the custom tag:
432
+
433
+ ```ts
434
+ // src/app/app.module.ts
435
+ import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
436
+ import '@helpai/elements/web-component';
437
+
438
+ @NgModule({
439
+ // ...
440
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
441
+ })
442
+ export class AppModule {}
443
+ ```
444
+
445
+ ```html
446
+ <!-- app.component.html -->
447
+ <web-ai-chat public-key="agent_pk_..." ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" theme="auto"></web-ai-chat>
448
+ ```
449
+
450
+ For programmatic control:
451
+
452
+ ```ts
453
+ // chat.service.ts
454
+ import { Injectable } from '@angular/core';
455
+ import { init, type ChatWidgetInstance } from '@helpai/elements';
456
+
457
+ import { environment } from '../environments/environment';
458
+
459
+ @Injectable({ providedIn: 'root' })
460
+ export class ChatService {
461
+ private instance: ChatWidgetInstance | null = null;
462
+
463
+ mount() {
464
+ if (!this.instance) {
465
+ this.instance = init({ publicKey: environment.helpaiKey, aiAgentDeploymentId: environment.helpaiDeploymentId });
466
+ }
467
+ }
468
+
469
+ destroy() {
470
+ this.instance?.destroy();
471
+ this.instance = null;
472
+ }
473
+
474
+ open() {
475
+ this.instance?.open();
476
+ }
477
+ }
478
+ ```
479
+
480
+ ---
481
+
482
+ ## WordPress
483
+
484
+ Add the snippet via the Theme Header/Footer plugin OR directly in
485
+ your theme's `header.php`:
486
+
487
+ ```html
488
+ <!-- Before </head> -->
489
+ <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>
490
+ ```
491
+
492
+ ### Via a plugin
493
+
494
+ Any "Insert Headers and Footers" plugin works:
495
+
496
+ 1. Install the plugin (e.g., "Insert Headers and Footers" by WPBeginner).
497
+ 2. Settings → Insert Headers and Footers.
498
+ 3. Paste the script tag into the "Scripts in Header" box.
499
+ 4. Save.
500
+
501
+ ### Via functions.php
502
+
503
+ ```php
504
+ <?php
505
+ // Add to your theme's functions.php
506
+ function helpai_enqueue_chat() {
507
+ ?>
508
+ <script
509
+ src="https://cdn.help.ai/sdk/js/elements/latest/elements.js"
510
+ data-public-key="agent_pk_abc123..."
511
+ data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"
512
+ defer
513
+ ></script>
514
+ <?php
515
+ }
516
+ add_action('wp_head', 'helpai_enqueue_chat');
517
+ ```
518
+
519
+ ### Personalization for logged-in users
520
+
521
+ ```php
522
+ <?php
523
+ function helpai_enqueue_chat_personalized() {
524
+ $user = wp_get_current_user();
525
+ $attrs = 'data-public-key="agent_pk_..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"';
526
+ if ($user->ID) {
527
+ $attrs .= ' data-user-id="' . esc_attr($user->ID) . '"';
528
+ $attrs .= ' data-user-email="' . esc_attr($user->user_email) . '"';
529
+ $attrs .= ' data-user-name="' . esc_attr($user->display_name) . '"';
530
+ $attrs .= ' data-user-context=\'' . esc_attr(json_encode(array("plan" => "pro"))) . '\'';
531
+ }
532
+ ?>
533
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" <?php echo $attrs; ?> defer></script>
534
+ <?php
535
+ }
536
+ add_action('wp_head', 'helpai_enqueue_chat_personalized');
537
+ ```
538
+
539
+ > Note: `data-user-*` / `data-page-*` are host-asserted context for the
540
+ > AI, never an auth credential — the browser deployment is selected by
541
+ > `data-public-key` + `data-ai-agent-deployment-id`. Explicit
542
+ > scalar attributes win over keys in the `data-user-context` /
543
+ > `data-page-context` JSON.
544
+
545
+ ---
546
+
547
+ ## Shopify
548
+
549
+ Shopify has a "custom pixels" and "additional scripts" workflow.
550
+ Easiest path:
551
+
552
+ 1. In your admin: **Online Store → Themes → Edit code**.
553
+ 2. Open `theme.liquid`.
554
+ 3. Paste before `</head>`:
555
+
556
+ ```html
557
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" data-public-key="agent_pk_..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" defer></script>
558
+ ```
559
+
560
+ ### With Shopify customer data
561
+
562
+ ```html
563
+ {% if customer %}
564
+ <script
565
+ src="https://cdn.help.ai/sdk/js/elements/latest/elements.js"
566
+ data-public-key="agent_pk_..."
567
+ data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"
568
+ data-user-id="{{ customer.id }}"
569
+ data-user-email="{{ customer.email }}"
570
+ data-user-name="{{ customer.first_name }} {{ customer.last_name }}"
571
+ data-page-area="orders"
572
+ defer
573
+ ></script>
574
+ {% else %}
575
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" data-public-key="agent_pk_..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" defer></script>
576
+ {% endif %}
577
+ ```
578
+
579
+ ---
580
+
581
+ ## Webflow
582
+
583
+ Webflow lets you add site-wide custom code:
584
+
585
+ 1. Site settings → **Custom code → Head code**.
586
+ 2. Paste:
587
+
588
+ ```html
589
+ <script src="https://cdn.help.ai/sdk/js/elements/latest/elements.js" data-public-key="agent_pk_..." data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000" defer></script>
590
+ ```
591
+
592
+ 3. Save + publish.
593
+
594
+ Page-specific customization: open the page → **Page settings →
595
+ Inside `<head>` tag** and paste a page-specific snippet.
596
+
597
+ ---
598
+
599
+ ## Squarespace / Wix
600
+
601
+ **Squarespace:**
602
+
603
+ 1. Settings → **Advanced → Code Injection**.
604
+ 2. Paste the script tag into the **Header** section.
605
+
606
+ **Wix:**
607
+
608
+ 1. Settings → **Custom Code**.
609
+ 2. Add new snippet.
610
+ 3. Paste + choose "All pages" + "Head" placement.
611
+
612
+ ---
613
+
614
+ ## Framework-agnostic troubleshooting
615
+
616
+ **The widget doesn't appear in SSR previews** — that's expected. The
617
+ widget is client-only. It appears after hydration / mount.
618
+
619
+ **The widget flashes open then closes on SPA navigation** — you're
620
+ probably calling `destroy()` then `init()` on every route change. Use
621
+ `init()` alone — it's idempotent. If there's an existing instance at
622
+ the same `widgetId`, it merges the new options in.
623
+
624
+ **Hot module replacement breaks the widget** — Next.js / Vite HMR
625
+ sometimes mounts duplicate instances. Always pair your `useEffect` /
626
+ `onMounted` with a cleanup that calls `destroy()`.
627
+
628
+ **CSP blocks the widget** — the widget inlines styles inside a shadow
629
+ root (requires `style-src 'unsafe-inline'` or a nonce pattern). See
630
+ [Security & privacy § CSP](./security-privacy.md#content-security-policy).
631
+
632
+ More: [Troubleshooting](./troubleshooting.md).
633
+
634
+ ---
635
+
636
+ ## Next
637
+
638
+ - [Recipes](./recipes.md) — programmatic control patterns.
639
+ - [Events & hooks](./events-and-hooks.md) — analytics integration.
640
+ - [Configuration](./configuration.md) — every option in detail.