bridgeapp-ai-chat-widget 0.1.6 → 0.2.6

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.
Files changed (31) hide show
  1. package/README.md +243 -25
  2. package/dist/App.d.ts +5 -3
  3. package/dist/api/messages.api.d.ts +2 -2
  4. package/dist/api/{api.types.d.ts → messages.api.types.d.ts} +1 -1
  5. package/dist/api/voice.api.d.ts +4 -29
  6. package/dist/api/voice.api.types.d.ts +37 -0
  7. package/dist/avatar/AnimationManager.d.ts +2 -2
  8. package/dist/avatar/Avatar.d.ts +6 -2
  9. package/dist/bridge-ai-chat-widget.es.js +9578 -9283
  10. package/dist/bridge-ai-chat-widget.umd.js +224 -224
  11. package/dist/context/ChatWidgetContext.d.ts +24 -0
  12. package/dist/domain/message/cable/AnyCable.service.d.ts +0 -3
  13. package/dist/domain/message/cable/CableContext.d.ts +2 -2
  14. package/dist/domain/message/message.store.d.ts +23 -4
  15. package/dist/domain/voice/voice.store.d.ts +18 -7
  16. package/dist/index.d.ts +29 -4
  17. package/dist/index.types.d.ts +4 -14
  18. package/dist/lib/logger.d.ts +4 -4
  19. package/dist/lib/retrieveContent.d.ts +1 -1
  20. package/dist/react/mount.d.ts +1 -3
  21. package/dist/types/index.d.ts +2 -0
  22. package/dist/ui/components/ChatFooter.d.ts +1 -1
  23. package/dist/ui/components/ChatListItem.d.ts +1 -1
  24. package/dist/ui/components/ChatMessage.d.ts +4 -4
  25. package/dist/ui/components/ChatWidget.d.ts +2 -29
  26. package/dist/ui/components/VoiceMessage.d.ts +4 -4
  27. package/dist/ui/components/overlays/ChatBlurOverlayBottom.d.ts +1 -1
  28. package/dist/ui/components/overlays/ChatBlurOverlayTop.d.ts +1 -1
  29. package/dist/ui/components/overlays/ChatDarkOverlay.d.ts +1 -1
  30. package/package.json +17 -14
  31. /package/dist/api/{notifySessionConflictOn409.d.ts → messages.api.utils.d.ts} +0 -0
package/README.md CHANGED
@@ -1,47 +1,265 @@
1
1
  # Bridge AI Chat Widget
2
2
 
3
- Embeddable AI chat widget for web applications.
3
+ Embeddable React + TypeScript chat widget for Bridge AI agents, with:
4
4
 
5
- ## Installation
5
+ - text chat
6
+ - optional real-time voice mode (WebRTC)
7
+ - optional animated 3D avatar (three.js)
8
+ - WebSocket live message updates (AnyCable)
9
+ - Shadow DOM isolation for host-page style safety
6
10
 
7
- ```bash
8
- npm install bridge-ai-chat-widget
11
+ ## What This Project Is
12
+
13
+ `bridgeapp-ai-chat-widget` is a library bundle (ESM + UMD) that host applications instantiate via:
14
+
15
+ - `new ChatWidgetInstance(config)`
16
+
17
+ The instance mounts a React app into a host element's Shadow DOM, fetches static asset mappings from a remote manifest, initializes chat/voice/avatar modules, and exposes a small integration API (`setOpen`, `destroy`, `setCustomerInteractionSession`, events).
18
+
19
+ ## Tech Stack
20
+
21
+ - React 19
22
+ - TypeScript
23
+ - Vite (dev app + library build)
24
+ - Tailwind CSS 4
25
+ - Zustand (state stores)
26
+ - three.js + FBX assets (avatar rendering/animation)
27
+ - AnyCable Web client (live chat transport)
28
+ - WebRTC + data channels (voice mode)
29
+
30
+ ## Repository Layout
31
+
32
+ High-level structure:
33
+
34
+ - `src/index.ts` - public library entry (`ChatWidgetInstance`)
35
+ - `src/index.types.ts` - public config/types surface
36
+ - `src/App.tsx` - provider composition root
37
+ - `src/react/mount.tsx` - React root mount helper
38
+ - `src/ui/components` - UI components (chat widget, routes, controls)
39
+ - `src/domain/message` - message store + websocket channel integration
40
+ - `src/domain/voice` - voice lifecycle + WebRTC state
41
+ - `src/avatar` - three.js avatar scene, animation manager, blend shapes
42
+ - `src/api` - HTTP clients for messages/voice/session-related calls
43
+ - `src/context/ChatWidgetContext.tsx` - widget-level UI state/context
44
+ - `src/dev` - local/dev/demo stand bootstrap (non-library integration path)
45
+ - `src/assets` - local assets used by the widget and avatar (not used directly, rather this whole folder is getting pushed to assets repo)
46
+ - `scripts` - utility scripts (for example asset sync to remote static repo)
47
+
48
+ ## Runtime Architecture
49
+
50
+ ### 1) Host Integration Layer
51
+
52
+ `ChatWidgetInstance` (`src/index.ts`) is the host-facing API:
53
+
54
+ - validates `mountElementId`
55
+ - creates `shadowRoot` on mount element
56
+ - injects bundled CSS into shadow root
57
+ - fetches `${ASSETS_URL}/manifest.json`
58
+ - resolves hashed asset paths via `resolveAssetPath(path)`
59
+ - optionally merges remote config (`remoteConfig`)
60
+ - mounts React app and wires `AppApi` callbacks/events
61
+
62
+ ### 2) React App + Providers
63
+
64
+ `App` composes:
65
+
66
+ - `ChatWidgetContextProvider` (widget open state, sizing, config, loading progress)
67
+ - `CableProvider` (AnyCable websocket service + network status)
68
+ - `HashRouter` (voice/text/list routes)
69
+
70
+ ### 3) Messaging Domain
71
+
72
+ `useMessageStore` handles:
73
+
74
+ - initial thread bootstrap (`ListMessages`, `ListMessagesForThreads`)
75
+ - optimistic and server-driven message updates
76
+ - thread map and per-thread message derivation
77
+ - message send (`CreateMessage`)
78
+ - agentic steps merge/update and rendering support
79
+
80
+ Live updates arrive via AnyCable channel events and are pushed into the store.
81
+
82
+ ### 4) Voice Domain
83
+
84
+ `useVoiceStore` manages full voice call lifecycle:
85
+
86
+ - starts voice session via API (`StartVoiceSession`)
87
+ - builds RTCPeerConnection using TURN credentials from session payload
88
+ - sends SDP offer / receives answer
89
+ - buffers + sends ICE candidates
90
+ - consumes voice data channels (`voice-events`, `voice-input`)
91
+ - streams remote audio to an `Audio` element
92
+ - handles mic enable/disable, device switching, and teardown (`EndSession` + `EndVoiceSession`)
93
+
94
+ ### 5) Avatar Domain
95
+
96
+ `Avatar` + `AnimationManager`:
97
+
98
+ - initializes three.js renderer/camera/lights/materials
99
+ - loads model + texture + animation assets from resolved remote paths
100
+ - emits `loading_progress`
101
+ - starts animation mixer loops
102
+ - maps viseme timeline to morph target animation during speech
103
+ - exposes external animation actions (`excited`, `fix_hair`, `spin`, `kiss`)
104
+
105
+ When all assets load, widget emits `avatar_ready` through `ChatWidgetInstance`.
106
+
107
+ ## Public API
108
+
109
+ Import:
110
+
111
+ ```ts
112
+ import { ChatWidgetInstance, type WidgetConfig } from "bridgeapp-ai-chat-widget";
9
113
  ```
10
114
 
11
- ## Usage
115
+ ### `ExternalWidgetConfig`
116
+
117
+ - `mountElementId: string`
118
+ - - `configName: string` - remote config, lives at the same place as other static assets
119
+ - `customerInteractionSession?: { accessToken; chatId; expiresAt }` - if there's no session (separate case for dev/demo mode) - we look for customerId in remote config and create session ourselves.
120
+
121
+ ### `RemoteWidgetConfig`
122
+
123
+ Actual widget config
124
+
125
+ - `agentId: string`
126
+ - `customerId?: string`
127
+ - `wsUrl: string`
128
+ - `apiUrl: string`
129
+ - `AIAvatar?: boolean` - is 3D avatar be used
130
+ - `voiceEnabled?: boolean` - is voice mode enebled
131
+ - `background?: boolean` - clear or visible background
132
+ - `footer?: boolean` - is footer rendered
133
+ - `debug?: boolean` (avatar debug GUI)
134
+ - `manualOpen?: boolean` - is widget trigger rendered or should it be open only programatically
135
+
136
+ ### Instance Methods
137
+
138
+ - `setOpen(open: boolean): void` - open/close widget
139
+ - `setCustomerInteractionSession(session): void` - update session after initialization (on session expire)
140
+ - `playAnimation(name): void`
141
+ - `destroy(): void`
142
+
143
+ ### Events
144
+
145
+ - `avatar_ready` - emitted once avatar assets are fully loaded
146
+ - `renew_session` - emitted when session is expired or near expiry
147
+
148
+ ## Host Usage Example
12
149
 
13
- ```javascript
14
- import { ChatWidgetInstance } from "bridge-ai-chat-widget";
150
+ ```ts
151
+ import { ChatWidgetInstance } from "bridgeapp-ai-chat-widget";
15
152
 
16
153
  const widget = new ChatWidgetInstance({
17
154
  mountElementId: "chat-widget-root",
18
- agentId: "222d810a-92dd-4bb4-9ce6-1fee92238e7f",
19
- customerInteractionSession: {
20
- accessToken: "ACCESS_TOKEN",
21
- chatId: "CHAT_ID",
22
- expiresAt: new Date(Date.now() + 86400000).toISOString(),
23
- },
24
- assetsUrl: "https://metamediastatic.com",
25
- wsUrl: "wss://ws.brid93.com/ws",
26
- apiUrl: "https://api.brid93.com",
155
+ customerInteractionSession: CustomerInteractionSession,
27
156
  AIAvatar: true,
28
157
  background: false,
29
158
  voiceEnabled: true,
30
159
  footer: false,
31
- manualOpen: false
160
+ manualOpen: true,
32
161
  });
33
162
 
34
163
  widget.addEventListener("avatar_ready", () => {
35
- widget.setOpen(true)
164
+ widget.setOpen(true);
36
165
  });
37
166
 
38
- //this event is thrown either when session is already expired or about to be expired
39
- widget.addEventListener("renew_session", () => {
40
- const session = //... aquire session
41
- widget.setCustomerInteractionSession(session)
167
+ widget.addEventListener("renew_session", async () => {
168
+ const newSession = await fetchNewSessionSomehow();
169
+ widget.setCustomerInteractionSession(newSession);
42
170
  });
43
171
  ```
44
172
 
45
- ```javascript
46
- widget.destroy()
47
- ```
173
+ Teardown:
174
+
175
+ ```ts
176
+ widget.destroy();
177
+ ```
178
+
179
+ ## Local Development
180
+
181
+ Install:
182
+
183
+ ```bash
184
+ npm install
185
+ ```
186
+
187
+ Start dev server:
188
+
189
+ ```bash
190
+ npm run dev
191
+ ```
192
+
193
+ Notes:
194
+
195
+ - Vite dev server runs on `5179` by default.
196
+ - `src/dev/main.tsx` is the local bootstrap path (creates session and mounts widget directly).
197
+ - `vite.config.ts` is for app/dev runtime.
198
+ - `vite.config.lib.ts` is for distributable library build.
199
+
200
+ ## Build, Publish, and Deployment
201
+
202
+ ### Build local dev app
203
+
204
+ ```bash
205
+ npm run build-dev
206
+ ```
207
+
208
+ ### Build library artifacts
209
+
210
+ ```bash
211
+ npm run build-lib
212
+ ```
213
+
214
+ Outputs include:
215
+
216
+ - `dist/bridge-ai-chat-widget.es.js`
217
+ - `dist/bridge-ai-chat-widget.umd.js`
218
+ - `dist/index.types.d.ts`
219
+
220
+ ### Publish flow
221
+
222
+ - `prepublishOnly` runs `build-lib`
223
+ - package exports are defined in `package.json` under `"exports"`
224
+
225
+ ### Other project scripts
226
+
227
+ - `npm run lint`
228
+ - `npm run lint-fix`
229
+ - `npm run format`
230
+ - `npm run format:check`
231
+ - deployment scripts for S3 buckets (project-specific, to be reworked)
232
+ - `npm run push-assets-metamediastatic` (sync `src/assets` into remote static assets repo)
233
+
234
+ ## Session Handling
235
+
236
+ Widget relies on `customerInteractionSession`:
237
+
238
+ - used as Bearer token for HTTP APIs
239
+ - used in websocket query for AnyCable auth
240
+ - periodically checked by `useSessionExpiryChecks`
241
+
242
+ When session is near expiry or invalid, `renew_session` event is emitted; host app must provide a fresh session via `setCustomerInteractionSession`.
243
+
244
+ ## Asset Pipeline
245
+
246
+ Runtime asset resolution:
247
+
248
+ 1. fetch remote manifest from `https://metamediastatic.com/manifest.json`
249
+ 2. map logical path (for example `animations/idle_1.fbx`) to hashed URL
250
+ 3. load via `resolveAssetPath`
251
+
252
+ This allows cache-friendly hashed asset delivery without changing code references.
253
+
254
+ ## Troubleshooting
255
+
256
+ - Widget does not mount:
257
+ - verify mount element exists and `mountElementId` is correct
258
+ - No messages or send failures:
259
+ - verify `apiUrl`, `accessToken`, and `chatId`
260
+ - No live updates:
261
+ - verify `wsUrl`, websocket reachability, and token validity
262
+ - Voice fails to start:
263
+ - verify mic permission, TURN reachability, and voice API endpoints
264
+ - Avatar never becomes ready:
265
+ - verify manifest URL + asset availability under static host
package/dist/App.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import type { WidgetConfig } from "./index.types";
2
- import { type OnAvatarReadyCB } from "./react/mount";
1
+ import type { OnAvatarReady } from "./types";
2
+ import type { WidgetConfig } from ".";
3
+ import type { ExternallyAvailableActions } from "./avatar/Avatar";
3
4
  export type AppApi = {
4
- onAvatarReady: OnAvatarReadyCB;
5
+ onAvatarReady: OnAvatarReady;
5
6
  onSessionExpired: () => void;
6
7
  registerSetOpen: (fn: ((open: boolean) => void) | null) => void;
7
8
  resolveAssetPath: (path: string) => string;
9
+ registerSendServiceMessage: ((fn: ((message: string, animationName: ExternallyAvailableActions) => void) | null) => void);
8
10
  };
9
11
  type AppProps = {
10
12
  config: WidgetConfig;
@@ -1,6 +1,6 @@
1
- import type { MessageData } from "./api.types";
2
- import type { WidgetConfig } from "@/index.types";
1
+ import type { MessageData } from "./messages.api.types";
3
2
  import type { AppApi } from "@/App";
3
+ import type { WidgetConfig } from "..";
4
4
  export declare const MAX_FETCH_MESSAGES = 100;
5
5
  export type PaginationParams = {
6
6
  beforeMessageId?: string;
@@ -30,7 +30,7 @@ export type MessageData = {
30
30
  status: MessageStatus;
31
31
  agenticSteps: AgenticStep[];
32
32
  content: MessageContent;
33
- data: {};
33
+ data: object;
34
34
  threadId?: string;
35
35
  threadMessagesCount: number;
36
36
  inReplyToMessageId: string | null;
@@ -1,33 +1,7 @@
1
- import type { ChatParticipant } from "./api.types";
2
- import type { WidgetConfig } from "@/index.types";
1
+ import type { ChatParticipant } from "./messages.api.types";
3
2
  import type { AppApi } from "@/App";
4
- type StartVoiceSessionResponse = {
5
- status: number;
6
- json: VoiceSessionData | ApiErrorResponse;
7
- };
8
- export type ApiErrorResponse = {
9
- code: string;
10
- message: string;
11
- };
12
- export type VoiceSessionData = {
13
- session: {
14
- id: string;
15
- companyId: string;
16
- agentId: string;
17
- chatId: string;
18
- participant: ChatParticipant;
19
- };
20
- sessionToken: string;
21
- voiceProtocolVersion: number;
22
- voiceBaseUrl: string;
23
- voiceTurn: {
24
- urls: string[];
25
- username: string;
26
- useSessionTokenCredential: boolean;
27
- realm: string;
28
- credentialExpiresAt: string;
29
- };
30
- };
3
+ import type { GetTurnBootstrapPayload, StartVoiceSessionResponse, VoiceSessionData, VoiceTurn } from "./voice.api.types";
4
+ import type { WidgetConfig } from "..";
31
5
  export declare const startVoiceSession: (config: WidgetConfig, api: AppApi) => Promise<StartVoiceSessionResponse>;
32
6
  export declare function getVoiceSession(config: WidgetConfig, api: AppApi, id: string): Promise<VoiceSessionData>;
33
7
  type VoiceSessionStatus = "VOICE_SESSION_STATUS_ENDED" | "VOICE_SESSION_STATUS_ACTIVE";
@@ -87,4 +61,5 @@ type EndWebRtcSessionPayload = {
87
61
  reason?: string;
88
62
  };
89
63
  export declare function endWebRtcSession({ voiceBaseUrl, voiceSessionId, sessionToken, clientInstanceId, connectionId, connectionGeneration, reason, }: EndWebRtcSessionPayload): Promise<void>;
64
+ export declare function getTurnBootstrap({ voiceBaseUrl, voiceSessionId, sessionToken, clientInstanceId, protocolVersion, }: GetTurnBootstrapPayload): Promise<VoiceTurn>;
90
65
  export {};
@@ -0,0 +1,37 @@
1
+ import type { ChatParticipant } from "./messages.api.types";
2
+ export type StartVoiceSessionResponse = {
3
+ status: number;
4
+ json: VoiceSessionData | ApiErrorResponse;
5
+ };
6
+ export type ApiErrorResponse = {
7
+ code: string;
8
+ message: string;
9
+ };
10
+ export type VoiceSessionData = {
11
+ session: {
12
+ id: string;
13
+ companyId: string;
14
+ agentId: string;
15
+ chatId: string;
16
+ participant: ChatParticipant;
17
+ };
18
+ sessionToken: string;
19
+ voiceProtocolVersion: number;
20
+ voiceBaseUrl: string;
21
+ voiceTurn?: VoiceTurn;
22
+ };
23
+ export type GetTurnBootstrapPayload = {
24
+ voiceBaseUrl: string;
25
+ voiceSessionId: string;
26
+ sessionToken: string;
27
+ clientInstanceId: string;
28
+ protocolVersion: number;
29
+ };
30
+ export type VoiceTurn = {
31
+ urls: string[];
32
+ username: string;
33
+ credential: string;
34
+ credentialExpiresAt: string;
35
+ realm: string;
36
+ useSessionTokenCredential: boolean;
37
+ };
@@ -6,12 +6,12 @@ export type BlendShapeKey = (typeof BLEND_SHAPE_KEYS)[number];
6
6
  export default class AnimationManager {
7
7
  private model;
8
8
  private avatar;
9
+ activeActionName: string;
9
10
  private animations;
10
11
  private zeroMorphingInfluences;
11
12
  private visemeDataIndex;
12
13
  private visemeData;
13
14
  private mixer;
14
- private activeActionName;
15
15
  private actions;
16
16
  private loadingManager;
17
17
  private fbxLoader;
@@ -24,7 +24,7 @@ export default class AnimationManager {
24
24
  private onLoadAnimation;
25
25
  private fadeDuration;
26
26
  fadeToAction(name: string): void;
27
- reset(): void;
27
+ resetVisemeData(): void;
28
28
  setVisemeData(): void;
29
29
  convertVisemeDataToAnimationScenario(): void;
30
30
  update(delta: number, timePassed?: number): void;
@@ -1,7 +1,7 @@
1
1
  import { Mesh, Vector3 } from "three";
2
2
  import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js";
3
- import type { WidgetConfig } from "@/index.types";
4
3
  import type { AppApi } from "@/App";
4
+ import type { WidgetConfig } from "..";
5
5
  export interface VisemeData {
6
6
  viseme: Viseme;
7
7
  time: number;
@@ -12,7 +12,7 @@ type CameraParams = {
12
12
  lookAt: Vector3;
13
13
  fov: number;
14
14
  };
15
- export type ExternallyAvailableActions = 'excited' | 'fix_hair' | 'spin' | 'kiss';
15
+ export type ExternallyAvailableActions = "excited" | "fix_hair" | "spin" | "kiss";
16
16
  export declare class Avatar extends EventTarget {
17
17
  private canvas;
18
18
  private videoContainer;
@@ -44,6 +44,10 @@ export declare class Avatar extends EventTarget {
44
44
  private height;
45
45
  onResize: (width?: number, height?: number) => void;
46
46
  animate: () => void;
47
+ private SPEAKING_ANIMATION_NAME_1;
48
+ private SPEAKING_ANIMATION_NAME_2;
49
+ private IDLE_ANIMATION_NAME_1;
50
+ private IDLE_ANIMATION_NAME_2;
47
51
  private speakingAnimationName;
48
52
  private idleAnimationName;
49
53
  setTalking(talking: boolean): void;