@convai/web-sdk 0.3.2-beta.0 → 0.3.2-beta.1

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 (58) hide show
  1. package/dist/core/BlendshapeQueue.d.ts +26 -42
  2. package/dist/core/BlendshapeQueue.d.ts.map +1 -1
  3. package/dist/core/BlendshapeQueue.js +44 -101
  4. package/dist/core/BlendshapeQueue.js.map +1 -1
  5. package/dist/core/ConvaiClient.d.ts +0 -14
  6. package/dist/core/ConvaiClient.d.ts.map +1 -1
  7. package/dist/core/ConvaiClient.js +2 -23
  8. package/dist/core/ConvaiClient.js.map +1 -1
  9. package/dist/core/MessageHandler.d.ts.map +1 -1
  10. package/dist/core/MessageHandler.js +1 -2
  11. package/dist/core/MessageHandler.js.map +1 -1
  12. package/dist/core/index.d.ts +0 -1
  13. package/dist/core/index.d.ts.map +1 -1
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/core/types.d.ts +6 -11
  16. package/dist/core/types.d.ts.map +1 -1
  17. package/dist/lipsync-helpers/createBlendshapeQueue.d.ts +86 -0
  18. package/dist/lipsync-helpers/createBlendshapeQueue.d.ts.map +1 -0
  19. package/dist/lipsync-helpers/createBlendshapeQueue.js +126 -0
  20. package/dist/lipsync-helpers/createBlendshapeQueue.js.map +1 -0
  21. package/dist/lipsync-helpers/declarativeMapping.d.ts +93 -0
  22. package/dist/lipsync-helpers/declarativeMapping.d.ts.map +1 -0
  23. package/dist/lipsync-helpers/declarativeMapping.js +228 -0
  24. package/dist/lipsync-helpers/declarativeMapping.js.map +1 -0
  25. package/dist/lipsync-helpers/index.d.ts +9 -4
  26. package/dist/lipsync-helpers/index.d.ts.map +1 -1
  27. package/dist/lipsync-helpers/index.js +24 -12
  28. package/dist/lipsync-helpers/index.js.map +1 -1
  29. package/dist/lipsync-helpers/mappingTypes.d.ts +64 -0
  30. package/dist/lipsync-helpers/mappingTypes.d.ts.map +1 -0
  31. package/dist/lipsync-helpers/mappingTypes.js +28 -0
  32. package/dist/lipsync-helpers/mappingTypes.js.map +1 -0
  33. package/dist/lipsync-helpers/presetMappers.d.ts +24 -0
  34. package/dist/lipsync-helpers/presetMappers.d.ts.map +1 -0
  35. package/dist/lipsync-helpers/presetMappers.js +38 -0
  36. package/dist/lipsync-helpers/presetMappers.js.map +1 -0
  37. package/dist/react/hooks/useConvaiClient.d.ts.map +1 -1
  38. package/dist/react/hooks/useConvaiClient.js +0 -4
  39. package/dist/react/hooks/useConvaiClient.js.map +1 -1
  40. package/dist/vanilla/ConvaiWidget.d.ts +13 -6
  41. package/dist/vanilla/ConvaiWidget.d.ts.map +1 -1
  42. package/dist/vanilla/ConvaiWidget.js +107 -70
  43. package/dist/vanilla/ConvaiWidget.js.map +1 -1
  44. package/dist/vanilla/types.d.ts +22 -2
  45. package/dist/vanilla/types.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/lipsync-helpers/arkitBlendshapeHelpers.d.ts +0 -80
  48. package/dist/lipsync-helpers/arkitBlendshapeHelpers.d.ts.map +0 -1
  49. package/dist/lipsync-helpers/arkitBlendshapeHelpers.js +0 -201
  50. package/dist/lipsync-helpers/arkitBlendshapeHelpers.js.map +0 -1
  51. package/dist/lipsync-helpers/arkitPhonemeReference.d.ts +0 -155
  52. package/dist/lipsync-helpers/arkitPhonemeReference.d.ts.map +0 -1
  53. package/dist/lipsync-helpers/arkitPhonemeReference.js +0 -362
  54. package/dist/lipsync-helpers/arkitPhonemeReference.js.map +0 -1
  55. package/dist/lipsync-helpers/neurosyncBlendshapeMapper.d.ts +0 -30
  56. package/dist/lipsync-helpers/neurosyncBlendshapeMapper.d.ts.map +0 -1
  57. package/dist/lipsync-helpers/neurosyncBlendshapeMapper.js +0 -315
  58. package/dist/lipsync-helpers/neurosyncBlendshapeMapper.js.map +0 -1
@@ -5,6 +5,7 @@
5
5
  import { AudioRenderer } from "./AudioRenderer";
6
6
  import { aeroTheme, injectGlobalStyles } from "./styles";
7
7
  import { Icons } from "./icons";
8
+ import { ConvaiClient } from "../core/ConvaiClient";
8
9
  /**
9
10
  * Create a Convai chat widget in the specified container
10
11
  *
@@ -12,12 +13,22 @@ import { Icons } from "./icons";
12
13
  * @param options - Widget configuration options
13
14
  * @returns VanillaWidget instance with destroy method
14
15
  *
15
- * @example
16
+ * @example Simple usage with config
17
+ * ```typescript
18
+ * import { createConvaiWidget } from '@convai/web-sdk/vanilla';
19
+ *
20
+ * const widget = createConvaiWidget(document.body, {
21
+ * apiKey: 'your-api-key',
22
+ * characterId: 'your-character-id',
23
+ * enableVideo: false
24
+ * });
25
+ * ```
26
+ *
27
+ * @example Advanced usage with client instance
16
28
  * ```typescript
17
29
  * import { ConvaiClient, createConvaiWidget } from '@convai/web-sdk/vanilla';
18
30
  *
19
- * const client = new ConvaiClient();
20
- * await client.connect({
31
+ * const client = new ConvaiClient({
21
32
  * apiKey: 'your-api-key',
22
33
  * characterId: 'your-character-id'
23
34
  * });
@@ -28,13 +39,23 @@ import { Icons } from "./icons";
28
39
  * showScreenShare: true,
29
40
  * defaultVoiceMode: true
30
41
  * });
31
- *
32
- * // Later, cleanup
33
- * widget.destroy();
34
42
  * ```
35
43
  */
36
44
  export function createConvaiWidget(container, options) {
37
- const { convaiClient, showVideo = true, showScreenShare = true, defaultVoiceMode = true } = options;
45
+ // Create client if not provided - guarantee it's defined
46
+ const client = options.convaiClient || (() => {
47
+ if (!options.apiKey || !options.characterId) {
48
+ throw new Error('Either convaiClient or (apiKey + characterId) must be provided');
49
+ }
50
+ return new ConvaiClient({
51
+ apiKey: options.apiKey,
52
+ characterId: options.characterId,
53
+ enableVideo: options.enableVideo ?? false,
54
+ startWithVideoOn: options.startWithVideoOn ?? false,
55
+ enableLipsync: options.enableLipsync ?? false,
56
+ });
57
+ })();
58
+ const { showVideo = true, showScreenShare = true, defaultVoiceMode = true, onConnect, onDisconnect, onMessage } = options;
38
59
  // Inject global styles
39
60
  injectGlobalStyles();
40
61
  // State
@@ -69,16 +90,16 @@ export function createConvaiWidget(container, options) {
69
90
  let source = null;
70
91
  // Fetch character info - matches React useCharacterInfo hook
71
92
  const fetchCharacterInfo = async () => {
72
- if (!convaiClient.apiKey || !convaiClient.characterId)
93
+ if (!client.apiKey || !client.characterId)
73
94
  return;
74
95
  try {
75
96
  const response = await fetch("https://api.convai.com/character/get", {
76
97
  method: "POST",
77
98
  headers: {
78
99
  "Content-Type": "application/json",
79
- "CONVAI-API-KEY": convaiClient.apiKey,
100
+ "CONVAI-API-KEY": client.apiKey,
80
101
  },
81
- body: JSON.stringify({ charID: convaiClient.characterId }),
102
+ body: JSON.stringify({ charID: client.characterId }),
82
103
  });
83
104
  if (response.ok) {
84
105
  const data = await response.json();
@@ -261,8 +282,8 @@ export function createConvaiWidget(container, options) {
261
282
  if (!voiceModeOverlay)
262
283
  return;
263
284
  const bars = voiceModeOverlay.querySelectorAll(".voice-bar");
264
- const isTalking = convaiClient.state.isSpeaking;
265
- const isListening = !convaiClient.audioControls.isAudioMuted;
285
+ const isTalking = client.state.isSpeaking;
286
+ const isListening = !client.audioControls.isAudioMuted;
266
287
  const isAnimating = isListening || isTalking;
267
288
  // Update colors based on state
268
289
  bars.forEach((bar) => {
@@ -576,8 +597,8 @@ export function createConvaiWidget(container, options) {
576
597
  border: none;
577
598
  `;
578
599
  voiceExitButton.addEventListener("click", async () => {
579
- convaiClient.sendInterruptMessage();
580
- await convaiClient.audioControls.muteAudio(); // Mute on exit
600
+ client.sendInterruptMessage();
601
+ await client.audioControls.muteAudio(); // Mute on exit
581
602
  isVoiceMode = false;
582
603
  updateVoiceMode();
583
604
  });
@@ -647,8 +668,8 @@ export function createConvaiWidget(container, options) {
647
668
  }
648
669
  else {
649
670
  // Toggle Voice Mode
650
- convaiClient.sendInterruptMessage();
651
- await convaiClient.audioControls.unmuteAudio(); // Unmute on enter
671
+ client.sendInterruptMessage();
672
+ await client.audioControls.unmuteAudio(); // Unmute on enter
652
673
  isVoiceMode = true;
653
674
  updateVoiceMode();
654
675
  }
@@ -761,8 +782,9 @@ export function createConvaiWidget(container, options) {
761
782
  resetIcon.style.width = "18px";
762
783
  resetIcon.style.height = "18px";
763
784
  settingsRow.appendChild(createOption(resetIcon, "Reset", handleReset));
764
- // Video
765
- if (convaiClient.connectionType === "video" && showVideo) {
785
+ // Video - only show if connection type is video
786
+ const connectionType = client.connectionType;
787
+ if (connectionType === "video" && showVideo) {
766
788
  const videoIcon = isVideoVisible
767
789
  ? Icons.Video("md")
768
790
  : Icons.VideoOff("md");
@@ -772,9 +794,9 @@ export function createConvaiWidget(container, options) {
772
794
  videoBtn.id = "convai-settings-video-btn";
773
795
  settingsRow.appendChild(videoBtn);
774
796
  }
775
- // Screen Share
776
- if (convaiClient.connectionType === "video" && showScreenShare) {
777
- const isSharing = convaiClient.screenShareControls.isScreenShareActive;
797
+ // Screen Share - only show if connection type is video
798
+ if (connectionType === "video" && showScreenShare) {
799
+ const isSharing = client.screenShareControls.isScreenShareActive;
778
800
  const shareIcon = isSharing
779
801
  ? Icons.StopScreenShare("md")
780
802
  : Icons.ScreenShare("md");
@@ -859,7 +881,8 @@ export function createConvaiWidget(container, options) {
859
881
  resetIcon.style.height = "18px";
860
882
  settingsRow.appendChild(createOption(resetIcon, "Reset", handleReset));
861
883
  // Video - Always show if showVideo is true and connection type is video
862
- if (convaiClient.connectionType === "video" && showVideo) {
884
+ const connectionType = client.connectionType;
885
+ if (connectionType === "video" && showVideo) {
863
886
  const videoIcon = isVideoVisible
864
887
  ? Icons.Video("md")
865
888
  : Icons.VideoOff("md");
@@ -870,8 +893,8 @@ export function createConvaiWidget(container, options) {
870
893
  settingsRow.appendChild(videoBtn);
871
894
  }
872
895
  // Screen Share - Always show if showScreenShare is true and connection type is video
873
- if (convaiClient.connectionType === "video" && showScreenShare) {
874
- const isSharing = convaiClient.screenShareControls.isScreenShareActive;
896
+ if (connectionType === "video" && showScreenShare) {
897
+ const isSharing = client.screenShareControls.isScreenShareActive;
875
898
  const shareIcon = isSharing
876
899
  ? Icons.StopScreenShare("md")
877
900
  : Icons.ScreenShare("md");
@@ -957,7 +980,7 @@ export function createConvaiWidget(container, options) {
957
980
  `;
958
981
  closeButton.addEventListener("click", (e) => {
959
982
  e.stopPropagation();
960
- convaiClient.videoControls.disableVideo();
983
+ client.videoControls.disableVideo();
961
984
  });
962
985
  closeButton.addEventListener("mouseenter", () => {
963
986
  closeButton.style.transform = "scale(1.1)";
@@ -1077,9 +1100,9 @@ export function createConvaiWidget(container, options) {
1077
1100
  return;
1078
1101
  }
1079
1102
  // Connect on first click if not already connected/connecting
1080
- if (!convaiClient.state.isConnected && !convaiClient.state.isConnecting) {
1103
+ if (!client.state.isConnected && !client.state.isConnecting) {
1081
1104
  try {
1082
- await convaiClient.connect();
1105
+ await client.connect();
1083
1106
  setIsOpen(true);
1084
1107
  }
1085
1108
  catch (error) {
@@ -1095,8 +1118,8 @@ export function createConvaiWidget(container, options) {
1095
1118
  setIsOpen(false);
1096
1119
  };
1097
1120
  const handleSend = () => {
1098
- if (inputValue.trim() && convaiClient.state.isConnected) {
1099
- convaiClient.sendUserTextMessage(inputValue);
1121
+ if (inputValue.trim() && client.state.isConnected) {
1122
+ client.sendUserTextMessage(inputValue);
1100
1123
  inputValue = "";
1101
1124
  inputElement.value = "";
1102
1125
  updateSendButton();
@@ -1105,8 +1128,8 @@ export function createConvaiWidget(container, options) {
1105
1128
  // Microphone toggle removed - only controlled via voice mode now
1106
1129
  const handleToggleMute = () => {
1107
1130
  isMuted = !isMuted;
1108
- if (convaiClient.room) {
1109
- const remoteParticipants = Array.from(convaiClient.room.remoteParticipants.values());
1131
+ if (client.room) {
1132
+ const remoteParticipants = Array.from(client.room.remoteParticipants.values());
1110
1133
  remoteParticipants.forEach((participant) => {
1111
1134
  participant.audioTrackPublications.forEach((publication) => {
1112
1135
  if (publication.track) {
@@ -1120,8 +1143,8 @@ export function createConvaiWidget(container, options) {
1120
1143
  const handleReset = async () => {
1121
1144
  setIsSettingsOpen(false);
1122
1145
  try {
1123
- await convaiClient.disconnect();
1124
- convaiClient.resetSession();
1146
+ await client.disconnect();
1147
+ client.resetSession();
1125
1148
  isMuted = false;
1126
1149
  isVoiceMode = false;
1127
1150
  hasEnteredDefaultVoiceMode = false;
@@ -1135,7 +1158,7 @@ export function createConvaiWidget(container, options) {
1135
1158
  };
1136
1159
  const handleDisconnect = async () => {
1137
1160
  setIsSettingsOpen(false);
1138
- await convaiClient.disconnect();
1161
+ await client.disconnect();
1139
1162
  isMuted = false;
1140
1163
  isVoiceMode = false;
1141
1164
  hasEnteredDefaultVoiceMode = false;
@@ -1144,14 +1167,14 @@ export function createConvaiWidget(container, options) {
1144
1167
  updateSendButton();
1145
1168
  };
1146
1169
  const handleToggleVideo = async () => {
1147
- if (convaiClient.connectionType !== "video")
1170
+ if (client.connectionType !== "video")
1148
1171
  return;
1149
1172
  try {
1150
1173
  if (isVideoVisible) {
1151
- await convaiClient.videoControls.disableVideo();
1174
+ await client.videoControls.disableVideo();
1152
1175
  }
1153
1176
  else {
1154
- await convaiClient.videoControls.enableVideo();
1177
+ await client.videoControls.enableVideo();
1155
1178
  }
1156
1179
  // State will be updated via event listener below
1157
1180
  }
@@ -1161,7 +1184,7 @@ export function createConvaiWidget(container, options) {
1161
1184
  };
1162
1185
  const handleToggleScreenShare = async () => {
1163
1186
  try {
1164
- await convaiClient.screenShareControls.toggleScreenShare();
1187
+ await client.screenShareControls.toggleScreenShare();
1165
1188
  // State will be updated via event listener above
1166
1189
  }
1167
1190
  catch (e) {
@@ -1190,10 +1213,10 @@ export function createConvaiWidget(container, options) {
1190
1213
  chatContent.style.transform = "scale(1)";
1191
1214
  chatContent.style.pointerEvents = "auto";
1192
1215
  // Auto-enter voice mode if defaultVoiceMode is true and not already entered
1193
- if (defaultVoiceMode && !hasEnteredDefaultVoiceMode && convaiClient.state.isConnected) {
1216
+ if (defaultVoiceMode && !hasEnteredDefaultVoiceMode && client.state.isConnected) {
1194
1217
  setTimeout(async () => {
1195
1218
  try {
1196
- await convaiClient.audioControls.unmuteAudio();
1219
+ await client.audioControls.unmuteAudio();
1197
1220
  isVoiceMode = true;
1198
1221
  hasEnteredDefaultVoiceMode = true;
1199
1222
  updateVoiceMode();
@@ -1246,15 +1269,15 @@ export function createConvaiWidget(container, options) {
1246
1269
  };
1247
1270
  const updateVideoTrack = () => {
1248
1271
  if (!floatingVideo ||
1249
- !convaiClient.room ||
1250
- !convaiClient.room.localParticipant)
1272
+ !client.room ||
1273
+ !client.room.localParticipant)
1251
1274
  return;
1252
1275
  const videoEl = floatingVideo.querySelector("#floating-video-element");
1253
1276
  const placeholder = floatingVideo.querySelector("#camera-off-placeholder");
1254
1277
  if (!videoEl || !placeholder)
1255
1278
  return;
1256
1279
  if (isVideoVisible && !isVoiceMode) {
1257
- const tracks = Array.from(convaiClient.room.localParticipant.videoTrackPublications.values());
1280
+ const tracks = Array.from(client.room.localParticipant.videoTrackPublications.values());
1258
1281
  const videoPub = tracks.find((t) => t.kind === "video");
1259
1282
  if (videoPub && videoPub.track) {
1260
1283
  videoPub.track.attach(videoEl);
@@ -1300,8 +1323,8 @@ export function createConvaiWidget(container, options) {
1300
1323
  }
1301
1324
  else {
1302
1325
  // Ensure microphone is muted when not in voice mode
1303
- if (!convaiClient.audioControls.isAudioMuted) {
1304
- await convaiClient.audioControls.muteAudio();
1326
+ if (!client.audioControls.isAudioMuted) {
1327
+ await client.audioControls.muteAudio();
1305
1328
  }
1306
1329
  // Hide Overlay
1307
1330
  if (voiceModeOverlay)
@@ -1571,16 +1594,16 @@ export function createConvaiWidget(container, options) {
1571
1594
  messageListElement.scrollTop = messageListElement.scrollHeight;
1572
1595
  };
1573
1596
  const getBotStatusColor = () => {
1574
- if (!convaiClient.state.isConnected) {
1597
+ if (!client.state.isConnected) {
1575
1598
  return { color: "#ef4444", label: "Disconnected" };
1576
1599
  }
1577
- if (convaiClient.state.isConnecting || !convaiClient.isBotReady) {
1600
+ if (client.state.isConnecting || !client.isBotReady) {
1578
1601
  return { color: "#f59e0b", label: "Connecting" };
1579
1602
  }
1580
1603
  return { color: "#10b981", label: "Connected" };
1581
1604
  };
1582
1605
  const formatMessages = () => {
1583
- const filteredMessages = convaiClient.chatMessages.filter((msg) => (msg.type === "user-transcription" ||
1606
+ const filteredMessages = client.chatMessages.filter((msg) => (msg.type === "user-transcription" ||
1584
1607
  msg.type === "user-llm-text" ||
1585
1608
  msg.type === "bot-llm-text" ||
1586
1609
  msg.type === "bot-emotion") &&
@@ -1600,10 +1623,10 @@ export function createConvaiWidget(container, options) {
1600
1623
  };
1601
1624
  // Setup event listeners for client state changes
1602
1625
  const setupClientListeners = () => {
1603
- convaiClient.on("stateChange", () => {
1626
+ client.on("stateChange", () => {
1604
1627
  updateHeader();
1605
1628
  // Update pulse animation based on connecting state
1606
- if (convaiClient.state.isConnecting) {
1629
+ if (client.state.isConnecting) {
1607
1630
  morphingContainer.style.animation =
1608
1631
  "convai-pulse 2s ease-in-out infinite";
1609
1632
  }
@@ -1611,21 +1634,21 @@ export function createConvaiWidget(container, options) {
1611
1634
  morphingContainer.style.animation = "none";
1612
1635
  }
1613
1636
  // Update speaking animation state
1614
- if (convaiClient.state.isSpeaking) {
1637
+ if (client.state.isSpeaking) {
1615
1638
  if (!startTime) {
1616
1639
  startTime = Date.now();
1617
1640
  }
1618
1641
  }
1619
1642
  else {
1620
1643
  // Reset when not speaking
1621
- if (startTime && !convaiClient.state.isSpeaking) {
1644
+ if (startTime && !client.state.isSpeaking) {
1622
1645
  startTime = 0;
1623
1646
  currentLevels = Array(40).fill(0.05);
1624
1647
  targetLevels = Array(40).fill(0.05);
1625
1648
  }
1626
1649
  }
1627
1650
  // Auto-collapse when disconnected
1628
- if (!convaiClient.state.isConnected && !convaiClient.state.isConnecting) {
1651
+ if (!client.state.isConnected && !client.state.isConnecting) {
1629
1652
  if (isOpen) {
1630
1653
  setIsOpen(false);
1631
1654
  }
@@ -1637,14 +1660,14 @@ export function createConvaiWidget(container, options) {
1637
1660
  updateVoiceMode();
1638
1661
  }
1639
1662
  // Auto-enter voice mode on first connection if defaultVoiceMode is true
1640
- if (convaiClient.state.isConnected &&
1663
+ if (client.state.isConnected &&
1641
1664
  defaultVoiceMode &&
1642
1665
  isOpen &&
1643
1666
  !hasEnteredDefaultVoiceMode &&
1644
1667
  !isVoiceMode) {
1645
1668
  setTimeout(async () => {
1646
1669
  try {
1647
- await convaiClient.audioControls.unmuteAudio();
1670
+ await client.audioControls.unmuteAudio();
1648
1671
  isVoiceMode = true;
1649
1672
  hasEnteredDefaultVoiceMode = true;
1650
1673
  updateVoiceMode();
@@ -1657,7 +1680,7 @@ export function createConvaiWidget(container, options) {
1657
1680
  });
1658
1681
  // Audio state changes now only affect voice mode (microphone button removed)
1659
1682
  // Video state change listener
1660
- convaiClient.videoControls.on("videoStateChange", (videoState) => {
1683
+ client.videoControls.on("videoStateChange", (videoState) => {
1661
1684
  if (videoState.isVideoEnabled !== undefined) {
1662
1685
  isVideoVisible = videoState.isVideoEnabled;
1663
1686
  updateVoiceMode();
@@ -1684,7 +1707,7 @@ export function createConvaiWidget(container, options) {
1684
1707
  }
1685
1708
  });
1686
1709
  // Screen share state change listener
1687
- convaiClient.screenShareControls.on("screenShareStateChange", (screenShareState) => {
1710
+ client.screenShareControls.on("screenShareStateChange", (screenShareState) => {
1688
1711
  if (screenShareState.isScreenShareActive !== undefined) {
1689
1712
  const isSharing = screenShareState.isScreenShareActive;
1690
1713
  const shareBtn = document.getElementById("convai-settings-share-btn");
@@ -1710,28 +1733,41 @@ export function createConvaiWidget(container, options) {
1710
1733
  }
1711
1734
  }
1712
1735
  });
1713
- convaiClient.on("messagesChange", () => {
1736
+ client.on("messagesChange", () => {
1714
1737
  updateMessageList();
1738
+ // Call onMessage callback if provided
1739
+ if (onMessage && client.chatMessages.length > 0) {
1740
+ const lastMessage = client.chatMessages[client.chatMessages.length - 1];
1741
+ onMessage(lastMessage);
1742
+ }
1715
1743
  });
1716
1744
  // Bot ready listener - updates UI when bot becomes ready
1717
- convaiClient.on("botReady", () => {
1745
+ client.on("botReady", () => {
1718
1746
  updateHeader(); // Update header to show green status
1719
1747
  });
1720
- convaiClient.on("connect", () => {
1748
+ client.on("connect", () => {
1721
1749
  // Initialize audio renderer on connection
1722
- if (convaiClient.room && !audioRenderer) {
1723
- audioRenderer = new AudioRenderer(convaiClient.room);
1750
+ if (client.room && !audioRenderer) {
1751
+ audioRenderer = new AudioRenderer(client.room);
1724
1752
  }
1725
1753
  // Fetch character info
1726
1754
  fetchCharacterInfo();
1755
+ // Call onConnect callback if provided
1756
+ if (onConnect) {
1757
+ onConnect();
1758
+ }
1727
1759
  });
1728
- convaiClient.on("disconnect", () => {
1760
+ client.on("disconnect", () => {
1729
1761
  // Cleanup audio renderer
1730
1762
  if (audioRenderer) {
1731
1763
  audioRenderer.destroy();
1732
1764
  audioRenderer = null;
1733
1765
  }
1734
1766
  updateMessageList();
1767
+ // Call onDisconnect callback if provided
1768
+ if (onDisconnect) {
1769
+ onDisconnect();
1770
+ }
1735
1771
  });
1736
1772
  };
1737
1773
  // Initialize
@@ -1744,13 +1780,14 @@ export function createConvaiWidget(container, options) {
1744
1780
  updateVoiceMode();
1745
1781
  }
1746
1782
  // If already connected, initialize audio renderer and fetch character info
1747
- if (convaiClient.state.isConnected && convaiClient.room) {
1748
- audioRenderer = new AudioRenderer(convaiClient.room);
1783
+ if (client.state.isConnected && client.room) {
1784
+ audioRenderer = new AudioRenderer(client.room);
1749
1785
  fetchCharacterInfo();
1750
1786
  }
1751
1787
  // Return widget instance
1752
1788
  const widget = {
1753
1789
  element: rootElement,
1790
+ client: client,
1754
1791
  destroy: () => {
1755
1792
  // Cleanup audio renderer
1756
1793
  if (audioRenderer) {
@@ -1767,11 +1804,11 @@ export function createConvaiWidget(container, options) {
1767
1804
  floatingVideo.remove();
1768
1805
  }
1769
1806
  // Unsubscribe from client events
1770
- convaiClient.off("stateChange", () => { });
1771
- convaiClient.off("messagesChange", () => { });
1772
- convaiClient.off("botReady", () => { });
1773
- convaiClient.off("connect", () => { });
1774
- convaiClient.off("disconnect", () => { });
1807
+ client.off("stateChange", () => { });
1808
+ client.off("messagesChange", () => { });
1809
+ client.off("botReady", () => { });
1810
+ client.off("connect", () => { });
1811
+ client.off("disconnect", () => { });
1775
1812
  },
1776
1813
  };
1777
1814
  return widget;