@alxxsck/ai-assistant 1.0.0 → 1.1.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 +12 -12
- package/dist/{Avatar-XISRbl7X.js → Avatar-B6ktAPlH.js} +23 -5
- package/dist/{OrbitControls-XelJ40xM.js → OrbitControls-BE3BANZi.js} +1 -1
- package/dist/api/audio.api.d.ts +20 -0
- package/dist/api/voice.api.d.ts +8 -63
- package/dist/api/voice.api.types.d.ts +15 -31
- package/dist/avatar/AnimationManager.d.ts +3 -1
- package/dist/avatar/Avatar.d.ts +4 -0
- package/dist/bridge-ai-chat-widget.es.js +2985 -3046
- package/dist/domain/command/CommandRuntime.d.ts +3 -1
- package/dist/domain/command/command.types.d.ts +24 -1
- package/dist/domain/message/cable/widget-frame.d.ts +13 -0
- package/dist/domain/message/cable/widget-message.d.ts +2 -1
- package/dist/domain/message/message.store.d.ts +3 -3
- package/dist/domain/speech/SpeechPlayer.d.ts +15 -0
- package/dist/domain/speech/speech.constants.d.ts +5 -0
- package/dist/domain/voice/voice.store.d.ts +14 -45
- package/dist/hooks/useSessionExpiryChecks.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.types.d.ts +2 -2
- package/dist/lib/isSessionExpired.d.ts +2 -1
- package/dist/ui/chat-routes.d.ts +2 -0
- package/package.json +3 -3
- package/dist/api/messages.api.utils.d.ts +0 -2
- package/dist/hooks/useDeviceManager.d.ts +0 -25
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
|
|
6
|
+
- optional realtime voice mode backed by Lola WebRTC
|
|
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
|
+
- Lola + OpenAI Realtime WebRTC (voice mode)
|
|
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` - voice
|
|
40
|
+
- `src/domain/voice` - Lola voice session, WebRTC media, and 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
|
|
@@ -81,15 +81,14 @@ Live updates arrive via AnyCable channel events and are pushed into the store.
|
|
|
81
81
|
|
|
82
82
|
### 4) Voice Domain
|
|
83
83
|
|
|
84
|
-
`useVoiceStore` manages
|
|
84
|
+
`useVoiceStore` manages the Lola voice turn lifecycle:
|
|
85
85
|
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
- handles mic enable/disable, device switching, and teardown (`EndSession` + `EndVoiceSession`)
|
|
86
|
+
- 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
92
|
|
|
94
93
|
### 5) Avatar Domain
|
|
95
94
|
|
|
@@ -131,6 +130,7 @@ Actual widget config
|
|
|
131
130
|
- `apiUrl: string`
|
|
132
131
|
- `AIAvatar?: boolean` - is 3D avatar be used
|
|
133
132
|
- `voiceEnabled?: boolean` - is voice mode enebled
|
|
133
|
+
- `voiceConfig?: { voice: string }` - optional Lola Realtime voice
|
|
134
134
|
- `background?: boolean` - clear or visible background
|
|
135
135
|
- `footer?: boolean` - is footer rendered
|
|
136
136
|
- `debug?: boolean` (avatar debug GUI)
|
|
@@ -340,6 +340,6 @@ This allows cache-friendly hashed asset delivery without changing code reference
|
|
|
340
340
|
- No live updates:
|
|
341
341
|
- verify `wsUrl`, websocket reachability, and token validity
|
|
342
342
|
- Voice fails to start:
|
|
343
|
-
- verify mic permission,
|
|
343
|
+
- verify mic permission, Lola `apiUrl`, backend `Project.settings.voiceEnabled`, and OpenAI Realtime configuration
|
|
344
344
|
- Avatar never becomes ready:
|
|
345
345
|
- verify manifest URL + asset availability under static host
|
|
@@ -29156,6 +29156,7 @@ class nS {
|
|
|
29156
29156
|
animations = [];
|
|
29157
29157
|
zeroMorphingInfluences = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
29158
29158
|
visemeDataIndex = 0;
|
|
29159
|
+
previousSpeechTime = 0;
|
|
29159
29160
|
visemeData;
|
|
29160
29161
|
mixer;
|
|
29161
29162
|
actions = /* @__PURE__ */ new Map();
|
|
@@ -29205,9 +29206,12 @@ class nS {
|
|
|
29205
29206
|
this.actions.set(t.name, {
|
|
29206
29207
|
animation: i,
|
|
29207
29208
|
blendShapeScenario: r
|
|
29208
|
-
}), this.loadedAnimations++, this.totalAnimations === this.loadedAnimations && this.avatar.onAllAssetsLoaded();
|
|
29209
|
+
}), this.avatar.onAnimationLoaded(t.name), this.loadedAnimations++, this.totalAnimations === this.loadedAnimations && this.avatar.onAllAssetsLoaded();
|
|
29209
29210
|
}
|
|
29210
29211
|
fadeDuration = 0.2;
|
|
29212
|
+
hasAction(e) {
|
|
29213
|
+
return this.actions.has(e);
|
|
29214
|
+
}
|
|
29211
29215
|
fadeToAction(e) {
|
|
29212
29216
|
const t = this.actions.get(this.activeActionName);
|
|
29213
29217
|
this.activeActionName = e;
|
|
@@ -29218,7 +29222,7 @@ class nS {
|
|
|
29218
29222
|
this.actions.get(this.activeActionName).blendShapeScenario?.play();
|
|
29219
29223
|
}
|
|
29220
29224
|
resetVisemeData() {
|
|
29221
|
-
this.visemeDataIndex = 0;
|
|
29225
|
+
this.visemeDataIndex = 0, this.previousSpeechTime = 0;
|
|
29222
29226
|
}
|
|
29223
29227
|
setVisemeData() {
|
|
29224
29228
|
this.visemeData = Jy.trim().split(`
|
|
@@ -29233,7 +29237,7 @@ class nS {
|
|
|
29233
29237
|
convertVisemeDataToAnimationScenario() {
|
|
29234
29238
|
}
|
|
29235
29239
|
update(e, t = 0) {
|
|
29236
|
-
this.mixer && this.mixer.update(e), !(this.visemeDataIndex >= this.visemeData.length) && t && t >= this.visemeData[this.visemeDataIndex].time - this.blendShapeResetAnimationLength * 0.7 && (this.playVisemeAnimation(
|
|
29240
|
+
this.mixer && this.mixer.update(e), t < this.previousSpeechTime && (this.visemeDataIndex = 0), this.previousSpeechTime = t, !(this.visemeDataIndex >= this.visemeData.length) && t && t >= this.visemeData[this.visemeDataIndex].time - this.blendShapeResetAnimationLength * 0.7 && (this.playVisemeAnimation(
|
|
29237
29241
|
t,
|
|
29238
29242
|
this.visemeData[this.visemeDataIndex],
|
|
29239
29243
|
this.visemeData[this.visemeDataIndex + 1]
|
|
@@ -29531,8 +29535,22 @@ class iS extends EventTarget {
|
|
|
29531
29535
|
IDLE_ANIMATION_NAME_2 = "waiting_idle_2";
|
|
29532
29536
|
speakingAnimationName = this.SPEAKING_ANIMATION_NAME_1;
|
|
29533
29537
|
idleAnimationName = this.IDLE_ANIMATION_NAME_1;
|
|
29538
|
+
talkingRequested = !1;
|
|
29539
|
+
talkingApplied = !1;
|
|
29534
29540
|
setTalking(e) {
|
|
29535
|
-
|
|
29541
|
+
if (this.talkingRequested = e, e) {
|
|
29542
|
+
this.applyTalkingWhenReady();
|
|
29543
|
+
return;
|
|
29544
|
+
}
|
|
29545
|
+
if (!this.talkingApplied || !this.sound || !this.animationManager) return;
|
|
29546
|
+
const t = this.animationManager.activeActionName === this.speakingAnimationName;
|
|
29547
|
+
this.sound.loop(!1), this.sound.stop(), this.sound.seek(0), this.animationManager.resetMorphing(), this.animationManager.resetVisemeData(), this.talkingApplied = !1, this.speakingAnimationName = this.speakingAnimationName === this.SPEAKING_ANIMATION_NAME_1 ? this.SPEAKING_ANIMATION_NAME_2 : this.SPEAKING_ANIMATION_NAME_1, this.idleAnimationName = this.idleAnimationName === this.IDLE_ANIMATION_NAME_1 ? this.IDLE_ANIMATION_NAME_2 : this.IDLE_ANIMATION_NAME_1, t && this.animationManager.hasAction(this.idleAnimationName) && this.animationManager.fadeToAction(this.idleAnimationName);
|
|
29548
|
+
}
|
|
29549
|
+
applyTalkingWhenReady() {
|
|
29550
|
+
!this.talkingRequested || this.talkingApplied || !this.sound || !this.animationManager?.hasAction(this.speakingAnimationName) || (this.sound.loop(!0), this.sound.play(), this.animationManager.fadeToAction(this.speakingAnimationName), this.talkingApplied = !0);
|
|
29551
|
+
}
|
|
29552
|
+
onAnimationLoaded(e) {
|
|
29553
|
+
e === this.speakingAnimationName && this.applyTalkingWhenReady();
|
|
29536
29554
|
}
|
|
29537
29555
|
playReaction(e, t) {
|
|
29538
29556
|
t === "win_big" && (t = "win_small"), t === "welcome_game" && (t = "spin"), this.animationManager.fadeToAction(t), this.animationManager.resetVisemeData(), this.speakPhrase(e);
|
|
@@ -29574,7 +29592,7 @@ class iS extends EventTarget {
|
|
|
29574
29592
|
this.api.onAvatarReady(this.externalLaunchAnimationAction);
|
|
29575
29593
|
};
|
|
29576
29594
|
async initDebugControls(e) {
|
|
29577
|
-
const { OrbitControls: t } = await import("./OrbitControls-
|
|
29595
|
+
const { OrbitControls: t } = await import("./OrbitControls-BE3BANZi.js");
|
|
29578
29596
|
this.controls = new t(e, this.renderer.domElement);
|
|
29579
29597
|
}
|
|
29580
29598
|
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-B6ktAPlH.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,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AppApi } from "@/App";
|
|
2
|
+
import type { ProtectedAudioMimeType, StreamingSpeechMimeType } from "@/domain/speech/speech.constants";
|
|
3
|
+
import type { WidgetConfig } from "@/index.types";
|
|
4
|
+
export type ProtectedAudioRequest = {
|
|
5
|
+
audioPath: string;
|
|
6
|
+
method: "POST";
|
|
7
|
+
mimeType: ProtectedAudioMimeType;
|
|
8
|
+
};
|
|
9
|
+
export type StreamingAudioRequest = ProtectedAudioRequest & {
|
|
10
|
+
mimeType: StreamingSpeechMimeType;
|
|
11
|
+
format: "pcm16";
|
|
12
|
+
sampleRate: 24_000;
|
|
13
|
+
channels: 1;
|
|
14
|
+
};
|
|
15
|
+
export declare class AudioApiError extends Error {
|
|
16
|
+
readonly status: number;
|
|
17
|
+
constructor(status: number, message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare const fetchProtectedAudio: (config: WidgetConfig, api: AppApi, request: ProtectedAudioRequest) => Promise<Blob>;
|
|
20
|
+
export declare const fetchProtectedAudioStream: (config: WidgetConfig, api: AppApi, request: StreamingAudioRequest, signal: AbortSignal) => Promise<ReadableStream<Uint8Array>>;
|
package/dist/api/voice.api.d.ts
CHANGED
|
@@ -1,65 +1,10 @@
|
|
|
1
|
-
import type { ChatParticipant } from "./messages.api.types";
|
|
2
1
|
import type { AppApi } from "@/App";
|
|
3
|
-
import type { GetTurnBootstrapPayload, StartVoiceSessionResponse, VoiceSessionData, VoiceTurn } from "./voice.api.types";
|
|
4
2
|
import type { WidgetConfig } from "@/index.types";
|
|
5
|
-
|
|
6
|
-
export declare
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
chatId: string;
|
|
14
|
-
companyId: string;
|
|
15
|
-
createdAt: string;
|
|
16
|
-
endedAt: string;
|
|
17
|
-
id: string;
|
|
18
|
-
modelId: string;
|
|
19
|
-
participant: ChatParticipant;
|
|
20
|
-
signalingUrl: string;
|
|
21
|
-
status: VoiceSessionStatus;
|
|
22
|
-
tokenExpiresAt: string;
|
|
23
|
-
updatedAt: string;
|
|
24
|
-
voiceConfig: VoiceConfig;
|
|
25
|
-
};
|
|
26
|
-
export declare function endVoiceSession(config: WidgetConfig, api: AppApi, id: string, reason?: string): Promise<EndVoiceSessionResponse>;
|
|
27
|
-
type SendOfferPayload = {
|
|
28
|
-
voiceBaseUrl: string;
|
|
29
|
-
voiceSessionId: string;
|
|
30
|
-
sessionToken: string;
|
|
31
|
-
clientInstanceId: string;
|
|
32
|
-
protocolVersion: number;
|
|
33
|
-
expectedConnectionGeneration: number;
|
|
34
|
-
offerSdp: string;
|
|
35
|
-
};
|
|
36
|
-
type SendOfferResponse = {
|
|
37
|
-
answerSdp: string;
|
|
38
|
-
protocolVersion: number;
|
|
39
|
-
voiceSessionId: string;
|
|
40
|
-
connectionId: string;
|
|
41
|
-
connectionGeneration: number;
|
|
42
|
-
};
|
|
43
|
-
export declare function sendOffer({ voiceBaseUrl, voiceSessionId, sessionToken, clientInstanceId, protocolVersion, expectedConnectionGeneration, offerSdp, }: SendOfferPayload): Promise<SendOfferResponse>;
|
|
44
|
-
type SendCandidatesPayload = {
|
|
45
|
-
voiceBaseUrl: string;
|
|
46
|
-
voiceSessionId: string;
|
|
47
|
-
sessionToken: string;
|
|
48
|
-
clientInstanceId: string;
|
|
49
|
-
connectionId: string;
|
|
50
|
-
connectionGeneration: number;
|
|
51
|
-
candidates: RTCIceCandidateInit[];
|
|
52
|
-
};
|
|
53
|
-
export declare function sendCandidates({ voiceBaseUrl, voiceSessionId, sessionToken, clientInstanceId, connectionId, connectionGeneration, candidates, }: SendCandidatesPayload): Promise<void>;
|
|
54
|
-
type EndWebRtcSessionPayload = {
|
|
55
|
-
voiceBaseUrl: string;
|
|
56
|
-
voiceSessionId: string;
|
|
57
|
-
sessionToken: string;
|
|
58
|
-
clientInstanceId: string;
|
|
59
|
-
connectionId: string;
|
|
60
|
-
connectionGeneration: number;
|
|
61
|
-
reason?: string;
|
|
62
|
-
};
|
|
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>;
|
|
65
|
-
export {};
|
|
3
|
+
import type { ConnectVoiceSessionData, VoiceSessionData } from "./voice.api.types";
|
|
4
|
+
export declare class VoiceApiError extends Error {
|
|
5
|
+
readonly status: number;
|
|
6
|
+
constructor(status: number, message: string);
|
|
7
|
+
}
|
|
8
|
+
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 endVoiceSession: (config: WidgetConfig, api: AppApi, voiceSessionId: string, reason?: string) => Promise<VoiceSessionData>;
|
|
@@ -1,37 +1,21 @@
|
|
|
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
1
|
export type VoiceSessionData = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
participant: ChatParticipant;
|
|
2
|
+
voiceSessionId: string;
|
|
3
|
+
conversationId: string;
|
|
4
|
+
status: string;
|
|
5
|
+
voiceConfig: {
|
|
6
|
+
voice: string;
|
|
17
7
|
};
|
|
18
|
-
sessionToken: string;
|
|
19
8
|
voiceProtocolVersion: number;
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
webrtc: {
|
|
10
|
+
offerPath: string;
|
|
11
|
+
dataChannel: string;
|
|
12
|
+
eventProtocol: string;
|
|
13
|
+
};
|
|
22
14
|
};
|
|
23
|
-
export type
|
|
24
|
-
voiceBaseUrl: string;
|
|
15
|
+
export type ConnectVoiceSessionData = {
|
|
25
16
|
voiceSessionId: string;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export type VoiceTurn = {
|
|
31
|
-
urls: string[];
|
|
32
|
-
username: string;
|
|
33
|
-
credential: string;
|
|
34
|
-
credentialExpiresAt: string;
|
|
35
|
-
realm: string;
|
|
36
|
-
useSessionTokenCredential: boolean;
|
|
17
|
+
answerSdp: string;
|
|
18
|
+
model: string;
|
|
19
|
+
voice: string;
|
|
20
|
+
dataChannel: string;
|
|
37
21
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Object3D } from "three";
|
|
2
2
|
import type { VisemeData } from "./Avatar";
|
|
3
|
-
import { Avatar } from "./Avatar";
|
|
3
|
+
import type { Avatar } from "./Avatar";
|
|
4
4
|
export declare const BLEND_SHAPE_KEYS: readonly ["blendShape1.AE", "blendShape1.ChJ", "blendShape1.FV", "blendShape1.Oh", "blendShape1.Ah", "blendShape1.Ih", "blendShape1.L", "blendShape1.SZ", "blendShape1.Woo", "blendShape1.MBP", "blendShape1.Smile_Closed", "blendShape1.Smile_Open", "blendShape1.Kiss"];
|
|
5
5
|
export type BlendShapeKey = (typeof BLEND_SHAPE_KEYS)[number];
|
|
6
6
|
export default class AnimationManager {
|
|
@@ -10,6 +10,7 @@ export default class AnimationManager {
|
|
|
10
10
|
private animations;
|
|
11
11
|
private zeroMorphingInfluences;
|
|
12
12
|
private visemeDataIndex;
|
|
13
|
+
private previousSpeechTime;
|
|
13
14
|
private visemeData;
|
|
14
15
|
private mixer;
|
|
15
16
|
private actions;
|
|
@@ -23,6 +24,7 @@ export default class AnimationManager {
|
|
|
23
24
|
private loadAnimationLazy;
|
|
24
25
|
private onLoadAnimation;
|
|
25
26
|
private fadeDuration;
|
|
27
|
+
hasAction(name: string): boolean;
|
|
26
28
|
fadeToAction(name: string): void;
|
|
27
29
|
applyActiveActionBlendShapeScenario(): void;
|
|
28
30
|
resetVisemeData(): void;
|
package/dist/avatar/Avatar.d.ts
CHANGED
|
@@ -53,7 +53,11 @@ export declare class Avatar extends EventTarget {
|
|
|
53
53
|
private IDLE_ANIMATION_NAME_2;
|
|
54
54
|
private speakingAnimationName;
|
|
55
55
|
private idleAnimationName;
|
|
56
|
+
private talkingRequested;
|
|
57
|
+
private talkingApplied;
|
|
56
58
|
setTalking(talking: boolean): void;
|
|
59
|
+
private applyTalkingWhenReady;
|
|
60
|
+
onAnimationLoaded(name: string): void;
|
|
57
61
|
playReaction(speech: string, animation: string): void;
|
|
58
62
|
speakPhrase(phrase: string): void;
|
|
59
63
|
externalLaunchAnimationAction: (name: ExternallyAvailableActions) => void;
|