bridgeapp-ai-chat-widget
v0.2.12
Published
Embeddable chat widget for communication with Bridge AI agent
Downloads
1,734
Readme
Bridge AI Chat Widget
Embeddable React + TypeScript chat widget for Bridge AI agents, with:
- text chat
- optional real-time voice mode (WebRTC)
- optional animated 3D avatar (three.js)
- WebSocket live message updates (AnyCable)
- Shadow DOM isolation for host-page style safety
What This Project Is
bridgeapp-ai-chat-widget is a library bundle (ESM + UMD) that host applications instantiate via:
new ChatWidgetInstance(config)
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).
Tech Stack
- React 19
- TypeScript
- Vite (dev app + library build)
- Tailwind CSS 4
- Zustand (state stores)
- three.js + FBX assets (avatar rendering/animation)
- AnyCable Web client (live chat transport)
- WebRTC + data channels (voice mode)
Repository Layout
High-level structure:
src/index.ts- public library entry (ChatWidgetInstance)src/index.types.ts- public config/types surfacesrc/App.tsx- provider composition rootsrc/react/mount.tsx- React root mount helpersrc/ui/components- UI components (chat widget, routes, controls)src/domain/message- message store + websocket channel integrationsrc/domain/voice- voice lifecycle + WebRTC statesrc/avatar- three.js avatar scene, animation manager, blend shapessrc/api- HTTP clients for messages/voice/session-related callssrc/context/ChatWidgetContext.tsx- widget-level UI state/contextsrc/dev- local/dev/demo stand bootstrap (non-library integration path)src/assets- local assets used by the widget and avatar (not used directly, rather this whole folder is getting pushed to assets repo)scripts- utility scripts (for example asset sync to remote static repo)
Runtime Architecture
1) Host Integration Layer
ChatWidgetInstance (src/index.ts) is the host-facing API:
- validates
mountElementId - creates
shadowRooton mount element - injects bundled CSS into shadow root
- fetches
${ASSETS_URL}/manifest.json - resolves hashed asset paths via
resolveAssetPath(path) - optionally merges remote config (
remoteConfig) - mounts React app and wires
AppApicallbacks/events
2) React App + Providers
App composes:
ChatWidgetContextProvider(widget open state, sizing, config, loading progress)CableProvider(AnyCable websocket service + network status)HashRouter(voice/text/list routes)
3) Messaging Domain
useMessageStore handles:
- initial thread bootstrap (
ListMessages,ListMessagesForThreads) - optimistic and server-driven message updates
- thread map and per-thread message derivation
- message send (
CreateMessage) - agentic steps merge/update and rendering support
Live updates arrive via AnyCable channel events and are pushed into the store.
4) Voice Domain
useVoiceStore manages full voice call lifecycle:
- starts voice session via API (
StartVoiceSession) - builds RTCPeerConnection using TURN credentials from session payload
- sends SDP offer / receives answer
- buffers + sends ICE candidates
- consumes voice data channels (
voice-events,voice-input) - streams remote audio to an
Audioelement - handles mic enable/disable, device switching, and teardown (
EndSession+EndVoiceSession)
5) Avatar Domain
Avatar + AnimationManager:
- initializes three.js renderer/camera/lights/materials
- loads model + texture + animation assets from resolved remote paths
- emits
loading_progress - starts animation mixer loops
- maps viseme timeline to morph target animation during speech
- exposes external animation actions (
excited,fix_hair,spin,kiss)
When all assets load, widget emits avatar_ready through ChatWidgetInstance.
Public API
Import:
import { ChatWidgetInstance, type WidgetConfig } from "bridgeapp-ai-chat-widget";ExternalWidgetConfig
mountElementId: stringconfigName: string- remote config, lives at the same place as other static assets
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.
RemoteWidgetConfig
Actual widget config
agentId: stringcustomerId?: stringwsUrl: stringapiUrl: stringAIAvatar?: boolean- is 3D avatar be usedvoiceEnabled?: boolean- is voice mode enebledbackground?: boolean- clear or visible backgroundfooter?: boolean- is footer rendereddebug?: boolean(avatar debug GUI)manualOpen?: boolean- is widget trigger rendered or should it be open only programatically
Instance Methods
setOpen(open: boolean): void- open/close widgetsetCustomerInteractionSession(session): void- update session after initialization (on session expire)playAnimation(name): voiddestroy(): void
Events
avatar_ready- emitted once avatar assets are fully loadedrenew_session- emitted when session is expired or near expiry
Host Usage Example
import { ChatWidgetInstance } from "bridgeapp-ai-chat-widget";
const widget = new ChatWidgetInstance({
mountElementId: "chat-widget-root",
customerInteractionSession: CustomerInteractionSession,
AIAvatar: true,
background: false,
voiceEnabled: true,
footer: false,
manualOpen: true,
});
widget.addEventListener("avatar_ready", () => {
widget.setOpen(true);
});
widget.addEventListener("renew_session", async () => {
const newSession = await fetchNewSessionSomehow();
widget.setCustomerInteractionSession(newSession);
});Teardown:
widget.destroy();Local Development
Install:
npm installStart dev server:
npm run devNotes:
- Vite dev server runs on
5179by default. src/dev/main.tsxis the local bootstrap path (creates session and mounts widget directly).vite.config.tsis for app/dev runtime.vite.config.lib.tsis for distributable library build.
Build, Publish, and Deployment
Build local dev app
npm run build-devBuild library artifacts
npm run build-libOutputs include:
dist/bridge-ai-chat-widget.es.jsdist/bridge-ai-chat-widget.umd.jsdist/index.types.d.ts
Publish flow
prepublishOnlyrunsbuild-lib- package exports are defined in
package.jsonunder"exports"
Other project scripts
npm run lintnpm run lint-fixnpm run formatnpm run format:check- deployment scripts for S3 buckets (project-specific, to be reworked)
npm run push-assets-metamediastatic(syncsrc/assetsinto remote static assets repo)
Session Handling
Widget relies on customerInteractionSession:
- used as Bearer token for HTTP APIs
- used in websocket query for AnyCable auth
- periodically checked by
useSessionExpiryChecks
When session is near expiry or invalid, renew_session event is emitted; host app must provide a fresh session via setCustomerInteractionSession.
Asset Pipeline
Runtime asset resolution:
- fetch remote manifest from
https://metamediastatic.com/manifest.json - map logical path (for example
animations/idle_1.fbx) to hashed URL - load via
resolveAssetPath
This allows cache-friendly hashed asset delivery without changing code references.
Troubleshooting
- Widget does not mount:
- verify mount element exists and
mountElementIdis correct
- verify mount element exists and
- No messages or send failures:
- verify
apiUrl,accessToken, andchatId
- verify
- No live updates:
- verify
wsUrl, websocket reachability, and token validity
- verify
- Voice fails to start:
- verify mic permission, TURN reachability, and voice API endpoints
- Avatar never becomes ready:
- verify manifest URL + asset availability under static host
