@alxxsck/ai-assistant 1.5.0 → 1.7.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.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Embeddable React + TypeScript chat widget for Bridge AI agents, with:
4
4
 
5
5
  - text chat
6
- - optional realtime voice mode backed by Lola WebRTC
6
+ - optional realtime voice mode backed by the Lola Socket.IO proxy and xAI
7
7
  - optional animated 3D avatar (three.js)
8
8
  - WebSocket live message updates (AnyCable)
9
9
  - Shadow DOM isolation for host-page style safety
@@ -25,7 +25,7 @@ The instance mounts a React app into a host element's Shadow DOM, fetches static
25
25
  - Zustand (state stores)
26
26
  - three.js + FBX assets (avatar rendering/animation)
27
27
  - AnyCable Web client (live chat transport)
28
- - Lola + OpenAI Realtime WebRTC (voice mode)
28
+ - Socket.IO + AudioWorklet (realtime voice mode through Lola Backend)
29
29
 
30
30
  ## Repository Layout
31
31
 
@@ -37,7 +37,7 @@ High-level structure:
37
37
  - `src/react/mount.tsx` - React root mount helper
38
38
  - `src/ui/components` - UI components (chat widget, routes, controls)
39
39
  - `src/domain/message` - message store + websocket channel integration
40
- - `src/domain/voice` - Lola voice session, WebRTC media, and realtime events
40
+ - `src/domain/voice` - Lola voice session, PCM media, and xAI realtime events
41
41
  - `src/avatar` - three.js avatar scene, animation manager, blend shapes
42
42
  - `src/api` - HTTP clients for messages/voice/session-related calls
43
43
  - `src/context/ChatWidgetContext.tsx` - widget-level UI state/context
@@ -78,17 +78,27 @@ High-level structure:
78
78
  - agentic steps merge/update and rendering support
79
79
 
80
80
  Live updates arrive via AnyCable channel events and are pushed into the store.
81
+ Versioned `messageDelta` updates are applied by `messageId` and increasing
82
+ `sequence`; each frame contains the current accumulated text, so duplicate or
83
+ stale frames cannot append content twice. The UI renders every accepted frame
84
+ immediately and animates only the newly arrived suffix. See
85
+ [`docs/chat-streaming.ru.md`](docs/chat-streaming.ru.md) for the wire contract,
86
+ lifecycle, rendering, and scroll behavior.
81
87
 
82
88
  ### 4) Voice Domain
83
89
 
84
90
  `useVoiceStore` manages the Lola voice turn lifecycle:
85
91
 
86
92
  - creates a canonical `POST /voice/sessions` session
87
- - captures the microphone with `getUserMedia`
88
- - exchanges SDP through the backend-provided `/voice/sessions/:id/webrtc` path
89
- - plays the OpenAI Realtime remote audio track
90
- - renders native realtime transcripts and keeps avatar talking state in sync
91
- - closes media resources and calls `/voice/sessions/:id/end`
93
+ - captures mono microphone input and converts it to PCM16 24 kHz in an AudioWorklet
94
+ - connects to the authenticated `/assistant` Socket.IO namespace, activates the
95
+ provider session with `voice.connect`, and sends sequenced Lola protocol v4
96
+ `voice.audio.append` events with acknowledgements
97
+ - plays `response.output_audio.delta` through a continuous AudioWorklet queue
98
+ - unwraps canonical `voice.event` envelopes, renders Grok realtime transcripts,
99
+ and keeps avatar talking state in sync
100
+ - creates a fresh VoiceSession after an unexpected voice socket disconnect
101
+ - closes the session through `voice.disconnect` with the REST endpoint as a fallback
92
102
 
93
103
  ### 5) Avatar Domain
94
104
 
@@ -130,7 +140,7 @@ Actual widget config
130
140
  - `apiUrl: string`
131
141
  - `AIAvatar?: boolean` - is 3D avatar be used
132
142
  - `voiceEnabled?: boolean` - is voice mode enebled
133
- - `voiceConfig?: { voice: string }` - optional Lola Realtime voice
143
+ - `voiceConfig?: { voice: "ara" | "eve" | "leo" | "rex" | "sal" }` - optional xAI voice
134
144
  - `background?: boolean` - clear or visible background
135
145
  - `footer?: boolean` - is footer rendered
136
146
  - `debug?: boolean` (avatar debug GUI)
@@ -226,6 +236,10 @@ Outputs include:
226
236
 
227
237
  ### Other project scripts
228
238
 
239
+ The demo app reads its service credential from
240
+ `VITE_SERVICE_ACCOUNT_API_KEY`; credentials must not be committed to source or
241
+ static config files.
242
+
229
243
  - `npm run lint`
230
244
  - `npm run lint-fix`
231
245
  - `npm run format`
@@ -346,6 +360,6 @@ This allows cache-friendly hashed asset delivery without changing code reference
346
360
  - No live updates:
347
361
  - verify `wsUrl`, websocket reachability, and token validity
348
362
  - Voice fails to start:
349
- - verify mic permission, Lola `apiUrl`, backend `Project.settings.voiceEnabled`, and OpenAI Realtime configuration
363
+ - verify mic permission, Lola `apiUrl`, Socket.IO reachability, and backend `Project.settings.voiceEnabled`
350
364
  - Avatar never becomes ready:
351
365
  - verify manifest URL + asset availability under static host
@@ -16,5 +16,5 @@ export declare const listConversations: (config: WidgetConfig, api: AppApi) => P
16
16
  export declare const listConversationMessages: (config: WidgetConfig, api: AppApi, conversationId: string) => Promise<MessageData[]>;
17
17
  export declare const createConversation: (config: WidgetConfig, api: AppApi) => Promise<ConversationData>;
18
18
  export declare const selectConversation: (config: WidgetConfig, api: AppApi, conversationId: string) => Promise<ConversationData>;
19
- export declare function sendMessage(config: WidgetConfig, api: AppApi, messageText: string, conversationId?: string): Promise<MessageData>;
19
+ export declare function sendMessage(config: WidgetConfig, api: AppApi, messageText: string, conversationId?: string, clientMessageId?: `${string}-${string}-${string}-${string}-${string}`): Promise<MessageData>;
20
20
  export {};
@@ -85,4 +85,10 @@ export type AgenticStepsAppendedData = {
85
85
  threadId: string;
86
86
  agenticSteps: AgenticStep[];
87
87
  };
88
+ export type ChatMessageDeltaData = {
89
+ messageId: string;
90
+ sequence: number;
91
+ text: string;
92
+ done: boolean;
93
+ };
88
94
  export {};
@@ -1,10 +1,11 @@
1
1
  import type { AppApi } from "@/App";
2
2
  import type { WidgetConfig } from "@/index.types";
3
- import type { ConnectVoiceSessionData, VoiceSessionData } from "./voice.api.types";
3
+ import type { VoiceSessionData } from "./voice.api.types";
4
4
  export declare class VoiceApiError extends Error {
5
5
  readonly status: number;
6
6
  constructor(status: number, message: string);
7
7
  }
8
+ export declare const parseVoiceSession: (value: unknown) => VoiceSessionData;
8
9
  export declare const startVoiceSession: (config: WidgetConfig, api: AppApi, conversationId?: string) => Promise<VoiceSessionData>;
9
- export declare const connectVoiceSession: (config: WidgetConfig, api: AppApi, offerPath: string, offerSdp: string) => Promise<ConnectVoiceSessionData>;
10
+ export declare const getVoiceSession: (config: WidgetConfig, api: AppApi, voiceSessionId: string) => Promise<VoiceSessionData>;
10
11
  export declare const endVoiceSession: (config: WidgetConfig, api: AppApi, voiceSessionId: string, reason?: string) => Promise<VoiceSessionData>;
@@ -1,21 +1,19 @@
1
+ export type VoiceSessionStatus = "CREATED" | "CONNECTING" | "ACTIVE" | "ENDED" | "FAILED" | "EXPIRED";
2
+ export type VoiceWebsocketContract = {
3
+ namespace: "/assistant";
4
+ connectEvent: "voice.connect";
5
+ audioAppendEvent: "voice.audio.append";
6
+ disconnectEvent: "voice.disconnect";
7
+ serverEvent: "voice.event";
8
+ eventProtocol: "xai-realtime-v1";
9
+ };
1
10
  export type VoiceSessionData = {
2
11
  voiceSessionId: string;
3
12
  conversationId: string;
4
- status: string;
13
+ status: VoiceSessionStatus;
5
14
  voiceConfig: {
6
15
  voice: string;
7
16
  };
8
17
  voiceProtocolVersion: number;
9
- webrtc: {
10
- offerPath: string;
11
- dataChannel: string;
12
- eventProtocol: string;
13
- };
14
- };
15
- export type ConnectVoiceSessionData = {
16
- voiceSessionId: string;
17
- answerSdp: string;
18
- model: string;
19
- voice: string;
20
- dataChannel: string;
18
+ websocket: VoiceWebsocketContract;
21
19
  };