@alxxsck/ai-assistant 1.5.0 → 1.8.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 +24 -10
- package/dist/{Avatar-_GvrPzkn.js → Avatar-BAlmduYw.js} +14 -3
- package/dist/{OrbitControls-BCIW0o5s.js → OrbitControls-BhEr8WlG.js} +1 -1
- package/dist/api/messages.api.d.ts +1 -1
- package/dist/api/messages.api.types.d.ts +6 -0
- package/dist/api/voice.api.d.ts +3 -2
- package/dist/api/voice.api.types.d.ts +11 -13
- package/dist/avatar/Avatar.d.ts +3 -0
- package/dist/bridge-ai-chat-widget.es.js +8478 -4731
- package/dist/domain/command/command.types.d.ts +0 -1
- package/dist/domain/message/cable/widget-frame.d.ts +1 -1
- package/dist/domain/message/cable/widget-message.d.ts +2 -1
- package/dist/domain/message/message-stream.d.ts +10 -0
- package/dist/domain/message/message.store.d.ts +27 -1
- package/dist/domain/voice/voice.store.d.ts +4 -4
- package/dist/index.types.d.ts +1 -1
- package/dist/lib/retrieveContent.d.ts +3 -1
- package/dist/ui/chat-routes.d.ts +4 -0
- package/dist/ui/components/ChatListItem.d.ts +4 -6
- package/dist/ui/components/ChatMessage.d.ts +1 -1
- package/dist/ui/components/ChatMessageSkeleton.d.ts +1 -0
- package/dist/ui/components/ChatTypingIndicator.d.ts +4 -2
- package/dist/ui/components/OptimisticCustomerMessage.d.ts +6 -0
- package/dist/ui/components/PendingAssistantMessage.d.ts +6 -0
- package/dist/ui/components/StreamingMessageText.d.ts +10 -0
- package/dist/ui/hooks/usePinnedAutoScroll.d.ts +10 -0
- package/dist/ui/hooks/useSmoothStreamingText.d.ts +7 -0
- package/dist/ui/streaming/smooth-text.d.ts +21 -0
- package/package.json +2 -1
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
|
|
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
|
-
-
|
|
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,
|
|
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
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
-
|
|
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:
|
|
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
|
|
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
|
|
@@ -29337,6 +29337,8 @@ class iS extends EventTarget {
|
|
|
29337
29337
|
};
|
|
29338
29338
|
controls;
|
|
29339
29339
|
locale;
|
|
29340
|
+
animationFrame = null;
|
|
29341
|
+
renderingEnabled = !0;
|
|
29340
29342
|
lightAmbient;
|
|
29341
29343
|
lightDirectional;
|
|
29342
29344
|
loadingManager;
|
|
@@ -29526,10 +29528,19 @@ class iS extends EventTarget {
|
|
|
29526
29528
|
!e || !t || e == this.width && t == this.height || (this.camera.aspect = e / t, this.camera.updateProjectionMatrix(), this.renderer.setSize(e, t, !1), this.render(), this.width = e, this.height = t);
|
|
29527
29529
|
};
|
|
29528
29530
|
animate = () => {
|
|
29529
|
-
|
|
29531
|
+
if (this.animationFrame = null, !this.renderingEnabled) return;
|
|
29530
29532
|
const e = this.clock.getDelta(), t = this.sound?.seek();
|
|
29531
|
-
this.animationManager.update(e, t), this.controls?.update(), this.render();
|
|
29533
|
+
this.animationManager.update(e, t), this.controls?.update(), this.render(), this.animationFrame = requestAnimationFrame(this.animate);
|
|
29532
29534
|
};
|
|
29535
|
+
setRenderingEnabled(e) {
|
|
29536
|
+
if (this.renderingEnabled !== e) {
|
|
29537
|
+
if (this.renderingEnabled = e, !e) {
|
|
29538
|
+
this.animationFrame !== null && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), this.clock.stop();
|
|
29539
|
+
return;
|
|
29540
|
+
}
|
|
29541
|
+
this.clock.start(), this.animationFrame === null && this.animationManager && (this.animationFrame = requestAnimationFrame(this.animate));
|
|
29542
|
+
}
|
|
29543
|
+
}
|
|
29533
29544
|
SPEAKING_ANIMATION_NAME_1 = "generic_speaking_1";
|
|
29534
29545
|
SPEAKING_ANIMATION_NAME_2 = "generic_speaking_2";
|
|
29535
29546
|
IDLE_ANIMATION_NAME_1 = "idle_1";
|
|
@@ -29593,7 +29604,7 @@ class iS extends EventTarget {
|
|
|
29593
29604
|
this.api.onAvatarReady(this.externalLaunchAnimationAction);
|
|
29594
29605
|
};
|
|
29595
29606
|
async initDebugControls(e) {
|
|
29596
|
-
const { OrbitControls: t } = await import("./OrbitControls-
|
|
29607
|
+
const { OrbitControls: t } = await import("./OrbitControls-BhEr8WlG.js");
|
|
29597
29608
|
this.controls = new t(e, this.renderer.domElement);
|
|
29598
29609
|
}
|
|
29599
29610
|
async addDebugGui() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as S, V as p, M as u, T as _, Q as E, S as P, a as c, R as w, P as O, b as R } from "./Avatar-
|
|
1
|
+
import { C as S, V as p, M as u, T as _, Q as E, S as P, a as c, R as w, P as O, b as R } from "./Avatar-BAlmduYw.js";
|
|
2
2
|
const D = { type: "change" }, b = { type: "start" }, M = { type: "end" }, f = new w(), T = new O(), j = Math.cos(70 * R.DEG2RAD), n = new p(), r = 2 * Math.PI, o = {
|
|
3
3
|
NONE: -1,
|
|
4
4
|
ROTATE: 0,
|
|
@@ -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 {};
|
package/dist/api/voice.api.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { AppApi } from "@/App";
|
|
2
2
|
import type { WidgetConfig } from "@/index.types";
|
|
3
|
-
import type {
|
|
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
|
|
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:
|
|
13
|
+
status: VoiceSessionStatus;
|
|
5
14
|
voiceConfig: {
|
|
6
15
|
voice: string;
|
|
7
16
|
};
|
|
8
17
|
voiceProtocolVersion: number;
|
|
9
|
-
|
|
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
|
};
|
package/dist/avatar/Avatar.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export declare class Avatar extends EventTarget {
|
|
|
34
34
|
private cameraParams;
|
|
35
35
|
private controls?;
|
|
36
36
|
private locale?;
|
|
37
|
+
private animationFrame;
|
|
38
|
+
private renderingEnabled;
|
|
37
39
|
constructor(canvas: HTMLCanvasElement, videoContainer: HTMLDivElement, config: WidgetConfig, api: AppApi);
|
|
38
40
|
private lightAmbient;
|
|
39
41
|
private lightDirectional;
|
|
@@ -47,6 +49,7 @@ export declare class Avatar extends EventTarget {
|
|
|
47
49
|
private height;
|
|
48
50
|
onResize: (width?: number, height?: number) => void;
|
|
49
51
|
animate: () => void;
|
|
52
|
+
setRenderingEnabled(enabled: boolean): void;
|
|
50
53
|
private SPEAKING_ANIMATION_NAME_1;
|
|
51
54
|
private SPEAKING_ANIMATION_NAME_2;
|
|
52
55
|
private IDLE_ANIMATION_NAME_1;
|