@aiden-voice/widget 0.0.0 → 0.1.1

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/LICENSE.md ADDED
@@ -0,0 +1,17 @@
1
+ # Aiden Widget License
2
+
3
+ Copyright (c) 2026 Whoo. All rights reserved.
4
+
5
+ This package ("the Widget") is provided solely for embedding the Aiden service in
6
+ websites and applications operated by, or on behalf of, customers of the Aiden service
7
+ under the applicable Aiden terms of service.
8
+
9
+ Permitted: installing the Widget from npm or loading it from the Aiden CDN, and
10
+ integrating it into such websites and applications.
11
+
12
+ Not permitted without prior written consent from Whoo: redistributing, sublicensing,
13
+ selling or hosting the Widget separately from the Aiden service; modifying, decompiling
14
+ or otherwise deriving works from it; removing this notice.
15
+
16
+ The Widget is provided "as is", without warranty of any kind, express or implied. Whoo
17
+ is not liable for any damages arising from its use, to the extent permitted by law.
package/README.md CHANGED
@@ -1,3 +1,303 @@
1
1
  # @aiden-voice/widget
2
2
 
3
- Placeholder release (0.0.0). Real releases start at 0.1.0.
3
+ An embeddable chat widget for whoo-aiden. Drop in a `<script>` tag (or
4
+ install the npm package) and get a working, ready-to-use chat bubble in
5
+ the corner of your site, backed entirely by whoo-aiden's public widget API
6
+ — no UI code of your own required.
7
+
8
+ ## Installation
9
+
10
+ ### Option 1: CDN `<script>` tag
11
+
12
+ ```html
13
+ <script src="https://widget.aidenvoice.com/v0/widget.iife.js"></script>
14
+ <script>
15
+ window.whooAidenWidget.init({
16
+ publishableKey: 'pk_live_...',
17
+ })
18
+ </script>
19
+ ```
20
+
21
+ ### Option 2: npm package (bundler-based integration)
22
+
23
+ ```bash
24
+ pnpm add @aiden-voice/widget
25
+ ```
26
+
27
+ ```ts
28
+ import { init } from '@aiden-voice/widget'
29
+
30
+ init({
31
+ publishableKey: 'pk_live_...',
32
+ })
33
+ ```
34
+
35
+ Both paths expose the identical `init(config)` call — one code path, not
36
+ two — whether you load the widget via the CDN script tag
37
+ (`window.whooAidenWidget.init`) or import it in a bundler-based project
38
+ (`import { init } from '@aiden-voice/widget'`).
39
+
40
+ ## Usage
41
+
42
+ ```ts
43
+ init({
44
+ publishableKey: 'pk_live_...', // OR sessionToken -- exactly one, never both
45
+ // sessionToken: '<jwt from your own backend's identify-session call>',
46
+ position: 'bottom-right', // optional theming, all have defaults
47
+ color: '#4f46e5',
48
+ greeting: 'Hi! How can we help?',
49
+ logoUrl: 'https://example.com/logo.png',
50
+ })
51
+ ```
52
+
53
+ - **`publishableKey` mode**: the widget calls whoo-aiden's
54
+ `POST /widget/session` itself on load, subject to that endpoint's
55
+ Origin-allowlist check. This is the common case — you have no backend
56
+ of your own and are embedding the widget directly on your site.
57
+ - **`sessionToken` mode**: the widget skips session-minting entirely and
58
+ uses the token you supply as-is. Use this if your own backend already
59
+ called `POST /widget/identify-session` server-to-server (using a secret
60
+ key that must never reach the browser) and handed the resulting
61
+ short-lived JWT to the page some other way. The widget itself never
62
+ holds, requests, or sees a secret key under any circumstance.
63
+
64
+ `publishableKey` and `sessionToken` are mutually exclusive — `init()`
65
+ throws synchronously, at call time, if you pass both or neither.
66
+
67
+ ## Configuration reference
68
+
69
+ | Option | Description | Default |
70
+ | --- | --- | --- |
71
+ | `publishableKey` | Public key used to mint a session server-side. Mutually exclusive with `sessionToken`. | none (required unless `sessionToken` is set) |
72
+ | `sessionToken` | A pre-minted session JWT from your own backend. Mutually exclusive with `publishableKey`. | none (required unless `publishableKey` is set) |
73
+ | `position` | Launcher/panel corner: `'bottom-right'` or `'bottom-left'`. | `'bottom-right'` |
74
+ | `color` | Primary accent color (any valid CSS color). | `'#4f46e5'` |
75
+ | `greeting` | Welcome text shown in the panel before the first message. | none |
76
+ | `logoUrl` | URL of an image shown on the launcher bubble instead of the default icon. | none (default icon) |
77
+ | `apiBaseUrl` | Override the whoo-aiden API host the widget talks to. | `'https://api.aidenvoice.com'` |
78
+
79
+ ## Versions
80
+
81
+ The widget is published to a CDN under `https://widget.aidenvoice.com/<version>/`, plus
82
+ npm as `@aiden-voice/widget`.
83
+
84
+ - Exact versions (e.g. `/v1.3.0/`, including prereleases like `/v1.3.0-rc.1/`) are
85
+ immutable — once published, a given version's contents never change.
86
+ - `/vX.Y/` and `/vX/` are aliases that follow the newest stable release in that major/minor
87
+ or major line — e.g. `/v1.3/` follows the newest `1.3.x`, `/v1/` follows the newest `1.x.x`.
88
+ An alias only exists once a matching stable release has shipped: today's newest stable is
89
+ `0.x.x`, so the current CDN snippet above uses `/v0/`, not `/v1/` — `/v1/` will exist the
90
+ day `1.0.0` ships.
91
+ - `/next/` is an alias for the newest prerelease.
92
+ - The CDN root (`https://widget.aidenvoice.com/`) serves a small index page describing this
93
+ layout, rather than a bare directory listing or a 404.
94
+ - On npm, prereleases are published under the `next` dist-tag
95
+ (`pnpm add @aiden-voice/widget@next`). A release that isn't the newest of its kind (e.g.
96
+ a patch published for an older minor after a newer stable already shipped) is published
97
+ under the `maintenance` dist-tag instead, so an out-of-order release never moves what
98
+ `latest`/`next` — and therefore plain `npm install`/`pnpm add` — resolve to.
99
+
100
+ Because the CDN's global cache header is `public, max-age=31536000, immutable` (see
101
+ `staticwebapp.config.json`), fetching an alias or version URL that doesn't exist yet returns
102
+ a 404 that a browser is free to cache for a year. Only embed an alias URL once you know the
103
+ version it needs exists — don't pre-embed `/v1/` in anticipation of a `1.0.0` release.
104
+
105
+ ## Releasing
106
+
107
+ Releases are tag-driven via `.github/workflows/release.yml` (the `Release`
108
+ workflow), running in the `release` GitHub environment. It has two jobs:
109
+ `cdn` (build, archive upload, alias rewrite, Static Web App deploy) and
110
+ `publish` (npm), with `publish` gated on `cdn` succeeding first.
111
+
112
+ 1. Bump `version` in **both** `package.json` and `src/version.ts`'s
113
+ `WIDGET_VERSION` (a normal PR — `tests/version.test.ts` asserts the two
114
+ stay in sync), and merge it to `main`.
115
+ 2. Tag the merge commit and push the tag:
116
+
117
+ ```sh
118
+ git tag v<version> && git push origin v<version>
119
+ ```
120
+
121
+ The tag must match `package.json`'s `version` exactly, for a stable
122
+ release (e.g. `v1.3.0` ↔ `1.3.0`) and for a prerelease alike (e.g.
123
+ `v1.3.0-rc.1` ↔ `1.3.0-rc.1`) — the workflow refuses a mismatch.
124
+ Prereleases use a `-rc.N` suffix.
125
+
126
+ Pushing a `v*` tag runs the workflow. The `cdn` job:
127
+
128
+ - Builds and re-runs the full quality gate (lint, typecheck, unit tests,
129
+ build) against the tagged commit.
130
+ - Logs into Azure via OIDC (`azure/login`) — no stored Azure credentials.
131
+ - Plans the release from the tag and the archive's existing versions
132
+ (`scripts/release-plan.mjs plan`, fed via the pure, unit-tested
133
+ `versions()`/`existingVersions()` helper rather than a shell regex),
134
+ computing the immutable version folder, which alias folders move, and
135
+ the npm dist-tag.
136
+ - Uploads the build to the immutable `/v<version>/` folder in the
137
+ `widget-releases` blob container (`--overwrite false` — a version's
138
+ contents can never change once published), refusing to run if the
139
+ computed folder name doesn't look like a version.
140
+ - Rewrites only the alias folders the plan says should move (`/vX.Y/`,
141
+ `/vX/`, or `/next/` for a prerelease) — version folders are never
142
+ touched by this step.
143
+ - Downloads the whole archive, sanity-checks that the version just
144
+ uploaded is actually present and that the archive grew by at least one
145
+ version, and deploys it as-is to the `widget.aidenvoice.com` Static Web
146
+ App (a SWA deploy replaces the whole site, so the full archive — every
147
+ version and alias — is redeployed each release), with a generated
148
+ `staticwebapp.config.json` adding one cache route per alias folder
149
+ found in the archive.
150
+
151
+ Once `cdn` succeeds, the `publish` job rebuilds from the same tag and
152
+ publishes the package to npm as `@aiden-voice/widget`, tagged `latest` for
153
+ a stable release or `next` for a prerelease.
154
+
155
+ Publishing uses npm **trusted publishing** (OIDC) — no npm token exists
156
+ for this package at all. The package's Trusted Publisher, configured on
157
+ npmjs.com, is org `whoo-ai`, repository `whoo-aiden-widget`, workflow
158
+ `release.yml`, environment `release` (all four fields are case-sensitive
159
+ and must match exactly). Because trusted publishing needs npm ≥ 11.5.1
160
+ and Node 22 bundles npm 10, the `publish` job upgrades npm
161
+ (`npm install -g npm@latest`) immediately before publishing. Provenance
162
+ attestations are **not** generated, because the source repository is
163
+ private (provenance requires either a public repo or npm-side allowlisting
164
+ that isn't set up here).
165
+
166
+ The pnpm version this repo uses is pinned via the top-level `packageManager`
167
+ field, not `devEngines.packageManager` — `npm publish` enforces
168
+ `devEngines` for every command it runs, so a `pnpm`-only `devEngines` entry
169
+ makes `npm publish` refuse to run at all.
170
+
171
+ A stable release lands at `/v1.3.0/`, `/v1.3/`, and `/v1/`; a prerelease
172
+ lands only at its exact version folder and `/next/`. `-rc.N` prereleases
173
+ are never aliased under `/vX.Y/` or `/vX/`, and are published to npm under
174
+ the `next` dist-tag rather than `latest`. Shipping a stable release does
175
+ **not** retire `next` — `next` still points at the newest prerelease, if
176
+ any, even after a newer stable ships. If you'd rather have `next` catch up
177
+ to a stable release (e.g. there's no prerelease queued), move it by hand:
178
+
179
+ ```sh
180
+ npm dist-tag add @aiden-voice/widget@<stable-version> next
181
+ ```
182
+
183
+ If `publish` fails (a transient npm outage, a misconfigured Trusted
184
+ Publisher) after `cdn` has already succeeded, re-run just that job from
185
+ the GitHub Actions UI
186
+ (Actions → the failed run → **Re-run failed jobs**) — it rebuilds and
187
+ publishes from the same tag without re-touching the archive or
188
+ redeploying the Static Web App. The tag itself is never re-pushed.
189
+
190
+ ### Recovering a failed release
191
+
192
+ If the `cdn` job fails partway through — after the version folder was
193
+ already uploaded to the archive — simply re-pushing the same tag will not
194
+ work: `scripts/release-plan.mjs plan` refuses a version that already
195
+ exists in the archive, since versions are immutable by design. To recover,
196
+ delete that version's folder from the archive first, then re-push the tag.
197
+ Run this as an operator, not the workflow's identity — the release
198
+ identity is the only principal with a data-plane role on the container, so
199
+ an operator's own `az storage blob` command needs `--auth-mode key`
200
+ instead (Contributor on the resource group is enough to fetch the account
201
+ key for that):
202
+
203
+ ```sh
204
+ az storage blob delete-batch --account-name <acct> --source widget-releases \
205
+ --pattern "vX.Y.Z/*" --auth-mode key
206
+ git push origin :refs/tags/vX.Y.Z # delete the remote tag
207
+ git tag -d vX.Y.Z && git tag vX.Y.Z && git push origin vX.Y.Z
208
+ ```
209
+
210
+ If the failed run also rewrote an alias before failing later, any alias
211
+ folders it touched (`vX.Y/*`, `vX/*`, or `next/*`) can be cleaned up the
212
+ same way — the next successful release rewrites them from scratch anyway,
213
+ so deleting them first isn't required, just a way to clear out a
214
+ partial/inconsistent alias sooner:
215
+
216
+ ```sh
217
+ az storage blob delete-batch --account-name <acct> --source widget-releases \
218
+ --pattern "vX.Y/*" --auth-mode key
219
+ ```
220
+
221
+ To verify a release landed, check the CDN alias (cache headers should show
222
+ a short max-age on an alias path, and immutable on a version path) — use
223
+ whichever alias currently exists (see "Versions" above; `/v0/` today):
224
+
225
+ ```sh
226
+ curl -sI https://widget.aidenvoice.com/v0/widget.iife.js
227
+ ```
228
+
229
+ and confirm the npm publish:
230
+
231
+ ```sh
232
+ npm view @aiden-voice/widget dist-tags
233
+ ```
234
+
235
+ ## Accessibility
236
+
237
+ The widget targets WCAG 2.1 AA and ships with dialog focus management,
238
+ keyboard support (Tab-trapping while the panel is open, Escape to close),
239
+ screen-reader announcements for streaming replies and errors, and labeled
240
+ form controls out of the box. A few things stay the embedding site's
241
+ responsibility:
242
+
243
+ - **Theme color contrast:** for any valid CSS color (hex, named colors like
244
+ `'yellow'`, `rgb()`, `hsl()`, etc.), the widget automatically picks black
245
+ or white text against whatever `color` (or server-provided
246
+ `primaryColor`) is in effect on button/bubble surfaces, so text stays
247
+ readable there regardless of how light or dark your color is. One
248
+ exception: the panel's "New conversation" and "Close chat" text buttons
249
+ render your `color` directly as text on the panel's white background,
250
+ which is **not** auto-corrected — pick a color with reasonable contrast
251
+ against white if you care about that spot (the default `#4f46e5` works
252
+ well; very light/pastel colors will not). See
253
+ `docs/accessibility-report.md` for details.
254
+ - **Page language:** the widget renders inside your page's DOM and
255
+ inherits language from your document's `<html lang>` attribute; there is
256
+ no separate widget-level locale override.
257
+ - **Don't hide the mount point:** don't place the widget's container (or
258
+ move it, via your own script) inside an element with `aria-hidden="true"`
259
+ or `inert` — that hides it from assistive technology regardless of the
260
+ widget's own markup.
261
+ - **Escape key:** while the panel is open, the widget closes itself on
262
+ `Escape`. If your page has its own global `Escape` handling, be aware of
263
+ the potential conflict.
264
+
265
+ See `docs/accessibility-checklist.md` in this repository for the manual QA
266
+ checklist run before each release.
267
+
268
+ ## Error handling
269
+
270
+ The widget distinguishes two deliberately different error tiers:
271
+
272
+ - **Init/auth failures** (an invalid `publishableKey`, an invalid/expired
273
+ `sessionToken`, an Origin not on the allowlist, or a malformed `init()`
274
+ config) are entirely silent to the visitor — no launcher bubble renders
275
+ at all. The failure is logged via `console.error` with enough detail to
276
+ diagnose it, but nothing partial or broken-looking ever appears on the
277
+ page.
278
+ - **Failures once the widget is already visible and in an active chat**
279
+ (a network error, an SSE connection dropping mid-stream, a
280
+ quota-exceeded response) show a short, non-technical inline message in
281
+ the chat panel itself (e.g. "Something went wrong, please try again"),
282
+ leaving any partial response already rendered in place above it.
283
+
284
+ ## License
285
+
286
+ Proprietary. This package is licensed only for embedding the Aiden service under
287
+ the applicable Aiden terms of service — see [`LICENSE.md`](./LICENSE.md).
288
+
289
+ ## Local development
290
+
291
+ This repo uses [pnpm](https://pnpm.io) and [Biome](https://biomejs.dev)
292
+ (not npm/yarn or eslint/prettier).
293
+
294
+ | Command | Description |
295
+ | --- | --- |
296
+ | `pnpm build` | Build the ESM (npm) and IIFE (CDN) bundles into `dist/`, once. |
297
+ | `pnpm build:watch` | Same, but rebuilds `dist/` automatically on every source change. Run this in the background whenever you're developing against a sibling `whoo-aiden-dashboard` checkout (its `file:` dependency on this repo resolves to `dist/`, not `src/`, so without this a dashboard rebuild/reload can silently keep serving stale widget code). |
298
+ | `pnpm dev` | Run the Vite dev server. |
299
+ | `pnpm lint` | Lint the repo with Biome. |
300
+ | `pnpm format` | Format the repo with Biome. |
301
+ | `pnpm typecheck` | Type-check with `tsc --noEmit`. |
302
+ | `pnpm test` | Run the Vitest unit/component test suite. |
303
+ | `pnpm e2e` | Run the Playwright end-to-end embed smoke test against the built IIFE bundle. |
@@ -0,0 +1,14 @@
1
+ export declare class WidgetApiError extends Error {
2
+ readonly status?: number;
3
+ constructor(message: string, status?: number);
4
+ }
5
+ export declare function mintSession(apiBaseUrl: string, publishableKey: string): Promise<{
6
+ token: string;
7
+ primaryColor: string | null;
8
+ }>;
9
+ export declare function submitFeedback(apiBaseUrl: string, sessionToken: string, body: {
10
+ messageId: string;
11
+ rating: 'up' | 'down';
12
+ correction?: string;
13
+ category?: 'factual' | 'incomplete' | 'tone' | 'other';
14
+ }): Promise<void>;
@@ -0,0 +1,17 @@
1
+ export type ChatEvent = {
2
+ type: 'conversation_id';
3
+ conversationId: string;
4
+ } | {
5
+ type: 'delta';
6
+ delta: string;
7
+ verbatim?: true;
8
+ } | {
9
+ type: 'done';
10
+ conversationId: string;
11
+ messageId: string | null;
12
+ } | {
13
+ type: 'error';
14
+ error: string;
15
+ conversationId?: string;
16
+ };
17
+ export declare function streamChat(apiBaseUrl: string, sessionToken: string, conversationId: string | null, message: string, signal?: AbortSignal): AsyncGenerator<ChatEvent>;
@@ -0,0 +1,4 @@
1
+ import { WidgetController } from '../controller';
2
+ export declare function App({ controller }: {
3
+ controller: WidgetController;
4
+ }): import("preact").JSX.Element | null;
@@ -0,0 +1,8 @@
1
+ import { RefObject } from 'preact';
2
+ export declare function Composer({ value, onChange, onSend, disabled, inputRef, }: {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ onSend: () => void;
6
+ disabled: boolean;
7
+ inputRef?: RefObject<HTMLInputElement>;
8
+ }): import("preact").JSX.Element;
@@ -0,0 +1,3 @@
1
+ export declare function ErrorBanner({ message }: {
2
+ message: string;
3
+ }): import("preact").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { RefObject } from 'preact';
2
+ export declare function Launcher({ isOpen, onClick, logoUrl, buttonRef, }: {
3
+ isOpen: boolean;
4
+ onClick: () => void;
5
+ logoUrl?: string;
6
+ buttonRef?: RefObject<HTMLButtonElement>;
7
+ }): import("preact").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { RefObject } from 'preact';
2
+ import { FeedbackCategory, MessageFeedbackState } from '../controller';
3
+ export declare function MessageFeedback({ state, containerRef, onThumbsUp, onThumbsDown, onSubmitDetailed, onCancel, }: {
4
+ state: MessageFeedbackState;
5
+ containerRef?: RefObject<HTMLElement>;
6
+ onThumbsUp: () => void;
7
+ onThumbsDown: () => void;
8
+ onSubmitDetailed: (correction: string, category: '' | FeedbackCategory) => void;
9
+ onCancel: () => void;
10
+ }): import("preact").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { FeedbackCategory, Message, MessageFeedbackState } from '../controller';
2
+ export declare function MessageList({ messages, messageFeedback, lastCompletedMessageText, onThumbsUp, onThumbsDown, onSubmitDetailed, onCancelFeedback, }: {
3
+ messages: Message[];
4
+ messageFeedback: Record<string, MessageFeedbackState>;
5
+ lastCompletedMessageText: string | null;
6
+ onThumbsUp: (messageId: string) => void;
7
+ onThumbsDown: (messageId: string) => void;
8
+ onSubmitDetailed: (messageId: string, correction: string, category: '' | FeedbackCategory) => void;
9
+ onCancelFeedback: (messageId: string) => void;
10
+ }): import("preact").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { WidgetController, WidgetState } from '../controller';
2
+ export declare function getFocusableElements(container: HTMLElement): HTMLElement[];
3
+ export declare function Panel({ state, controller, greeting, }: {
4
+ state: WidgetState;
5
+ controller: WidgetController;
6
+ greeting?: string;
7
+ }): import("preact").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import { WidgetConfig } from './types';
2
+ export declare const DEFAULT_API_BASE_URL = "https://api.aidenvoice.com";
3
+ export declare function validateConfig(config: WidgetConfig): void;
@@ -0,0 +1,45 @@
1
+ import { WidgetConfig } from './types';
2
+ export interface Message {
3
+ role: 'user' | 'assistant';
4
+ content: string;
5
+ id: string | null;
6
+ }
7
+ export type FeedbackCategory = 'factual' | 'incomplete' | 'tone' | 'other';
8
+ export interface MessageFeedbackState {
9
+ status: 'idle' | 'expanded' | 'submitting' | 'submitted' | 'error';
10
+ rating?: 'up' | 'down';
11
+ }
12
+ export interface WidgetState {
13
+ isOpen: boolean;
14
+ messages: Message[];
15
+ isStreaming: boolean;
16
+ initError: string | null;
17
+ runtimeError: string | null;
18
+ messageFeedback: Record<string, MessageFeedbackState>;
19
+ primaryColor: string | null;
20
+ lastCompletedMessageText: string | null;
21
+ }
22
+ type Listener = (state: WidgetState) => void;
23
+ export declare class WidgetController {
24
+ readonly config: WidgetConfig;
25
+ private state;
26
+ private readonly listeners;
27
+ private sessionToken;
28
+ private conversationId;
29
+ private readonly storageIdentifier;
30
+ private activeRequestController;
31
+ private readonly prefersReducedMotion;
32
+ constructor(config: WidgetConfig);
33
+ getState(): WidgetState;
34
+ subscribe(listener: Listener): () => void;
35
+ private setState;
36
+ init(): Promise<void>;
37
+ open(): void;
38
+ resetConversation(): void;
39
+ close(): void;
40
+ sendMessage(text: string): Promise<void>;
41
+ expandFeedback(messageId: string): void;
42
+ cancelFeedback(messageId: string): void;
43
+ submitFeedback(messageId: string, rating: 'up' | 'down', correction?: string, category?: FeedbackCategory): Promise<void>;
44
+ }
45
+ export {};
@@ -0,0 +1,3 @@
1
+ import { WidgetConfig, WidgetHandle } from './types';
2
+ export declare function init(config: WidgetConfig): WidgetHandle;
3
+ export type { WidgetConfig, WidgetHandle };
@@ -0,0 +1,2 @@
1
+ import { ComponentChildren } from 'preact';
2
+ export declare function renderMarkdown(text: string): ComponentChildren;
@@ -0,0 +1,6 @@
1
+ export interface RevealOptions {
2
+ signal?: AbortSignal;
3
+ wordIntervalMs?: number;
4
+ tokenIntervalMs?: number;
5
+ }
6
+ export declare function revealText(fullText: string, onChunk: (partial: string) => void, options?: RevealOptions): Promise<void>;
@@ -0,0 +1,23 @@
1
+ export type Token = {
2
+ type: 'text';
3
+ value: string;
4
+ start: number;
5
+ end: number;
6
+ } | {
7
+ type: 'bold';
8
+ value: string;
9
+ start: number;
10
+ end: number;
11
+ } | {
12
+ type: 'italic';
13
+ value: string;
14
+ start: number;
15
+ end: number;
16
+ } | {
17
+ type: 'link';
18
+ text: string;
19
+ url: string;
20
+ start: number;
21
+ end: number;
22
+ };
23
+ export declare function tokenize(text: string): Token[];
@@ -0,0 +1,2 @@
1
+ import { WidgetConfig, WidgetHandle } from './types';
2
+ export declare function mount(config: WidgetConfig): WidgetHandle;
@@ -0,0 +1,3 @@
1
+ export declare function getStoredConversationId(identifier: string): string | null;
2
+ export declare function setStoredConversationId(identifier: string, conversationId: string): void;
3
+ export declare function clearStoredConversationId(identifier: string): void;
@@ -0,0 +1,3 @@
1
+ import { WidgetConfig } from './types';
2
+ export declare function pickTextColor(cssColor: string): '#000000' | '#ffffff';
3
+ export declare function applyTheme(root: HTMLElement, config: WidgetConfig): void;
@@ -0,0 +1,12 @@
1
+ export interface WidgetConfig {
2
+ publishableKey?: string;
3
+ sessionToken?: string;
4
+ position?: 'bottom-right' | 'bottom-left';
5
+ color?: string;
6
+ greeting?: string;
7
+ logoUrl?: string;
8
+ apiBaseUrl?: string;
9
+ }
10
+ export interface WidgetHandle {
11
+ unmount(): void;
12
+ }
@@ -0,0 +1 @@
1
+ export declare const WIDGET_VERSION = "0.1.1";
@@ -0,0 +1,6 @@
1
+ var whooAidenWidget=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){if((typeof e.publishableKey==`string`&&e.publishableKey.length>0)==(typeof e.sessionToken==`string`&&e.sessionToken.length>0))throw Error("whoo-aiden-widget: init() requires exactly one of `publishableKey` or `sessionToken`, not both or neither.")}var n,r,i,a,o,s,c,l,u,d,f,p,m,h,g={},_=[],v=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,y=Array.isArray;function b(e,t){for(var n in t)e[n]=t[n];return e}function x(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function S(e,t,r){var i,a,o,s={};for(o in t)o==`key`?i=t[o]:o==`ref`?a=t[o]:s[o]=t[o];if(arguments.length>2&&(s.children=arguments.length>3?n.call(arguments,2):r),typeof e==`function`&&e.defaultProps!=null)for(o in e.defaultProps)s[o]===void 0&&(s[o]=e.defaultProps[o]);return C(e,s,i,a,null)}function C(e,t,n,a,o){var s={type:e,props:t,key:n,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++i,__i:-1,__u:0};return o==null&&r.vnode!=null&&r.vnode(s),s}function w(e){return e.children}function T(e,t){this.props=e,this.context=t}function E(e,t){if(t==null)return e.__?E(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type==`function`?E(e):null}function D(e){if(e.__P&&e.__d){var t=e.__v,n=t.__e,i=[],a=[],o=b({},t);o.__v=t.__v+1,r.vnode&&r.vnode(o),I(e.__P,o,t,e.__n,e.__P.namespaceURI,32&t.__u?[n]:null,i,n??E(t),!!(32&t.__u),a),o.__v=t.__v,o.__.__k[o.__i]=o,re(i,o,a),t.__e=t.__=null,o.__e!=n&&O(o)}}function O(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),O(e)}function k(e){(!e.__d&&(e.__d=!0)&&a.push(e)&&!A.__r++||o!=r.debounceRendering)&&((o=r.debounceRendering)||s)(A)}function A(){try{for(var e,t=1;a.length;)a.length>t&&a.sort(c),e=a.shift(),t=a.length,D(e)}finally{a.length=A.__r=0}}function ee(e,t,n,r,i,a,o,s,c,l,u){var d,f,p,m,h,v,y=r&&r.__k||_,b=t.length;for(c=j(n,t,y,c,b),d=0;d<b;d++)(p=n.__k[d])!=null&&(f=p.__i!=-1&&y[p.__i]||g,p.__i=d,v=I(e,p,f,i,a,o,s,c,l,u),m=p.__e,p.ref&&f.ref!=p.ref&&(f.ref&&L(f.ref,null,p),u.push(p.ref,p.__c||m,p)),h==null&&m!=null&&(h=m),4&p.__u?(c=M(p,c,e),f.__e&&(f.__e=null)):typeof p.type==`function`&&v!==void 0?c=v:m&&(c=m.nextSibling),p.__u&=-7);return n.__e=h,c}function j(e,t,n,r,i){var a,o,s,c,l,u=n.length,d=u,f=0;for(e.__k=Array(i),a=0;a<i;a++)(o=t[a])!=null&&typeof o!=`boolean`&&typeof o!=`function`?(typeof o==`string`||typeof o==`number`||typeof o==`bigint`||o.constructor==String?o=e.__k[a]=C(null,o,null,null,null):y(o)?o=e.__k[a]=C(w,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[a]=C(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[a]=o,c=a+f,o.__=e,o.__b=e.__b+1,s=null,(l=o.__i=N(o,n,c,d))!=-1&&(d--,(s=n[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(i>u?f--:i<u&&f++),typeof o.type!=`function`&&(o.__u|=4)):l!=c&&(l==c-1?f--:l==c+1?f++:(l>c?f--:f++,o.__u|=4))):e.__k[a]=null;if(d)for(a=0;a<u;a++)(s=n[a])!=null&&!(2&s.__u)&&(s.__e==r&&(r=E(s)),oe(s,s));return r}function M(e,t,n){var r,i;if(typeof e.type==`function`){for(r=e.__k,i=0;r&&i<r.length;i++)r[i]&&(r[i].__=e,t=M(r[i],t,n));return t}e.__e!=t&&(t&&e.type&&!t.parentNode&&(t=E(e)),t=n.insertBefore(e.__e,t||null));do t&&=t.nextSibling;while(t!=null&&t.nodeType==8);return t}function N(e,t,n,r){var i,a,o,s=e.key,c=e.type,l=t[n],u=l!=null&&!(2&l.__u);if(l===null&&s==null||u&&s==l.key&&c==l.type)return n;if(r>+!!u){for(i=n-1,a=n+1;i>=0||a<t.length;)if((l=t[o=i>=0?i--:a++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return o}return-1}function P(e,t,n){t[0]==`-`?e.setProperty(t,n??``):e[t]=n==null?``:typeof n!=`number`||v.test(t)?n:n+`px`}function F(e,t,n,r,i){var a,o;n:if(t==`style`){if(typeof n==`string`)e.style.cssText=n;else{if(typeof r==`string`&&(e.style.cssText=r=``),r)for(t in r)n&&t in n||P(e.style,t,``);if(n)for(t in n)r&&n[t]==r[t]||P(e.style,t,n[t])}}else if(t[0]==`o`&&t[1]==`n`)a=t!=(t=t.replace(f,`$1`)),o=t.toLowerCase(),t=o in e||t==`onFocusOut`||t==`onFocusIn`?o.slice(2):t.slice(2),e.l||={},e.l[t+a]=n,n?r?n[d]=r[d]:(n[d]=p,e.addEventListener(t,a?h:m,a)):e.removeEventListener(t,a?h:m,a);else{if(i==`http://www.w3.org/2000/svg`)t=t.replace(/xlink(H|:h)/,`h`).replace(/sName$/,`s`);else if(t!=`width`&&t!=`height`&&t!=`href`&&t!=`list`&&t!=`form`&&t!=`tabIndex`&&t!=`download`&&t!=`rowSpan`&&t!=`colSpan`&&t!=`role`&&t!=`popover`&&t in e)try{e[t]=n??``;break n}catch{}typeof n==`function`||(n==null||!1===n&&t[4]!=`-`?e.removeAttribute(t):e.setAttribute(t,t==`popover`&&n==1?``:n))}}function te(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t[u]==null)t[u]=p++;else if(t[u]<n[d])return;return n(r.event?r.event(t):t)}}}function I(e,t,n,i,a,o,s,c,l,u){var d,f,p,m,h,g,v,S,C,D,O,k,A,j,M,N,P=t.type;if(t.constructor!==void 0)return null;128&n.__u&&(l=!!(32&n.__u),o=[c=t.__e=n.__e]),(d=r.__b)&&d(t);n:if(typeof P==`function`){f=s.length;try{if(C=t.props,D=P.prototype&&P.prototype.render,O=(d=P.contextType)&&i[d.__c],k=d?O?O.props.value:d.__:i,n.__c?S=(p=t.__c=n.__c).__=p.__E:(D?t.__c=p=new P(C,k):(t.__c=p=new T(C,k),p.constructor=P,p.render=se),O&&O.sub(p),p.state||(p.state={}),p.__n=i,m=p.__d=!0,p.__h=[],p._sb=[]),D&&p.__s==null&&(p.__s=p.state),D&&P.getDerivedStateFromProps!=null&&(p.__s==p.state&&(p.__s=b({},p.__s)),b(p.__s,P.getDerivedStateFromProps(C,p.__s))),h=p.props,g=p.state,p.__v=t,m)D&&P.getDerivedStateFromProps==null&&p.componentWillMount!=null&&p.componentWillMount(),D&&p.componentDidMount!=null&&p.__h.push(p.componentDidMount);else{if(D&&P.getDerivedStateFromProps==null&&C!==h&&p.componentWillReceiveProps!=null&&p.componentWillReceiveProps(C,k),t.__v==n.__v||!p.__e&&p.shouldComponentUpdate!=null&&!1===p.shouldComponentUpdate(C,p.__s,k)){t.__v!=n.__v&&(p.props=C,p.state=p.__s,p.__d=!1),t.__e=n.__e,t.__k=n.__k,t.__k.some(function(e){e&&(e.__=t)}),_.push.apply(p.__h,p._sb),p._sb=[],p.__h.length&&s.push(p),c=E(n);break n}p.componentWillUpdate!=null&&p.componentWillUpdate(C,p.__s,k),D&&p.componentDidUpdate!=null&&p.__h.push(function(){p.componentDidUpdate(h,g,v)})}if(p.context=k,p.props=C,p.__P=e,p.__e=!1,A=r.__r,j=0,D)p.state=p.__s,p.__d=!1,A&&A(t),d=p.render(p.props,p.state,p.context),_.push.apply(p.__h,p._sb),p._sb=[];else do p.__d=!1,A&&A(t),d=p.render(p.props,p.state,p.context),p.state=p.__s;while(p.__d&&++j<25);p.state=p.__s,p.getChildContext!=null&&(i=b(b({},i),p.getChildContext())),D&&!m&&p.getSnapshotBeforeUpdate!=null&&(v=p.getSnapshotBeforeUpdate(h,g)),M=d!=null&&d.type===w&&d.key==null?ie(d.props.children):d,c=ee(e,y(M)?M:[M],t,n,i,a,o,s,c,l,u),p.base=t.__e,t.__u&=-161,p.__h.length&&s.push(p),S&&(p.__E=p.__=null)}catch(e){if(s.length=f,t.__v=null,l||o!=null){if(e.then){for(t.__u|=l?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;o!=null&&(o[o.indexOf(c)]=null),t.__e=c}else if(o!=null)for(N=o.length;N--;)x(o[N])}else t.__e=n.__e;t.__k??=n.__k||[],e.then||ne(t),r.__e(e,t,n)}}else o==null&&t.__v==n.__v?(t.__k=n.__k,t.__e=n.__e):c=t.__e=ae(n.__e,t,n,i,a,o,s,l,u);return(d=r.diffed)&&d(t),128&t.__u?void 0:c}function ne(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(ne))}function re(e,t,n){for(var i=0;i<n.length;i++)L(n[i],n[++i],n[++i]);r.__c&&r.__c(t,e),e.some(function(t){try{e=t.__h,t.__h=[],e.some(function(e){e.call(t)})}catch(e){r.__e(e,t.__v)}})}function ie(e){return typeof e!=`object`||!e||e.__b>0?e:y(e)?e.map(ie):e.constructor===void 0?b({},e):null}function ae(e,t,i,a,o,s,c,l,u){var d,f,p,m,h,_,v,b=i.props||g,S=t.props,C=t.type;if(C==`svg`?o=`http://www.w3.org/2000/svg`:C==`math`?o=`http://www.w3.org/1998/Math/MathML`:o||=`http://www.w3.org/1999/xhtml`,s!=null){for(d=0;d<s.length;d++)if((h=s[d])&&`setAttribute`in h==!!C&&(C?h.localName==C:h.nodeType==3)){e=h,s[d]=null;break}}if(e==null){if(C==null)return document.createTextNode(S);e=document.createElementNS(o,C,S.is&&S),l&&=(r.__m&&r.__m(t,s),!1),s=null}if(C==null)b===S||l&&e.data==S||(e.data=S);else{if(s=C==`textarea`&&S.defaultValue!=null?null:s&&n.call(e.childNodes),!l&&s!=null)for(b={},d=0;d<e.attributes.length;d++)b[(h=e.attributes[d]).name]=h.value;for(d in b)h=b[d],d==`dangerouslySetInnerHTML`?p=h:d==`children`||d in S||d==`value`&&`defaultValue`in S||d==`checked`&&`defaultChecked`in S||F(e,d,null,h,o);for(d in S)h=S[d],d==`children`?m=h:d==`dangerouslySetInnerHTML`?f=h:d==`value`?_=h:d==`checked`?v=h:l&&typeof h!=`function`||b[d]===h||F(e,d,h,b[d],o);if(f)l||p&&(f.__html==p.__html||f.__html==e.innerHTML)||(e.innerHTML=f.__html),t.__k=[];else if(p&&(e.innerHTML=``),ee(t.type==`template`?e.content:e,y(m)?m:[m],t,i,a,C==`foreignObject`?`http://www.w3.org/1999/xhtml`:o,s,c,s?s[0]:i.__k&&E(i,0),l,u),s!=null)for(d=s.length;d--;)x(s[d]);l&&C!=`textarea`||(d=`value`,C==`progress`&&_==null?e.removeAttribute(`value`):_!=null&&(_!==e[d]||C==`progress`&&!_||C==`option`&&_!=b[d])&&F(e,d,_,b[d],o),d=`checked`,v!=null&&v!=e[d]&&F(e,d,v,b[d],o))}return e}function L(e,t,n){try{if(typeof e==`function`){var i=typeof e.__u==`function`;i&&e.__u(),i&&t==null||(e.__u=e(t))}else e.current=t}catch(e){r.__e(e,n)}}function oe(e,t,n){var i,a;if(r.unmount&&r.unmount(e),(i=e.ref)&&(i.current&&i.current!=e.__e||L(i,null,t)),(i=e.__c)!=null){if(i.componentWillUnmount)try{i.componentWillUnmount()}catch(e){r.__e(e,t)}i.base=i.__P=i.__n=null}if(i=e.__k)for(a=0;a<i.length;a++)i[a]&&oe(i[a],t,n||typeof e.type!=`function`);n||x(e.__e),e.__c=e.__=e.__e=void 0}function se(e,t,n){return this.constructor(e,n)}function ce(e,t,i){var a,o,s,c;t==document&&(t=document.documentElement),r.__&&r.__(e,t),o=(a=typeof i==`function`)?null:i&&i.__k||t.__k,s=[],c=[],I(t,e=(!a&&i||t).__k=S(w,null,[e]),o||g,g,t.namespaceURI,!a&&i?[i]:o?null:t.firstChild?n.call(t.childNodes):null,s,!a&&i?i:o?o.__e:t.firstChild,a,c),re(s,e,c),e.props.children=null}n=_.slice,r={__e:function(e,t,n,r){for(var i,a,o;t=t.__;)if((i=t.__c)&&!i.__)try{if((a=i.constructor)&&a.getDerivedStateFromError!=null&&(i.setState(a.getDerivedStateFromError(e)),o=i.__d),i.componentDidCatch!=null&&(i.componentDidCatch(e,r||{}),o=i.__d),o)return i.__E=i}catch(t){e=t}throw e}},i=0,T.prototype.setState=function(e,t){var n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=b({},this.state);typeof e==`function`&&(e=e(b({},n),this.props)),e&&b(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),k(this))},T.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),k(this))},T.prototype.render=w,a=[],s=typeof Promise==`function`?Promise.prototype.then.bind(Promise.resolve()):setTimeout,c=function(e,t){return e.__v.__b-t.__v.__b},A.__r=0,l=Math.random().toString(8),u=`__d`+l,d=`__a`+l,f=/(PointerCapture)$|Capture$/i,p=0,m=te(!1),h=te(!0);var R,z,B,le,V=0,ue=[],H=r,de=H.__b,fe=H.__r,pe=H.diffed,me=H.__c,he=H.unmount,ge=H.__;function U(e,t){H.__h&&H.__h(z,e,V||t),V=0;var n=z.__H||(z.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function W(e){return V=1,_e(Ce,e)}function _e(e,t,n){var r=U(R++,2);if(r.t=e,!r.__c&&(r.__=[n?n(t):Ce(void 0,t),function(e){var t=r.__N?r.__N[0]:r.__[0],n=r.t(t,e);t!==n&&(r.__N=[n,r.__[1]],r.__c.setState({}))}],r.__c=z,!z.__f)){var i=function(e,t,n){if(!r.__c.__H)return!0;var i=!1,o=r.__c.props!==e;if(r.__c.__H.__.some(function(e){if(e.__N){i=!0;var t=e.__[0];e.__=e.__N,e.__N=void 0,t!==e.__[0]&&(o=!0)}}),a){var s=a.call(this,e,t,n);return i?s||o:s}return!i||o};z.__f=!0;var a=z.shouldComponentUpdate,o=z.componentWillUpdate;z.componentWillUpdate=function(e,t,n){if(this.__e){var r=a;a=void 0,i(e,t,n),a=r}o&&o.call(this,e,t,n)},z.shouldComponentUpdate=i}return r.__N||r.__}function G(e,t){var n=U(R++,3);!H.__s&&Se(n.__H,t)&&(n.__=e,n.u=t,z.__H.__h.push(n))}function K(e){return V=5,ve(function(){return{current:e}},[])}function ve(e,t){var n=U(R++,7);return Se(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function ye(){for(var e;e=ue.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(q),t.__h.some(J),t.__h=[]}catch(n){t.__h=[],H.__e(n,e.__v)}}}H.__b=function(e){z=null,de&&de(e)},H.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),ge&&ge(e,t)},H.__r=function(e){fe&&fe(e),R=0;var t=(z=e.__c).__H;t&&(B===z?(t.__h=[],z.__h=[],t.__.some(function(e){e.__N&&(e.__=e.__N),e.u=e.__N=void 0})):(t.__h.some(q),t.__h.some(J),t.__h=[],R=0)),B=z},H.diffed=function(e){pe&&pe(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(ue.push(t)!==1&&le===H.requestAnimationFrame||((le=H.requestAnimationFrame)||xe)(ye)),t.__H.__.some(function(e){e.u&&=(e.__H=e.u,void 0)})),B=z=null},H.__c=function(e,t){t.some(function(e){try{e.__h.some(q),e.__h=e.__h.filter(function(e){return!e.__||J(e)})}catch(n){t.some(function(e){e.__h&&=[]}),t=[],H.__e(n,e.__v)}}),me&&me(e,t)},H.unmount=function(e){he&&he(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(e){try{q(e)}catch(e){t=e}}),n.__H=void 0,t&&H.__e(t,n.__v))};var be=typeof requestAnimationFrame==`function`;function xe(e){var t,n=function(){clearTimeout(r),be&&cancelAnimationFrame(t),setTimeout(e)},r=setTimeout(n,35);be&&(t=requestAnimationFrame(n))}function q(e){var t=z,n=e.__c;typeof n==`function`&&(e.__c=void 0,n()),z=t}function J(e){var t=z;e.__c=e.__(),z=t}function Se(e,t){return!e||e.length!==t.length||t.some(function(t,n){return t!==e[n]})}function Ce(e,t){return typeof t==`function`?t(e):t}var we=0;Array.isArray;function Y(e,t,n,i,a,o){t||={};var s,c,l=t;if(`ref`in l)for(c in l={},t)c==`ref`?s=t[c]:l[c]=t[c];var u={type:e,props:l,key:n,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--we,__i:-1,__u:0,__source:a,__self:o};if(typeof e==`function`&&(s=e.defaultProps))for(c in s)l[c]===void 0&&(l[c]=s[c]);return r.vnode&&r.vnode(u),u}function Te({isOpen:e,onClick:t,logoUrl:n,buttonRef:r}){return Y(`button`,{type:`button`,class:`whoo-widget-launcher`,onClick:t,"aria-label":e?`Close chat`:`Open chat`,ref:r,children:e?Y(`span`,{"aria-hidden":`true`,children:`✕`}):n?Y(`img`,{class:`whoo-widget-launcher-logo`,src:n,alt:``}):Y(`span`,{"aria-hidden":`true`,children:`💬`})})}function Ee({value:e,onChange:t,onSend:n,disabled:r,inputRef:i}){return Y(`div`,{class:`whoo-widget-composer`,children:[Y(`label`,{class:`whoo-widget-sr-only`,for:`whoo-widget-composer-input`,children:`Message`}),Y(`input`,{id:`whoo-widget-composer-input`,class:`whoo-widget-composer-input`,value:e,readOnly:r,"aria-disabled":r,ref:i,onInput:e=>t(e.target.value),onKeyDown:e=>{e.key===`Enter`&&n()}}),Y(`button`,{type:`button`,class:`whoo-widget-composer-send`,onClick:n,disabled:r,children:`Send`})]})}function De({message:e}){return Y(`div`,{class:`whoo-widget-error-banner`,role:`alert`,children:e})}var X=/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/y,Z=/\*\*([^*]+)\*\*/y,Q=/(?<![A-Za-z0-9_])_([^_\s](?:[^_]*[^_\s])?)_(?![A-Za-z0-9_])/y,$=/\*([^*]+)\*/y,Oe=/https?:\/\/\S+/y,ke=/[.,;:!?)\]}>]+$/;function Ae(e){let t=[],n=0,r=0,i=n=>{n>r&&t.push({type:`text`,value:e.slice(r,n),start:r,end:n})};for(;n<e.length;){if(e[n]===`!`&&n+1<e.length&&e[n+1]===`[`){let t=/!\[([^\]]*)\]\(([^)]*)\)/y;t.lastIndex=n;let r=t.exec(e);if(r){n+=r[0].length;continue}}if(e[n-1]!==`!`){X.lastIndex=n;let a=X.exec(e);if(a){i(n),t.push({type:`link`,text:a[1],url:a[2],start:n,end:X.lastIndex}),n=X.lastIndex,r=n;continue}}Z.lastIndex=n;let a=Z.exec(e);if(a){i(n),t.push({type:`bold`,value:a[1],start:n,end:Z.lastIndex}),n=Z.lastIndex,r=n;continue}Q.lastIndex=n;let o=Q.exec(e);if(o){i(n),t.push({type:`italic`,value:o[1],start:n,end:Q.lastIndex}),n=Q.lastIndex,r=n;continue}$.lastIndex=n;let s=$.exec(e);if(s){i(n),t.push({type:`italic`,value:s[1],start:n,end:$.lastIndex}),n=$.lastIndex,r=n;continue}Oe.lastIndex=n;let c=Oe.exec(e);if(c){let e=c[0].match(ke),a=e?c[0].slice(0,c[0].length-e[0].length):c[0];if(a.length>0){i(n),t.push({type:`link`,text:a,url:a,start:n,end:n+a.length}),n+=a.length,r=n;continue}}n+=1}return i(e.length),t}function je(e){return Ae(e).map((e,t)=>{switch(e.type){case`bold`:return Y(`strong`,{children:e.value},t);case`italic`:return Y(`em`,{children:e.value},t);case`link`:return Y(`a`,{href:e.url,target:`_blank`,rel:`noopener noreferrer`,children:[e.text,Y(`span`,{class:`whoo-widget-sr-only`,children:` (opens in new tab)`})]},t);default:return e.value}})}var Me=[{value:``,label:`Select a reason (optional)`},{value:`factual`,label:`Factual error`},{value:`incomplete`,label:`Incomplete`},{value:`tone`,label:`Off tone`},{value:`other`,label:`Other`}];function Ne({state:e,containerRef:t,onThumbsUp:n,onThumbsDown:r,onSubmitDetailed:i,onCancel:a}){let[o,s]=W(``),[c,l]=W(``),u=K(e.status);if(G(()=>{let n=u.current;if(u.current=e.status,n===e.status)return;let r=t?.current;if(!r)return;let i=r.getRootNode();r.contains(i.activeElement)||r.focus()},[e.status,t]),e.status===`submitted`)return Y(`div`,{class:`whoo-widget-feedback-submitted`,role:`status`,children:`Thanks for the feedback`});if(e.status===`expanded`)return Y(`div`,{class:`whoo-widget-feedback-form`,children:[Y(`label`,{children:[Y(`span`,{class:`whoo-widget-sr-only`,children:`Additional feedback`}),Y(`textarea`,{class:`whoo-widget-feedback-textarea`,value:o,onInput:e=>s(e.target.value),placeholder:`What was wrong? (optional)`})]}),Y(`select`,{class:`whoo-widget-feedback-select`,"aria-label":`Feedback category`,value:c,onChange:e=>l(e.target.value),children:Me.map(e=>Y(`option`,{value:e.value,children:e.label},e.value))}),Y(`div`,{class:`whoo-widget-feedback-form-buttons`,children:[Y(`button`,{type:`button`,class:`whoo-widget-feedback-submit`,onClick:()=>i(o,c),children:`Submit`}),Y(`button`,{type:`button`,class:`whoo-widget-feedback-cancel`,onClick:a,children:`Cancel`})]})]});let d=e.status===`submitting`;return Y(`div`,{class:`whoo-widget-feedback`,children:[Y(`div`,{class:`whoo-widget-feedback-buttons`,children:[Y(`button`,{type:`button`,"aria-label":`Good response`,disabled:d,onClick:n,children:`👍`}),Y(`button`,{type:`button`,"aria-label":`Bad response`,disabled:d,onClick:r,children:`👎`})]}),e.status===`error`?Y(`div`,{class:`whoo-widget-feedback-error`,role:`status`,children:`Couldn't send feedback`}):null]})}function Pe({message:e,feedbackState:t,onThumbsUp:n,onThumbsDown:r,onSubmitDetailed:i,onCancelFeedback:a}){let o=K(null),s=e.id;return Y(`div`,{ref:o,tabIndex:-1,class:`whoo-widget-message whoo-widget-message--${e.role}`,children:[je(e.content),e.role===`assistant`&&s?Y(Ne,{state:t,containerRef:o,onThumbsUp:()=>n(s),onThumbsDown:()=>r(s),onSubmitDetailed:(e,t)=>i(s,e,t),onCancel:()=>a(s)}):null]})}function Fe({messages:e,messageFeedback:t,lastCompletedMessageText:n,onThumbsUp:r,onThumbsDown:i,onSubmitDetailed:a,onCancelFeedback:o}){let s=K(null);return G(()=>{let e=s.current;e&&(e.scrollTop=e.scrollHeight)},[e]),Y(w,{children:[Y(`div`,{class:`whoo-widget-message-list`,ref:s,children:e.map((e,n)=>Y(Pe,{message:e,feedbackState:e.id?t[e.id]??{status:`idle`}:{status:`idle`},onThumbsUp:r,onThumbsDown:i,onSubmitDetailed:a,onCancelFeedback:o},e.id??n))}),Y(`div`,{class:`whoo-widget-sr-only`,"aria-live":`polite`,"aria-atomic":`true`,children:n??``})]})}var Ie=`button:not(:disabled), [href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])`;function Le(e){return Array.from(e.querySelectorAll(Ie)).filter(e=>{let t=getComputedStyle(e);return t.display!==`none`&&t.visibility!==`hidden`})}function Re({state:e,controller:t,greeting:n}){let[r,i]=W(``),a=K(null),o=K(null);G(()=>{o.current?.focus()},[]);let s=e=>{if(e.key===`Escape`){t.close();return}if(e.key!==`Tab`)return;let n=a.current;if(!n)return;let r=Le(n);if(r.length===0)return;let i=r[0],o=r[r.length-1],s=n.getRootNode().activeElement;if(!n.contains(s)){e.preventDefault(),i.focus();return}e.shiftKey&&s===i?(e.preventDefault(),o.focus()):!e.shiftKey&&s===o&&(e.preventDefault(),i.focus())},c=()=>{let n=r.trim();!n||e.isStreaming||(i(``),t.sendMessage(n))},l=e.messages.length===0&&n?[{role:`assistant`,content:n,id:null}]:e.messages;return Y(`div`,{class:`whoo-widget-panel`,role:`dialog`,"aria-modal":`true`,"aria-label":`Chat with Aiden`,ref:a,onKeyDown:s,children:[Y(`div`,{class:`whoo-widget-header`,children:[Y(`button`,{type:`button`,class:`whoo-widget-header-close`,onClick:()=>t.close(),children:`Close chat`}),e.messages.length>0&&Y(`button`,{type:`button`,class:`whoo-widget-header-reset`,onClick:()=>t.resetConversation(),children:`New conversation`})]}),Y(Fe,{messages:l,messageFeedback:e.messageFeedback,lastCompletedMessageText:e.lastCompletedMessageText,onThumbsUp:e=>t.submitFeedback(e,`up`),onThumbsDown:e=>t.expandFeedback(e),onSubmitDetailed:(e,n,r)=>t.submitFeedback(e,`down`,n||void 0,r||void 0),onCancelFeedback:e=>t.cancelFeedback(e)}),e.runtimeError&&Y(De,{message:e.runtimeError}),Y(Ee,{value:r,onChange:i,onSend:c,disabled:e.isStreaming,inputRef:o})]})}function ze({controller:e}){let[t,n]=W(e.getState()),r=K(null),i=K(t.isOpen);return G(()=>(n(e.getState()),e.subscribe(n)),[e]),G(()=>{i.current&&!t.isOpen&&r.current?.focus(),i.current=t.isOpen},[t.isOpen]),t.initError?null:Y(w,{children:[Y(Te,{isOpen:t.isOpen,onClick:()=>t.isOpen?e.close():e.open(),logoUrl:e.config.logoUrl,buttonRef:r}),t.isOpen&&Y(Re,{state:t,controller:e,greeting:e.config.greeting})]})}var Be=class extends Error{status;constructor(e,t){super(e),this.name=`WidgetApiError`,this.status=t}};async function Ve(e,t){let n=await fetch(`${e}/api/v1/widget/session`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({publishable_key:t})});if(!n.ok)throw new Be(`Failed to create widget session (${n.status})`,n.status);let r=await n.json();return{token:r.token,primaryColor:r.primary_color??null}}async function He(e,t,n){let r=await fetch(`${e}/api/v1/widget/feedback`,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${t}`},body:JSON.stringify({message_id:n.messageId,rating:n.rating,correction:n.correction??null,category:n.category??null})});if(!r.ok)throw new Be(`Feedback submission failed (${r.status})`,r.status)}async function*Ue(e,t,n,r,i){let a=await fetch(`${e}/api/v1/widget/chat`,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${t}`},body:JSON.stringify({conversation_id:n,message:r}),signal:i});if(!a.ok||!a.body)throw new Be(`Chat request failed (${a.status})`,a.status);let o=a.body.getReader(),s=new TextDecoder,c=``;for(;;){let{done:e,value:t}=await o.read();if(e)break;c+=s.decode(t,{stream:!0});let n=c.indexOf(`
2
+
3
+ `);for(;n!==-1;){let e=c.slice(0,n);c=c.slice(n+2);let t=e.split(`
4
+ `).find(e=>e.startsWith(`data: `));t&&(yield We(JSON.parse(t.slice(6)))),n=c.indexOf(`
5
+
6
+ `)}}}function We(e){return typeof e.error==`string`?{type:`error`,error:e.error,conversationId:e.conversation_id}:e.done===!0?{type:`done`,conversationId:e.conversation_id,messageId:e.message_id??null}:typeof e.delta==`string`?e.verbatim===!0?{type:`delta`,delta:e.delta,verbatim:!0}:{type:`delta`,delta:e.delta}:{type:`conversation_id`,conversationId:e.conversation_id}}var Ge=40,Ke=120;function qe(e){let t=[];for(let n of Ae(e))if(n.type===`text`){let e=n.value.split(/(\s+)/),r=n.start;for(let n of e)r+=n.length,n.trim().length>0&&t.push(r)}else t.push(n.end);return t}function Je(e){let t=null;return e?.addEventListener(`abort`,()=>{t?.()},{once:!0}),function(e){return new Promise(n=>{let r=setTimeout(()=>{t=null,n()},e);t=()=>{clearTimeout(r),t=null,n()}})}}async function Ye(e,t,n){let r=n?.wordIntervalMs??Ge,i=n?.tokenIntervalMs??Ke,a=n?.signal,o=Ae(e),s=new Set(o.filter(e=>e.type!==`text`).map(e=>e.end)),c=Je(a),l=``;for(let n of qe(e))if(a?.aborted||(l=e.slice(0,n),t(l),await c(s.has(n)?i:r),a?.aborted))return;l!==e&&t(e)}function Xe(e){return`whoo-aiden-widget:conversation:${e}`}function Ze(e){try{return window.sessionStorage.getItem(Xe(e))}catch{return null}}function Qe(e,t){try{window.sessionStorage.setItem(Xe(e),t)}catch{}}function $e(e){try{window.sessionStorage.removeItem(Xe(e))}catch{}}var et=class{config;state;listeners=new Set;sessionToken=null;conversationId=null;storageIdentifier;activeRequestController=null;prefersReducedMotion;constructor(e){this.config=e,this.storageIdentifier=e.publishableKey??`session-token-mode`,this.prefersReducedMotion=typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches,this.state={isOpen:!1,messages:[],isStreaming:!1,initError:null,runtimeError:null,messageFeedback:{},primaryColor:null,lastCompletedMessageText:null}}getState(){return this.state}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}setState(e){this.state={...this.state,...e};for(let e of this.listeners)e(this.state)}async init(){let e=this.config.apiBaseUrl??`https://api.aidenvoice.com`;try{if(this.config.sessionToken)this.sessionToken=this.config.sessionToken;else if(this.config.publishableKey){let{token:t,primaryColor:n}=await Ve(e,this.config.publishableKey);this.sessionToken=t,this.setState({primaryColor:n})}else throw Error(`WidgetConfig must include exactly one of publishableKey or sessionToken`)}catch(e){console.error(`[whoo-aiden-widget] initialization failed:`,e),this.setState({initError:e instanceof Error?e.message:String(e)});return}this.conversationId=Ze(this.storageIdentifier)}open(){this.setState({isOpen:!0})}resetConversation(){this.activeRequestController?.abort(),this.activeRequestController=null,this.conversationId=null,$e(this.storageIdentifier),this.setState({messages:[],messageFeedback:{},runtimeError:null,isStreaming:!1,lastCompletedMessageText:null})}close(){this.activeRequestController?.abort(),this.activeRequestController=null,this.setState({isOpen:!1})}async sendMessage(e){if(!this.sessionToken)return;let t=this.config.apiBaseUrl??`https://api.aidenvoice.com`,n={role:`user`,content:e,id:null},r={role:`assistant`,content:``,id:null};this.setState({messages:[...this.state.messages,n,r],isStreaming:!0,runtimeError:null});let i=e=>{let t=[...this.state.messages];t[t.length-1]={...t[t.length-1],content:e},this.setState({messages:t})};this.activeRequestController=new AbortController;let{signal:a}=this.activeRequestController,o=``;try{for await(let n of Ue(t,this.sessionToken,this.conversationId,e,a))if(n.type===`conversation_id`)this.conversationId=n.conversationId,Qe(this.storageIdentifier,n.conversationId);else if(n.type===`delta`)n.verbatim&&!this.prefersReducedMotion?(await Ye(n.delta,e=>i(e),{signal:a}),o=n.delta):n.verbatim?(o=n.delta,i(o)):(o+=n.delta,i(o));else if(n.type===`done`){if(n.conversationId&&(this.conversationId=n.conversationId,Qe(this.storageIdentifier,n.conversationId)),n.messageId){let e=[...this.state.messages];e[e.length-1]={...e[e.length-1],id:n.messageId},this.setState({messages:e})}this.setState({lastCompletedMessageText:o})}else n.type===`error`&&(console.error(`[whoo-aiden-widget] chat error:`,n.error),this.setState({runtimeError:`Something went wrong, please try again.`}))}catch(e){e?.name===`AbortError`||(console.error(`[whoo-aiden-widget] chat request failed:`,e),this.setState({runtimeError:`Something went wrong, please try again.`}))}finally{this.activeRequestController=null,this.setState({isStreaming:!1})}}expandFeedback(e){this.setState({messageFeedback:{...this.state.messageFeedback,[e]:{status:`expanded`}}})}cancelFeedback(e){this.setState({messageFeedback:{...this.state.messageFeedback,[e]:{status:`idle`}}})}async submitFeedback(e,t,n,r){if(!this.sessionToken)return;let i=this.config.apiBaseUrl??`https://api.aidenvoice.com`;this.setState({messageFeedback:{...this.state.messageFeedback,[e]:{status:`submitting`,rating:t}}});try{await He(i,this.sessionToken,{messageId:e,rating:t,correction:n,category:r}),this.setState({messageFeedback:{...this.state.messageFeedback,[e]:{status:`submitted`,rating:t}}})}catch(n){console.error(`[whoo-aiden-widget] feedback submission failed:`,n),this.setState({messageFeedback:{...this.state.messageFeedback,[e]:{status:`error`,rating:t}}})}}},tt=`:host,.whoo-widget-root{all:initial;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.whoo-widget-launcher{bottom:20px;left:var(--whoo-widget-inset-left,auto);right:var(--whoo-widget-inset-right,20px);background:var(--whoo-widget-color,#4f46e5);width:56px;height:56px;color:var(--whoo-widget-text-on-color,#fff);cursor:pointer;border:none;border-radius:50%;justify-content:center;align-items:center;padding:0;font-size:24px;display:flex;position:fixed;box-shadow:0 2px 8px #0003}.whoo-widget-launcher:focus-visible{box-shadow:0 2px 8px #0003,0 0 0 4px #fff}.whoo-widget-launcher-logo{object-fit:cover;border-radius:50%;width:32px;height:32px}.whoo-widget-panel{bottom:88px;left:var(--whoo-widget-inset-left,auto);right:var(--whoo-widget-inset-right,20px);background:#fff;border-radius:12px;flex-direction:column;width:360px;height:480px;max-height:90vh;display:flex;position:fixed;overflow:hidden;box-shadow:0 4px 16px #0003}.whoo-widget-header{flex-shrink:0;justify-content:space-between;align-items:center;padding:8px 12px 0;display:flex}.whoo-widget-header-close,.whoo-widget-header-reset{color:var(--whoo-widget-color,#4f46e5);cursor:pointer;background:0 0;border:none;padding:4px 6px;font-size:12px}.whoo-widget-message-list{flex:1;min-height:100px;padding:12px;overflow-y:auto}.whoo-widget-message{border-radius:8px;max-width:80%;margin-bottom:8px;padding:8px 12px}.whoo-widget-message--user{background:var(--whoo-widget-color,#4f46e5);color:var(--whoo-widget-text-on-color,#fff);margin-left:auto}.whoo-widget-message--assistant{color:#111;background:#f0f0f0}.whoo-widget-error-banner{color:#991b1b;background:#fee2e2;padding:8px 12px;font-size:13px}.whoo-widget-composer{border-top:1px solid #e5e5e5;gap:8px;padding:12px;display:flex}.whoo-widget-composer-input{border:1px solid #6b7280;border-radius:6px;flex:1;padding:8px}.whoo-widget-composer-send{background:var(--whoo-widget-color,#4f46e5);color:var(--whoo-widget-text-on-color,#fff);cursor:pointer;border:none;border-radius:6px;padding:8px 16px}.whoo-widget-composer-send:disabled,.whoo-widget-composer-input[aria-disabled=true]{opacity:.6;cursor:not-allowed}.whoo-widget-feedback{align-items:center;gap:6px;margin-top:4px;display:flex}.whoo-widget-feedback-buttons{gap:6px;display:flex}.whoo-widget-feedback-buttons button{cursor:pointer;opacity:.6;background:0 0;border:none;padding:2px 4px;font-size:14px}.whoo-widget-feedback-buttons button:disabled{opacity:.3;cursor:not-allowed}.whoo-widget-feedback-form{flex-direction:column;gap:6px;margin-top:6px;display:flex}.whoo-widget-feedback-textarea{box-sizing:border-box;resize:vertical;border:1px solid #6b7280;border-radius:6px;width:100%;padding:6px;font-size:12px}.whoo-widget-feedback-select{border:1px solid #6b7280;border-radius:6px;padding:4px;font-size:12px}.whoo-widget-feedback-form-buttons{gap:6px;display:flex}.whoo-widget-feedback-submit{background:var(--whoo-widget-color,#4f46e5);color:var(--whoo-widget-text-on-color,#fff);cursor:pointer;border:none;border-radius:6px;padding:8px 16px;font-size:13px}.whoo-widget-feedback-cancel{color:#111;cursor:pointer;background:#fff;border:1px solid #6b7280;border-radius:6px;padding:8px 16px;font-size:13px}.whoo-widget-feedback-submitted{color:#6b7280;margin-top:4px;font-size:12px}.whoo-widget-feedback-error{color:#991b1b;font-size:12px}.whoo-widget-sr-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible,a:focus-visible{outline-offset:2px;outline:2px solid #111827;box-shadow:0 0 0 4px #fff}`;function nt(e){let t=document.createElement(`div`);if(t.style.color=e,t.style.color===``)return null;document.body.appendChild(t);let n=getComputedStyle(t).color;document.body.removeChild(t);let r=n.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);return r?[Number(r[1]),Number(r[2]),Number(r[3])]:null}function rt([e,t,n]){let r=e=>{let t=e/255;return t<=.03928?t/12.92:((t+.055)/1.055)**2.4};return .2126*r(e)+.7152*r(t)+.0722*r(n)}function it(e,t){let n=Math.max(e,t),r=Math.min(e,t);return(n+.05)/(r+.05)}function at(e){let t=nt(e);if(!t)return console.warn(`[whoo-aiden-widget] Could not parse color "${e}" for contrast calculation; defaulting to white text.`),`#ffffff`;let n=rt(t);return it(n,rt([0,0,0]))>=it(n,rt([255,255,255]))?`#000000`:`#ffffff`}function ot(e,t){let n=t.color??`#4f46e5`;e.style.setProperty(`--whoo-widget-color`,n),e.style.setProperty(`--whoo-widget-text-on-color`,at(n));let r=t.position===`bottom-left`;e.style.setProperty(`--whoo-widget-inset-left`,r?`20px`:`auto`),e.style.setProperty(`--whoo-widget-inset-right`,r?`auto`:`20px`)}function st(e){let t=document.createElement(`div`);t.id=`whoo-aiden-widget-host`;let n=t.attachShadow({mode:`open`}),r=document.createElement(`style`);r.textContent=tt,n.appendChild(r);let i=document.createElement(`div`);n.appendChild(i),ot(i,e);let a=new et(e);return a.subscribe(e=>{e.primaryColor&&(i.style.setProperty(`--whoo-widget-color`,e.primaryColor),i.style.setProperty(`--whoo-widget-text-on-color`,at(e.primaryColor)))}),ce(Y(ze,{controller:a}),i),a.init(),document.body.appendChild(t),{unmount(){ce(null,i),t.remove()}}}function ct(e){return t(e),st(e)}return e.init=ct,e})({});