@convai/web-sdk 0.3.1-beta.2 → 0.3.2-beta.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.
Files changed (287) hide show
  1. package/README.md +535 -1077
  2. package/dist/core/AudioManager.d.ts.map +1 -0
  3. package/dist/core/AudioManager.js +262 -0
  4. package/dist/core/AudioManager.js.map +1 -0
  5. package/dist/core/BlendshapeQueue.d.ts +128 -0
  6. package/dist/core/BlendshapeQueue.d.ts.map +1 -0
  7. package/dist/core/BlendshapeQueue.js +229 -0
  8. package/dist/core/BlendshapeQueue.js.map +1 -0
  9. package/dist/{types/core → core}/ConvaiClient.d.ts +19 -15
  10. package/dist/core/ConvaiClient.d.ts.map +1 -0
  11. package/dist/core/ConvaiClient.js +623 -0
  12. package/dist/core/ConvaiClient.js.map +1 -0
  13. package/dist/core/EventEmitter.d.ts.map +1 -0
  14. package/dist/core/EventEmitter.js +68 -0
  15. package/dist/core/EventEmitter.js.map +1 -0
  16. package/dist/{types/core → core}/MessageHandler.d.ts +7 -0
  17. package/dist/core/MessageHandler.d.ts.map +1 -0
  18. package/dist/core/MessageHandler.js +333 -0
  19. package/dist/core/MessageHandler.js.map +1 -0
  20. package/dist/core/ScreenShareManager.d.ts.map +1 -0
  21. package/dist/core/ScreenShareManager.js +207 -0
  22. package/dist/core/ScreenShareManager.js.map +1 -0
  23. package/dist/core/VideoManager.d.ts.map +1 -0
  24. package/dist/core/VideoManager.js +205 -0
  25. package/dist/core/VideoManager.js.map +1 -0
  26. package/dist/{types/core → core}/index.d.ts +2 -0
  27. package/dist/core/index.d.ts.map +1 -0
  28. package/dist/core/index.js +14 -1970
  29. package/dist/core/index.js.map +1 -0
  30. package/dist/{types/core → core}/types.d.ts +12 -21
  31. package/dist/core/types.d.ts.map +1 -0
  32. package/dist/core/types.js +2 -0
  33. package/dist/core/types.js.map +1 -0
  34. package/dist/dev.d.ts.map +1 -0
  35. package/dist/dev.js +12 -0
  36. package/dist/dev.js.map +1 -0
  37. package/dist/index.d.ts +4 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +6 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/lipsync-helpers/arkitBlendshapeHelpers.d.ts.map +1 -0
  42. package/dist/lipsync-helpers/arkitBlendshapeHelpers.js +201 -0
  43. package/dist/lipsync-helpers/arkitBlendshapeHelpers.js.map +1 -0
  44. package/dist/lipsync-helpers/arkitOrder61.d.ts.map +1 -0
  45. package/dist/lipsync-helpers/arkitOrder61.js +287 -0
  46. package/dist/lipsync-helpers/arkitOrder61.js.map +1 -0
  47. package/dist/lipsync-helpers/arkitPhonemeReference.d.ts.map +1 -0
  48. package/dist/lipsync-helpers/arkitPhonemeReference.js +362 -0
  49. package/dist/lipsync-helpers/arkitPhonemeReference.js.map +1 -0
  50. package/dist/{types/lipsync-helpers → lipsync-helpers}/index.d.ts +1 -0
  51. package/dist/lipsync-helpers/index.d.ts.map +1 -0
  52. package/dist/lipsync-helpers/index.js +20 -1165
  53. package/dist/lipsync-helpers/index.js.map +1 -0
  54. package/dist/lipsync-helpers/metahumanOrder251.d.ts +115 -0
  55. package/dist/lipsync-helpers/metahumanOrder251.d.ts.map +1 -0
  56. package/dist/lipsync-helpers/metahumanOrder251.js +432 -0
  57. package/dist/lipsync-helpers/metahumanOrder251.js.map +1 -0
  58. package/dist/lipsync-helpers/neurosyncBlendshapeMapper.d.ts.map +1 -0
  59. package/dist/lipsync-helpers/neurosyncBlendshapeMapper.js +315 -0
  60. package/dist/lipsync-helpers/neurosyncBlendshapeMapper.js.map +1 -0
  61. package/dist/react/components/ConvaiWidget.d.ts.map +1 -0
  62. package/dist/react/components/ConvaiWidget.js +505 -0
  63. package/dist/react/components/ConvaiWidget.js.map +1 -0
  64. package/dist/react/components/index.d.ts.map +1 -0
  65. package/dist/react/components/index.js +3 -0
  66. package/dist/react/components/index.js.map +1 -0
  67. package/dist/react/components/rtc-widget/components/AudioSettingsPanel.d.ts.map +1 -0
  68. package/dist/react/components/rtc-widget/components/AudioSettingsPanel.js +316 -0
  69. package/dist/react/components/rtc-widget/components/AudioSettingsPanel.js.map +1 -0
  70. package/dist/react/components/rtc-widget/components/AudioVisualizer.d.ts.map +1 -0
  71. package/dist/react/components/rtc-widget/components/AudioVisualizer.js +259 -0
  72. package/dist/react/components/rtc-widget/components/AudioVisualizer.js.map +1 -0
  73. package/dist/react/components/rtc-widget/components/ConviMessage.d.ts.map +1 -0
  74. package/dist/react/components/rtc-widget/components/ConviMessage.js +14 -0
  75. package/dist/react/components/rtc-widget/components/ConviMessage.js.map +1 -0
  76. package/dist/react/components/rtc-widget/components/FloatingVideo.d.ts.map +1 -0
  77. package/dist/react/components/rtc-widget/components/FloatingVideo.js +122 -0
  78. package/dist/react/components/rtc-widget/components/FloatingVideo.js.map +1 -0
  79. package/dist/react/components/rtc-widget/components/MarkdownRenderer.d.ts.map +1 -0
  80. package/dist/react/components/rtc-widget/components/MarkdownRenderer.js +68 -0
  81. package/dist/react/components/rtc-widget/components/MarkdownRenderer.js.map +1 -0
  82. package/dist/react/components/rtc-widget/components/MessageBubble.d.ts.map +1 -0
  83. package/dist/react/components/rtc-widget/components/MessageBubble.js +23 -0
  84. package/dist/react/components/rtc-widget/components/MessageBubble.js.map +1 -0
  85. package/dist/react/components/rtc-widget/components/MessageList.d.ts.map +1 -0
  86. package/dist/react/components/rtc-widget/components/MessageList.js +89 -0
  87. package/dist/react/components/rtc-widget/components/MessageList.js.map +1 -0
  88. package/dist/react/components/rtc-widget/components/UserMessage.d.ts.map +1 -0
  89. package/dist/react/components/rtc-widget/components/UserMessage.js +15 -0
  90. package/dist/react/components/rtc-widget/components/UserMessage.js.map +1 -0
  91. package/dist/react/components/rtc-widget/components/conviComponents/ConviButton.d.ts.map +1 -0
  92. package/dist/react/components/rtc-widget/components/conviComponents/ConviButton.js +15 -0
  93. package/dist/react/components/rtc-widget/components/conviComponents/ConviButton.js.map +1 -0
  94. package/dist/react/components/rtc-widget/components/conviComponents/ConviFooter.d.ts.map +1 -0
  95. package/dist/react/components/rtc-widget/components/conviComponents/ConviFooter.js +172 -0
  96. package/dist/react/components/rtc-widget/components/conviComponents/ConviFooter.js.map +1 -0
  97. package/dist/react/components/rtc-widget/components/conviComponents/ConviHeader.d.ts.map +1 -0
  98. package/dist/react/components/rtc-widget/components/conviComponents/ConviHeader.js +66 -0
  99. package/dist/react/components/rtc-widget/components/conviComponents/ConviHeader.js.map +1 -0
  100. package/dist/react/components/rtc-widget/components/conviComponents/SettingsTray.d.ts.map +1 -0
  101. package/dist/react/components/rtc-widget/components/conviComponents/SettingsTray.js +68 -0
  102. package/dist/react/components/rtc-widget/components/conviComponents/SettingsTray.js.map +1 -0
  103. package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.d.ts.map +1 -0
  104. package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js +255 -0
  105. package/dist/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.js.map +1 -0
  106. package/dist/react/components/rtc-widget/components/conviComponents/index.d.ts.map +1 -0
  107. package/dist/react/components/rtc-widget/components/conviComponents/index.js +6 -0
  108. package/dist/react/components/rtc-widget/components/conviComponents/index.js.map +1 -0
  109. package/dist/react/components/rtc-widget/components/index.d.ts.map +1 -0
  110. package/dist/react/components/rtc-widget/components/index.js +15 -0
  111. package/dist/react/components/rtc-widget/components/index.js.map +1 -0
  112. package/dist/react/components/rtc-widget/index.d.ts.map +1 -0
  113. package/dist/react/components/rtc-widget/index.js +9 -0
  114. package/dist/react/components/rtc-widget/index.js.map +1 -0
  115. package/dist/react/components/rtc-widget/styles/framerConfig.d.ts.map +1 -0
  116. package/dist/react/components/rtc-widget/styles/framerConfig.js +73 -0
  117. package/dist/react/components/rtc-widget/styles/framerConfig.js.map +1 -0
  118. package/dist/react/components/rtc-widget/styles/icons.d.ts.map +1 -0
  119. package/dist/react/components/rtc-widget/styles/icons.js +257 -0
  120. package/dist/react/components/rtc-widget/styles/icons.js.map +1 -0
  121. package/dist/react/components/rtc-widget/styles/index.d.ts.map +1 -0
  122. package/dist/react/components/rtc-widget/styles/index.js +9 -0
  123. package/dist/react/components/rtc-widget/styles/index.js.map +1 -0
  124. package/dist/react/components/rtc-widget/styles/styledComponents.d.ts.map +1 -0
  125. package/dist/react/components/rtc-widget/styles/styledComponents.js +663 -0
  126. package/dist/react/components/rtc-widget/styles/styledComponents.js.map +1 -0
  127. package/dist/react/components/rtc-widget/styles/theme.d.ts.map +1 -0
  128. package/dist/react/components/rtc-widget/styles/theme.js +290 -0
  129. package/dist/react/components/rtc-widget/styles/theme.js.map +1 -0
  130. package/dist/react/components/rtc-widget/types/index.d.ts.map +1 -0
  131. package/dist/react/components/rtc-widget/types/index.js +2 -0
  132. package/dist/react/components/rtc-widget/types/index.js.map +1 -0
  133. package/dist/react/hooks/index.d.ts.map +1 -0
  134. package/dist/react/hooks/index.js +6 -0
  135. package/dist/react/hooks/index.js.map +1 -0
  136. package/dist/react/hooks/useCharacterInfo.d.ts.map +1 -0
  137. package/dist/react/hooks/useCharacterInfo.js +60 -0
  138. package/dist/react/hooks/useCharacterInfo.js.map +1 -0
  139. package/dist/react/hooks/useConvaiClient.d.ts +35 -0
  140. package/dist/react/hooks/useConvaiClient.d.ts.map +1 -0
  141. package/dist/react/hooks/useConvaiClient.js +183 -0
  142. package/dist/react/hooks/useConvaiClient.js.map +1 -0
  143. package/dist/react/hooks/useLocalCameraTrack.d.ts.map +1 -0
  144. package/dist/react/hooks/useLocalCameraTrack.js +34 -0
  145. package/dist/react/hooks/useLocalCameraTrack.js.map +1 -0
  146. package/dist/{types/react → react}/index.d.ts +0 -2
  147. package/dist/react/index.d.ts.map +1 -0
  148. package/dist/react/index.js +13 -0
  149. package/dist/react/index.js.map +1 -0
  150. package/dist/types/index.d.ts +260 -1
  151. package/dist/types/index.d.ts.map +1 -1
  152. package/dist/types/index.js +2 -0
  153. package/dist/types/index.js.map +1 -0
  154. package/dist/utils/LatencyMonitor.d.ts.map +1 -0
  155. package/dist/utils/LatencyMonitor.js +136 -0
  156. package/dist/utils/LatencyMonitor.js.map +1 -0
  157. package/dist/utils/logger.d.ts.map +1 -0
  158. package/dist/utils/logger.js +96 -0
  159. package/dist/utils/logger.js.map +1 -0
  160. package/dist/utils/speakerManagement.d.ts.map +1 -0
  161. package/dist/utils/speakerManagement.js +64 -0
  162. package/dist/utils/speakerManagement.js.map +1 -0
  163. package/dist/{types/vanilla → vanilla}/AudioRenderer.d.ts +5 -0
  164. package/dist/vanilla/AudioRenderer.d.ts.map +1 -0
  165. package/dist/vanilla/AudioRenderer.js +135 -0
  166. package/dist/vanilla/AudioRenderer.js.map +1 -0
  167. package/dist/vanilla/ConvaiWidget.d.ts.map +1 -0
  168. package/dist/vanilla/ConvaiWidget.js +1786 -0
  169. package/dist/vanilla/ConvaiWidget.js.map +1 -0
  170. package/dist/vanilla/icons.d.ts.map +1 -0
  171. package/dist/vanilla/icons.js +222 -0
  172. package/dist/vanilla/icons.js.map +1 -0
  173. package/dist/{types/vanilla → vanilla}/index.d.ts +1 -3
  174. package/dist/vanilla/index.d.ts.map +1 -0
  175. package/dist/vanilla/index.js +20 -5507
  176. package/dist/vanilla/index.js.map +1 -0
  177. package/dist/vanilla/styles.d.ts.map +1 -0
  178. package/dist/vanilla/styles.js +287 -0
  179. package/dist/vanilla/styles.js.map +1 -0
  180. package/dist/vanilla/types.d.ts +43 -0
  181. package/dist/vanilla/types.d.ts.map +1 -0
  182. package/dist/vanilla/types.js +2 -0
  183. package/dist/vanilla/types.js.map +1 -0
  184. package/package.json +33 -38
  185. package/CHANGELOG.md +0 -165
  186. package/dist/core/index.cjs +0 -1977
  187. package/dist/lipsync-helpers/index.cjs +0 -1195
  188. package/dist/types/core/AudioManager.d.ts.map +0 -1
  189. package/dist/types/core/ConvaiClient.d.ts.map +0 -1
  190. package/dist/types/core/EventEmitter.d.ts.map +0 -1
  191. package/dist/types/core/MessageHandler.d.ts.map +0 -1
  192. package/dist/types/core/ScreenShareManager.d.ts.map +0 -1
  193. package/dist/types/core/VideoManager.d.ts.map +0 -1
  194. package/dist/types/core/index.d.ts.map +0 -1
  195. package/dist/types/core/types.d.ts.map +0 -1
  196. package/dist/types/dev.d.ts.map +0 -1
  197. package/dist/types/lipsync-helpers/arkitBlendshapeHelpers.d.ts.map +0 -1
  198. package/dist/types/lipsync-helpers/arkitOrder61.d.ts.map +0 -1
  199. package/dist/types/lipsync-helpers/arkitPhonemeReference.d.ts.map +0 -1
  200. package/dist/types/lipsync-helpers/index.d.ts.map +0 -1
  201. package/dist/types/lipsync-helpers/neurosyncBlendshapeMapper.d.ts.map +0 -1
  202. package/dist/types/react/components/ConvaiWidget.d.ts.map +0 -1
  203. package/dist/types/react/components/index.d.ts.map +0 -1
  204. package/dist/types/react/components/rtc-widget/components/AudioSettingsPanel.d.ts.map +0 -1
  205. package/dist/types/react/components/rtc-widget/components/AudioVisualizer.d.ts.map +0 -1
  206. package/dist/types/react/components/rtc-widget/components/ConviMessage.d.ts.map +0 -1
  207. package/dist/types/react/components/rtc-widget/components/FloatingVideo.d.ts.map +0 -1
  208. package/dist/types/react/components/rtc-widget/components/MarkdownRenderer.d.ts.map +0 -1
  209. package/dist/types/react/components/rtc-widget/components/MessageBubble.d.ts.map +0 -1
  210. package/dist/types/react/components/rtc-widget/components/MessageList.d.ts.map +0 -1
  211. package/dist/types/react/components/rtc-widget/components/UserMessage.d.ts.map +0 -1
  212. package/dist/types/react/components/rtc-widget/components/conviComponents/ConviButton.d.ts.map +0 -1
  213. package/dist/types/react/components/rtc-widget/components/conviComponents/ConviFooter.d.ts.map +0 -1
  214. package/dist/types/react/components/rtc-widget/components/conviComponents/ConviHeader.d.ts.map +0 -1
  215. package/dist/types/react/components/rtc-widget/components/conviComponents/SettingsTray.d.ts.map +0 -1
  216. package/dist/types/react/components/rtc-widget/components/conviComponents/VoiceModeOverlay.d.ts.map +0 -1
  217. package/dist/types/react/components/rtc-widget/components/conviComponents/index.d.ts.map +0 -1
  218. package/dist/types/react/components/rtc-widget/components/index.d.ts.map +0 -1
  219. package/dist/types/react/components/rtc-widget/index.d.ts.map +0 -1
  220. package/dist/types/react/components/rtc-widget/styles/framerConfig.d.ts.map +0 -1
  221. package/dist/types/react/components/rtc-widget/styles/icons.d.ts.map +0 -1
  222. package/dist/types/react/components/rtc-widget/styles/index.d.ts.map +0 -1
  223. package/dist/types/react/components/rtc-widget/styles/styledComponents.d.ts.map +0 -1
  224. package/dist/types/react/components/rtc-widget/styles/theme.d.ts.map +0 -1
  225. package/dist/types/react/components/rtc-widget/types/index.d.ts.map +0 -1
  226. package/dist/types/react/hooks/index.d.ts.map +0 -1
  227. package/dist/types/react/hooks/useCharacterInfo.d.ts.map +0 -1
  228. package/dist/types/react/hooks/useConvaiClient.d.ts +0 -141
  229. package/dist/types/react/hooks/useConvaiClient.d.ts.map +0 -1
  230. package/dist/types/react/hooks/useLocalCameraTrack.d.ts.map +0 -1
  231. package/dist/types/react/index.d.ts.map +0 -1
  232. package/dist/types/types/index.d.ts +0 -261
  233. package/dist/types/types/index.d.ts.map +0 -1
  234. package/dist/types/utils/LatencyMonitor.d.ts.map +0 -1
  235. package/dist/types/utils/logger.d.ts.map +0 -1
  236. package/dist/types/utils/speakerManagement.d.ts.map +0 -1
  237. package/dist/types/vanilla/AudioRenderer.d.ts.map +0 -1
  238. package/dist/types/vanilla/ConvaiWidget.d.ts.map +0 -1
  239. package/dist/types/vanilla/icons.d.ts.map +0 -1
  240. package/dist/types/vanilla/index.d.ts.map +0 -1
  241. package/dist/types/vanilla/styles.d.ts.map +0 -1
  242. package/dist/types/vanilla/types.d.ts +0 -106
  243. package/dist/types/vanilla/types.d.ts.map +0 -1
  244. package/dist/umd/convai.umd.js +0 -1
  245. package/dist/vanilla/index.cjs +0 -5557
  246. /package/dist/{types/core → core}/AudioManager.d.ts +0 -0
  247. /package/dist/{types/core → core}/EventEmitter.d.ts +0 -0
  248. /package/dist/{types/core → core}/ScreenShareManager.d.ts +0 -0
  249. /package/dist/{types/core → core}/VideoManager.d.ts +0 -0
  250. /package/dist/{types/dev.d.ts → dev.d.ts} +0 -0
  251. /package/dist/{types/lipsync-helpers → lipsync-helpers}/arkitBlendshapeHelpers.d.ts +0 -0
  252. /package/dist/{types/lipsync-helpers → lipsync-helpers}/arkitOrder61.d.ts +0 -0
  253. /package/dist/{types/lipsync-helpers → lipsync-helpers}/arkitPhonemeReference.d.ts +0 -0
  254. /package/dist/{types/lipsync-helpers → lipsync-helpers}/neurosyncBlendshapeMapper.d.ts +0 -0
  255. /package/dist/{types/react → react}/components/ConvaiWidget.d.ts +0 -0
  256. /package/dist/{types/react → react}/components/index.d.ts +0 -0
  257. /package/dist/{types/react → react}/components/rtc-widget/components/AudioSettingsPanel.d.ts +0 -0
  258. /package/dist/{types/react → react}/components/rtc-widget/components/AudioVisualizer.d.ts +0 -0
  259. /package/dist/{types/react → react}/components/rtc-widget/components/ConviMessage.d.ts +0 -0
  260. /package/dist/{types/react → react}/components/rtc-widget/components/FloatingVideo.d.ts +0 -0
  261. /package/dist/{types/react → react}/components/rtc-widget/components/MarkdownRenderer.d.ts +0 -0
  262. /package/dist/{types/react → react}/components/rtc-widget/components/MessageBubble.d.ts +0 -0
  263. /package/dist/{types/react → react}/components/rtc-widget/components/MessageList.d.ts +0 -0
  264. /package/dist/{types/react → react}/components/rtc-widget/components/UserMessage.d.ts +0 -0
  265. /package/dist/{types/react → react}/components/rtc-widget/components/conviComponents/ConviButton.d.ts +0 -0
  266. /package/dist/{types/react → react}/components/rtc-widget/components/conviComponents/ConviFooter.d.ts +0 -0
  267. /package/dist/{types/react → react}/components/rtc-widget/components/conviComponents/ConviHeader.d.ts +0 -0
  268. /package/dist/{types/react → react}/components/rtc-widget/components/conviComponents/SettingsTray.d.ts +0 -0
  269. /package/dist/{types/react → react}/components/rtc-widget/components/conviComponents/VoiceModeOverlay.d.ts +0 -0
  270. /package/dist/{types/react → react}/components/rtc-widget/components/conviComponents/index.d.ts +0 -0
  271. /package/dist/{types/react → react}/components/rtc-widget/components/index.d.ts +0 -0
  272. /package/dist/{types/react → react}/components/rtc-widget/index.d.ts +0 -0
  273. /package/dist/{types/react → react}/components/rtc-widget/styles/framerConfig.d.ts +0 -0
  274. /package/dist/{types/react → react}/components/rtc-widget/styles/icons.d.ts +0 -0
  275. /package/dist/{types/react → react}/components/rtc-widget/styles/index.d.ts +0 -0
  276. /package/dist/{types/react → react}/components/rtc-widget/styles/styledComponents.d.ts +0 -0
  277. /package/dist/{types/react → react}/components/rtc-widget/styles/theme.d.ts +0 -0
  278. /package/dist/{types/react → react}/components/rtc-widget/types/index.d.ts +0 -0
  279. /package/dist/{types/react → react}/hooks/index.d.ts +0 -0
  280. /package/dist/{types/react → react}/hooks/useCharacterInfo.d.ts +0 -0
  281. /package/dist/{types/react → react}/hooks/useLocalCameraTrack.d.ts +0 -0
  282. /package/dist/{types/utils → utils}/LatencyMonitor.d.ts +0 -0
  283. /package/dist/{types/utils → utils}/logger.d.ts +0 -0
  284. /package/dist/{types/utils → utils}/speakerManagement.d.ts +0 -0
  285. /package/dist/{types/vanilla → vanilla}/ConvaiWidget.d.ts +0 -0
  286. /package/dist/{types/vanilla → vanilla}/icons.d.ts +0 -0
  287. /package/dist/{types/vanilla → vanilla}/styles.d.ts +0 -0
@@ -1,1970 +1,14 @@
1
- import { RoomEvent, Room } from 'livekit-client';
2
-
3
- /**
4
- * Logger utility that only logs in development mode.
5
- * Completely silent in production builds and npm packages.
6
- *
7
- * This logger is designed to be tree-shakeable and completely removed
8
- * from production builds when not used.
9
- *
10
- * To test logger behavior:
11
- * - Development: Set NODE_ENV=development or run on localhost
12
- * - Production: Set NODE_ENV=production or deploy to production
13
- *
14
- * In npm packages, this logger will be completely silent by default.
15
- */
16
- // Environment detection with multiple fallbacks
17
- const detectEnvironment = () => {
18
- // Check if we're in a browser environment
19
- const isBrowser = typeof window !== 'undefined';
20
- // Safe process.env access (Vite/Webpack replaces this at build time)
21
- const getEnv = (key) => {
22
- try {
23
- // @ts-ignore - process.env is replaced at build time by bundlers
24
- return typeof process !== 'undefined' && process.env ? process.env[key] : undefined;
25
- }
26
- catch {
27
- return undefined;
28
- }
29
- };
30
- // Priority 1: Check NEXT_PUBLIC_ENVIRONMENT first (most explicit)
31
- const publicEnv = getEnv('NEXT_PUBLIC_ENVIRONMENT');
32
- if (publicEnv === 'PRODUCTION') {
33
- return 'production';
34
- }
35
- if (publicEnv === 'PREVIEW' || publicEnv === 'STAGING') {
36
- return 'development';
37
- }
38
- // Priority 2: Check NODE_ENV and REACT_APP_ENV (Vite replaces import.meta.env.MODE)
39
- const nodeEnv = getEnv('NODE_ENV');
40
- const reactEnv = getEnv('REACT_APP_ENV');
41
- if (nodeEnv === 'production' || reactEnv === 'production') {
42
- return 'production';
43
- }
44
- if (nodeEnv === 'development' || reactEnv === 'development') {
45
- return 'development';
46
- }
47
- // Priority 3: Check for Vite dev server
48
- const viteDevUrl = getEnv('VITE_DEV_SERVER_URL');
49
- if (viteDevUrl !== undefined) {
50
- return 'development';
51
- }
52
- // Priority 4: Check browser environment (only if no explicit env vars are set)
53
- if (isBrowser) {
54
- // Localhost is typically development
55
- if (window.location?.hostname === 'localhost' || window.location?.hostname === '127.0.0.1') {
56
- return 'development';
57
- }
58
- // Check for development ports
59
- if (window.location?.port && ['3000', '3001', '5173', '8080'].includes(window.location.port)) {
60
- return 'development';
61
- }
62
- }
63
- // Default to production for safety
64
- return 'production';
65
- };
66
- const environment = detectEnvironment();
67
- const isDevelopment = environment === 'development';
68
- // No-op function that gets completely removed by tree shaking
69
- const noop = () => {
70
- // This function is intentionally empty and will be removed in production
71
- };
72
- // Development logger functions
73
- const devLogger = {
74
- log: (...args) => console.log('[Convai]', ...args),
75
- warn: (...args) => console.warn('[Convai]', ...args),
76
- error: (...args) => console.error('[Convai]', ...args),
77
- info: (...args) => console.info('[Convai]', ...args),
78
- debug: (...args) => console.debug('[Convai]', ...args),
79
- trace: (...args) => console.trace('[Convai]', ...args),
80
- };
81
- // Production logger functions (all no-ops)
82
- const prodLogger = {
83
- log: noop,
84
- warn: noop,
85
- error: noop,
86
- info: noop,
87
- debug: noop,
88
- trace: noop,
89
- };
90
- // Export the appropriate logger based on environment
91
- const logger = isDevelopment ? devLogger : prodLogger;
92
-
93
- class EventEmitter {
94
- constructor() {
95
- this.events = new Map();
96
- }
97
- /**
98
- * Subscribe to an event
99
- * @param event Event name
100
- * @param callback Callback function
101
- * @returns Unsubscribe function
102
- */
103
- on(event, callback) {
104
- if (!this.events.has(event)) {
105
- this.events.set(event, new Set());
106
- }
107
- this.events.get(event).add(callback);
108
- // Return unsubscribe function
109
- return () => {
110
- this.off(event, callback);
111
- };
112
- }
113
- /**
114
- * Unsubscribe from an event
115
- * @param event Event name
116
- * @param callback Callback function to remove
117
- */
118
- off(event, callback) {
119
- const callbacks = this.events.get(event);
120
- if (callbacks) {
121
- callbacks.delete(callback);
122
- if (callbacks.size === 0) {
123
- this.events.delete(event);
124
- }
125
- }
126
- }
127
- /**
128
- * Emit an event to all subscribers
129
- * @param event Event name
130
- * @param args Arguments to pass to callbacks
131
- */
132
- emit(event, ...args) {
133
- const callbacks = this.events.get(event);
134
- if (callbacks) {
135
- callbacks.forEach((callback) => {
136
- try {
137
- callback(...args);
138
- }
139
- catch (error) {
140
- console.error(`Error in event handler for "${event}":`, error);
141
- }
142
- });
143
- }
144
- }
145
- /**
146
- * Remove all event listeners
147
- */
148
- removeAllListeners() {
149
- this.events.clear();
150
- }
151
- /**
152
- * Get the number of listeners for an event
153
- * @param event Event name
154
- * @returns Number of listeners
155
- */
156
- listenerCount(event) {
157
- return this.events.get(event)?.size ?? 0;
158
- }
159
- }
160
-
161
- /**
162
- * Manages audio controls for LiveKit room
163
- * Provides methods to enable/disable microphone, manage audio devices,
164
- * and control audio state.
165
- */
166
- class AudioManager extends EventEmitter {
167
- constructor(room) {
168
- super();
169
- this.room = null;
170
- this._isAudioEnabled = false;
171
- this._isAudioMuted = false;
172
- this._audioLevel = 0;
173
- if (room) {
174
- this.setRoom(room);
175
- }
176
- }
177
- get isAudioEnabled() {
178
- return this._isAudioEnabled;
179
- }
180
- get isAudioMuted() {
181
- return this._isAudioMuted;
182
- }
183
- get audioLevel() {
184
- return this._audioLevel;
185
- }
186
- /**
187
- * Set the LiveKit room instance
188
- */
189
- setRoom(room) {
190
- // Clean up previous room listeners
191
- if (this.room) {
192
- this.cleanupListeners();
193
- }
194
- this.room = room;
195
- if (room && room.state !== 'disconnected') {
196
- this.setupListeners();
197
- this.updateState();
198
- }
199
- }
200
- /**
201
- * Setup event listeners for room
202
- */
203
- setupListeners() {
204
- if (!this.room)
205
- return;
206
- const localParticipant = this.room.localParticipant;
207
- const handleTrackMuted = (track) => {
208
- if (track.source === 'microphone') {
209
- this._isAudioMuted = true;
210
- this._isAudioEnabled = false;
211
- this.emit('audioStateChange', { isAudioMuted: true, isAudioEnabled: false });
212
- }
213
- };
214
- const handleTrackUnmuted = (track) => {
215
- if (track.source === 'microphone') {
216
- this._isAudioMuted = false;
217
- this._isAudioEnabled = true;
218
- this.emit('audioStateChange', { isAudioMuted: false, isAudioEnabled: true });
219
- }
220
- };
221
- const handleTrackPublished = (track) => {
222
- if (track.source === 'microphone') {
223
- this._isAudioEnabled = true;
224
- this._isAudioMuted = false;
225
- this.emit('audioStateChange', { isAudioMuted: false, isAudioEnabled: true });
226
- }
227
- };
228
- const handleTrackUnpublished = (track) => {
229
- if (track.source === 'microphone') {
230
- this._isAudioEnabled = false;
231
- this._isAudioMuted = true;
232
- this.emit('audioStateChange', { isAudioMuted: true, isAudioEnabled: false });
233
- }
234
- };
235
- localParticipant.on('trackMuted', handleTrackMuted);
236
- localParticipant.on('trackUnmuted', handleTrackUnmuted);
237
- localParticipant.on('trackPublished', handleTrackPublished);
238
- localParticipant.on('trackUnpublished', handleTrackUnpublished);
239
- }
240
- /**
241
- * Clean up event listeners
242
- */
243
- cleanupListeners() {
244
- if (!this.room)
245
- return;
246
- // LiveKit handles cleanup automatically when participant is removed
247
- }
248
- /**
249
- * Force-sync the stored audio state with the underlying LiveKit room.
250
- * Useful right after a connection completes so the UI can reflect
251
- * the current microphone permission without waiting for a toggle event.
252
- */
253
- syncStateFromRoom(options = {}) {
254
- const { emit = false } = options;
255
- if (!this.room || this.room.state === 'disconnected') {
256
- const stateChanged = this._isAudioEnabled !== false ||
257
- this._isAudioMuted !== true ||
258
- this._audioLevel !== 0;
259
- this._isAudioEnabled = false;
260
- this._isAudioMuted = true;
261
- this._audioLevel = 0;
262
- if (emit && stateChanged) {
263
- this.emit('audioStateChange', {
264
- isAudioMuted: this._isAudioMuted,
265
- isAudioEnabled: this._isAudioEnabled,
266
- audioLevel: this._audioLevel,
267
- });
268
- }
269
- return;
270
- }
271
- const localParticipant = this.room.localParticipant;
272
- const isMicEnabled = localParticipant?.isMicrophoneEnabled ?? false;
273
- const nextMutedState = !isMicEnabled;
274
- const stateChanged = this._isAudioEnabled !== isMicEnabled ||
275
- this._isAudioMuted !== nextMutedState;
276
- this._isAudioEnabled = isMicEnabled;
277
- this._isAudioMuted = nextMutedState;
278
- if (emit && stateChanged) {
279
- this.emit('audioStateChange', {
280
- isAudioMuted: this._isAudioMuted,
281
- isAudioEnabled: this._isAudioEnabled,
282
- audioLevel: this._audioLevel,
283
- });
284
- }
285
- }
286
- /**
287
- * Update audio state from room
288
- */
289
- updateState() {
290
- this.syncStateFromRoom();
291
- }
292
- /**
293
- * Enable audio
294
- */
295
- async enableAudio() {
296
- if (!this.room) {
297
- throw new Error('Room not initialized');
298
- }
299
- try {
300
- await this.room.localParticipant.setMicrophoneEnabled(true);
301
- this._isAudioEnabled = true;
302
- this._isAudioMuted = false;
303
- this.emit('audioStateChange', {
304
- isAudioMuted: false,
305
- isAudioEnabled: true,
306
- audioLevel: this._audioLevel,
307
- });
308
- }
309
- catch (error) {
310
- logger.error("Failed to enable audio:", error);
311
- throw error;
312
- }
313
- }
314
- /**
315
- * Disable audio and completely release microphone
316
- */
317
- async disableAudio() {
318
- if (!this.room) {
319
- throw new Error('Room not initialized');
320
- }
321
- try {
322
- // Get the microphone track before disabling
323
- const micTrack = Array.from(this.room.localParticipant.audioTrackPublications.values()).find((publication) => publication.source === 'microphone' && publication.track)?.track;
324
- // Disable microphone (unpublishes the track)
325
- await this.room.localParticipant.setMicrophoneEnabled(false);
326
- // Explicitly stop the underlying MediaStreamTrack to release hardware
327
- // This removes the browser recording indicator
328
- if (micTrack) {
329
- micTrack.stop();
330
- }
331
- this._isAudioEnabled = false;
332
- this._isAudioMuted = true;
333
- this.emit('audioStateChange', {
334
- isAudioMuted: true,
335
- isAudioEnabled: false,
336
- audioLevel: 0,
337
- });
338
- }
339
- catch (error) {
340
- logger.error("Failed to disable audio:", error);
341
- throw error;
342
- }
343
- }
344
- /**
345
- * Mute audio
346
- */
347
- async muteAudio() {
348
- await this.disableAudio();
349
- }
350
- /**
351
- * Unmute audio
352
- */
353
- async unmuteAudio() {
354
- await this.enableAudio();
355
- }
356
- /**
357
- * Toggle audio
358
- */
359
- async toggleAudio() {
360
- if (this._isAudioMuted) {
361
- await this.unmuteAudio();
362
- }
363
- else {
364
- await this.muteAudio();
365
- }
366
- }
367
- /**
368
- * Set audio device
369
- */
370
- async setAudioDevice(deviceId) {
371
- if (!this.room) {
372
- throw new Error('Room not initialized');
373
- }
374
- try {
375
- await this.room.localParticipant.setMicrophoneEnabled(true, { deviceId });
376
- }
377
- catch (error) {
378
- logger.error("Failed to set audio device:", error);
379
- throw error;
380
- }
381
- }
382
- /**
383
- * Get available audio devices
384
- */
385
- async getAudioDevices() {
386
- try {
387
- const devices = await navigator.mediaDevices.enumerateDevices();
388
- const audioDevices = devices.filter(device => device.kind === 'audioinput');
389
- return audioDevices;
390
- }
391
- catch (error) {
392
- logger.error("Failed to get audio devices:", error);
393
- throw error;
394
- }
395
- }
396
- /**
397
- * Start audio level monitoring
398
- */
399
- startAudioLevelMonitoring() {
400
- if (!this.room) {
401
- return;
402
- }
403
- // TODO: Implement audio level monitoring when LiveKit provides the API
404
- }
405
- /**
406
- * Stop audio level monitoring
407
- */
408
- stopAudioLevelMonitoring() {
409
- this._audioLevel = 0;
410
- }
411
- /**
412
- * Reset state on disconnect
413
- */
414
- reset() {
415
- this._isAudioEnabled = false;
416
- this._isAudioMuted = false;
417
- this._audioLevel = 0;
418
- this.emit('audioStateChange', {
419
- isAudioMuted: false,
420
- isAudioEnabled: false,
421
- audioLevel: 0
422
- });
423
- }
424
- }
425
-
426
- /**
427
- * Manages video controls for LiveKit room
428
- * Provides methods to enable/disable camera, manage video devices,
429
- * and control video state.
430
- */
431
- class VideoManager extends EventEmitter {
432
- constructor(room) {
433
- super();
434
- this.room = null;
435
- this._isVideoEnabled = false;
436
- this._isVideoHidden = false;
437
- if (room) {
438
- this.setRoom(room);
439
- }
440
- }
441
- get isVideoEnabled() {
442
- return this._isVideoEnabled;
443
- }
444
- get isVideoHidden() {
445
- return this._isVideoHidden;
446
- }
447
- /**
448
- * Set the LiveKit room instance
449
- */
450
- setRoom(room) {
451
- // Clean up previous room listeners
452
- if (this.room) {
453
- this.cleanupListeners();
454
- }
455
- this.room = room;
456
- if (room && room.state !== 'disconnected') {
457
- this.setupListeners();
458
- this.updateState();
459
- }
460
- }
461
- /**
462
- * Setup event listeners for room
463
- */
464
- setupListeners() {
465
- if (!this.room)
466
- return;
467
- const localParticipant = this.room.localParticipant;
468
- const handleTrackMuted = (track) => {
469
- if (track.source === 'camera') {
470
- this._isVideoHidden = true;
471
- this.emit('videoStateChange', { isVideoHidden: true });
472
- }
473
- };
474
- const handleTrackUnmuted = (track) => {
475
- if (track.source === 'camera') {
476
- this._isVideoHidden = false;
477
- this.emit('videoStateChange', { isVideoHidden: false });
478
- }
479
- };
480
- const handleTrackPublished = (publication) => {
481
- if (publication.source === 'camera') {
482
- this._isVideoEnabled = true;
483
- this.emit('videoStateChange', { isVideoEnabled: true });
484
- }
485
- };
486
- const handleTrackUnpublished = (publication) => {
487
- if (publication.source === 'camera') {
488
- this._isVideoEnabled = false;
489
- this.emit('videoStateChange', { isVideoEnabled: false });
490
- }
491
- };
492
- localParticipant.on('trackMuted', handleTrackMuted);
493
- localParticipant.on('trackUnmuted', handleTrackUnmuted);
494
- localParticipant.on('trackPublished', handleTrackPublished);
495
- localParticipant.on('trackUnpublished', handleTrackUnpublished);
496
- }
497
- /**
498
- * Clean up event listeners
499
- */
500
- cleanupListeners() {
501
- if (!this.room)
502
- return;
503
- // LiveKit handles cleanup automatically when participant is removed
504
- }
505
- /**
506
- * Update video state from room
507
- */
508
- updateState() {
509
- if (!this.room || this.room.state === 'disconnected') {
510
- this._isVideoEnabled = false;
511
- this._isVideoHidden = false;
512
- return;
513
- }
514
- const localParticipant = this.room.localParticipant;
515
- this._isVideoEnabled = localParticipant.isCameraEnabled;
516
- this._isVideoHidden = false; // LiveKit doesn't have a direct hidden state for video
517
- }
518
- /**
519
- * Enable video
520
- */
521
- async enableVideo() {
522
- if (!this.room) {
523
- throw new Error('Room not initialized');
524
- }
525
- try {
526
- await this.room.localParticipant.setCameraEnabled(true);
527
- this._isVideoEnabled = true;
528
- this.emit('videoStateChange', { isVideoEnabled: true });
529
- }
530
- catch (error) {
531
- logger.error("Failed to enable video:", error);
532
- throw error;
533
- }
534
- }
535
- /**
536
- * Disable video
537
- */
538
- async disableVideo() {
539
- if (!this.room) {
540
- throw new Error('Room not initialized');
541
- }
542
- try {
543
- await this.room.localParticipant.setCameraEnabled(false);
544
- this._isVideoEnabled = false;
545
- this.emit('videoStateChange', { isVideoEnabled: false });
546
- }
547
- catch (error) {
548
- logger.error("Failed to disable video:", error);
549
- throw error;
550
- }
551
- }
552
- /**
553
- * Hide video
554
- */
555
- async hideVideo() {
556
- await this.disableVideo();
557
- }
558
- /**
559
- * Show video
560
- */
561
- async showVideo() {
562
- await this.enableVideo();
563
- }
564
- /**
565
- * Toggle video
566
- */
567
- async toggleVideo() {
568
- if (this._isVideoEnabled) {
569
- await this.disableVideo();
570
- }
571
- else {
572
- await this.enableVideo();
573
- }
574
- }
575
- /**
576
- * Set video device
577
- */
578
- async setVideoDevice(deviceId) {
579
- if (!this.room) {
580
- throw new Error('Room not initialized');
581
- }
582
- try {
583
- await this.room.localParticipant.setCameraEnabled(true, { deviceId });
584
- }
585
- catch (error) {
586
- logger.error("Failed to set video device:", error);
587
- throw error;
588
- }
589
- }
590
- /**
591
- * Get available video devices
592
- */
593
- async getVideoDevices() {
594
- try {
595
- const devices = await navigator.mediaDevices.enumerateDevices();
596
- const videoDevices = devices.filter(device => device.kind === 'videoinput');
597
- return videoDevices;
598
- }
599
- catch (error) {
600
- logger.error("Failed to get video devices:", error);
601
- throw error;
602
- }
603
- }
604
- /**
605
- * Set video quality
606
- */
607
- async setVideoQuality(quality) {
608
- if (!this.room) {
609
- throw new Error('Room not initialized');
610
- }
611
- try {
612
- // TODO: Implement video quality settings when LiveKit provides the API
613
- logger.warn('Video quality setting not yet implemented');
614
- }
615
- catch (error) {
616
- logger.error("Failed to set video quality:", error);
617
- throw error;
618
- }
619
- }
620
- /**
621
- * Reset state on disconnect
622
- */
623
- reset() {
624
- this._isVideoEnabled = false;
625
- this._isVideoHidden = false;
626
- this.emit('videoStateChange', {
627
- isVideoEnabled: false,
628
- isVideoHidden: false
629
- });
630
- }
631
- }
632
-
633
- /**
634
- * Manages screen sharing controls for LiveKit room
635
- * Provides methods to enable/disable screen sharing, manage screen share state,
636
- * and handle screen sharing with audio support.
637
- */
638
- class ScreenShareManager extends EventEmitter {
639
- constructor(room) {
640
- super();
641
- this.room = null;
642
- this._isScreenShareEnabled = false;
643
- this._isScreenShareActive = false;
644
- if (room) {
645
- this.setRoom(room);
646
- }
647
- }
648
- get isScreenShareEnabled() {
649
- return this._isScreenShareEnabled;
650
- }
651
- get isScreenShareActive() {
652
- return this._isScreenShareActive;
653
- }
654
- /**
655
- * Set the LiveKit room instance
656
- */
657
- setRoom(room) {
658
- // Clean up previous room listeners
659
- if (this.room) {
660
- this.cleanupListeners();
661
- }
662
- this.room = room;
663
- if (room && room.state !== 'disconnected') {
664
- this.setupListeners();
665
- this.updateState();
666
- }
667
- }
668
- /**
669
- * Setup event listeners for room
670
- */
671
- setupListeners() {
672
- if (!this.room)
673
- return;
674
- const localParticipant = this.room.localParticipant;
675
- const handleTrackPublished = (track) => {
676
- if (track.source === 'screen_share') {
677
- this._isScreenShareActive = true;
678
- this.emit('screenShareStateChange', { isScreenShareActive: true });
679
- }
680
- };
681
- const handleTrackUnpublished = (track) => {
682
- if (track.source === 'screen_share') {
683
- this._isScreenShareActive = false;
684
- this.emit('screenShareStateChange', { isScreenShareActive: false });
685
- }
686
- };
687
- const handleTrackMuted = (track) => {
688
- if (track.source === 'screen_share') {
689
- this._isScreenShareActive = false;
690
- this.emit('screenShareStateChange', { isScreenShareActive: false });
691
- }
692
- };
693
- const handleTrackUnmuted = (track) => {
694
- if (track.source === 'screen_share') {
695
- this._isScreenShareActive = true;
696
- this.emit('screenShareStateChange', { isScreenShareActive: true });
697
- }
698
- };
699
- localParticipant.on('trackPublished', handleTrackPublished);
700
- localParticipant.on('trackUnpublished', handleTrackUnpublished);
701
- localParticipant.on('trackMuted', handleTrackMuted);
702
- localParticipant.on('trackUnmuted', handleTrackUnmuted);
703
- }
704
- /**
705
- * Clean up event listeners
706
- */
707
- cleanupListeners() {
708
- if (!this.room)
709
- return;
710
- // LiveKit handles cleanup automatically when participant is removed
711
- }
712
- /**
713
- * Update screen share state from room
714
- */
715
- updateState() {
716
- if (!this.room || this.room.state === 'disconnected') {
717
- this._isScreenShareEnabled = false;
718
- this._isScreenShareActive = false;
719
- return;
720
- }
721
- const localParticipant = this.room.localParticipant;
722
- this._isScreenShareEnabled = localParticipant.isScreenShareEnabled;
723
- this._isScreenShareActive = localParticipant.isScreenShareEnabled;
724
- }
725
- /**
726
- * Enable screen share
727
- */
728
- async enableScreenShare() {
729
- if (!this.room) {
730
- throw new Error('Room not initialized');
731
- }
732
- try {
733
- // Use setScreenShareEnabled with options to include current tab
734
- await this.room.localParticipant.setScreenShareEnabled(true, {
735
- // @ts-ignore - selfBrowserSurface is a valid Chrome option but may not be in types
736
- selfBrowserSurface: "include",
737
- surfaceSwitching: "include",
738
- systemAudio: "include",
739
- });
740
- this._isScreenShareEnabled = true;
741
- this._isScreenShareActive = true;
742
- this.emit('screenShareStateChange', { isScreenShareEnabled: true, isScreenShareActive: true });
743
- }
744
- catch (error) {
745
- logger.error("Failed to enable screen share:", error);
746
- throw error;
747
- }
748
- }
749
- /**
750
- * Disable screen share
751
- */
752
- async disableScreenShare() {
753
- if (!this.room) {
754
- throw new Error('Room not initialized');
755
- }
756
- try {
757
- await this.room.localParticipant.setScreenShareEnabled(false);
758
- this._isScreenShareEnabled = false;
759
- this._isScreenShareActive = false;
760
- this.emit('screenShareStateChange', { isScreenShareEnabled: false, isScreenShareActive: false });
761
- }
762
- catch (error) {
763
- logger.error("Failed to disable screen share:", error);
764
- throw error;
765
- }
766
- }
767
- /**
768
- * Toggle screen share
769
- */
770
- async toggleScreenShare() {
771
- if (this._isScreenShareEnabled) {
772
- await this.disableScreenShare();
773
- }
774
- else {
775
- await this.enableScreenShare();
776
- }
777
- }
778
- /**
779
- * Enable screen share with audio
780
- */
781
- async enableScreenShareWithAudio() {
782
- if (!this.room) {
783
- throw new Error('Room not initialized');
784
- }
785
- try {
786
- // Create screen tracks with audio enabled and include current tab
787
- const tracks = await this.room.localParticipant.createScreenTracks({
788
- audio: true,
789
- // @ts-ignore - selfBrowserSurface is a valid Chrome option but may not be in types
790
- selfBrowserSurface: "include",
791
- surfaceSwitching: "include",
792
- systemAudio: "include",
793
- });
794
- // Publish each track
795
- tracks.forEach((track) => {
796
- this.room.localParticipant.publishTrack(track);
797
- });
798
- this._isScreenShareEnabled = true;
799
- }
800
- catch (error) {
801
- logger.error("Failed to enable screen share with audio:", error);
802
- throw error;
803
- }
804
- }
805
- /**
806
- * Get screen share tracks
807
- */
808
- async getScreenShareTracks() {
809
- if (!this.room) {
810
- return [];
811
- }
812
- try {
813
- const localParticipant = this.room.localParticipant;
814
- const screenShareTracks = [];
815
- // Get all published tracks
816
- localParticipant.trackPublications.forEach((publication) => {
817
- if (publication.source === 'screen_share' && publication.track) {
818
- screenShareTracks.push(publication.track);
819
- }
820
- });
821
- return screenShareTracks;
822
- }
823
- catch (error) {
824
- logger.error("Failed to get screen share tracks:", error);
825
- throw error;
826
- }
827
- }
828
- /**
829
- * Reset state on disconnect
830
- */
831
- reset() {
832
- this._isScreenShareEnabled = false;
833
- this._isScreenShareActive = false;
834
- this.emit('screenShareStateChange', {
835
- isScreenShareEnabled: false,
836
- isScreenShareActive: false
837
- });
838
- }
839
- }
840
-
841
- /**
842
- * Handles incoming messages from Convai
843
- * Processes various message types from the AI assistant and updates chat history
844
- */
845
- class MessageHandler extends EventEmitter {
846
- constructor(room, latencyMonitor) {
847
- super();
848
- this.room = null;
849
- this.chatMessages = [];
850
- this.userTranscription = "";
851
- this.isBotResponding = false;
852
- this.isSpeaking = false;
853
- this.latencyMonitor = null;
854
- if (latencyMonitor) {
855
- this.latencyMonitor = latencyMonitor;
856
- }
857
- if (room) {
858
- this.setRoom(room);
859
- }
860
- }
861
- /**
862
- * Set the latency monitor
863
- */
864
- setLatencyMonitor(monitor) {
865
- this.latencyMonitor = monitor;
866
- }
867
- /**
868
- * Get current chat messages
869
- */
870
- getChatMessages() {
871
- return this.chatMessages;
872
- }
873
- /**
874
- * Get current user transcription
875
- */
876
- getUserTranscription() {
877
- return this.userTranscription;
878
- }
879
- /**
880
- * Get bot responding state
881
- */
882
- getIsBotResponding() {
883
- return this.isBotResponding;
884
- }
885
- /**
886
- * Get speaking state
887
- */
888
- getIsSpeaking() {
889
- return this.isSpeaking;
890
- }
891
- /**
892
- * Set the LiveKit room instance
893
- */
894
- setRoom(room) {
895
- // Clean up previous room listeners
896
- if (this.room) {
897
- this.cleanupListeners();
898
- }
899
- this.room = room;
900
- this.setupListeners();
901
- }
902
- /**
903
- * Setup message listeners
904
- */
905
- setupListeners() {
906
- if (!this.room)
907
- return;
908
- this.room.on(RoomEvent.DataReceived, this.handleDataReceived.bind(this));
909
- }
910
- /**
911
- * Clean up message listeners
912
- */
913
- cleanupListeners() {
914
- if (!this.room)
915
- return;
916
- this.room.off(RoomEvent.DataReceived, this.handleDataReceived.bind(this));
917
- }
918
- /**
919
- * Handle incoming data message
920
- */
921
- handleDataReceived(payload, participant) {
922
- try {
923
- // Decode bytes to string
924
- const decoder = new TextDecoder();
925
- const messageString = decoder.decode(payload);
926
- // Parse JSON
927
- const messageData = JSON.parse(messageString);
928
- // Extract and categorize messages for chat display
929
- const timestamp = new Date().toISOString();
930
- const messageId = `${messageData.type}-${Date.now()}-${Math.random()}`;
931
- // Handle different message types
932
- switch (messageData.type) {
933
- // User text messages - Skip these, only allow user-transcription
934
- case "user_text_message":
935
- case "user-llm-text":
936
- case "text-input":
937
- case "user-input":
938
- case "text-message":
939
- case "chat-message":
940
- case "input-text":
941
- case "message":
942
- // Skip user text messages - only allow user-transcription
943
- break;
944
- // Bot Ready - Bot is ready, connection should be true
945
- case "bot-ready":
946
- this.emit("botReady");
947
- break;
948
- // Bot LLM Started - Begin streaming response
949
- case "bot-llm-started":
950
- this.isBotResponding = true;
951
- this.emit("botRespondingChange", true);
952
- // Mark any accumulated user message as final before starting bot response
953
- const lastMsg = this.chatMessages[this.chatMessages.length - 1];
954
- if (lastMsg &&
955
- lastMsg.type === "user-transcription" &&
956
- lastMsg.isFinal) {
957
- this.chatMessages[this.chatMessages.length - 1] = {
958
- ...lastMsg,
959
- isFinal: false,
960
- };
961
- }
962
- // Add initial streaming message to chat
963
- const streamingMessage = {
964
- id: messageId,
965
- type: "bot-llm-text",
966
- content: "",
967
- timestamp: timestamp,
968
- isFinal: true,
969
- };
970
- this.chatMessages.push(streamingMessage);
971
- this.emit("messagesChange", this.chatMessages);
972
- break;
973
- // Bot LLM Text Messages - Streaming chunks
974
- case "bot-llm-text":
975
- if (messageData.data?.text) {
976
- const newChunk = messageData.data.text;
977
- // Update the streaming message in chat
978
- const lastMessage = this.chatMessages[this.chatMessages.length - 1];
979
- if (lastMessage && lastMessage.isFinal) {
980
- this.chatMessages[this.chatMessages.length - 1] = {
981
- ...lastMessage,
982
- content: lastMessage.content + newChunk,
983
- };
984
- }
985
- else {
986
- // If no streaming message exists yet, create one
987
- const streamingMsg = {
988
- id: `${messageData.type}-${Date.now()}-${Math.random()}`,
989
- type: "bot-llm-text",
990
- content: newChunk,
991
- timestamp: new Date().toISOString(),
992
- isFinal: true,
993
- };
994
- this.chatMessages.push(streamingMsg);
995
- }
996
- this.emit("messagesChange", this.chatMessages);
997
- }
998
- break;
999
- // Bot LLM Stopped - Complete streaming response
1000
- case "bot-llm-stopped":
1001
- const lastBotMsg = this.chatMessages[this.chatMessages.length - 1];
1002
- if (lastBotMsg && lastBotMsg.isFinal) {
1003
- this.chatMessages[this.chatMessages.length - 1] = {
1004
- ...lastBotMsg,
1005
- isFinal: false,
1006
- };
1007
- }
1008
- // Clear streaming state
1009
- this.isBotResponding = false;
1010
- this.emit("botRespondingChange", false);
1011
- this.emit("messagesChange", this.chatMessages);
1012
- break;
1013
- // User Transcription Messages
1014
- case "user-transcription":
1015
- if (messageData.data?.text) {
1016
- const messageContent = messageData.data.text;
1017
- if (messageData.data?.final) {
1018
- // Final transcription - accumulate in single bubble until bot responds
1019
- const lastUserMsg = this.chatMessages[this.chatMessages.length - 1];
1020
- // Start latency measurement for voice when user finishes speaking
1021
- if (this.latencyMonitor) {
1022
- this.latencyMonitor.startMeasurement("voice", messageContent);
1023
- }
1024
- // Check if last message is an accumulating user message (isFinal=true)
1025
- if (lastUserMsg &&
1026
- lastUserMsg.type === "user-transcription" &&
1027
- lastUserMsg.isFinal === true) {
1028
- // Accumulate: append new content to existing message with space
1029
- const accumulatedContent = lastUserMsg.content.trim() + " " + messageContent.trim();
1030
- this.chatMessages[this.chatMessages.length - 1] = {
1031
- ...lastUserMsg,
1032
- content: accumulatedContent,
1033
- timestamp: timestamp,
1034
- };
1035
- }
1036
- else {
1037
- // Start new accumulating message (isFinal=true means "still accumulating")
1038
- const userMessage = {
1039
- id: messageId,
1040
- type: "user-transcription",
1041
- content: messageContent,
1042
- timestamp: timestamp,
1043
- isFinal: true,
1044
- };
1045
- this.chatMessages.push(userMessage);
1046
- }
1047
- this.emit("messagesChange", this.chatMessages);
1048
- // Clear live transcription when final
1049
- this.userTranscription = "";
1050
- this.emit("userTranscriptionChange", "");
1051
- }
1052
- else {
1053
- // Non-final transcription - show in input field
1054
- this.userTranscription = messageContent;
1055
- this.emit("userTranscriptionChange", messageContent);
1056
- }
1057
- }
1058
- break;
1059
- // Bot Emotion Messages
1060
- case "bot-emotion":
1061
- if (messageData.data?.emotion) {
1062
- const emotionMessage = {
1063
- id: messageId,
1064
- type: "bot-emotion",
1065
- content: `${messageData.data.emotion} (scale: ${messageData.data.scale || 1})`,
1066
- timestamp,
1067
- };
1068
- this.chatMessages.push(emotionMessage);
1069
- console.log("BotEmotions", emotionMessage);
1070
- this.emit("messagesChange", this.chatMessages);
1071
- }
1072
- break;
1073
- // Bot Started Speaking
1074
- case "bot-started-speaking":
1075
- this.isSpeaking = true;
1076
- this.emit("speakingChange", true);
1077
- break;
1078
- // Bot Stopped Speaking
1079
- case "bot-stopped-speaking":
1080
- this.isSpeaking = false;
1081
- this.emit("speakingChange", false);
1082
- break;
1083
- // Action Response Messages
1084
- case "action-response":
1085
- if (messageData.data?.actions) {
1086
- const actionMessage = {
1087
- id: messageId,
1088
- type: "action",
1089
- content: `Actions: ${messageData.data.actions.join(", ")}`,
1090
- timestamp,
1091
- };
1092
- this.chatMessages.push(actionMessage);
1093
- this.emit("messagesChange", this.chatMessages);
1094
- }
1095
- break;
1096
- // Behavior Tree Response Messages
1097
- case "behavior-tree-response":
1098
- if (messageData.data?.narrative_section_id) {
1099
- const behaviorMessage = {
1100
- id: messageId,
1101
- type: "behavior-tree",
1102
- content: `Narrative Section: ${messageData.data.narrative_section_id}`,
1103
- timestamp,
1104
- };
1105
- this.chatMessages.push(behaviorMessage);
1106
- this.emit("messagesChange", this.chatMessages);
1107
- }
1108
- break;
1109
- // Server Message - Contains various data types like blendshapes
1110
- case "server-message":
1111
- if (messageData.data) {
1112
- const serverData = messageData.data;
1113
- // Check if this is blendshape data
1114
- if (serverData.type === "chunked-neurosync-blendshapes" &&
1115
- serverData.blendshapes) {
1116
- // Emit event for external listeners
1117
- this.emit("blendshapes", serverData);
1118
- }
1119
- else {
1120
- // Log other server message types
1121
- logger.info("[MessageHandler] Server message:", {
1122
- type: serverData.type,
1123
- dataKeys: Object.keys(serverData),
1124
- });
1125
- }
1126
- }
1127
- break;
1128
- // Silently ignore other message types
1129
- case "moderation-response":
1130
- case "trigger-message":
1131
- case "update-template-keys":
1132
- case "update-dynamic-info":
1133
- case "interrupt-bot":
1134
- break;
1135
- }
1136
- }
1137
- catch (error) {
1138
- logger.error("Failed to parse data message:", error);
1139
- }
1140
- }
1141
- /**
1142
- * Reset message state
1143
- */
1144
- reset() {
1145
- this.chatMessages = [];
1146
- this.userTranscription = "";
1147
- this.isBotResponding = false;
1148
- this.isSpeaking = false;
1149
- this.emit("messagesChange", this.chatMessages);
1150
- this.emit("userTranscriptionChange", "");
1151
- this.emit("botRespondingChange", false);
1152
- this.emit("speakingChange", false);
1153
- }
1154
- }
1155
-
1156
- /**
1157
- * Resolves the speaker ID based on the end user ID.
1158
- *
1159
- * - If endUserId is not provided (anonymous mode), returns null (no speaker ID sent to API)
1160
- * - If endUserId is provided, calls Convai API to get or create a speaker ID for the user
1161
- *
1162
- * @param endUserId - The end user identifier (optional). If not provided, anonymous mode is used.
1163
- * @param apiKey - Convai API key for authentication
1164
- * @returns Promise resolving to the speaker ID or null if no endUserId provided
1165
- */
1166
- async function resolveSpeakerId(endUserId, apiKey) {
1167
- // Anonymous mode: No speaker ID (no persistent memory or analytics)
1168
- if (!endUserId) {
1169
- logger.info('Anonymous mode: No endUserId provided, speaker ID will not be sent');
1170
- return null;
1171
- }
1172
- // User tracking mode: Get or create speaker ID via Convai API (enables memory and analytics)
1173
- try {
1174
- const speakerId = await getOrCreateSpeakerId(endUserId, apiKey);
1175
- logger.info(`Resolved speaker ID: ${speakerId.substring(0, 8)}...`);
1176
- return speakerId;
1177
- }
1178
- catch (error) {
1179
- logger.error('Failed to resolve speaker ID:', error);
1180
- throw error;
1181
- }
1182
- }
1183
- /**
1184
- * Gets or creates a speaker ID from Convai's speaker management API.
1185
- * If the same endUserId is used multiple times, the same speaker ID is returned (idempotent).
1186
- *
1187
- * @param endUserId - The device or user identifier
1188
- * @param apiKey - Convai API key for authentication
1189
- * @returns Promise resolving to the speaker ID
1190
- * @throws Error if API request fails
1191
- */
1192
- async function getOrCreateSpeakerId(endUserId, apiKey) {
1193
- const url = 'https://api.convai.com/user/speaker/new';
1194
- logger.info(`Requesting speaker ID for endUserId: ${endUserId.substring(0, 8)}...`);
1195
- try {
1196
- const response = await fetch(url, {
1197
- method: 'POST',
1198
- headers: {
1199
- 'Content-Type': 'application/json',
1200
- 'CONVAI-API-KEY': apiKey,
1201
- },
1202
- body: JSON.stringify({
1203
- deviceId: endUserId,
1204
- }),
1205
- });
1206
- if (!response.ok) {
1207
- const errorText = await response.text();
1208
- throw new Error(`Speaker API request failed: ${response.status} ${response.statusText} - ${errorText}`);
1209
- }
1210
- const data = await response.json();
1211
- if (!data.speakerId) {
1212
- throw new Error('Speaker API did not return a speaker ID');
1213
- }
1214
- return data.speakerId;
1215
- }
1216
- catch (error) {
1217
- if (error instanceof Error) {
1218
- throw new Error(`Failed to get speaker ID: ${error.message}`);
1219
- }
1220
- throw new Error('Failed to get speaker ID: Unknown error');
1221
- }
1222
- }
1223
-
1224
- /**
1225
- * LatencyMonitor - Tracks and manages latency measurements
1226
- * This is for dev/debugging purposes only
1227
- */
1228
- class LatencyMonitor extends EventEmitter {
1229
- constructor(enabled = false) {
1230
- super();
1231
- this.measurements = [];
1232
- this.pendingMeasurement = null;
1233
- this.maxMeasurements = 100; // Keep last 100 measurements
1234
- this.isEnabled = false;
1235
- this.isEnabled = enabled;
1236
- }
1237
- /**
1238
- * Enable latency monitoring
1239
- */
1240
- enable() {
1241
- this.isEnabled = true;
1242
- this.emit("enabledChange", true);
1243
- }
1244
- /**
1245
- * Disable latency monitoring
1246
- */
1247
- disable() {
1248
- this.isEnabled = false;
1249
- this.emit("enabledChange", false);
1250
- }
1251
- /**
1252
- * Check if monitoring is enabled
1253
- */
1254
- get enabled() {
1255
- return this.isEnabled;
1256
- }
1257
- /**
1258
- * Start a new latency measurement
1259
- */
1260
- startMeasurement(type, userMessage) {
1261
- if (!this.isEnabled)
1262
- return;
1263
- const id = `${type}-${Date.now()}-${Math.random()}`;
1264
- this.pendingMeasurement = {
1265
- id,
1266
- startTime: Date.now(),
1267
- type,
1268
- userMessage: userMessage?.substring(0, 50), // Keep first 50 chars
1269
- };
1270
- }
1271
- /**
1272
- * Complete the current measurement
1273
- */
1274
- endMeasurement() {
1275
- if (!this.isEnabled || !this.pendingMeasurement)
1276
- return;
1277
- const endTime = Date.now();
1278
- const latency = endTime - this.pendingMeasurement.startTime;
1279
- const measurement = {
1280
- id: this.pendingMeasurement.id,
1281
- startTime: this.pendingMeasurement.startTime,
1282
- endTime,
1283
- latency,
1284
- type: this.pendingMeasurement.type,
1285
- userMessage: this.pendingMeasurement.userMessage,
1286
- };
1287
- // Add to measurements array
1288
- this.measurements.push(measurement);
1289
- // Keep only the last N measurements
1290
- if (this.measurements.length > this.maxMeasurements) {
1291
- this.measurements.shift();
1292
- }
1293
- // Clear pending measurement
1294
- this.pendingMeasurement = null;
1295
- // Emit new measurement
1296
- this.emit("measurement", measurement);
1297
- this.emit("measurementsChange", this.measurements);
1298
- }
1299
- /**
1300
- * Cancel the current pending measurement
1301
- */
1302
- cancelMeasurement() {
1303
- this.pendingMeasurement = null;
1304
- }
1305
- /**
1306
- * Get all measurements
1307
- */
1308
- getMeasurements() {
1309
- return [...this.measurements];
1310
- }
1311
- /**
1312
- * Get the latest measurement
1313
- */
1314
- getLatestMeasurement() {
1315
- return this.measurements.length > 0
1316
- ? this.measurements[this.measurements.length - 1]
1317
- : null;
1318
- }
1319
- /**
1320
- * Get latency statistics
1321
- */
1322
- getStats() {
1323
- if (this.measurements.length === 0)
1324
- return null;
1325
- const latencies = this.measurements.map((m) => m.latency).sort((a, b) => a - b);
1326
- const sum = latencies.reduce((acc, val) => acc + val, 0);
1327
- const p95Index = Math.floor(latencies.length * 0.95);
1328
- return {
1329
- average: sum / latencies.length,
1330
- min: latencies[0],
1331
- max: latencies[latencies.length - 1],
1332
- median: latencies[Math.floor(latencies.length / 2)],
1333
- p95: latencies[p95Index],
1334
- count: latencies.length,
1335
- };
1336
- }
1337
- /**
1338
- * Clear all measurements
1339
- */
1340
- clear() {
1341
- this.measurements = [];
1342
- this.pendingMeasurement = null;
1343
- this.emit("measurementsChange", this.measurements);
1344
- }
1345
- /**
1346
- * Check if there's a pending measurement
1347
- */
1348
- get hasPendingMeasurement() {
1349
- return this.pendingMeasurement !== null;
1350
- }
1351
- /**
1352
- * Get pending measurement info
1353
- */
1354
- getPendingMeasurement() {
1355
- return this.pendingMeasurement ? { ...this.pendingMeasurement } : null;
1356
- }
1357
- }
1358
-
1359
- /**
1360
- * Main Convai client class for managing AI voice assistant connections
1361
- * Provides complete interface for connecting to Convai's voice assistants,
1362
- * managing real-time audio/video conversations, and handling messages.
1363
- *
1364
- * @example
1365
- * ```typescript
1366
- * import { ConvaiClient } from '@convai/web-sdk/core';
1367
- *
1368
- * // Recommended: Pass config to constructor
1369
- * const client = new ConvaiClient({
1370
- * apiKey: 'your-api-key',
1371
- * characterId: 'your-character-id',
1372
- * endUserId: 'user-uuid', // Optional: enables memory & analytics
1373
- * enableVideo: true,
1374
- * });
1375
- *
1376
- * // Listen for state changes
1377
- * client.on('stateChange', (state) => {
1378
- * console.log('State:', state);
1379
- * });
1380
- *
1381
- * // Connect (uses stored config from constructor)
1382
- * await client.connect();
1383
- *
1384
- * // Send a message
1385
- * client.sendUserTextMessage('Hello!');
1386
- *
1387
- * // Advanced: Override config or connect without constructor config
1388
- * await client.connect({ apiKey: 'different-key', characterId: 'different-id' });
1389
- *
1390
- * // Disconnect and reconnect
1391
- * await client.disconnect();
1392
- * await client.reconnect();
1393
- * ```
1394
- */
1395
- class ConvaiClient extends EventEmitter {
1396
- constructor(config) {
1397
- super();
1398
- this._connectionType = null;
1399
- this._apiKey = null;
1400
- this._characterId = null;
1401
- this._speakerId = null;
1402
- this._characterSessionId = "-1";
1403
- this._isBotReady = false;
1404
- this._participantSid = "";
1405
- this._storedConfig = null;
1406
- // Store config if provided
1407
- if (config) {
1408
- this._storedConfig = config;
1409
- }
1410
- // Initialize room
1411
- this._room = new Room();
1412
- // Initialize state
1413
- this._state = {
1414
- isConnected: false,
1415
- isConnecting: false,
1416
- isListening: false,
1417
- isThinking: false,
1418
- isSpeaking: false,
1419
- agentState: "disconnected",
1420
- };
1421
- // Default audio settings for optimal interruption handling
1422
- this._audioSettings = {
1423
- echoCancellation: true,
1424
- noiseSuppression: true,
1425
- autoGainControl: true,
1426
- sampleRate: 48000,
1427
- channelCount: 1,
1428
- };
1429
- // Initialize managers
1430
- this._audioManager = new AudioManager(this._room);
1431
- this._videoManager = new VideoManager(this._room);
1432
- this._screenShareManager = new ScreenShareManager(this._room);
1433
- this._latencyMonitor = new LatencyMonitor(false); // Disabled by default
1434
- this._messageHandler = new MessageHandler(this._room, this._latencyMonitor);
1435
- // Setup event listeners
1436
- this.setupEventListeners();
1437
- }
1438
- // Getters
1439
- get state() {
1440
- return { ...this._state };
1441
- }
1442
- get connectionType() {
1443
- return this._connectionType;
1444
- }
1445
- get apiKey() {
1446
- return this._apiKey;
1447
- }
1448
- get characterId() {
1449
- return this._characterId;
1450
- }
1451
- get speakerId() {
1452
- return this._speakerId;
1453
- }
1454
- get room() {
1455
- return this._room;
1456
- }
1457
- get chatMessages() {
1458
- return this._messageHandler.getChatMessages();
1459
- }
1460
- get userTranscription() {
1461
- return this._messageHandler.getUserTranscription();
1462
- }
1463
- get characterSessionId() {
1464
- return this._characterSessionId;
1465
- }
1466
- get isBotReady() {
1467
- return this._isBotReady;
1468
- }
1469
- get audioControls() {
1470
- return this._audioManager;
1471
- }
1472
- get videoControls() {
1473
- return this._videoManager;
1474
- }
1475
- get screenShareControls() {
1476
- return this._screenShareManager;
1477
- }
1478
- get latencyMonitor() {
1479
- return this._latencyMonitor;
1480
- }
1481
- // Convenience getters to match React hook's top-level reactive state
1482
- // These provide direct access without going through the control managers
1483
- /**
1484
- * Whether the user's microphone is currently muted
1485
- * Convenience getter - equivalent to audioControls.isAudioMuted
1486
- */
1487
- get isAudioMuted() {
1488
- return this._audioManager.isAudioMuted;
1489
- }
1490
- /**
1491
- * Whether the user's video is currently enabled
1492
- * Convenience getter - equivalent to videoControls.isVideoEnabled
1493
- */
1494
- get isVideoEnabled() {
1495
- return this._videoManager.isVideoEnabled;
1496
- }
1497
- /**
1498
- * Whether screen sharing is currently active
1499
- * Convenience getter - equivalent to screenShareControls.isScreenShareActive
1500
- */
1501
- get isScreenShareActive() {
1502
- return this._screenShareManager.isScreenShareActive;
1503
- }
1504
- /**
1505
- * Setup event listeners for room and managers
1506
- */
1507
- setupEventListeners() {
1508
- // Room event listeners
1509
- this._room.on(RoomEvent.Disconnected, this.handleDisconnected.bind(this));
1510
- this._room.on(RoomEvent.ConnectionStateChanged, this.handleConnectionStateChanged.bind(this));
1511
- // Message handler events
1512
- this._messageHandler.on("botReady", () => {
1513
- this._isBotReady = true;
1514
- this.updateState({ isConnected: true });
1515
- this.emit("botReady");
1516
- });
1517
- this._messageHandler.on("messagesChange", (messages) => {
1518
- this.emit("message", messages[messages.length - 1]);
1519
- this.emit("messagesChange", messages);
1520
- });
1521
- this._messageHandler.on("userTranscriptionChange", (transcription) => {
1522
- this.emit("userTranscriptionChange", transcription);
1523
- });
1524
- this._messageHandler.on("speakingChange", (isSpeaking) => {
1525
- this.updateState({ isSpeaking });
1526
- this.emit("speakingChange", isSpeaking);
1527
- // End latency measurement when bot starts speaking
1528
- if (isSpeaking) {
1529
- this._latencyMonitor.endMeasurement();
1530
- }
1531
- });
1532
- // Forward blendshapes events
1533
- this._messageHandler.on("blendshapes", (data) => {
1534
- this.emit("blendshapes", data);
1535
- });
1536
- this._messageHandler.on("botRespondingChange", (isResponding) => {
1537
- this.updateState({ isThinking: isResponding });
1538
- });
1539
- // Forward latency monitor events
1540
- this._latencyMonitor.on("measurement", (measurement) => {
1541
- this.emit("latencyMeasurement", measurement);
1542
- });
1543
- }
1544
- /**
1545
- * Handle room disconnection
1546
- */
1547
- handleDisconnected() {
1548
- this.updateState({
1549
- isConnected: false,
1550
- isConnecting: false,
1551
- isSpeaking: false,
1552
- isThinking: false,
1553
- isListening: false,
1554
- });
1555
- this._isBotReady = false;
1556
- this._messageHandler.reset();
1557
- this.emit("disconnect");
1558
- }
1559
- /**
1560
- * Handle connection state changes
1561
- */
1562
- handleConnectionStateChanged(state) {
1563
- if (state === "disconnected") {
1564
- this.updateState({
1565
- isConnected: false,
1566
- isConnecting: false,
1567
- });
1568
- this._isBotReady = false;
1569
- this._messageHandler.reset();
1570
- }
1571
- else if (state === "connected") {
1572
- this.updateState({
1573
- isConnected: true,
1574
- isConnecting: false,
1575
- });
1576
- this._isBotReady = false; // Wait for bot-ready message
1577
- }
1578
- else if (state === "connecting") {
1579
- this.updateState({
1580
- isConnecting: true,
1581
- isConnected: false,
1582
- });
1583
- this._isBotReady = false;
1584
- }
1585
- }
1586
- /**
1587
- * Update state and emit changes
1588
- */
1589
- updateState(updates) {
1590
- const oldState = { ...this._state };
1591
- this._state = { ...this._state, ...updates };
1592
- // Update agentState based on individual flags
1593
- if (!this._state.isConnected) {
1594
- this._state.agentState = "disconnected";
1595
- }
1596
- else if (this._state.isSpeaking) {
1597
- this._state.agentState = "speaking";
1598
- }
1599
- else if (this._state.isThinking) {
1600
- this._state.agentState = "thinking";
1601
- }
1602
- else if (this._state.isListening) {
1603
- this._state.agentState = "listening";
1604
- }
1605
- else {
1606
- this._state.agentState = "connected";
1607
- }
1608
- // Emit state change if changed
1609
- if (JSON.stringify(oldState) !== JSON.stringify(this._state)) {
1610
- this.emit("stateChange", this._state);
1611
- }
1612
- }
1613
- /**
1614
- * Connect to a Convai character
1615
- */
1616
- async connect(config) {
1617
- // Use provided config or stored config
1618
- const finalConfig = config || this._storedConfig;
1619
- if (!finalConfig) {
1620
- throw new Error("No configuration provided. Pass config to connect() or store it in the client");
1621
- }
1622
- // Store config for reconnection
1623
- this._storedConfig = finalConfig;
1624
- // Add default URL if not provided
1625
- const configWithDefaults = {
1626
- // url: "https://realtime-api.convai.com",
1627
- url: "https://realtime-api-stg.convai.com",
1628
- ...finalConfig,
1629
- };
1630
- if (!configWithDefaults.apiKey || !configWithDefaults.characterId) {
1631
- throw new Error("apiKey and characterId are required");
1632
- }
1633
- this.updateState({ isConnecting: true });
1634
- try {
1635
- // Store connection config
1636
- this._apiKey = configWithDefaults.apiKey;
1637
- this._characterId = configWithDefaults.characterId;
1638
- // Resolve speaker ID based on endUserId
1639
- if (configWithDefaults.endUserId) {
1640
- logger.info(`Resolving speaker ID for endUserId: ${configWithDefaults.endUserId}`);
1641
- }
1642
- else {
1643
- logger.info("Connecting in anonymous mode (no endUserId provided)");
1644
- }
1645
- const resolvedSpeakerId = await resolveSpeakerId(configWithDefaults.endUserId, configWithDefaults.apiKey);
1646
- this._speakerId = resolvedSpeakerId;
1647
- if (resolvedSpeakerId) {
1648
- logger.info(`Speaker ID resolved: ${resolvedSpeakerId.substring(0, 8)}...`);
1649
- }
1650
- else {
1651
- logger.info("No speaker ID - connecting without persistent memory");
1652
- }
1653
- // Determine connection type based on enableVideo
1654
- const connType = configWithDefaults.enableVideo ? "video" : "audio";
1655
- this._connectionType = connType;
1656
- // Prepare request body with required parameters
1657
- const requestBody = {
1658
- character_id: configWithDefaults.characterId,
1659
- ...(resolvedSpeakerId && { speaker_id: resolvedSpeakerId }),
1660
- transport: "livekit",
1661
- connection_type: connType,
1662
- use_dynamic_pipeline: false,
1663
- blendshape_provider: configWithDefaults.blendshapeProvider || "neurosync",
1664
- blendshape_config: {
1665
- enable_chunking: true,
1666
- format: configWithDefaults.blendshapeConfig?.format || "mha",
1667
- output_fps: 60,
1668
- },
1669
- llm_provider: "dynamic",
1670
- default_tts_enabled: configWithDefaults.ttsEnabled !== undefined
1671
- ? configWithDefaults.ttsEnabled
1672
- : true,
1673
- ...(this._characterSessionId &&
1674
- this._characterSessionId !== "-1" && {
1675
- character_session_id: this._characterSessionId,
1676
- }),
1677
- ...(configWithDefaults.actionConfig && {
1678
- action_config: configWithDefaults.actionConfig,
1679
- }),
1680
- };
1681
- // Call Core Service API
1682
- const response = await fetch(`${configWithDefaults.url}/connect`, {
1683
- method: "POST",
1684
- headers: {
1685
- "x-api-key": configWithDefaults.apiKey,
1686
- "Content-Type": "application/json",
1687
- },
1688
- body: JSON.stringify(requestBody),
1689
- });
1690
- if (!response.ok) {
1691
- const errorText = await response.text();
1692
- logger.error("API Error Response:", {
1693
- status: response.status,
1694
- statusText: response.statusText,
1695
- body: errorText,
1696
- });
1697
- let errorMessage = `HTTP error! status: ${response.status}`;
1698
- try {
1699
- const errorData = JSON.parse(errorText);
1700
- if (errorData.message) {
1701
- errorMessage = errorData.message;
1702
- }
1703
- else if (errorData.error) {
1704
- errorMessage = errorData.error;
1705
- }
1706
- else if (errorData.detail) {
1707
- errorMessage = errorData.detail;
1708
- }
1709
- }
1710
- catch (e) {
1711
- if (errorText) {
1712
- errorMessage = `${errorMessage}: ${errorText}`;
1713
- }
1714
- }
1715
- throw new Error(errorMessage);
1716
- }
1717
- const connectionData = await response.json();
1718
- // Capture character_session_id from connection response
1719
- if (connectionData.character_session_id) {
1720
- this._characterSessionId = connectionData.character_session_id;
1721
- }
1722
- // Connect to LiveKit room
1723
- await this._room.connect(connectionData.room_url, connectionData.token, {
1724
- rtcConfig: {
1725
- iceTransportPolicy: "relay",
1726
- },
1727
- });
1728
- // Enable microphone only if startWithAudioOn is true (default: false)
1729
- // If false, microphone stays off until user enables it via audioControls
1730
- if (configWithDefaults.startWithAudioOn) {
1731
- await this._room.localParticipant.setMicrophoneEnabled(true, {
1732
- echoCancellation: this._audioSettings.echoCancellation,
1733
- noiseSuppression: this._audioSettings.noiseSuppression,
1734
- autoGainControl: this._audioSettings.autoGainControl,
1735
- sampleRate: this._audioSettings.sampleRate,
1736
- channelCount: this._audioSettings.channelCount,
1737
- });
1738
- }
1739
- // Enable camera only if enableVideo is true AND startWithVideoOn is true
1740
- if (configWithDefaults.enableVideo &&
1741
- configWithDefaults.startWithVideoOn) {
1742
- await this._room.localParticipant.setCameraEnabled(true);
1743
- }
1744
- // Ensure audio manager mirrors the actual microphone permission state
1745
- this._audioManager.syncStateFromRoom({ emit: true });
1746
- // Capture participant SID
1747
- this._participantSid = this._room.localParticipant.sid;
1748
- this.updateState({
1749
- isConnected: true,
1750
- isConnecting: false,
1751
- });
1752
- this.emit("connect");
1753
- }
1754
- catch (error) {
1755
- logger.error("Connection failed:", error);
1756
- this.updateState({
1757
- isConnected: false,
1758
- isConnecting: false,
1759
- });
1760
- this.emit("error", error);
1761
- throw error;
1762
- }
1763
- }
1764
- /**
1765
- * Disconnect from the current character session
1766
- */
1767
- async disconnect() {
1768
- if (this._room && this._room.state !== "disconnected") {
1769
- try {
1770
- await this._room.disconnect();
1771
- this.resetConnectionState();
1772
- }
1773
- catch (error) {
1774
- // Handle disconnect errors gracefully
1775
- if (!(error instanceof Error && error.message.includes("disconnect"))) {
1776
- logger.error("Disconnect error:", error);
1777
- }
1778
- this.resetConnectionState();
1779
- }
1780
- }
1781
- }
1782
- /**
1783
- * Reset connection state
1784
- */
1785
- resetConnectionState() {
1786
- this.updateState({
1787
- isConnected: false,
1788
- isConnecting: false,
1789
- isSpeaking: false,
1790
- isThinking: false,
1791
- isListening: false,
1792
- });
1793
- this._isBotReady = false;
1794
- this._connectionType = null;
1795
- this._apiKey = null;
1796
- this._characterId = null;
1797
- this._speakerId = null;
1798
- this._messageHandler.reset();
1799
- this._audioManager.reset();
1800
- this._videoManager.reset();
1801
- this._screenShareManager.reset();
1802
- }
1803
- /**
1804
- * Reconnect - disconnect and connect again using stored config
1805
- */
1806
- async reconnect() {
1807
- if (!this._storedConfig) {
1808
- throw new Error("No stored config available for reconnection");
1809
- }
1810
- await this.disconnect();
1811
- await this.connect(this._storedConfig);
1812
- }
1813
- /**
1814
- * Reset the session ID to start a new conversation
1815
- */
1816
- resetSession() {
1817
- this._characterSessionId = "-1";
1818
- this._messageHandler.reset();
1819
- }
1820
- /**
1821
- * Send a text message to the character
1822
- */
1823
- sendUserTextMessage(text) {
1824
- if (!this._room ||
1825
- this._room.state === "disconnected" ||
1826
- !this._room.localParticipant) {
1827
- logger.warn("Cannot send message: not connected");
1828
- return;
1829
- }
1830
- if (!text || !text.trim()) {
1831
- return;
1832
- }
1833
- try {
1834
- // Start latency measurement for text message
1835
- this._latencyMonitor.startMeasurement("text", text.trim());
1836
- const message = {
1837
- type: "user_text_message",
1838
- data: {
1839
- text: text.trim(),
1840
- participant_sid: this._participantSid || this._room.localParticipant.sid,
1841
- },
1842
- };
1843
- const encodedData = new TextEncoder().encode(JSON.stringify(message));
1844
- this._room.localParticipant.publishData(encodedData, {
1845
- reliable: true,
1846
- });
1847
- }
1848
- catch (error) {
1849
- logger.error("Failed to send user text message:", error);
1850
- // Cancel latency measurement on error
1851
- this._latencyMonitor.cancelMeasurement();
1852
- throw error;
1853
- }
1854
- }
1855
- /**
1856
- * Send a trigger message to invoke specific character actions
1857
- */
1858
- sendTriggerMessage(triggerName, triggerMessage) {
1859
- if (this._room && this._room.localParticipant) {
1860
- const message = {
1861
- type: "trigger-message",
1862
- data: {
1863
- ...(triggerName && { trigger_name: triggerName }),
1864
- ...(triggerMessage && { trigger_message: triggerMessage }),
1865
- participant_sid: this._participantSid || this._room.localParticipant.sid,
1866
- },
1867
- };
1868
- const encodedData = new TextEncoder().encode(JSON.stringify(message));
1869
- this._room.localParticipant.publishData(encodedData, {
1870
- reliable: true,
1871
- });
1872
- }
1873
- }
1874
- /**
1875
- * Send an interrupt message to stop the bot's current response
1876
- */
1877
- sendInterruptMessage() {
1878
- if (!this._room ||
1879
- this._room.state === "disconnected" ||
1880
- !this._room.localParticipant) {
1881
- logger.warn("Cannot send interrupt message: not connected");
1882
- return;
1883
- }
1884
- try {
1885
- const message = {
1886
- type: "interrupt-bot",
1887
- data: {
1888
- participant_sid: this._participantSid || this._room.localParticipant.sid,
1889
- },
1890
- };
1891
- const encodedData = new TextEncoder().encode(JSON.stringify(message));
1892
- this._room.localParticipant.publishData(encodedData, {
1893
- reliable: true,
1894
- });
1895
- logger.info("Interrupt message sent");
1896
- }
1897
- catch (error) {
1898
- logger.error("Failed to send interrupt message:", error);
1899
- throw error;
1900
- }
1901
- }
1902
- /**
1903
- * Update template keys in the character's context
1904
- */
1905
- updateTemplateKeys(templateKeys) {
1906
- if (this._room &&
1907
- this._room.localParticipant &&
1908
- Object.keys(templateKeys).length > 0) {
1909
- const message = {
1910
- type: "update-template-keys",
1911
- data: {
1912
- template_keys: templateKeys,
1913
- participant_sid: this._participantSid || this._room.localParticipant.sid,
1914
- },
1915
- };
1916
- const encodedData = new TextEncoder().encode(JSON.stringify(message));
1917
- this._room.localParticipant.publishData(encodedData, {
1918
- reliable: true,
1919
- });
1920
- }
1921
- }
1922
- /**
1923
- * Update dynamic information about the current context
1924
- */
1925
- updateDynamicInfo(dynamicInfo) {
1926
- if (this._room && this._room.localParticipant && dynamicInfo.text?.trim()) {
1927
- const message = {
1928
- type: "update-dynamic-info",
1929
- data: {
1930
- dynamic_info: {
1931
- text: dynamicInfo.text.trim(),
1932
- },
1933
- participant_sid: this._participantSid || this._room.localParticipant.sid,
1934
- },
1935
- };
1936
- const encodedData = new TextEncoder().encode(JSON.stringify(message));
1937
- this._room.localParticipant.publishData(encodedData, {
1938
- reliable: true,
1939
- });
1940
- }
1941
- }
1942
- /**
1943
- * Toggle text-to-speech on or off
1944
- */
1945
- toggleTts(enabled) {
1946
- if (!this._room ||
1947
- this._room.state === "disconnected" ||
1948
- !this._room.localParticipant) {
1949
- return;
1950
- }
1951
- try {
1952
- const message = {
1953
- type: "tts-toggle",
1954
- data: {
1955
- enabled: enabled,
1956
- },
1957
- };
1958
- const encodedData = new TextEncoder().encode(JSON.stringify(message));
1959
- this._room.localParticipant.publishData(encodedData, {
1960
- reliable: true,
1961
- });
1962
- }
1963
- catch (error) {
1964
- logger.error("Failed to toggle TTS:", error);
1965
- throw error;
1966
- }
1967
- }
1968
- }
1969
-
1970
- export { AudioManager, ConvaiClient, EventEmitter, MessageHandler, ScreenShareManager, VideoManager };
1
+ // Main client
2
+ export { ConvaiClient } from './ConvaiClient';
3
+ // Types (including IConvaiClient interface)
4
+ export * from './types';
5
+ // Managers (for advanced usage)
6
+ export { AudioManager } from './AudioManager';
7
+ export { VideoManager } from './VideoManager';
8
+ export { ScreenShareManager } from './ScreenShareManager';
9
+ export { MessageHandler } from './MessageHandler';
10
+ // Blendshape queue for lipsync
11
+ export { BlendshapeQueue } from './BlendshapeQueue';
12
+ // Event Emitter (for advanced usage)
13
+ export { EventEmitter } from './EventEmitter';
14
+ //# sourceMappingURL=index.js.map