@memori.ai/memori-react 8.45.2 → 8.45.4

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 (543) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/components/Chat/Chat.js +4 -1
  3. package/dist/components/Chat/Chat.js.map +1 -1
  4. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +39 -0
  5. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js +66 -0
  6. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -0
  7. package/dist/components/DrawerFooter/DrawerFooter.d.ts +12 -0
  8. package/dist/components/DrawerFooter/DrawerFooter.js +10 -0
  9. package/dist/components/DrawerFooter/DrawerFooter.js.map +1 -0
  10. package/dist/components/IconButton/IconButton.d.ts +9 -0
  11. package/dist/components/IconButton/IconButton.js +36 -0
  12. package/dist/components/IconButton/IconButton.js.map +1 -0
  13. package/dist/components/MemoriWidget/MemoriWidget.d.ts +1 -1
  14. package/dist/components/MemoriWidget/MemoriWidget.js +2 -1
  15. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  16. package/dist/components/MobileSessionPanel/MobileSessionPanel.d.ts +65 -0
  17. package/dist/components/MobileSessionPanel/MobileSessionPanel.js +294 -0
  18. package/dist/components/MobileSessionPanel/MobileSessionPanel.js.map +1 -0
  19. package/dist/components/PositionPopover/PositionPopover.d.ts +22 -0
  20. package/dist/components/PositionPopover/PositionPopover.js +208 -0
  21. package/dist/components/PositionPopover/PositionPopover.js.map +1 -0
  22. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.d.ts +4 -0
  23. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js +64 -0
  24. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -0
  25. package/dist/components/layouts/chat.css +27 -4
  26. package/dist/context/widgetSurfaceContext.d.ts +3 -0
  27. package/dist/context/widgetSurfaceContext.js +11 -0
  28. package/dist/context/widgetSurfaceContext.js.map +1 -0
  29. package/dist/helpers/positionPopover.d.ts +2 -0
  30. package/dist/helpers/positionPopover.js +16 -0
  31. package/dist/helpers/positionPopover.js.map +1 -0
  32. package/dist/helpers/tts/muteSpeaker.d.ts +13 -0
  33. package/dist/helpers/tts/muteSpeaker.js +27 -0
  34. package/dist/helpers/tts/muteSpeaker.js.map +1 -0
  35. package/dist/helpers/usePressTooltip.d.ts +13 -0
  36. package/dist/helpers/usePressTooltip.js +24 -0
  37. package/dist/helpers/usePressTooltip.js.map +1 -0
  38. package/dist/icons/FacebookIcon.d.ts +3 -0
  39. package/dist/icons/FacebookIcon.js +6 -0
  40. package/dist/icons/FacebookIcon.js.map +1 -0
  41. package/dist/icons/LinkedinIcon.d.ts +3 -0
  42. package/dist/icons/LinkedinIcon.js +6 -0
  43. package/dist/icons/LinkedinIcon.js.map +1 -0
  44. package/dist/icons/MemoriIcon.d.ts +3 -0
  45. package/dist/icons/MemoriIcon.js +8 -0
  46. package/dist/icons/MemoriIcon.js.map +1 -0
  47. package/dist/icons/TelegramIcon.d.ts +3 -0
  48. package/dist/icons/TelegramIcon.js +6 -0
  49. package/dist/icons/TelegramIcon.js.map +1 -0
  50. package/dist/icons/TwitterIcon.d.ts +3 -0
  51. package/dist/icons/TwitterIcon.js +6 -0
  52. package/dist/icons/TwitterIcon.js.map +1 -0
  53. package/dist/icons/WhatsappIcon.d.ts +3 -0
  54. package/dist/icons/WhatsappIcon.js +6 -0
  55. package/dist/icons/WhatsappIcon.js.map +1 -0
  56. package/dist/icons/artifacts/DocumentIcon.d.ts +3 -0
  57. package/dist/icons/artifacts/DocumentIcon.js +6 -0
  58. package/dist/icons/artifacts/DocumentIcon.js.map +1 -0
  59. package/dist/icons/artifacts/HtmlIcon.d.ts +3 -0
  60. package/dist/icons/artifacts/HtmlIcon.js +6 -0
  61. package/dist/icons/artifacts/HtmlIcon.js.map +1 -0
  62. package/dist/icons/artifacts/JavascriptIcon.d.ts +3 -0
  63. package/dist/icons/artifacts/JavascriptIcon.js +6 -0
  64. package/dist/icons/artifacts/JavascriptIcon.js.map +1 -0
  65. package/dist/icons/artifacts/MarkdownIcon.d.ts +3 -0
  66. package/dist/icons/artifacts/MarkdownIcon.js +6 -0
  67. package/dist/icons/artifacts/MarkdownIcon.js.map +1 -0
  68. package/dist/icons/artifacts/PythonIcon.d.ts +3 -0
  69. package/dist/icons/artifacts/PythonIcon.js +6 -0
  70. package/dist/icons/artifacts/PythonIcon.js.map +1 -0
  71. package/dist/icons/artifacts/SvgIcon.d.ts +3 -0
  72. package/dist/icons/artifacts/SvgIcon.js +6 -0
  73. package/dist/icons/artifacts/SvgIcon.js.map +1 -0
  74. package/dist/icons/artifacts/XmlIcon.d.ts +3 -0
  75. package/dist/icons/artifacts/XmlIcon.js +6 -0
  76. package/dist/icons/artifacts/XmlIcon.js.map +1 -0
  77. package/dist/icons/artifacts/cssIcon.d.ts +3 -0
  78. package/dist/icons/artifacts/cssIcon.js +6 -0
  79. package/dist/icons/artifacts/cssIcon.js.map +1 -0
  80. package/dist/styles.css +1 -2
  81. package/dist/version.d.ts +1 -1
  82. package/dist/version.js +1 -1
  83. package/esm/components/Chat/Chat.js +4 -1
  84. package/esm/components/Chat/Chat.js.map +1 -1
  85. package/esm/components/MemoriWidget/MemoriWidget.d.ts +1 -1
  86. package/esm/components/MemoriWidget/MemoriWidget.js +2 -1
  87. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  88. package/esm/components/layouts/chat.css +27 -4
  89. package/esm/styles.css +1 -2
  90. package/esm/version.d.ts +1 -1
  91. package/esm/version.js +1 -1
  92. package/package.json +6 -7
  93. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.d.ts +0 -3
  94. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.js +0 -64
  95. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.js.map +0 -1
  96. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.d.ts +0 -3
  97. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.js +0 -62
  98. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.js.map +0 -1
  99. package/src/I18nWrapper.tsx +0 -13
  100. package/src/__snapshots__/index.test.tsx.snap +0 -96
  101. package/src/components/AgeVerificationModal/AgeVerificationModal.css +0 -95
  102. package/src/components/AgeVerificationModal/AgeVerificationModal.stories.tsx +0 -46
  103. package/src/components/AgeVerificationModal/AgeVerificationModal.test.tsx +0 -33
  104. package/src/components/AgeVerificationModal/AgeVerificationModal.tsx +0 -99
  105. package/src/components/AgeVerificationModal/__snapshots__/AgeVerificationModal.test.tsx.snap +0 -19
  106. package/src/components/Auth/Auth.css +0 -57
  107. package/src/components/Auth/Auth.stories.tsx +0 -90
  108. package/src/components/Auth/Auth.test.tsx +0 -63
  109. package/src/components/Auth/Auth.tsx +0 -201
  110. package/src/components/Auth/__snapshots__/Auth.test.tsx.snap +0 -143
  111. package/src/components/Avatar/Avatar.css +0 -151
  112. package/src/components/Avatar/Avatar.stories.tsx +0 -160
  113. package/src/components/Avatar/Avatar.test.tsx +0 -158
  114. package/src/components/Avatar/Avatar.tsx +0 -245
  115. package/src/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.tsx +0 -103
  116. package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +0 -105
  117. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +0 -338
  118. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/types.ts +0 -44
  119. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.ts +0 -1091
  120. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarPositionController.ts +0 -83
  121. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +0 -301
  122. package/src/components/Avatar/AvatarView/AvatarComponent/components/controls.tsx +0 -100
  123. package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +0 -152
  124. package/src/components/Avatar/AvatarView/AvatarComponent/components/loader.tsx +0 -31
  125. package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +0 -141
  126. package/src/components/Avatar/AvatarView/AvatarComponent/lights/Lights.tsx +0 -145
  127. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +0 -111
  128. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.tsx +0 -201
  129. package/src/components/Avatar/AvatarView/AvatarView.stories.tsx +0 -254
  130. package/src/components/Avatar/AvatarView/index.tsx +0 -194
  131. package/src/components/Avatar/AvatarView/utils/hideHands.ts +0 -11
  132. package/src/components/Avatar/AvatarView/utils/useEyeBlink.ts +0 -100
  133. package/src/components/Avatar/AvatarView/utils/useHeadMovement.ts +0 -72
  134. package/src/components/Avatar/AvatarView/utils/useLoadingMorphAnim.ts +0 -38
  135. package/src/components/Avatar/AvatarView/utils/useMouthAnimation.ts +0 -93
  136. package/src/components/Avatar/AvatarView/utils/useSmile.ts +0 -39
  137. package/src/components/Avatar/AvatarView/utils/utils.ts +0 -55
  138. package/src/components/Avatar/__snapshots__/Avatar.test.tsx.snap +0 -246
  139. package/src/components/Blob/Blob.css +0 -286
  140. package/src/components/Blob/Blob.stories.tsx +0 -53
  141. package/src/components/Blob/Blob.test.tsx +0 -18
  142. package/src/components/Blob/Blob.tsx +0 -26
  143. package/src/components/Blob/__snapshots__/Blob.test.tsx.snap +0 -80
  144. package/src/components/BlockedMemoriBadge/BlockedMemoriBadge.css +0 -40
  145. package/src/components/BlockedMemoriBadge/BlockedMemoriBadge.stories.tsx +0 -58
  146. package/src/components/BlockedMemoriBadge/BlockedMemoriBadge.test.tsx +0 -53
  147. package/src/components/BlockedMemoriBadge/BlockedMemoriBadge.tsx +0 -74
  148. package/src/components/BlockedMemoriBadge/__snapshots__/BlockedMemoriBadge.test.tsx.snap +0 -181
  149. package/src/components/ChangeMode/ChangeMode.css +0 -22
  150. package/src/components/ChangeMode/ChangeMode.stories.tsx +0 -50
  151. package/src/components/ChangeMode/ChangeMode.test.tsx +0 -24
  152. package/src/components/ChangeMode/ChangeMode.tsx +0 -51
  153. package/src/components/ChangeMode/__snapshots__/ChangeMode.test.tsx.snap +0 -107
  154. package/src/components/Chat/Chat.css +0 -285
  155. package/src/components/Chat/Chat.stories.tsx +0 -1684
  156. package/src/components/Chat/Chat.test.tsx +0 -372
  157. package/src/components/Chat/Chat.tsx +0 -778
  158. package/src/components/Chat/__snapshots__/Chat.test.tsx.snap +0 -6816
  159. package/src/components/ChatBubble/ChatBubble.css +0 -430
  160. package/src/components/ChatBubble/ChatBubble.stories.tsx +0 -1192
  161. package/src/components/ChatBubble/ChatBubble.test.tsx +0 -481
  162. package/src/components/ChatBubble/ChatBubble.tsx +0 -776
  163. package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +0 -8734
  164. package/src/components/ChatHistoryDrawer/ChatHistory.css +0 -587
  165. package/src/components/ChatHistoryDrawer/ChatHistory.stories.tsx +0 -410
  166. package/src/components/ChatHistoryDrawer/ChatHistory.test.tsx +0 -107
  167. package/src/components/ChatHistoryDrawer/ChatHistory.tsx +0 -1298
  168. package/src/components/ChatHistoryDrawer/__snapshots__/ChatHistory.test.tsx.snap +0 -27
  169. package/src/components/ChatInputs/ChatInputs.css +0 -340
  170. package/src/components/ChatInputs/ChatInputs.stories.tsx +0 -251
  171. package/src/components/ChatInputs/ChatInputs.tsx +0 -487
  172. package/src/components/ChatTextArea/ChatTextArea.css +0 -185
  173. package/src/components/ChatTextArea/ChatTextArea.stories.tsx +0 -55
  174. package/src/components/ChatTextArea/ChatTextArea.test.tsx +0 -28
  175. package/src/components/ChatTextArea/ChatTextArea.tsx +0 -171
  176. package/src/components/ChatTextArea/__snapshots__/ChatTextArea.test.tsx.snap +0 -68
  177. package/src/components/CompletionProviderStatus/CompletionProviderStatus.css +0 -36
  178. package/src/components/CompletionProviderStatus/CompletionProviderStatus.stories.tsx +0 -296
  179. package/src/components/CompletionProviderStatus/CompletionProviderStatus.test.tsx +0 -31
  180. package/src/components/CompletionProviderStatus/CompletionProviderStatus.tsx +0 -232
  181. package/src/components/CompletionProviderStatus/__snapshots__/CompletionProviderStatus.test.tsx.snap +0 -96
  182. package/src/components/ContentPreviewModal/ContentPreviewModal.css +0 -128
  183. package/src/components/ContentPreviewModal/ContentPreviewModal.tsx +0 -67
  184. package/src/components/ContentPreviewModal/index.ts +0 -2
  185. package/src/components/CustomGLBModelViewer/ModelViewer.css +0 -11
  186. package/src/components/CustomGLBModelViewer/ModelViewer.stories.tsx +0 -58
  187. package/src/components/CustomGLBModelViewer/ModelViewer.test.tsx +0 -13
  188. package/src/components/CustomGLBModelViewer/ModelViewer.tsx +0 -37
  189. package/src/components/CustomGLBModelViewer/__snapshots__/ModelViewer.test.tsx.snap +0 -20
  190. package/src/components/DateSelector/DateSelector.css +0 -158
  191. package/src/components/DateSelector/DateSelector.stories.tsx +0 -47
  192. package/src/components/DateSelector/DateSelector.test.tsx +0 -149
  193. package/src/components/DateSelector/DateSelector.tsx +0 -251
  194. package/src/components/DateSelector/__snapshots__/DateSelector.test.tsx.snap +0 -1954
  195. package/src/components/ErrorBoundary/ErrorBoundary.tsx +0 -54
  196. package/src/components/ExpertsDrawer/ExpertsDrawer.css +0 -67
  197. package/src/components/ExpertsDrawer/ExpertsDrawer.stories.tsx +0 -54
  198. package/src/components/ExpertsDrawer/ExpertsDrawer.test.tsx +0 -53
  199. package/src/components/ExpertsDrawer/ExpertsDrawer.tsx +0 -69
  200. package/src/components/ExpertsDrawer/__snapshots__/ExpertsDrawer.test.tsx.snap +0 -11
  201. package/src/components/ExportHistoryButton/ExportHistoryButton.css +0 -22
  202. package/src/components/FeedbackButtons/FeedbackButtons.css +0 -107
  203. package/src/components/FeedbackButtons/FeedbackButtons.stories.tsx +0 -52
  204. package/src/components/FeedbackButtons/FeedbackButtons.test.tsx +0 -11
  205. package/src/components/FeedbackButtons/FeedbackButtons.tsx +0 -152
  206. package/src/components/FeedbackButtons/__snapshots__/FeedbackButtons.test.tsx.snap +0 -42
  207. package/src/components/FilePreview/FilePreview.css +0 -358
  208. package/src/components/FilePreview/FilePreview.stories.tsx +0 -63
  209. package/src/components/FilePreview/FilePreview.test.tsx +0 -26
  210. package/src/components/FilePreview/FilePreview.tsx +0 -277
  211. package/src/components/FilePreview/__snapshots__/FilePreview.test.tsx.snap +0 -185
  212. package/src/components/Header/ChatConsumptionDropdown.test.tsx +0 -117
  213. package/src/components/Header/ChatConsumptionDropdown.tsx +0 -275
  214. package/src/components/Header/Header.css +0 -282
  215. package/src/components/Header/Header.stories.tsx +0 -338
  216. package/src/components/Header/Header.test.tsx +0 -371
  217. package/src/components/Header/Header.tsx +0 -442
  218. package/src/components/Header/__snapshots__/Header.test.tsx.snap +0 -964
  219. package/src/components/KnownFacts/KnownFacts.css +0 -19
  220. package/src/components/KnownFacts/KnownFacts.stories.tsx +0 -80
  221. package/src/components/KnownFacts/KnownFacts.test.tsx +0 -57
  222. package/src/components/KnownFacts/KnownFacts.tsx +0 -376
  223. package/src/components/KnownFacts/__snapshots__/KnownFacts.test.tsx.snap +0 -19
  224. package/src/components/LoginDrawer/LoginDrawer.css +0 -1616
  225. package/src/components/LoginDrawer/LoginDrawer.stories.tsx +0 -102
  226. package/src/components/LoginDrawer/LoginDrawer.test.tsx +0 -199
  227. package/src/components/LoginDrawer/LoginDrawer.tsx +0 -600
  228. package/src/components/LoginDrawer/__snapshots__/LoginDrawer.test.tsx.snap +0 -91
  229. package/src/components/MediaWidget/DocumentCard.test.tsx +0 -45
  230. package/src/components/MediaWidget/DocumentCard.tsx +0 -19
  231. package/src/components/MediaWidget/MediaItemWidget.css +0 -1398
  232. package/src/components/MediaWidget/MediaItemWidget.stories.tsx +0 -423
  233. package/src/components/MediaWidget/MediaItemWidget.test.tsx +0 -248
  234. package/src/components/MediaWidget/MediaItemWidget.tsx +0 -997
  235. package/src/components/MediaWidget/MediaItemWidget.types.ts +0 -65
  236. package/src/components/MediaWidget/MediaItemWidget.utils.test.ts +0 -367
  237. package/src/components/MediaWidget/MediaItemWidget.utils.ts +0 -227
  238. package/src/components/MediaWidget/MediaPreviewModal.test.tsx +0 -271
  239. package/src/components/MediaWidget/MediaPreviewModal.tsx +0 -425
  240. package/src/components/MediaWidget/MediaWidget.css +0 -131
  241. package/src/components/MediaWidget/MediaWidget.stories.tsx +0 -319
  242. package/src/components/MediaWidget/MediaWidget.test.tsx +0 -41
  243. package/src/components/MediaWidget/MediaWidget.tsx +0 -107
  244. package/src/components/MediaWidget/__snapshots__/DocumentCard.test.tsx.snap +0 -24
  245. package/src/components/MediaWidget/__snapshots__/MediaItemWidget.test.tsx.snap +0 -791
  246. package/src/components/MediaWidget/__snapshots__/MediaWidget.test.tsx.snap +0 -154
  247. package/src/components/MemoriArtifactSystem/ArtifactDrawer.stories.tsx +0 -2299
  248. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +0 -163
  249. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.tsx +0 -250
  250. package/src/components/MemoriArtifactSystem/components/ArtifactActions/CopyButton.stories.tsx +0 -30
  251. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.tsx +0 -467
  252. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.tsx +0 -89
  253. package/src/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.ts +0 -619
  254. package/src/components/MemoriArtifactSystem/components/ArtifactActions/index.ts +0 -9
  255. package/src/components/MemoriArtifactSystem/components/ArtifactActions/styles.css +0 -489
  256. package/src/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.tsx +0 -90
  257. package/src/components/MemoriArtifactSystem/components/ArtifactActions/types.ts +0 -73
  258. package/src/components/MemoriArtifactSystem/components/ArtifactActions/utils/PDFExporter.ts +0 -454
  259. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +0 -1059
  260. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +0 -431
  261. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.css +0 -150
  262. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.tsx +0 -79
  263. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +0 -253
  264. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.tsx +0 -561
  265. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +0 -355
  266. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.tsx +0 -160
  267. package/src/components/MemoriArtifactSystem/context/ArtifactContext.tsx +0 -167
  268. package/src/components/MemoriArtifactSystem/types/artifact.types.ts +0 -35
  269. package/src/components/MemoriArtifactSystem/utils/ArtifactAPI.test.tsx +0 -308
  270. package/src/components/MemoriArtifactSystem/utils/ArtifactAPI.tsx +0 -379
  271. package/src/components/MemoriArtifactSystem/utils/applyEdits.test.ts +0 -85
  272. package/src/components/MemoriArtifactSystem/utils/applyEdits.ts +0 -173
  273. package/src/components/MemoriWidget/MemoriWidget.css +0 -220
  274. package/src/components/MemoriWidget/MemoriWidget.stories.tsx +0 -274
  275. package/src/components/MemoriWidget/MemoriWidget.tsx +0 -3930
  276. package/src/components/MicrophoneButton/MicrophoneButton.css +0 -142
  277. package/src/components/MicrophoneButton/MicrophoneButton.stories.tsx +0 -52
  278. package/src/components/MicrophoneButton/MicrophoneButton.tsx +0 -128
  279. package/src/components/PositionDrawer/PositionDrawer.stories.tsx +0 -51
  280. package/src/components/PositionDrawer/PositionDrawer.test.tsx +0 -39
  281. package/src/components/PositionDrawer/PositionDrawer.tsx +0 -50
  282. package/src/components/PositionDrawer/__snapshots__/PositionDrawer.test.tsx.snap +0 -17
  283. package/src/components/PoweredBy/PoweredBy.css +0 -47
  284. package/src/components/PoweredBy/PoweredBy.stories.tsx +0 -75
  285. package/src/components/PoweredBy/PoweredBy.test.tsx +0 -15
  286. package/src/components/PoweredBy/PoweredBy.tsx +0 -44
  287. package/src/components/PoweredBy/__snapshots__/PoweredBy.test.tsx.snap +0 -55
  288. package/src/components/SendOnEnterMenu/SendOnEnterMenu.css +0 -47
  289. package/src/components/SendOnEnterMenu/SendOnEnterMenu.stories.tsx +0 -58
  290. package/src/components/SendOnEnterMenu/SendOnEnterMenu.test.tsx +0 -17
  291. package/src/components/SendOnEnterMenu/SendOnEnterMenu.tsx +0 -82
  292. package/src/components/SendOnEnterMenu/__snapshots__/SendOnEnterMenu.test.tsx.snap +0 -57
  293. package/src/components/SettingsDrawer/SettingsDrawer.css +0 -24
  294. package/src/components/SettingsDrawer/SettingsDrawer.stories.tsx +0 -94
  295. package/src/components/SettingsDrawer/SettingsDrawer.test.tsx +0 -202
  296. package/src/components/SettingsDrawer/SettingsDrawer.tsx +0 -255
  297. package/src/components/SettingsDrawer/__snapshots__/SettingsDrawer.test.tsx.snap +0 -59
  298. package/src/components/ShareButton/ShareButton.css +0 -73
  299. package/src/components/ShareButton/ShareButton.stories.tsx +0 -98
  300. package/src/components/ShareButton/ShareButton.test.tsx +0 -53
  301. package/src/components/ShareButton/ShareButton.tsx +0 -395
  302. package/src/components/ShareButton/__snapshots__/ShareButton.test.tsx.snap +0 -316
  303. package/src/components/Snippet/Snippet.css +0 -142
  304. package/src/components/Snippet/Snippet.stories.tsx +0 -286
  305. package/src/components/Snippet/Snippet.test.tsx +0 -176
  306. package/src/components/Snippet/Snippet.tsx +0 -148
  307. package/src/components/Snippet/__snapshots__/Snippet.test.tsx.snap +0 -603
  308. package/src/components/StartPanel/StartPanel.css +0 -273
  309. package/src/components/StartPanel/StartPanel.stories.tsx +0 -404
  310. package/src/components/StartPanel/StartPanel.test.tsx +0 -360
  311. package/src/components/StartPanel/StartPanel.tsx +0 -426
  312. package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +0 -1986
  313. package/src/components/Typing/Typing.stories.tsx +0 -130
  314. package/src/components/Typing/Typing.test.tsx +0 -62
  315. package/src/components/Typing/Typing.tsx +0 -154
  316. package/src/components/Typing/__snapshots__/Typing.test.tsx.snap +0 -133
  317. package/src/components/UploadButton/UploadButton.css +0 -542
  318. package/src/components/UploadButton/UploadButton.stories.tsx +0 -376
  319. package/src/components/UploadButton/UploadButton.test.tsx +0 -11
  320. package/src/components/UploadButton/UploadButton.tsx +0 -688
  321. package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +0 -421
  322. package/src/components/UploadButton/UploadImages/UploadImages.tsx +0 -538
  323. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +0 -165
  324. package/src/components/VenueWidget/VenueWidget.css +0 -163
  325. package/src/components/VenueWidget/VenueWidget.stories.tsx +0 -66
  326. package/src/components/VenueWidget/VenueWidget.test.tsx +0 -49
  327. package/src/components/VenueWidget/VenueWidget.tsx +0 -458
  328. package/src/components/VenueWidget/__snapshots__/VenueWidget.test.tsx.snap +0 -370
  329. package/src/components/WhyThisAnswer/WhyThisAnswer.css +0 -171
  330. package/src/components/WhyThisAnswer/WhyThisAnswer.stories.tsx +0 -285
  331. package/src/components/WhyThisAnswer/WhyThisAnswer.test.tsx +0 -245
  332. package/src/components/WhyThisAnswer/WhyThisAnswer.tsx +0 -233
  333. package/src/components/WhyThisAnswer/__snapshots__/WhyThisAnswer.test.tsx.snap +0 -27
  334. package/src/components/icons/AI.tsx +0 -34
  335. package/src/components/icons/Alert.tsx +0 -31
  336. package/src/components/icons/ArrowUp.tsx +0 -28
  337. package/src/components/icons/Bug.tsx +0 -81
  338. package/src/components/icons/Chat.tsx +0 -30
  339. package/src/components/icons/ChevronDown.tsx +0 -26
  340. package/src/components/icons/ChevronLeft.tsx +0 -28
  341. package/src/components/icons/ChevronRight.tsx +0 -28
  342. package/src/components/icons/ChevronUp.tsx +0 -24
  343. package/src/components/icons/Clear.tsx +0 -23
  344. package/src/components/icons/Close.tsx +0 -30
  345. package/src/components/icons/Code.tsx +0 -24
  346. package/src/components/icons/Copy.tsx +0 -23
  347. package/src/components/icons/DeepThought.tsx +0 -38
  348. package/src/components/icons/Delete.tsx +0 -23
  349. package/src/components/icons/Document.tsx +0 -50
  350. package/src/components/icons/Download.tsx +0 -23
  351. package/src/components/icons/Edit.tsx +0 -17
  352. package/src/components/icons/Expand.tsx +0 -23
  353. package/src/components/icons/Eye.tsx +0 -17
  354. package/src/components/icons/EyeInvisible.tsx +0 -24
  355. package/src/components/icons/Facebook.tsx +0 -23
  356. package/src/components/icons/Feedback.tsx +0 -24
  357. package/src/components/icons/File.tsx +0 -17
  358. package/src/components/icons/FileExcel.tsx +0 -23
  359. package/src/components/icons/FilePdf.tsx +0 -23
  360. package/src/components/icons/FileWord.tsx +0 -23
  361. package/src/components/icons/Fullscreen.tsx +0 -23
  362. package/src/components/icons/FullscreenExit.tsx +0 -23
  363. package/src/components/icons/GasStation.tsx +0 -36
  364. package/src/components/icons/Group.tsx +0 -30
  365. package/src/components/icons/History.tsx +0 -33
  366. package/src/components/icons/Image.tsx +0 -37
  367. package/src/components/icons/Info.tsx +0 -31
  368. package/src/components/icons/Link.tsx +0 -17
  369. package/src/components/icons/Linkedin.tsx +0 -23
  370. package/src/components/icons/Loading.tsx +0 -28
  371. package/src/components/icons/Logout.tsx +0 -27
  372. package/src/components/icons/Mail.tsx +0 -17
  373. package/src/components/icons/MapMarker.tsx +0 -26
  374. package/src/components/icons/MenuHorizontal.tsx +0 -29
  375. package/src/components/icons/MenuVertical.tsx +0 -29
  376. package/src/components/icons/Message.tsx +0 -23
  377. package/src/components/icons/Microphone.tsx +0 -23
  378. package/src/components/icons/Minus.tsx +0 -23
  379. package/src/components/icons/MinusCircle.tsx +0 -24
  380. package/src/components/icons/PaperClip.tsx +0 -23
  381. package/src/components/icons/Picture.tsx +0 -23
  382. package/src/components/icons/Plus.tsx +0 -18
  383. package/src/components/icons/Preview.tsx +0 -29
  384. package/src/components/icons/Print.tsx +0 -38
  385. package/src/components/icons/QuestionHelp.tsx +0 -30
  386. package/src/components/icons/Refresh.tsx +0 -30
  387. package/src/components/icons/SelectIcon.tsx +0 -28
  388. package/src/components/icons/Send.tsx +0 -17
  389. package/src/components/icons/Setting.tsx +0 -23
  390. package/src/components/icons/Share.tsx +0 -23
  391. package/src/components/icons/Sound.tsx +0 -23
  392. package/src/components/icons/SoundDeactivated.tsx +0 -27
  393. package/src/components/icons/Telegram.tsx +0 -26
  394. package/src/components/icons/ThumbDown.tsx +0 -29
  395. package/src/components/icons/ThumbUp.tsx +0 -29
  396. package/src/components/icons/Translation.tsx +0 -24
  397. package/src/components/icons/Twitter.tsx +0 -23
  398. package/src/components/icons/Upload.tsx +0 -34
  399. package/src/components/icons/User.tsx +0 -17
  400. package/src/components/icons/Warning.tsx +0 -24
  401. package/src/components/icons/WhatsApp.tsx +0 -24
  402. package/src/components/icons/icons.stories.css +0 -5
  403. package/src/components/icons/icons.stories.tsx +0 -81
  404. package/src/components/icons/loading.css +0 -14
  405. package/src/components/layouts/Chat.test.tsx +0 -40
  406. package/src/components/layouts/Chat.tsx +0 -69
  407. package/src/components/layouts/FullBody/FullBody.stories.tsx +0 -110
  408. package/src/components/layouts/FullPage.test.tsx +0 -65
  409. package/src/components/layouts/FullPage.tsx +0 -80
  410. package/src/components/layouts/HiddenChat.test.tsx +0 -40
  411. package/src/components/layouts/HiddenChat.tsx +0 -233
  412. package/src/components/layouts/Totem/Totem.stories.tsx +0 -131
  413. package/src/components/layouts/Totem.test.tsx +0 -40
  414. package/src/components/layouts/Totem.tsx +0 -49
  415. package/src/components/layouts/WebsiteAssistant.test.tsx +0 -40
  416. package/src/components/layouts/WebsiteAssistant.tsx +0 -132
  417. package/src/components/layouts/ZoomedFullBody/ZoomedFullBody.stories.tsx +0 -131
  418. package/src/components/layouts/ZoomedFullBody.test.tsx +0 -40
  419. package/src/components/layouts/ZoomedFullBody.tsx +0 -81
  420. package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +0 -457
  421. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +0 -1045
  422. package/src/components/layouts/__snapshots__/HiddenChat.test.tsx.snap +0 -460
  423. package/src/components/layouts/__snapshots__/Totem.test.tsx.snap +0 -566
  424. package/src/components/layouts/__snapshots__/WebsiteAssistant.test.tsx.snap +0 -59
  425. package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +0 -523
  426. package/src/components/layouts/chat.css +0 -451
  427. package/src/components/layouts/hidden-chat.css +0 -276
  428. package/src/components/layouts/layouts.stories.tsx +0 -231
  429. package/src/components/layouts/totem.css +0 -221
  430. package/src/components/layouts/website-assistant.css +0 -677
  431. package/src/components/layouts/zoomed-full-body.css +0 -18
  432. package/src/components/ui/Alert.css +0 -93
  433. package/src/components/ui/Alert.stories.tsx +0 -137
  434. package/src/components/ui/Alert.test.tsx +0 -129
  435. package/src/components/ui/Alert.tsx +0 -123
  436. package/src/components/ui/Button.css +0 -167
  437. package/src/components/ui/Button.stories.tsx +0 -156
  438. package/src/components/ui/Button.test.tsx +0 -74
  439. package/src/components/ui/Button.tsx +0 -107
  440. package/src/components/ui/Card.css +0 -65
  441. package/src/components/ui/Card.stories.tsx +0 -95
  442. package/src/components/ui/Card.test.tsx +0 -47
  443. package/src/components/ui/Card.tsx +0 -48
  444. package/src/components/ui/Checkbox.css +0 -116
  445. package/src/components/ui/Checkbox.stories.tsx +0 -74
  446. package/src/components/ui/Checkbox.test.tsx +0 -28
  447. package/src/components/ui/Checkbox.tsx +0 -58
  448. package/src/components/ui/ConfirmDialog.css +0 -42
  449. package/src/components/ui/ConfirmDialog.stories.tsx +0 -216
  450. package/src/components/ui/ConfirmDialog.test.tsx +0 -124
  451. package/src/components/ui/ConfirmDialog.tsx +0 -58
  452. package/src/components/ui/Details.css +0 -66
  453. package/src/components/ui/Drawer.css +0 -270
  454. package/src/components/ui/Drawer.stories.tsx +0 -267
  455. package/src/components/ui/Drawer.test.tsx +0 -130
  456. package/src/components/ui/Drawer.tsx +0 -293
  457. package/src/components/ui/Dropdown.css +0 -173
  458. package/src/components/ui/Dropdown.tsx +0 -63
  459. package/src/components/ui/Expandable.css +0 -15
  460. package/src/components/ui/Expandable.stories.tsx +0 -180
  461. package/src/components/ui/Expandable.test.tsx +0 -107
  462. package/src/components/ui/Expandable.tsx +0 -121
  463. package/src/components/ui/Modal.css +0 -98
  464. package/src/components/ui/Modal.stories.tsx +0 -171
  465. package/src/components/ui/Modal.test.tsx +0 -111
  466. package/src/components/ui/Modal.tsx +0 -117
  467. package/src/components/ui/Select.css +0 -138
  468. package/src/components/ui/Select.stories.tsx +0 -79
  469. package/src/components/ui/Select.test.tsx +0 -84
  470. package/src/components/ui/Select.tsx +0 -79
  471. package/src/components/ui/Slider.css +0 -192
  472. package/src/components/ui/Slider.stories.tsx +0 -63
  473. package/src/components/ui/Slider.tsx +0 -142
  474. package/src/components/ui/Spin.css +0 -31
  475. package/src/components/ui/Spin.stories.tsx +0 -64
  476. package/src/components/ui/Spin.test.tsx +0 -41
  477. package/src/components/ui/Spin.tsx +0 -31
  478. package/src/components/ui/Table.css +0 -132
  479. package/src/components/ui/Tooltip.css +0 -171
  480. package/src/components/ui/Tooltip.stories.tsx +0 -109
  481. package/src/components/ui/Tooltip.test.tsx +0 -102
  482. package/src/components/ui/Tooltip.tsx +0 -163
  483. package/src/components/ui/__snapshots__/Alert.test.tsx.snap +0 -59
  484. package/src/components/ui/__snapshots__/Button.test.tsx.snap +0 -204
  485. package/src/components/ui/__snapshots__/Card.test.tsx.snap +0 -321
  486. package/src/components/ui/__snapshots__/Checkbox.test.tsx.snap +0 -131
  487. package/src/components/ui/__snapshots__/ConfirmDialog.test.tsx.snap +0 -35
  488. package/src/components/ui/__snapshots__/Drawer.test.tsx.snap +0 -67
  489. package/src/components/ui/__snapshots__/Expandable.test.tsx.snap +0 -159
  490. package/src/components/ui/__snapshots__/Modal.test.tsx.snap +0 -59
  491. package/src/components/ui/__snapshots__/Select.test.tsx.snap +0 -254
  492. package/src/components/ui/__snapshots__/Spin.test.tsx.snap +0 -193
  493. package/src/components/ui/__snapshots__/Tooltip.test.tsx.snap +0 -113
  494. package/src/components/ui/definitions.stories.tsx +0 -139
  495. package/src/context/visemeContext.tsx +0 -384
  496. package/src/helpers/chatPdfExport.ts +0 -544
  497. package/src/helpers/configuration.ts +0 -42
  498. package/src/helpers/constants.ts +0 -268
  499. package/src/helpers/convertDocument.test.ts +0 -67
  500. package/src/helpers/convertDocument.ts +0 -35
  501. package/src/helpers/credits.ts +0 -54
  502. package/src/helpers/error.test.ts +0 -29
  503. package/src/helpers/error.ts +0 -278
  504. package/src/helpers/imageCompression.ts +0 -230
  505. package/src/helpers/llmUsage.ts +0 -328
  506. package/src/helpers/markedExtendedTables.js +0 -217
  507. package/src/helpers/media.test.ts +0 -169
  508. package/src/helpers/media.ts +0 -81
  509. package/src/helpers/message.test.ts +0 -126
  510. package/src/helpers/message.ts +0 -219
  511. package/src/helpers/nats/getNatsConfig.ts +0 -148
  512. package/src/helpers/nats/isSessionExpiredError.test.ts +0 -38
  513. package/src/helpers/nats/isSessionExpiredError.ts +0 -51
  514. package/src/helpers/nats/useNats.test.tsx +0 -81
  515. package/src/helpers/nats/useNats.ts +0 -204
  516. package/src/helpers/nats/useNatsSession.test.ts +0 -16
  517. package/src/helpers/nats/useNatsSession.ts +0 -460
  518. package/src/helpers/piiDetection.test.ts +0 -186
  519. package/src/helpers/piiDetection.ts +0 -91
  520. package/src/helpers/sanitizer.ts +0 -17
  521. package/src/helpers/statistics.ts +0 -58
  522. package/src/helpers/stt/useSTT.ts +0 -515
  523. package/src/helpers/translations.ts +0 -39
  524. package/src/helpers/tts/ttsVoiceUtility.ts +0 -275
  525. package/src/helpers/tts/useTTS.ts +0 -621
  526. package/src/helpers/utils.test.ts +0 -331
  527. package/src/helpers/utils.ts +0 -591
  528. package/src/helpers/venue.ts +0 -36
  529. package/src/i18n.ts +0 -39
  530. package/src/index.stories.tsx +0 -220
  531. package/src/index.test.tsx +0 -156
  532. package/src/index.tsx +0 -592
  533. package/src/locales/de.json +0 -680
  534. package/src/locales/en.json +0 -754
  535. package/src/locales/es.json +0 -682
  536. package/src/locales/fr.json +0 -682
  537. package/src/locales/it.json +0 -750
  538. package/src/mocks/data.ts +0 -790
  539. package/src/styles.css +0 -126
  540. package/src/types/integration.test.ts +0 -32
  541. package/src/types/integration.ts +0 -31
  542. package/src/types/layout.ts +0 -50
  543. package/src/version.ts +0 -2
@@ -1,3930 +0,0 @@
1
- // Typings
2
- import {
3
- DialogState,
4
- Memori,
5
- Integration,
6
- Venue,
7
- Message,
8
- Medium,
9
- OpenSession,
10
- MemoriConfig,
11
- TranslatedHint,
12
- Tenant,
13
- MemoriSession,
14
- User,
15
- ExpertReference,
16
- ResponseSpec,
17
- ChatLog,
18
- } from '@memori.ai/memori-api-client/src/types';
19
- import { ArtifactData } from '../MemoriArtifactSystem/types/artifact.types';
20
- import { ArtifactAPIBridge } from '../MemoriArtifactSystem/utils/ArtifactAPI';
21
- import type { LayoutName, PiiDetectionConfig } from '../../types/layout';
22
- import { checkPii } from '../../helpers/piiDetection'; // PII check when integrationConfig.layout has piiDetection.enabled
23
-
24
- // Libraries
25
- import React, {
26
- useState,
27
- useEffect,
28
- useCallback,
29
- CSSProperties,
30
- useRef,
31
- useMemo,
32
- } from 'react';
33
- import { useTranslation } from 'react-i18next';
34
- import memoriApiClient from '@memori.ai/memori-api-client';
35
- import { IAudioContext } from 'standardized-audio-context';
36
- import cx from 'classnames';
37
- import { DateTime } from 'luxon';
38
- import toast from 'react-hot-toast';
39
-
40
- // Components
41
- import PositionDrawer from '../PositionDrawer/PositionDrawer';
42
- import MemoriAuth from '../Auth/Auth';
43
- import Chat, { Props as ChatProps } from '../Chat/Chat';
44
- import StartPanel, { Props as StartPanelProps } from '../StartPanel/StartPanel';
45
- import Avatar, { Props as AvatarProps } from '../Avatar/Avatar';
46
- import Header, { Props as HeaderProps } from '../Header/Header';
47
- import PoweredBy from '../PoweredBy/PoweredBy';
48
- import AgeVerificationModal from '../AgeVerificationModal/AgeVerificationModal';
49
- import SettingsDrawer from '../SettingsDrawer/SettingsDrawer';
50
- import KnownFacts from '../KnownFacts/KnownFacts';
51
- import ExpertsDrawer from '../ExpertsDrawer/ExpertsDrawer';
52
- import LoginDrawer from '../LoginDrawer/LoginDrawer';
53
- import Button from '../ui/Button';
54
- import CloseIcon from '../icons/Close';
55
-
56
- // Layout
57
- import FullPageLayout from '../layouts/FullPage';
58
- import TotemLayout from '../layouts/Totem';
59
- import ChatLayout from '../layouts/Chat';
60
- import WebsiteAssistantLayout from '../layouts/WebsiteAssistant';
61
- import HiddenChatLayout from '../layouts/HiddenChat';
62
- import ZoomedFullBodyLayout from '../layouts/ZoomedFullBody';
63
-
64
- // Helpers / Utils
65
- import { getTranslation } from '../../helpers/translations';
66
- import {
67
- setLocalConfig,
68
- getLocalConfig,
69
- removeLocalConfig,
70
- } from '../../helpers/configuration';
71
- import {
72
- hasTouchscreen,
73
- stripDuplicates,
74
- installMathJax,
75
- } from '../../helpers/utils';
76
- import { getTTSVoice } from '../../helpers/tts/ttsVoiceUtility';
77
- import {
78
- allowedMediaTypes,
79
- anonTag,
80
- uiLanguages,
81
- } from '../../helpers/constants';
82
- import { getErrori18nKey } from '../../helpers/error';
83
- import { getCredits } from '../../helpers/credits';
84
- import { sanitizeText } from '../../helpers/sanitizer';
85
- import { TTSConfig, useTTS } from '../../helpers/tts/useTTS';
86
- import ChatHistoryDrawer from '../ChatHistoryDrawer/ChatHistory';
87
- import { STTConfig, useSTT } from '../../helpers/stt/useSTT';
88
- import { useNats } from '../../helpers/nats/useNats';
89
- import {
90
- NatsProgressEvent,
91
- NatsDialogResponseEvent,
92
- NatsErrorEvent,
93
- } from '../../helpers/nats/useNatsSession';
94
- import {
95
- isSessionExpiredNatsError,
96
- isSessionExpiredNatsResponse,
97
- } from '../../helpers/nats/isSessionExpiredError';
98
-
99
- // Widget utilities and helpers
100
- const getMemoriState = (integrationId?: string): object | null => {
101
- let widget = integrationId
102
- ? document.querySelector(
103
- `.memori-widget[data-memori-integration="${integrationId}"]`
104
- ) ||
105
- document
106
- .querySelector('memori-client')
107
- ?.shadowRoot?.querySelector(`.memori-widget[data-memori-integration]`)
108
- : document.querySelector('.memori-widget') ||
109
- document
110
- .querySelector('memori-client')
111
- ?.shadowRoot?.querySelector('.memori-widget');
112
-
113
- if (!widget) return null;
114
-
115
- let engineState = (widget as HTMLElement).dataset?.memoriEngineState;
116
- if (!engineState) return null;
117
-
118
- let dialogState = JSON.parse(engineState);
119
-
120
- let loginToken = getLocalConfig<string | undefined>('loginToken', undefined);
121
-
122
- return {
123
- ...dialogState,
124
- loginToken,
125
- };
126
- };
127
-
128
- /** Unified login token resolution: React state > ref > call-site override > props. */
129
- const resolveLoginTokenValue = (
130
- loginToken: string | undefined,
131
- userTokenRef: string | undefined,
132
- callAdditionalInfoLoginToken: string | undefined,
133
- additionalInfoLoginToken: string | undefined,
134
- authTokenProp: string | undefined
135
- ): string | undefined =>
136
- loginToken ??
137
- userTokenRef ??
138
- callAdditionalInfoLoginToken ??
139
- additionalInfoLoginToken ??
140
- authTokenProp;
141
-
142
- /** Place spec with all nulls for postEnterTextAsync when position is not set or user chose "I don't want to provide my position". */
143
- const NULL_PLACE_SPEC = {
144
- placeName: null,
145
- latitude: null,
146
- longitude: null,
147
- uncertaintyKm: null,
148
- } as const;
149
-
150
- const logWidgetError = (context: string, detail?: unknown) => {
151
- console.error(`[MemoriWidget] ${context}`, detail ?? '');
152
- };
153
-
154
- /** Reads correlation id from HTTP async response (supports camelCase / snake_case). */
155
- function readCorrelationID(response: {
156
- correlationID?: string;
157
- }): string | undefined {
158
- const value = response.correlationID;
159
- return typeof value === 'string' && value.length > 0 ? value : undefined;
160
- }
161
-
162
- /** Stable engine-owned fields used to tell whether a pending turn completed. */
163
- export function dialogStateFingerprint(state: DialogState | undefined): string {
164
- if (!state) return '';
165
- return JSON.stringify({
166
- state: state.state,
167
- previousState: state.previousState,
168
- emission: state.emission,
169
- emitter: state.emitter,
170
- lastMatchedMemoryID: state.lastMatchedMemoryID,
171
- currentDate: state.currentDate,
172
- currentMemoryID: state.currentMemoryID,
173
- contextVars: state.contextVars,
174
- emittedMedia: state.emittedMedia,
175
- });
176
- }
177
-
178
- type MemoriTextEnteredEvent = CustomEvent<{
179
- text: string;
180
- waitForPrevious?: boolean;
181
- hidden?: boolean;
182
- typingText?: string;
183
- useLoaderTextAsMsg?: boolean;
184
- hasBatchQueued?: boolean;
185
- }>;
186
-
187
- /**
188
- * Dispatches a MemoriTextEntered event to simulate a user typing a message
189
- * @param message The text message to send
190
- * @param waitForPrevious Whether to wait for previous message to finish before sending (default true)
191
- * @param hidden Whether to hide the message from chat history (default false)
192
- * @param typingText Optional custom typing indicator text
193
- * @param useLoaderTextAsMsg Whether to use the loader text as the message (default false)
194
- * @param hasBatchQueued Whether there are more messages queued to be sent (default false)
195
- */
196
- const typeMessage = (
197
- message: string,
198
- waitForPrevious = true,
199
- hidden = false,
200
- typingText?: string,
201
- useLoaderTextAsMsg = false,
202
- hasBatchQueued = false
203
- ) => {
204
- const e: MemoriTextEnteredEvent = new CustomEvent('MemoriTextEntered', {
205
- detail: {
206
- text: message,
207
- waitForPrevious,
208
- hidden,
209
- typingText,
210
- useLoaderTextAsMsg,
211
- hasBatchQueued,
212
- },
213
- });
214
- document.dispatchEvent(e);
215
-
216
- // Special handling for Safari on iOS devices
217
- const isSafariIOS =
218
- window.navigator.userAgent.includes('Safari') &&
219
- !window.navigator.userAgent.includes('Chrome') &&
220
- /iPad|iPhone|iPod/.test(navigator.userAgent);
221
-
222
- if (isSafariIOS) {
223
- // Dispatch end speak event after short delay for iOS Safari
224
- setTimeout(() => {
225
- document.dispatchEvent(new CustomEvent('MemoriEndSpeak'));
226
- }, 300);
227
- }
228
- };
229
-
230
- /**
231
- * Helper function to send a hidden message
232
- * Wraps typeMessage with hidden=true and passes through other params
233
- */
234
- const typeMessageHidden = (
235
- message: string,
236
- waitForPrevious = true,
237
- typingText?: string,
238
- useLoaderTextAsMsg = false,
239
- hasBatchQueued = false
240
- ) =>
241
- typeMessage(
242
- message,
243
- waitForPrevious,
244
- true,
245
- typingText,
246
- useLoaderTextAsMsg,
247
- hasBatchQueued
248
- );
249
-
250
- const typeBatchMessages = (
251
- messages: {
252
- message: string;
253
- waitForPrevious?: boolean;
254
- hidden?: boolean;
255
- typingText?: string;
256
- useLoaderTextAsMsg?: boolean;
257
- }[]
258
- ) => {
259
- function disableInputs() {
260
- document
261
- .querySelector('fieldset#chat-fieldset')
262
- ?.setAttribute('disabled', '');
263
-
264
- const styles = `opacity: 0.5; touch-action: none; pointer-events: none;`;
265
- document
266
- .querySelector('textarea.memori-chat-textarea--input')
267
- ?.setAttribute('style', styles);
268
- document
269
- .querySelector('button.memori-chat-inputs--send')
270
- ?.setAttribute('style', styles);
271
- document
272
- .querySelector('button.memori-chat-inputs--mic')
273
- ?.setAttribute('style', styles);
274
- }
275
-
276
- function reEnableInputs() {
277
- document
278
- .querySelector('fieldset#chat-fieldset')
279
- ?.removeAttribute('disabled');
280
-
281
- document
282
- .querySelector('textarea.memori-chat-textarea--input')
283
- ?.removeAttribute('style');
284
- document
285
- .querySelector('button.memori-chat-inputs--send')
286
- ?.removeAttribute('style');
287
- document
288
- .querySelector('button.memori-chat-inputs--mic')
289
- ?.removeAttribute('style');
290
- }
291
-
292
- function areInputsDisabled() {
293
- return !!document
294
- .querySelector('fieldset#chat-fieldset')
295
- ?.hasAttribute('disabled');
296
- }
297
-
298
- const isSafariIOS =
299
- window.navigator.userAgent.includes('Safari') &&
300
- !window.navigator.userAgent.includes('Chrome') &&
301
- /iPad|iPhone|iPod/.test(navigator.userAgent);
302
-
303
- const stepsGenerator = (function* () {
304
- yield* messages;
305
- })();
306
-
307
- disableInputs();
308
-
309
- const submitNewMessage = () => {
310
- const next = stepsGenerator.next();
311
- const step = next.value;
312
-
313
- if (step) {
314
- if (!areInputsDisabled()) {
315
- disableInputs();
316
- }
317
-
318
- let waitForPrevious = step.waitForPrevious;
319
- if (isSafariIOS) waitForPrevious = false;
320
-
321
- typeMessage(
322
- step.message,
323
- waitForPrevious,
324
- step.hidden,
325
- step.typingText,
326
- step.useLoaderTextAsMsg,
327
- !next.done
328
- );
329
-
330
- if (isSafariIOS) {
331
- setTimeout(() => {
332
- document.dispatchEvent(new CustomEvent('MemoriEndSpeak'));
333
- reEnableInputs();
334
- }, 3000);
335
- }
336
- } else if (areInputsDisabled()) {
337
- reEnableInputs();
338
- }
339
-
340
- if (next.done) {
341
- document.removeEventListener('MemoriEndSpeak', submitNewMessage);
342
- if (areInputsDisabled()) reEnableInputs();
343
- return;
344
- }
345
- };
346
-
347
- document.addEventListener('MemoriEndSpeak', submitNewMessage);
348
-
349
- submitNewMessage();
350
- };
351
-
352
- type MemoriNewDialogStateEvent = CustomEvent<DialogState>;
353
-
354
- type ArtifactCreatedEvent = CustomEvent<{
355
- artifact: ArtifactData;
356
- message: Message;
357
- }>;
358
-
359
- interface CustomEventMap {
360
- MemoriTextEntered: MemoriTextEnteredEvent;
361
- MemoriEndSpeak: CustomEvent;
362
- MemoriResetUIEffects: CustomEvent;
363
- MemoriNewDialogState: MemoriNewDialogStateEvent;
364
- artifactCreated: ArtifactCreatedEvent;
365
- }
366
- declare global {
367
- interface Document {
368
- addEventListener<K extends keyof CustomEventMap>(
369
- type: K,
370
- listener: (this: Document, ev: CustomEventMap[K]) => void
371
- ): void;
372
- removeEventListener<K extends keyof CustomEventMap>(
373
- type: K,
374
- listener: (this: Document, ev: CustomEventMap[K]) => void
375
- ): void;
376
- dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
377
- }
378
-
379
- interface Window {
380
- getMemoriState: typeof getMemoriState;
381
- typeMessage: typeof typeMessage;
382
- typeMessageHidden: typeof typeMessageHidden;
383
- typeBatchMessages: typeof typeBatchMessages;
384
- MemoriArtifactAPI?: {
385
- openArtifact: (artifact: ArtifactData) => void;
386
- createAndOpenArtifact: (
387
- content: string,
388
- mimeType?: string,
389
- title?: string
390
- ) => void;
391
- createFromOutputElement: (outputElement: HTMLOutputElement) => string;
392
- closeArtifact: () => void;
393
- toggleFullscreen: () => void;
394
- getState: () => {
395
- currentArtifact: ArtifactData | null;
396
- isDrawerOpen: boolean;
397
- isFullscreen: boolean;
398
- };
399
- };
400
- }
401
- }
402
- window.getMemoriState = getMemoriState;
403
- window.typeMessage = typeMessage;
404
- window.typeMessageHidden = typeMessageHidden;
405
- window.typeBatchMessages = typeBatchMessages;
406
-
407
- let audioContext: IAudioContext;
408
-
409
- let memoriPassword: string | undefined;
410
-
411
- export interface LayoutProps {
412
- Header?: typeof Header;
413
- headerProps?: HeaderProps;
414
- Avatar: typeof Avatar;
415
- avatarProps?: AvatarProps;
416
- Chat?: typeof Chat;
417
- chatProps?: ChatProps;
418
- StartPanel: typeof StartPanel;
419
- startPanelProps?: StartPanelProps;
420
- integrationStyle?: JSX.Element | null;
421
- integrationBackground?: JSX.Element | null;
422
- poweredBy?: JSX.Element | null;
423
- sessionId?: string;
424
- hasUserActivatedSpeak?: boolean;
425
- showUpload?: boolean;
426
- loading?: boolean;
427
- autoStart?: boolean;
428
- onSidebarToggle?: (isOpen: boolean) => void;
429
- /** When true or "true" (e.g. from integrationConfig or web component attribute), hide the 3D avatar. */
430
- avatar3dHidden?: boolean | string;
431
- }
432
-
433
- export interface Props {
434
- memori: Memori;
435
- ownerUserName?: string | null;
436
- ownerUserID?: string | null;
437
- tenantID: string;
438
- memoriConfigs?: MemoriConfig[];
439
- memoriLang?: string;
440
- /** UI language: labels, buttons, page translation (i18n) */
441
- uiLang?: string;
442
- /** Spoken/chat language: select box in StartPanel and conversation language */
443
- spokenLang?: string;
444
- multilingual?: boolean;
445
- integration?: Integration;
446
- layout?: LayoutName;
447
- customLayout?: React.FC<LayoutProps>;
448
- showShare?: boolean;
449
- showCopyButton?: boolean;
450
- showTranslationOriginal?: boolean;
451
- showInputs?: boolean;
452
- showDates?: boolean;
453
- showContextPerLine?: boolean;
454
- showMessageConsumption?: boolean;
455
- showSettings?: boolean;
456
- showClear?: boolean;
457
- showOnlyLastMessages?: boolean;
458
- showTypingText?: boolean;
459
- showLogin?: boolean;
460
- showUpload?: boolean;
461
- showChatHistory?: boolean;
462
- showReasoning?: boolean;
463
- /** When true and layout is WEBSITE_ASSISTANT, hide the 3D avatar in the expanded panel. */
464
- avatar3dHidden?: boolean;
465
- preview?: boolean;
466
- embed?: boolean;
467
- height?: number | string;
468
- secret?: string;
469
- baseUrl?: string;
470
- apiURL?: string;
471
- engineURL?: string;
472
- initialContextVars?: { [key: string]: string };
473
- initialQuestion?: string;
474
- ogImage?: string;
475
- sessionID?: string;
476
- tenant?: Tenant;
477
- personification?: {
478
- name?: string;
479
- tag: string;
480
- pin: string;
481
- };
482
- ttsProvider?: 'azure' | 'openai';
483
- enableAudio?: boolean;
484
- defaultSpeakerActive?: boolean;
485
- disableTextEnteredEvents?: boolean;
486
- onStateChange?: (state?: DialogState) => void;
487
- additionalInfo?: OpenSession['additionalInfo'] & { [key: string]: string };
488
- customMediaRenderer?: ChatProps['customMediaRenderer'];
489
- additionalSettings?: JSX.Element | null;
490
- userAvatar?: string | JSX.Element;
491
- useMathFormatting?: boolean;
492
- autoStart?: boolean;
493
- applyVarsToRoot?: boolean;
494
- showFunctionCache?: boolean;
495
- authToken?: string;
496
- __WEBCOMPONENT__?: boolean;
497
- /** Override total document payload and per-document content limit (character count). Default from constants. */
498
- maxTotalMessagePayload?: number;
499
- /** Max characters in chat textarea; shows counter and enforces paste + existing text does not exceed this limit. */
500
- maxTextareaCharacters?: number;
501
- }
502
-
503
- const MemoriWidget = ({
504
- memori,
505
- memoriConfigs,
506
- ownerUserID,
507
- ownerUserName,
508
- tenantID,
509
- memoriLang,
510
- uiLang,
511
- spokenLang,
512
- multilingual,
513
- integration,
514
- layout,
515
- customLayout,
516
- showShare,
517
- preview = false,
518
- embed = false,
519
- showCopyButton = true,
520
- showTranslationOriginal = false,
521
- showInputs = true,
522
- showDates = false,
523
- showContextPerLine = false,
524
- showMessageConsumption = false,
525
- showSettings,
526
- showTypingText = false,
527
- showClear = false,
528
- showLogin = false,
529
- showUpload,
530
- showOnlyLastMessages,
531
- showChatHistory,
532
- showReasoning,
533
- avatar3dHidden,
534
- height = '100vh',
535
- secret,
536
- baseUrl = 'https://aisuru-staging.aclambda.online',
537
- apiURL = 'https://backend-staging.memori.ai',
538
- engineURL = 'https://engine-staging.memori.ai',
539
- initialContextVars,
540
- initialQuestion,
541
- ttsProvider,
542
- ogImage,
543
- sessionID: initialSessionID,
544
- tenant,
545
- personification,
546
- authToken,
547
- enableAudio,
548
- defaultSpeakerActive = true,
549
- disableTextEnteredEvents = false,
550
- onStateChange,
551
- additionalInfo,
552
- additionalSettings,
553
- customMediaRenderer,
554
- userAvatar,
555
- __WEBCOMPONENT__ = false,
556
- useMathFormatting = false,
557
- autoStart = false,
558
- applyVarsToRoot = false,
559
- showFunctionCache = false,
560
- maxTotalMessagePayload,
561
- maxTextareaCharacters,
562
- }: Props) => {
563
- const { t, i18n } = useTranslation();
564
-
565
- const [isClient, setIsClient] = useState(false);
566
- useEffect(() => {
567
- setIsClient(true);
568
- }, []);
569
-
570
- // API calls methods
571
- const client = memoriApiClient(apiURL, engineURL);
572
- const {
573
- initSession,
574
- deleteSession,
575
- postEnterTextAsync,
576
- postTextEnteredEvent,
577
- postPlaceChangedEvent,
578
- postDateChangedEvent,
579
- postTagChangedEvent,
580
- getSession,
581
- getExpertReferences,
582
- getSessionChatLogs,
583
- } = client;
584
-
585
- const [instruct, setInstruct] = useState(false);
586
- const [enableFocusChatInput, setEnableFocusChatInput] = useState(true);
587
-
588
- const [loginToken, setLoginToken] = useState<string | undefined>(
589
- additionalInfo?.loginToken ?? authToken
590
- );
591
- const userTokenRef = useRef<string | undefined>(
592
- additionalInfo?.loginToken ?? authToken
593
- );
594
- const prevPropsLoginTokenRef = useRef<string | undefined>(
595
- additionalInfo?.loginToken ?? authToken
596
- );
597
-
598
- const [user, setUser] = useState<User | undefined>({
599
- avatarURL: typeof userAvatar === 'string' ? userAvatar : undefined,
600
- } as User);
601
-
602
- const resolveLoginToken = useCallback(
603
- (callAdditionalInfoLoginToken?: string) =>
604
- resolveLoginTokenValue(
605
- loginToken,
606
- userTokenRef.current,
607
- callAdditionalInfoLoginToken,
608
- additionalInfo?.loginToken,
609
- authToken
610
- ),
611
- [loginToken, additionalInfo?.loginToken, authToken]
612
- );
613
-
614
- // Sync loginToken when props change (including explicit removal from parent)
615
- useEffect(() => {
616
- const incomingToken = additionalInfo?.loginToken ?? authToken;
617
- const prevPropsToken = prevPropsLoginTokenRef.current;
618
- prevPropsLoginTokenRef.current = incomingToken;
619
-
620
- if (incomingToken !== prevPropsToken) {
621
- setLoginToken(incomingToken);
622
- userTokenRef.current = incomingToken;
623
- if (!incomingToken) {
624
- removeLocalConfig('loginToken');
625
- setUser(undefined);
626
- }
627
- }
628
- }, [additionalInfo?.loginToken, authToken]);
629
-
630
- useEffect(() => {
631
- if (!loginToken || !(showLogin || memori.requireLoginToken)) {
632
- return;
633
- }
634
-
635
- let cancelled = false;
636
-
637
- client.backend
638
- .pwlGetCurrentUser(loginToken)
639
- .then(({ user: fetchedUser, resultCode }) => {
640
- if (cancelled) return;
641
-
642
- if (fetchedUser && resultCode === 0) {
643
- setUser(fetchedUser);
644
- setLocalConfig('loginToken', loginToken);
645
- userTokenRef.current = loginToken;
646
-
647
- if (!birthDate && fetchedUser.birthDate) {
648
- setBirthDate(fetchedUser.birthDate);
649
- setLocalConfig('birthDate', fetchedUser.birthDate);
650
- }
651
- } else {
652
- setLoginToken(undefined);
653
- userTokenRef.current = undefined;
654
- setUser(undefined);
655
- removeLocalConfig('loginToken');
656
- }
657
- });
658
-
659
- return () => {
660
- cancelled = true;
661
- };
662
- }, [loginToken, showLogin, memori.requireLoginToken]);
663
- const [showLoginDrawer, setShowLoginDrawer] = useState(false);
664
-
665
- const [clickedStart, setClickedStart] = useState(false);
666
- const sessionStartingRef = useRef(false);
667
-
668
- const language =
669
- memori.culture?.split('-')?.[0]?.toUpperCase()! ||
670
- memoriConfigs
671
- ?.find(c => c.memoriConfigID === memori.memoriConfigurationID)
672
- ?.culture?.split('-')?.[0]
673
- ?.toUpperCase()!;
674
- const integrationConfig = integration?.customData
675
- ? JSON.parse(integration.customData)
676
- : null;
677
-
678
- const isMultilanguageEnabled =
679
- multilingual !== undefined
680
- ? multilingual
681
- : !!integrationConfig?.multilanguage;
682
- const forcedTimeout = integrationConfig?.forcedTimeout as number | undefined;
683
- const [userLang, setUserLang] = useState(
684
- spokenLang ??
685
- memoriLang ??
686
- integrationConfig?.lang ??
687
- language ??
688
- integrationConfig?.uiLang ??
689
- i18n.language ??
690
- 'IT'
691
- );
692
-
693
- // Sync userLang when parent passes spokenLang (select box in StartPanel)
694
- useEffect(() => {
695
- if (spokenLang != null) {
696
- setUserLang(spokenLang);
697
- }
698
- }, [spokenLang]);
699
-
700
- const applyMathFormatting =
701
- useMathFormatting !== undefined
702
- ? useMathFormatting
703
- : !!integrationConfig?.useMathFormatting;
704
- useEffect(() => {
705
- if (applyMathFormatting) installMathJax();
706
- }, [applyMathFormatting]);
707
-
708
- /**
709
- * Sets the UI language in the i18n instance (page translation). uiLang takes precedence.
710
- */
711
- useEffect(() => {
712
- const langToApply =
713
- uiLang && uiLanguages.includes(uiLang.toLowerCase())
714
- ? uiLang.toLowerCase()
715
- : userLang && uiLanguages.includes(userLang.toLowerCase())
716
- ? userLang.toLowerCase()
717
- : null;
718
- if (langToApply && typeof i18n?.changeLanguage === 'function') {
719
- // @ts-ignore
720
- i18n.changeLanguage(langToApply);
721
- }
722
- }, [uiLang, userLang]);
723
-
724
- const [loading, setLoading] = useState(false);
725
- const [memoriTyping, setMemoriTyping] = useState<boolean>(false);
726
- const [typingText, setTypingText] = useState<string>();
727
-
728
- type EnterTextRetryParams = {
729
- text?: string;
730
- media?: Medium[];
731
- translate?: boolean;
732
- translatedText?: string;
733
- hidden?: boolean;
734
- typingText?: string;
735
- useLoaderTextAsMsg?: boolean;
736
- hasBatchQueued?: boolean;
737
- expiredSessionID?: string;
738
- continueFromChatLogID?: string;
739
- };
740
-
741
- type PendingEnterText = EnterTextRetryParams & {
742
- msg?: string;
743
- stateBeforeRequest?: string;
744
- waitForResponse?: {
745
- resolve: (event: NatsDialogResponseEvent) => void;
746
- reject: (error: Error) => void;
747
- timeoutId: ReturnType<typeof setTimeout>;
748
- };
749
- };
750
- const pendingEnterTextRef = useRef<Map<string, PendingEnterText>>(new Map());
751
- const bufferedNatsResponsesRef = useRef<Map<string, NatsDialogResponseEvent>>(
752
- new Map()
753
- );
754
-
755
- // Layout: from prop (string only) or integrationConfig. PII detection is only from integrationConfig (customData.layout as object with piiDetection).
756
- const layoutName =
757
- typeof layout === 'string'
758
- ? layout
759
- : typeof integrationConfig?.layout === 'string'
760
- ? integrationConfig.layout
761
- : integrationConfig?.layout?.name;
762
- const selectedLayout = layoutName || 'DEFAULT';
763
- const piiDetection: PiiDetectionConfig | undefined =
764
- typeof integrationConfig?.layout === 'object' &&
765
- integrationConfig?.layout !== null &&
766
- integrationConfig?.layout?.piiDetection?.enabled
767
- ? integrationConfig.layout.piiDetection
768
- : undefined;
769
-
770
- const defaultEnableAudio =
771
- enableAudio ?? integrationConfig?.enableAudio ?? true;
772
-
773
- const [hasUserActivatedListening, setHasUserActivatedListening] =
774
- useState(false);
775
- const [hasUserTypedMessage, setHasUserTypedMessage] = useState(false);
776
- const [showPositionDrawer, setShowPositionDrawer] = useState(false);
777
- const [showSettingsDrawer, setShowSettingsDrawer] = useState(false);
778
- const [showChatHistoryDrawer, setShowChatHistoryDrawer] = useState(false);
779
- const [showKnownFactsDrawer, setShowKnownFactsDrawer] = useState(false);
780
- const [showExpertsDrawer, setShowExpertsDrawer] = useState(false);
781
- const [continuousSpeech, setContinuousSpeech] = useState(false);
782
- const [continuousSpeechTimeout, setContinuousSpeechTimeout] = useState(2);
783
- const [controlsPosition, setControlsPosition] = useState<'center' | 'bottom'>(
784
- 'center'
785
- );
786
-
787
- const [enablePositionControls, setEnablePositionControls] = useState(false);
788
- const [avatarType, setAvatarType] = useState<'blob' | 'avatar3d' | null>(
789
- null
790
- );
791
- const [hideEmissions, setHideEmissions] = useState(false);
792
- const [runtimeShowMessageConsumption, setRuntimeShowMessageConsumption] =
793
- useState(false);
794
-
795
- const speechSynthesizerRef = useRef<any | null>(null);
796
- const [memoriSpeaking, setMemoriSpeaking] = useState(false);
797
-
798
- useEffect(() => {
799
- setMemoriSpeaking(!!speechSynthesizerRef.current);
800
- }, [speechSynthesizerRef.current]);
801
-
802
- useEffect(() => {
803
- let defaultControlsPosition: 'center' | 'bottom' = 'bottom';
804
- let microphoneMode = getLocalConfig<string>(
805
- 'microphoneMode',
806
- 'HOLD_TO_TALK'
807
- );
808
-
809
- if (window.innerWidth <= 768) {
810
- // on mobile, default position is bottom
811
- defaultControlsPosition = 'bottom';
812
- // on mobile, keep only HOLD_TO_TALK mode
813
- microphoneMode = 'HOLD_TO_TALK';
814
- } else if (
815
- window.matchMedia('(orientation: portrait)').matches ||
816
- window.innerHeight > window.innerWidth
817
- ) {
818
- // on portrait, default position is center
819
- defaultControlsPosition = 'center';
820
- } else {
821
- // on landscape, default position is bottom
822
- defaultControlsPosition = 'bottom';
823
- }
824
-
825
- setContinuousSpeech(speakerMuted ? false : microphoneMode === 'CONTINUOUS');
826
- setContinuousSpeechTimeout(getLocalConfig('continuousSpeechTimeout', 2));
827
- setControlsPosition(
828
- getLocalConfig('controlsPosition', defaultControlsPosition)
829
- );
830
- setAvatarType(getLocalConfig('avatarType', 'avatar3d'));
831
- setHideEmissions(getLocalConfig('hideEmissions', false));
832
- setRuntimeShowMessageConsumption(
833
- getLocalConfig(
834
- 'showMessageConsumption',
835
- showMessageConsumption ??
836
- integrationConfig?.showMessageConsumption ??
837
- false
838
- )
839
- );
840
-
841
- if (!additionalInfo?.loginToken && !authToken) {
842
- setLoginToken(getLocalConfig<typeof loginToken>('loginToken', undefined));
843
- userTokenRef.current = getLocalConfig<typeof loginToken>('loginToken', undefined);
844
-
845
- setBirthDate(getLocalConfig<string | undefined>('birthDate', undefined));
846
- }
847
-
848
- // If audio is disabled, automatically mute the speaker
849
- if (!(enableAudio ?? integrationConfig?.enableAudio ?? true)) {
850
- setLocalConfig('muteSpeaker', true);
851
- }
852
- }, []);
853
-
854
- // Effect to handle enableAudio changes
855
- useEffect(() => {
856
- const isAudioEnabled =
857
- enableAudio ?? integrationConfig?.enableAudio ?? true;
858
- if (!isAudioEnabled) {
859
- // Force mute when audio is disabled
860
- setLocalConfig('muteSpeaker', true);
861
- }
862
- }, [enableAudio, integrationConfig?.enableAudio]);
863
-
864
- /**
865
- * Auth for private and secret memori
866
- */
867
- const [memoriPwd, setMemoriPwd] = useState<string | undefined>(secret);
868
- const [memoriTokens, setMemoriTokens] = useState<string[] | undefined>();
869
- const [authModalState, setAuthModalState] = useState<
870
- null | 'password' | 'tokens'
871
- >(null);
872
-
873
- /**
874
- * Position drawer
875
- */
876
- const [position, _setPosition] = useState<Venue>();
877
-
878
- /** True when the user has set a real position; false when position is missing or "I don't want to provide my position". */
879
- const hasUserProvidedPosition = useCallback((venue: Venue | undefined) => {
880
- if (!venue) return false;
881
- if (
882
- venue.placeName === 'Position' &&
883
- venue.latitude === 0 &&
884
- venue.longitude === 0
885
- ) {
886
- return false;
887
- }
888
- return true;
889
- }, []);
890
-
891
- /** Build optional place for EnterTextSpecs (placeName and/or lat/lon; lat/lon must be together). */
892
- const buildEnterTextPlace = useCallback((venue: Venue | undefined) => {
893
- if (!venue) return undefined;
894
- const place: {
895
- placeName?: string;
896
- latitude?: number;
897
- longitude?: number;
898
- uncertaintyKm?: number;
899
- } = {};
900
- if (venue.latitude != null && venue.longitude != null) {
901
- place.latitude = venue.latitude;
902
- place.longitude = venue.longitude;
903
- if (venue.placeName) place.placeName = venue.placeName;
904
- if (venue.uncertainty != null && venue.uncertainty > 0)
905
- place.uncertaintyKm = venue.uncertainty;
906
- } else if (venue.placeName) {
907
- place.placeName = venue.placeName;
908
- }
909
- return Object.keys(place).length > 0 ? place : undefined;
910
- }, []);
911
-
912
- /** Place to send with postEnterTextAsync: real place, nulls when no/declined position, or undefined when position not needed. */
913
- const getPlaceSpecForEnterText = useCallback(
914
- (venue: Venue | undefined) => {
915
- if (!memori.needsPosition) return undefined;
916
- return hasUserProvidedPosition(venue)
917
- ? buildEnterTextPlace(venue)
918
- : NULL_PLACE_SPEC;
919
- },
920
- [memori.needsPosition, hasUserProvidedPosition, buildEnterTextPlace]
921
- );
922
-
923
- const setPosition = (venue?: Venue) => {
924
- _setPosition(venue);
925
-
926
- // Only save position to local config if memori.needsPosition is true
927
- if (venue && memori.needsPosition) {
928
- setLocalConfig('position', JSON.stringify(venue));
929
- } else if (!venue) {
930
- removeLocalConfig('position');
931
- }
932
- };
933
-
934
- useEffect(() => {
935
- // Only load position from local config if memori.needsPosition is true
936
- if (memori.needsPosition) {
937
- const position = getLocalConfig<Venue | undefined>('position', undefined);
938
- if (position) {
939
- _setPosition(position);
940
- }
941
- }
942
- }, [memori.needsPosition]);
943
-
944
- /**
945
- * History e gestione invio messaggi
946
- */
947
- const [userMessage, setUserMessage] = useState<string>('');
948
- const onChangeUserMessage = (value: string) => {
949
- if (!value || value === '\n' || value.trim() === '') {
950
- setUserMessage('');
951
- // resetInteractionTimeout();
952
- return;
953
- }
954
- setUserMessage(value);
955
- clearInteractionTimeout();
956
- };
957
- const [listening, setListening] = useState(false);
958
- const [history, setHistory] = useState<Message[]>([]);
959
- const pushMessage = (message: Message) => {
960
- setHistory(history => [
961
- ...history,
962
- {
963
- ...message,
964
- media:
965
- message.media?.filter(
966
- m =>
967
- !(m.mimeType === 'text/javascript' && !!m.properties?.executable)
968
- ) ?? [],
969
- },
970
- ]);
971
- };
972
-
973
- // When a user resumes a chat, we need to set the chat reference link of the previous chat
974
- const [chatLogID, setChatLogID] = useState<string | undefined>(undefined);
975
- /**
976
- * Sends a message to the Memori and handles the response
977
- * @param text The text message to send
978
- * @param media Optional media attachments
979
- * @param newSessionId Optional new session ID to use
980
- * @param translate Whether to translate the message before sending (default true)
981
- * @param translatedText Optional pre-translated text
982
- * @param hidden Whether to hide the message from chat history (default false)
983
- * @param typingText Optional custom typing indicator text
984
- * @param useLoaderTextAsMsg Whether to use the loader text as the message (default false)
985
- * @param hasBatchQueued Whether there are more messages queued to be sent (default false)
986
- * @param skipHistoryPush Skip adding the user message to history (e.g. session-expired retry)
987
- */
988
- const sendMessage = async (
989
- text: string,
990
- media?: Medium[],
991
- newSessionId?: string,
992
- translate: boolean = true,
993
- translatedText?: string,
994
- hidden: boolean = false,
995
- typingText?: string,
996
- useLoaderTextAsMsg = false,
997
- hasBatchQueued = false,
998
- skipHistoryPush = false
999
- ) => {
1000
- // Get the session ID from params or global state
1001
- const sessionID =
1002
- newSessionId ||
1003
- sessionId ||
1004
- (window.getMemoriState() as MemoriSession)?.sessionID;
1005
- if (!sessionID || !text?.length) return;
1006
-
1007
- // Build full message text (same as what will be sent) so we can run PII check on it.
1008
- // Order: user text -> optional translation -> appended document attachment content.
1009
- let msg = text;
1010
- if (
1011
- !hidden &&
1012
- translate &&
1013
- isMultilanguageEnabled &&
1014
- userLang.toUpperCase() !== language.toUpperCase()
1015
- ) {
1016
- const translation = await getTranslation(
1017
- text,
1018
- language,
1019
- userLang,
1020
- baseUrl
1021
- );
1022
- msg = translation.text;
1023
- }
1024
- const mediaDocuments = media?.filter(
1025
- m => (m as any).type === 'document' && m.properties?.isAttachedFile
1026
- );
1027
- if (mediaDocuments && mediaDocuments.length > 0) {
1028
- const documentContents = mediaDocuments.map(doc => doc.content).join(' ');
1029
- msg = msg + ' ' + documentContents;
1030
- }
1031
-
1032
- // PII check: when layout has piiDetection.enabled, run regex rules on the full msg.
1033
- // If any rule matches, add the user message to history, then push the system error bubble and return without sending.
1034
- if (piiDetection?.enabled) {
1035
- const piiResult = checkPii(
1036
- msg,
1037
- piiDetection,
1038
- userLang?.toLowerCase() || 'en'
1039
- );
1040
- if (piiResult.matched && piiResult.errorText) {
1041
- if (!hidden) {
1042
- pushMessage({
1043
- text: text,
1044
- translatedText,
1045
- fromUser: true,
1046
- media: media ?? [],
1047
- initial: sessionId
1048
- ? !!newSessionId && newSessionId !== sessionId
1049
- : !!newSessionId,
1050
- });
1051
- }
1052
- pushMessage({
1053
- text: piiResult.errorText,
1054
- emitter: 'system',
1055
- fromUser: false,
1056
- initial: false,
1057
- contextVars: {},
1058
- date: new Date().toISOString(),
1059
- });
1060
- return;
1061
- }
1062
- }
1063
-
1064
- // Add user message to chat history if not hidden
1065
- if (!hidden && !skipHistoryPush)
1066
- pushMessage({
1067
- text: text,
1068
- translatedText,
1069
- fromUser: true,
1070
- media: media ?? [],
1071
- initial: sessionId
1072
- ? !!newSessionId && newSessionId !== sessionId
1073
- : !!newSessionId,
1074
- });
1075
-
1076
- // Show typing indicator after the async enter-text request is accepted (HTTP 200).
1077
- let gotError = false;
1078
-
1079
- try {
1080
- const placeSpec = getPlaceSpecForEnterText(position);
1081
- const response = await postEnterTextAsync({
1082
- sessionId: sessionID,
1083
- text: msg,
1084
- ...(memori.needsDateTime && {
1085
- dateUTC: DateTime.utc().toISO() ?? undefined,
1086
- }),
1087
- ...(placeSpec !== undefined && { place: placeSpec }),
1088
- });
1089
- const correlationID = readCorrelationID(response);
1090
- if (response.resultCode === 0 && correlationID) {
1091
- registerPendingEnterText(correlationID, {
1092
- msg,
1093
- stateBeforeRequest: dialogStateFingerprint(currentDialogState),
1094
- text,
1095
- media,
1096
- translate,
1097
- translatedText,
1098
- hidden,
1099
- typingText,
1100
- useLoaderTextAsMsg,
1101
- hasBatchQueued,
1102
- });
1103
- setMemoriTyping(true);
1104
- setTypingText(typingText);
1105
- } else if (response.resultCode === 0) {
1106
- logWidgetError('enter-text missing correlationID', response);
1107
- } else if (response.resultCode === 404) {
1108
- retryAfterExpiredSessionRef.current({
1109
- text,
1110
- media,
1111
- translate,
1112
- translatedText,
1113
- hidden,
1114
- typingText,
1115
- useLoaderTextAsMsg,
1116
- hasBatchQueued,
1117
- expiredSessionID: sessionID,
1118
- continueFromChatLogID: chatLogID,
1119
- });
1120
- } else if (response.resultCode === 500 && response.resultMessage) {
1121
- setHistory(h => [
1122
- ...h,
1123
- {
1124
- text: 'Error: ' + response.resultMessage,
1125
- emitter: 'system',
1126
- fromUser: false,
1127
- initial: false,
1128
- contextVars: {},
1129
- date: new Date().toISOString(),
1130
- },
1131
- ]);
1132
- } else {
1133
- return Promise.reject(response);
1134
- }
1135
- } catch (error) {
1136
- gotError = true;
1137
- logWidgetError('sendMessage failed', error);
1138
-
1139
- setTypingText(undefined);
1140
- setMemoriTyping(false);
1141
- }
1142
- };
1143
-
1144
- /**
1145
- * An enhanced version of translateDialogState that integrates smooth speaking
1146
- * This preserves all your existing logic while improving speech reliability
1147
- */
1148
- const translateDialogState = async (
1149
- state: DialogState,
1150
- userLang: string,
1151
- msg?: string,
1152
- avoidPushingMessage: boolean = false
1153
- ) => {
1154
- const emission = state?.emission ?? currentDialogState?.emission;
1155
-
1156
- let translatedState = { ...state };
1157
- let translatedMsg: any = null;
1158
-
1159
- // Skip translation if not needed
1160
- if (
1161
- !emission ||
1162
- language.toUpperCase() === userLang.toUpperCase() ||
1163
- !isMultilanguageEnabled ||
1164
- avoidPushingMessage
1165
- ) {
1166
- translatedState = { ...state, emission };
1167
- if (emission) {
1168
- translatedMsg = {
1169
- text: emission,
1170
- emitter: state.emitter,
1171
- media: state.emittedMedia ?? state.media,
1172
- llmUsage: (state as any).llmUsage,
1173
- fromUser: false,
1174
- questionAnswered: msg,
1175
- contextVars: state.contextVars,
1176
- date: state.currentDate,
1177
- placeName: state.currentPlaceName,
1178
- placeLatitude: state.currentLatitude,
1179
- placeLongitude: state.currentLongitude,
1180
- placeUncertaintyKm: state.currentUncertaintyKm,
1181
- tag: state.currentTag,
1182
- memoryTags: state.memoryTags,
1183
- };
1184
- }
1185
- } else {
1186
- try {
1187
- const t = await getTranslation(emission, userLang, language, baseUrl);
1188
-
1189
- // Handle hints translation if present
1190
- if (state.hints && state.hints.length > 0) {
1191
- const translatedHints = await Promise.all(
1192
- (state.hints ?? []).map(async hint => {
1193
- const tHint = await getTranslation(
1194
- hint,
1195
- userLang,
1196
- language,
1197
- baseUrl
1198
- );
1199
- return {
1200
- text: tHint?.text ?? hint,
1201
- originalText: hint,
1202
- } as TranslatedHint;
1203
- })
1204
- );
1205
- translatedState = {
1206
- ...state,
1207
- emission: t.text,
1208
- translatedHints,
1209
- };
1210
- } else {
1211
- translatedState = {
1212
- ...state,
1213
- emission: emission,
1214
- translatedEmission: t.text,
1215
- hints:
1216
- state.hints ??
1217
- (state.state === 'G1' ? currentDialogState?.hints : []),
1218
- };
1219
- }
1220
-
1221
- if (t.text.length > 0) {
1222
- translatedMsg = {
1223
- text: emission,
1224
- translatedText: t.text,
1225
- emitter: state.emitter,
1226
- media: state.emittedMedia ?? state.media,
1227
- llmUsage: (state as any).llmUsage,
1228
- fromUser: false,
1229
- questionAnswered: msg,
1230
- generatedByAI: !!state.completion,
1231
- contextVars: state.contextVars,
1232
- date: state.currentDate,
1233
- placeName: state.currentPlaceName,
1234
- placeLatitude: state.currentLatitude,
1235
- placeLongitude: state.currentLongitude,
1236
- placeUncertaintyKm: state.currentUncertaintyKm,
1237
- tag: state.currentTag,
1238
- memoryTags: state.memoryTags,
1239
- };
1240
- }
1241
- } catch (error) {
1242
- translatedState = { ...state, emission };
1243
- translatedMsg = {
1244
- text: emission,
1245
- emitter: state.emitter,
1246
- media: state.emittedMedia ?? state.media,
1247
- llmUsage: (state as any).llmUsage,
1248
- fromUser: false,
1249
- questionAnswered: msg,
1250
- contextVars: state.contextVars,
1251
- date: state.currentDate,
1252
- placeName: state.currentPlaceName,
1253
- placeLatitude: state.currentLatitude,
1254
- placeLongitude: state.currentLongitude,
1255
- placeUncertaintyKm: state.currentUncertaintyKm,
1256
- tag: state.currentTag,
1257
- memoryTags: state.memoryTags,
1258
- };
1259
- }
1260
- }
1261
-
1262
- setCurrentDialogState(translatedState);
1263
- if (!avoidPushingMessage && translatedMsg) {
1264
- pushMessage(translatedMsg);
1265
- }
1266
-
1267
- return translatedState;
1268
- };
1269
-
1270
- /**
1271
- * Age verification
1272
- */
1273
- const minAge =
1274
- memori.ageRestriction !== undefined
1275
- ? memori.ageRestriction
1276
- : memori.nsfw
1277
- ? 18
1278
- : memori.enableCompletions
1279
- ? 14
1280
- : 0;
1281
- const [birthDate, setBirthDate] = useState<string | undefined>();
1282
- const [showAgeVerification, setShowAgeVerification] = useState(false);
1283
-
1284
- const getCultureCodeByLanguage = (lang?: string): string => {
1285
- let voice = '';
1286
- let voiceLang = (
1287
- lang ||
1288
- memori.culture?.split('-')?.[0] ||
1289
- i18n.language ||
1290
- 'IT'
1291
- ).toUpperCase();
1292
- switch (voiceLang) {
1293
- case 'IT':
1294
- voice = 'it-IT';
1295
- break;
1296
- case 'DE':
1297
- voice = 'de-DE';
1298
- break;
1299
- case 'EN':
1300
- voice = 'en-GB';
1301
- break;
1302
- case 'ES':
1303
- voice = 'es-ES';
1304
- break;
1305
- case 'FR':
1306
- voice = 'fr-FR';
1307
- break;
1308
- case 'PT':
1309
- voice = 'pt-PT';
1310
- break;
1311
- case 'UK':
1312
- voice = 'uk-UK';
1313
- break;
1314
- case 'RU':
1315
- voice = 'ru-RU';
1316
- break;
1317
- case 'PL':
1318
- voice = 'pl-PL';
1319
- break;
1320
- case 'FI':
1321
- voice = 'fi-FI';
1322
- break;
1323
- case 'EL':
1324
- voice = 'el-GR';
1325
- break;
1326
- case 'AR':
1327
- voice = 'ar-SA';
1328
- break;
1329
- case 'ZH':
1330
- voice = 'zh-CN';
1331
- break;
1332
- case 'JA':
1333
- voice = 'ja-JP';
1334
- break;
1335
- default:
1336
- voice = 'it-IT';
1337
- break;
1338
- }
1339
- return voice;
1340
- };
1341
-
1342
- /**
1343
- * Sessione
1344
- */
1345
- const [sessionId, setSessionId] = useState<string | undefined>(
1346
- initialSessionID
1347
- );
1348
- const [currentDialogState, _setCurrentDialogState] = useState<DialogState>();
1349
- const setCurrentDialogState = (state?: DialogState) => {
1350
- _setCurrentDialogState(state);
1351
- if (onStateChange) {
1352
- onStateChange(state);
1353
- }
1354
-
1355
- const e: MemoriNewDialogStateEvent = new CustomEvent(
1356
- 'MemoriNewDialogState',
1357
- {
1358
- detail: state,
1359
- }
1360
- );
1361
- document.dispatchEvent(e);
1362
-
1363
- const executableSnippets = (state?.emittedMedia ?? state?.media)?.filter(
1364
- m => m.mimeType === 'text/javascript' && !!m.properties?.executable
1365
- );
1366
- executableSnippets?.forEach(s => {
1367
- try {
1368
- setTimeout(() => {
1369
- // eslint-disable-next-line no-new-func
1370
- new Function(s.content ?? '')();
1371
-
1372
- setTimeout(() => {
1373
- document
1374
- .querySelector('.memori-chat--content')
1375
- ?.scrollTo(
1376
- 0,
1377
- document.querySelector('.memori-chat--content')?.scrollHeight ??
1378
- 0
1379
- );
1380
- }, 400);
1381
- }, 1000);
1382
- } catch {
1383
- // ignore snippet execution errors
1384
- }
1385
- });
1386
- };
1387
-
1388
- useEffect(() => {
1389
- if (initialSessionID) {
1390
- setSessionId(initialSessionID);
1391
- onClickStart(undefined, false, undefined, initialSessionID);
1392
- }
1393
- }, [initialSessionID]);
1394
-
1395
- /**
1396
- * Opening Session
1397
- */
1398
- /**
1399
- * Fetches a new session with the given parameters
1400
- * @param params OpenSession parameters
1401
- * @returns Promise resolving to dialog state and session ID if successful, void otherwise
1402
- */
1403
- const fetchSession = async (
1404
- params: OpenSession
1405
- ): Promise<
1406
- | (ResponseSpec & {
1407
- dialogState?: DialogState;
1408
- sessionID: string;
1409
- })
1410
- | undefined
1411
- | void
1412
- > => {
1413
- let storageBirthDate = getLocalConfig<string | undefined>(
1414
- 'birthDate',
1415
- undefined
1416
- );
1417
- let userBirthDate = birthDate ?? params.birthDate ?? storageBirthDate;
1418
- if (!userBirthDate && !!minAge) {
1419
- setShowAgeVerification(true);
1420
- return;
1421
- }
1422
-
1423
- // Check if authentication is needed for private Memori
1424
- if (
1425
- memori.privacyType !== 'PUBLIC' &&
1426
- !memori.secretToken &&
1427
- !memoriPwd &&
1428
- !memoriTokens
1429
- ) {
1430
- setAuthModalState('password');
1431
- return;
1432
- }
1433
-
1434
- if (!(await checkCredits({ notify: true }))) {
1435
- return;
1436
- }
1437
-
1438
- setLoading(true);
1439
-
1440
- try {
1441
- // Check for and set giver invitation if available
1442
- // if (!memori.giverTag && !!memori.receivedInvitations?.length) {
1443
- // let giverInvitation = memori.receivedInvitations.find(
1444
- // (i: Invitation) => i.type === 'GIVER' && i.state === 'ACCEPTED'
1445
- // );
1446
-
1447
- // if (giverInvitation) {x
1448
- // memori.giverTag = giverInvitation.tag;
1449
- // memori.giverPIN = giverInvitation.pin;
1450
- // }
1451
- // }
1452
-
1453
- // Get referral URL
1454
- let referral;
1455
- try {
1456
- referral = (() => {
1457
- return window.location.href;
1458
- })();
1459
- } catch {
1460
- // ignore referral URL errors
1461
- }
1462
-
1463
- // Initialize session with parameters
1464
- const session = await initSession({
1465
- ...params,
1466
- birthDate: userBirthDate,
1467
- tag: params.tag ?? personification?.tag,
1468
- pin: params.pin ?? personification?.pin,
1469
- additionalInfo: {
1470
- ...(params.additionalInfo || additionalInfo || {}),
1471
- loginToken: resolveLoginToken(params.additionalInfo?.loginToken),
1472
- language: (
1473
- userLang ??
1474
- memori.culture?.split('-')?.[0] ??
1475
- 'IT'
1476
- ).toLowerCase(),
1477
- referral: referral,
1478
- timeZoneOffset: new Date().getTimezoneOffset().toString(),
1479
- },
1480
- });
1481
-
1482
- // Handle successful session creation
1483
- if (
1484
- session?.sessionID &&
1485
- session?.currentState &&
1486
- session.resultCode === 0
1487
- ) {
1488
- setSessionId(session.sessionID);
1489
-
1490
- // save giver state
1491
- if (currentDialogState?.currentTag && memori.giverTag) {
1492
- setInstruct(currentDialogState?.currentTag === memori.giverTag);
1493
- } else {
1494
- setInstruct(false);
1495
- }
1496
-
1497
- setLoading(false);
1498
- return {
1499
- dialogState: session.currentState,
1500
- sessionID: session.sessionID,
1501
- } as any;
1502
- }
1503
- // Handle age restriction error
1504
- else if (
1505
- session?.resultMessage.startsWith('This Memori is aged restricted')
1506
- ) {
1507
- toast.error(t('underageTwinSession', { age: minAge }));
1508
- }
1509
- // Handle authentication error
1510
- else if (session?.resultCode === 403 && memori.privacyType !== 'PUBLIC') {
1511
- setMemoriPwd(undefined);
1512
- setAuthModalState('password');
1513
- return session;
1514
- }
1515
- // Handle other errors
1516
- else {
1517
- toast.error(
1518
- tst => (
1519
- <div>
1520
- <p>{t(getErrori18nKey(session?.resultCode))}</p>
1521
- <Button
1522
- outlined
1523
- padded={false}
1524
- onClick={() => toast.dismiss(tst.id)}
1525
- icon={<CloseIcon />}
1526
- >
1527
- {t('close')}
1528
- </Button>
1529
- </div>
1530
- ),
1531
- {
1532
- duration: Infinity,
1533
- }
1534
- );
1535
- return session;
1536
- }
1537
- } catch (err) {
1538
- logWidgetError('fetchSession failed', err);
1539
- }
1540
- };
1541
-
1542
- /**
1543
- * Reopens an existing session with optional parameters
1544
- * @param updateDialogState Whether to update dialog state
1545
- * @param password Optional password for authentication
1546
- * @param recoveryTokens Optional recovery tokens
1547
- * @param tag Optional tag
1548
- * @param pin Optional PIN
1549
- * @param initialContextVars Optional initial context variables
1550
- * @param initialQuestion Optional initial question
1551
- * @param birthDate Optional birth date for age verification
1552
- * @returns Promise resolving to dialog state and session ID if successful, null otherwise
1553
- */
1554
- const reopenSession = async (
1555
- updateDialogState: boolean = false,
1556
- password?: string,
1557
- recoveryTokens?: string[],
1558
- tag?: string,
1559
- pin?: string,
1560
- initialContextVars?: { [key: string]: string },
1561
- initialQuestion?: string,
1562
- birthDate?: string,
1563
- additionalInfoProp?: { [key: string]: string | undefined },
1564
- continueFromChatLogID?: string,
1565
- continueFromSessionID?: string,
1566
- isSessionExpired?: boolean,
1567
- suppressHistoryUpdate?: boolean
1568
- ) => {
1569
- // Set loading state while reopening session
1570
- setLoading(true);
1571
-
1572
- // Get birth date from local storage if not provided
1573
- let storageBirthDate = getLocalConfig<string | undefined>(
1574
- 'birthDate',
1575
- undefined
1576
- );
1577
- let userBirthDate = birthDate ?? storageBirthDate;
1578
-
1579
- try {
1580
- // Show age verification if required and birth date not provided
1581
- if (!userBirthDate && !!minAge) {
1582
- setShowAgeVerification(true);
1583
- return;
1584
- }
1585
-
1586
- // Check if authentication is needed based on privacy type and credentials
1587
- if (
1588
- memori.privacyType !== 'PUBLIC' &&
1589
- !password &&
1590
- !memori.secretToken &&
1591
- !memoriPwd &&
1592
- !recoveryTokens &&
1593
- !memoriTokens
1594
- ) {
1595
- setAuthModalState('password');
1596
- return;
1597
- }
1598
-
1599
- if (!(await checkCredits({ notify: true }))) {
1600
- setLoading(false);
1601
- return null;
1602
- }
1603
-
1604
- // Get current URL as referral
1605
- let referral;
1606
- try {
1607
- referral = (() => {
1608
- return window.location.href;
1609
- })();
1610
- } catch {
1611
- // ignore referral URL errors
1612
- }
1613
-
1614
- // Initialize session with provided parameters
1615
- const { sessionID, currentState, ...response } = await initSession({
1616
- memoriID: memori.engineMemoriID ?? '',
1617
- password: password || memoriPwd || memori.secretToken,
1618
- recoveryTokens: recoveryTokens || memoriTokens,
1619
- tag: tag ?? personification?.tag,
1620
- pin: pin ?? personification?.pin,
1621
- continueFromChatLogID: continueFromChatLogID,
1622
- continueFromSessionID: continueFromSessionID,
1623
- initialContextVars: {
1624
- LANG: userLang,
1625
- PATHNAME: window.location.pathname,
1626
- ROUTE: window.location.pathname?.split('/')?.pop() || '',
1627
- ...(initialContextVars || {}),
1628
- },
1629
- initialQuestion,
1630
- birthDate: userBirthDate,
1631
- additionalInfo: {
1632
- ...(additionalInfoProp || additionalInfo || {}),
1633
- loginToken: resolveLoginToken(additionalInfoProp?.loginToken),
1634
- language: (
1635
- userLang ??
1636
- memori.culture?.split('-')?.[0] ??
1637
- 'IT'
1638
- ).toLowerCase(),
1639
- referral: referral,
1640
- timeZoneOffset: new Date().getTimezoneOffset().toString(),
1641
- },
1642
- });
1643
-
1644
- // Handle successful session initialization
1645
- if (sessionID && currentState && response.resultCode === 0) {
1646
- setSessionId(sessionID);
1647
-
1648
- // Update dialog state and history if requested
1649
- if (updateDialogState) {
1650
- setCurrentDialogState(currentState);
1651
-
1652
- const sessionExpiredStatus =
1653
- isSessionExpired && history.length > 1
1654
- ? t('sessionExpiredReopening')
1655
- : null;
1656
-
1657
- if (sessionExpiredStatus && suppressHistoryUpdate) {
1658
- pushMessage({
1659
- text: '',
1660
- emitter: 'system',
1661
- fromUser: false,
1662
- initial: sessionExpiredStatus as any,
1663
- contextVars: {},
1664
- date: new Date().toISOString(),
1665
- });
1666
- }
1667
-
1668
- if (currentState.emission && !suppressHistoryUpdate) {
1669
- // Determine initial status message based on context
1670
- // Show status message only if session expired and there's existing history
1671
- const initialStatus =
1672
- sessionExpiredStatus
1673
- ? sessionExpiredStatus
1674
- : history.length <= 1
1675
- ? true
1676
- : undefined;
1677
-
1678
- // Set initial message or append to existing history
1679
- history.length <= 1
1680
- ? setHistory([
1681
- {
1682
- text: currentState.emission,
1683
- emitter: currentState.emitter,
1684
- media: currentState.emittedMedia ?? currentState.media,
1685
- fromUser: false,
1686
- initial: (initialStatus === true
1687
- ? true
1688
- : initialStatus || undefined) as any,
1689
- contextVars: currentState.contextVars,
1690
- date: currentState.currentDate,
1691
- placeName: currentState.currentPlaceName,
1692
- placeLatitude: currentState.currentLatitude,
1693
- placeLongitude: currentState.currentLongitude,
1694
- placeUncertaintyKm: currentState.currentUncertaintyKm,
1695
- tag: currentState.currentTag,
1696
- memoryTags: currentState.memoryTags,
1697
- },
1698
- ])
1699
- : pushMessage({
1700
- text: currentState.emission,
1701
- emitter: currentState.emitter,
1702
- media: currentState.emittedMedia ?? currentState.media,
1703
- fromUser: false,
1704
- initial: (initialStatus === true
1705
- ? true
1706
- : initialStatus || undefined) as any,
1707
- contextVars: currentState.contextVars,
1708
- date: currentState.currentDate,
1709
- placeName: currentState.currentPlaceName,
1710
- placeLatitude: currentState.currentLatitude,
1711
- placeLongitude: currentState.currentLongitude,
1712
- placeUncertaintyKm: currentState.currentUncertaintyKm,
1713
- tag: currentState.currentTag,
1714
- memoryTags: currentState.memoryTags,
1715
- });
1716
- }
1717
- }
1718
-
1719
- setLoading(false);
1720
- return {
1721
- dialogState: currentState,
1722
- sessionID,
1723
- };
1724
- }
1725
- // Handle age restriction error
1726
- else if (
1727
- response?.resultMessage.startsWith('This Memori is aged restricted')
1728
- ) {
1729
- toast.error(t('underageTwinSession', { age: minAge }));
1730
- }
1731
- // Handle authentication error
1732
- else if (
1733
- response?.resultCode === 403 &&
1734
- memori.privacyType !== 'PUBLIC'
1735
- ) {
1736
- setMemoriPwd(undefined);
1737
- setAuthModalState('password');
1738
- }
1739
- // Handle other errors
1740
- else {
1741
- toast.error(t(getErrori18nKey(response.resultCode)));
1742
- }
1743
- } catch (err) {
1744
- logWidgetError('reopenSession failed', err);
1745
- }
1746
- // Reset loading state
1747
- setLoading(false);
1748
-
1749
- return null;
1750
- };
1751
-
1752
- const retryAfterExpiredSessionRef = useRef<
1753
- (params: EnterTextRetryParams) => ReturnType<typeof reopenSession>
1754
- >(() => Promise.resolve(null));
1755
-
1756
- retryAfterExpiredSessionRef.current = (params: EnterTextRetryParams) => {
1757
- const {
1758
- text,
1759
- media,
1760
- translate = true,
1761
- translatedText,
1762
- hidden = false,
1763
- typingText,
1764
- useLoaderTextAsMsg = false,
1765
- hasBatchQueued = false,
1766
- expiredSessionID,
1767
- continueFromChatLogID: continueFromChatLogIDParam,
1768
- } = params;
1769
-
1770
- const continueFromSessionID = expiredSessionID ?? sessionId;
1771
- const continueFromChatLogID = continueFromChatLogIDParam ?? chatLogID;
1772
-
1773
- const reopenAfterExpiry = () =>
1774
- reopenSession(
1775
- true,
1776
- memoriPwd || memori.secretToken,
1777
- memoriTokens,
1778
- undefined,
1779
- undefined,
1780
- {
1781
- LANG: userLang,
1782
- PATHNAME: window.location.pathname,
1783
- ROUTE: window.location.pathname?.split('/')?.pop() || '',
1784
- ...(initialContextVars || {}),
1785
- },
1786
- initialQuestion,
1787
- undefined,
1788
- undefined,
1789
- continueFromChatLogID,
1790
- continueFromSessionID,
1791
- true,
1792
- true
1793
- );
1794
-
1795
- const scheduleRetry = (newSessionID: string) => {
1796
- setTimeout(() => {
1797
- sendMessage(
1798
- text!,
1799
- media,
1800
- newSessionID,
1801
- translate,
1802
- translatedText,
1803
- hidden,
1804
- typingText,
1805
- useLoaderTextAsMsg,
1806
- hasBatchQueued,
1807
- true
1808
- );
1809
- }, 500);
1810
- };
1811
-
1812
- const handleReopenFailure = (state: Awaited<ReturnType<typeof reopenSession>>) => {
1813
- setMemoriTyping(false);
1814
- setTypingText(undefined);
1815
- // `null` = explicit failure; `undefined` = auth/age modal opened (message kept in history)
1816
- if (state === null && text && !hidden) {
1817
- toast.error(t('errors.SESSION_EXPIRED'));
1818
- }
1819
- };
1820
-
1821
- if (!text) {
1822
- return reopenAfterExpiry().then(state => {
1823
- if (!state?.sessionID) {
1824
- handleReopenFailure(state);
1825
- }
1826
- return state;
1827
- });
1828
- }
1829
-
1830
- return reopenAfterExpiry().then(state => {
1831
- if (state?.sessionID) {
1832
- scheduleRetry(state.sessionID);
1833
- } else {
1834
- handleReopenFailure(state);
1835
- }
1836
- return state;
1837
- });
1838
- };
1839
-
1840
- const changeTag = async (
1841
- memoriId: string,
1842
- sessionId: string,
1843
- tag?: string,
1844
- pin?: string
1845
- ) => {
1846
- if (!memoriId || !sessionId) {
1847
- return Promise.reject('Session not found');
1848
- }
1849
-
1850
- try {
1851
- const { currentState, resultCode } = await postTagChangedEvent(
1852
- sessionId,
1853
- tag ?? anonTag
1854
- );
1855
-
1856
- if (resultCode === 0) {
1857
- let textResult = 0;
1858
- if (
1859
- tag !== anonTag &&
1860
- pin &&
1861
- (currentState.state === 'X1a' || currentState.state === 'X1b')
1862
- ) {
1863
- const placeSpec = getPlaceSpecForEnterText(position);
1864
- const { resultCode: textResultCode } = await postTextEnteredEvent({
1865
- sessionId,
1866
- text: pin ?? '',
1867
- ...(memori.needsDateTime && {
1868
- dateUTC: DateTime.utc().toISO() ?? undefined,
1869
- }),
1870
- ...(placeSpec !== undefined && { place: placeSpec }),
1871
- });
1872
- textResult = textResultCode;
1873
- }
1874
-
1875
- if (textResult === 0) {
1876
- const { currentState, ...response } = await getSession(sessionId);
1877
-
1878
- if (response.resultCode === 0 && !!currentState) {
1879
- return {
1880
- currentState,
1881
- sessionId,
1882
- ...response,
1883
- };
1884
- }
1885
- } else if ([400, 401, 403, 404, 500].includes(resultCode)) {
1886
- let storageBirthDate = getLocalConfig<string | undefined>(
1887
- 'birthDate',
1888
- undefined
1889
- );
1890
-
1891
- let referral;
1892
- try {
1893
- referral = (() => {
1894
- return window.location.href;
1895
- })();
1896
- } catch {
1897
- // ignore referral URL errors
1898
- }
1899
-
1900
- fetchSession({
1901
- memoriID: memori.engineMemoriID ?? '',
1902
- password: secret || memoriPwd || memori.secretToken,
1903
- tag: tag ?? personification?.tag,
1904
- pin: pin ?? personification?.pin,
1905
- initialContextVars: {
1906
- LANG: userLang,
1907
- PATHNAME: window.location.pathname,
1908
- ROUTE: window.location.pathname?.split('/')?.pop() || '',
1909
- ...(initialContextVars || {}),
1910
- },
1911
- initialQuestion,
1912
- birthDate: birthDate || storageBirthDate || undefined,
1913
- additionalInfo: {
1914
- ...(additionalInfo || {}),
1915
- loginToken: resolveLoginToken(),
1916
- language: (
1917
- userLang ??
1918
- memori.culture?.split('-')?.[0] ??
1919
- 'IT'
1920
- ).toLowerCase(),
1921
- referral: referral,
1922
- timeZoneOffset: new Date().getTimezoneOffset().toString(),
1923
- },
1924
- });
1925
- } else if (!!currentState) {
1926
- return {
1927
- currentState,
1928
- sessionId,
1929
- resultCode,
1930
- };
1931
- }
1932
- }
1933
- } catch (_e) {
1934
- let err = _e as Error;
1935
- return Promise.reject(err);
1936
- }
1937
-
1938
- return null;
1939
- };
1940
-
1941
- /**
1942
- * Timeout conversazione
1943
- */
1944
- const [userInteractionTimeout, setUserInteractionTimeout] =
1945
- useState<NodeJS.Timeout>();
1946
- const timeoutRef = useRef<NodeJS.Timeout>();
1947
- const clearInteractionTimeout = () => {
1948
- if (userInteractionTimeout) {
1949
- clearTimeout(userInteractionTimeout);
1950
- setUserInteractionTimeout(undefined);
1951
- }
1952
- if (timeoutRef?.current) {
1953
- clearTimeout(timeoutRef.current);
1954
- timeoutRef.current = undefined;
1955
- }
1956
- };
1957
- useEffect(() => {
1958
- return () => {
1959
- setHasUserActivatedSpeak(false);
1960
- setClickedStart(false);
1961
- sessionStartingRef.current = false;
1962
- clearInteractionTimeout();
1963
- timeoutRef.current = undefined;
1964
- };
1965
- // eslint-disable-next-line react-hooks/exhaustive-deps
1966
- }, []);
1967
-
1968
- /**
1969
- * Speech recognition event handlers
1970
- */
1971
- const [requestedListening, setRequestedListening] = useState(false);
1972
- const startListeningRef = useRef<(() => Promise<void>) | null>(null);
1973
-
1974
- // Define TTS configuration
1975
- const ttsConfig = useMemo(
1976
- () => ({
1977
- provider: ttsProvider,
1978
- voice: getTTSVoice(
1979
- userLang || memori.culture?.split('-')?.[0] || 'EN',
1980
- ttsProvider,
1981
- memori.voiceType as 'MALE' | 'FEMALE' | 'NEUTRAL'
1982
- ),
1983
- tenant: tenantID,
1984
- region: 'westeurope',
1985
- voiceType: memori.voiceType,
1986
- layout: selectedLayout,
1987
- }),
1988
- [ttsProvider, userLang, memori.culture, memori.voiceType, selectedLayout]
1989
- );
1990
-
1991
- const sttConfig = useMemo(
1992
- () => ({
1993
- provider: ttsProvider,
1994
- language: getCultureCodeByLanguage(userLang),
1995
- tenant: tenantID,
1996
- }),
1997
- [ttsProvider, userLang]
1998
- );
1999
-
2000
- // Initialize TTS hook with basic options first
2001
- const {
2002
- speak: ttsSpeak,
2003
- stop: ttsStop,
2004
- isPlaying: isPlayingAudio,
2005
- speakerMuted,
2006
- toggleMute,
2007
- hasUserActivatedSpeak,
2008
- setHasUserActivatedSpeak,
2009
- } = useTTS(
2010
- ttsConfig as TTSConfig,
2011
- {
2012
- apiUrl: `${baseUrl}/api/tts`,
2013
- continuousSpeech: continuousSpeech,
2014
- preview: preview,
2015
- },
2016
- autoStart,
2017
- defaultEnableAudio,
2018
- defaultSpeakerActive ?? integrationConfig?.defaultSpeakerActive ?? true
2019
- );
2020
-
2021
- // Helper function to check if audio should be played.
2022
- // When defaultEnableAudio is false, default to muted so we never play before the sync effect runs (avoids audio on first conversation start when audio is disabled).
2023
- const shouldPlayAudio = (text?: string) => {
2024
- const currentSpeakerMuted = getLocalConfig(
2025
- 'muteSpeaker',
2026
- !defaultEnableAudio
2027
- );
2028
- return (
2029
- text &&
2030
- text.trim() &&
2031
- !preview &&
2032
- !currentSpeakerMuted &&
2033
- defaultEnableAudio
2034
- );
2035
- };
2036
-
2037
- // Create a single, centralized function to process and send messages
2038
- const processSpeechAndSendMessage = (text: string) => {
2039
- // Skip if already processing or no text
2040
- if (!text || text.trim().length === 0) {
2041
- return;
2042
- }
2043
-
2044
- try {
2045
- // Process the text
2046
- const message = stripDuplicates(text);
2047
-
2048
- if (message.length > 0) {
2049
- setUserMessage('');
2050
-
2051
- // Send the message
2052
- sendMessage(message);
2053
- }
2054
- } catch {
2055
- // ignore speech processing errors
2056
- }
2057
- };
2058
-
2059
- const {
2060
- isListening,
2061
-
2062
- // Actions
2063
- startRecording,
2064
- stopRecording,
2065
- } = useSTT(
2066
- sttConfig as STTConfig,
2067
- processSpeechAndSendMessage,
2068
- {
2069
- apiUrl: `${baseUrl}/api/stt`,
2070
- // continuousRecording: continuousSpeech,
2071
- // silenceTimeout: continuousSpeechTimeout,
2072
- // autoStart: autoStart,
2073
- },
2074
- defaultEnableAudio
2075
- );
2076
-
2077
- /**
2078
- * Enhanced handleSpeak that integrates with the improved useTTS hook
2079
- * Uses promise-based approach for better reliability
2080
- */
2081
- const handleSpeak = async (text: string) => {
2082
- if (!shouldPlayAudio(text)) {
2083
- const e = new CustomEvent('MemoriEndSpeak');
2084
- document.dispatchEvent(e);
2085
- return Promise.resolve();
2086
- }
2087
-
2088
- if (typeof stopRecording === 'function') {
2089
- stopRecording();
2090
- }
2091
-
2092
- // Reset the typing flag when Memori starts speaking
2093
- setHasUserTypedMessage(false);
2094
-
2095
- const processedText = sanitizeText(text);
2096
- return ttsSpeak(processedText);
2097
- };
2098
- /**
2099
- * Integrated solution for translating dialog state and speaking
2100
- * This uses promise chaining for reliable sequencing without timeouts
2101
- */
2102
- const translateAndSpeak = useCallback(
2103
- async (
2104
- dialogState: DialogState,
2105
- language: string,
2106
- msg?: string,
2107
- skipEmission: boolean = false
2108
- ) => {
2109
- try {
2110
- // First ensure we have a valid dialog state
2111
- if (!dialogState) {
2112
- return null;
2113
- }
2114
-
2115
- // Then translate the dialog state
2116
- const translatedState = await translateDialogState(
2117
- dialogState,
2118
- language,
2119
- msg,
2120
- skipEmission
2121
- );
2122
-
2123
- // If we're not skipping emission and there's something to speak, speak it
2124
- const textToSpeak =
2125
- translatedState.translatedEmission || translatedState.emission;
2126
-
2127
- // Always set hasUserActivatedSpeak to true when we have a valid dialog state,
2128
- // regardless of audio settings, so the chat can start properly
2129
- if (!hasUserActivatedSpeak) {
2130
- setHasUserActivatedSpeak(true);
2131
- }
2132
-
2133
- if (textToSpeak && !skipEmission && shouldPlayAudio(textToSpeak)) {
2134
- await handleSpeak(textToSpeak);
2135
- }
2136
-
2137
- return translatedState;
2138
- } catch {
2139
- // Still update activation state even if there's an error
2140
- if (!hasUserActivatedSpeak) {
2141
- setHasUserActivatedSpeak(true);
2142
- }
2143
- return dialogState;
2144
- }
2145
- },
2146
- [
2147
- translateDialogState,
2148
- handleSpeak,
2149
- hasUserActivatedSpeak,
2150
- setHasUserActivatedSpeak,
2151
- speakerMuted,
2152
- ]
2153
- );
2154
-
2155
- const processEnterTextDialogResponse = useCallback(
2156
- (event: NatsDialogResponseEvent, pending: PendingEnterText) => {
2157
- const {
2158
- msg,
2159
- typingText: pendingTypingText,
2160
- useLoaderTextAsMsg,
2161
- } = pending;
2162
- const currentState = event.currentState;
2163
-
2164
- if (event.resultCode !== 0 || !currentState) {
2165
- if (isSessionExpiredNatsResponse(event) && pending.text) {
2166
- setMemoriTyping(false);
2167
- setTypingText(undefined);
2168
- retryAfterExpiredSessionRef.current({
2169
- text: pending.text,
2170
- media: pending.media,
2171
- translate: pending.translate,
2172
- translatedText: pending.translatedText,
2173
- hidden: pending.hidden,
2174
- typingText: pending.typingText,
2175
- useLoaderTextAsMsg: pending.useLoaderTextAsMsg,
2176
- hasBatchQueued: pending.hasBatchQueued,
2177
- expiredSessionID: sessionId,
2178
- continueFromChatLogID: chatLogID,
2179
- });
2180
- return;
2181
- }
2182
-
2183
- if (event.resultCode === 500 && event.resultMessage) {
2184
- setHistory(h => [
2185
- ...h,
2186
- {
2187
- text: 'Error: ' + event.resultMessage,
2188
- emitter: 'system',
2189
- fromUser: false,
2190
- initial: false,
2191
- contextVars: {},
2192
- date: new Date().toISOString(),
2193
- },
2194
- ]);
2195
- }
2196
- return;
2197
- }
2198
-
2199
- if (!msg) {
2200
- return;
2201
- }
2202
-
2203
- setChatLogID(undefined);
2204
- const emission =
2205
- useLoaderTextAsMsg && pendingTypingText
2206
- ? pendingTypingText
2207
- : currentState.emission ?? currentDialogState?.emission;
2208
-
2209
- if (
2210
- userLang.toLowerCase() !== language.toLowerCase() &&
2211
- emission &&
2212
- isMultilanguageEnabled
2213
- ) {
2214
- currentState.emission = emission;
2215
-
2216
- translateDialogState(currentState, userLang, msg).then(ts => {
2217
- const text = ts.translatedEmission || ts.emission;
2218
- if (text && shouldPlayAudio(text)) {
2219
- handleSpeak(text);
2220
- }
2221
- });
2222
- } else {
2223
- setCurrentDialogState({
2224
- ...currentState,
2225
- emission,
2226
- });
2227
-
2228
- if (emission) {
2229
- pushMessage({
2230
- text: emission,
2231
- emitter: currentState.emitter,
2232
- media: currentState.emittedMedia ?? currentState.media,
2233
- llmUsage: (currentState as any).llmUsage,
2234
- fromUser: false,
2235
- questionAnswered: msg,
2236
- generatedByAI: !!currentState.completion,
2237
- contextVars: currentState.contextVars,
2238
- date: currentState.currentDate,
2239
- placeName: currentState.currentPlaceName,
2240
- placeLatitude: currentState.currentLatitude,
2241
- placeLongitude: currentState.currentLongitude,
2242
- placeUncertaintyKm: currentState.currentUncertaintyKm,
2243
- tag: currentState.currentTag,
2244
- memoryTags: currentState.memoryTags,
2245
- } as any);
2246
- if (emission && shouldPlayAudio(emission)) {
2247
- handleSpeak(emission);
2248
- }
2249
- }
2250
- }
2251
- },
2252
- [
2253
- userLang,
2254
- language,
2255
- isMultilanguageEnabled,
2256
- currentDialogState?.emission,
2257
- translateDialogState,
2258
- handleSpeak,
2259
- shouldPlayAudio,
2260
- ]
2261
- );
2262
-
2263
- const clearEnterTextPending = useCallback(
2264
- (correlationID: string, pending: PendingEnterText) => {
2265
- if (pending.waitForResponse?.timeoutId) {
2266
- clearTimeout(pending.waitForResponse.timeoutId);
2267
- }
2268
- pendingEnterTextRef.current.delete(correlationID);
2269
- },
2270
- []
2271
- );
2272
-
2273
- const deliverEnterTextNatsError = useCallback(
2274
- (event: NatsErrorEvent) => {
2275
- const correlationID = event.correlationID;
2276
- let pending: PendingEnterText | undefined;
2277
-
2278
- if (correlationID) {
2279
- pending = pendingEnterTextRef.current.get(correlationID);
2280
- if (pending) {
2281
- clearEnterTextPending(correlationID, pending);
2282
- pending.waitForResponse?.reject(
2283
- new Error(
2284
- event.errorMessage ?? String(event.errorCode ?? 'NATS error')
2285
- )
2286
- );
2287
- }
2288
- }
2289
-
2290
- if (isSessionExpiredNatsError(event) && pending?.text) {
2291
- setMemoriTyping(false);
2292
- setTypingText(undefined);
2293
- retryAfterExpiredSessionRef.current({
2294
- text: pending.text,
2295
- media: pending.media,
2296
- translate: pending.translate,
2297
- translatedText: pending.translatedText,
2298
- hidden: pending.hidden,
2299
- typingText: pending.typingText,
2300
- useLoaderTextAsMsg: pending.useLoaderTextAsMsg,
2301
- hasBatchQueued: pending.hasBatchQueued,
2302
- expiredSessionID: sessionId,
2303
- continueFromChatLogID: chatLogID,
2304
- });
2305
- return;
2306
- }
2307
-
2308
- const errorText = event.errorMessage
2309
- ? `Error: ${event.errorMessage}`
2310
- : event.errorCode
2311
- ? `Error: ${event.errorCode}`
2312
- : 'Error: An unexpected error occurred';
2313
-
2314
- pushMessage({
2315
- text: errorText,
2316
- emitter: 'system',
2317
- fromUser: false,
2318
- initial: false,
2319
- contextVars: {},
2320
- date: new Date().toISOString(),
2321
- });
2322
-
2323
- setMemoriTyping(false);
2324
- setTypingText(undefined);
2325
- },
2326
- [clearEnterTextPending]
2327
- );
2328
-
2329
- const deliverEnterTextNatsResponse = useCallback(
2330
- (correlationID: string, event: NatsDialogResponseEvent) => {
2331
- const pending = pendingEnterTextRef.current.get(correlationID);
2332
- if (!pending) {
2333
- bufferedNatsResponsesRef.current.set(correlationID, event);
2334
- return;
2335
- }
2336
-
2337
- clearEnterTextPending(correlationID, pending);
2338
-
2339
- if (pending.waitForResponse) {
2340
- pending.waitForResponse.resolve(event);
2341
- setMemoriTyping(false);
2342
- setTypingText(undefined);
2343
- return;
2344
- }
2345
-
2346
- processEnterTextDialogResponse(event, pending);
2347
-
2348
- if (!pending.hasBatchQueued) {
2349
- setMemoriTyping(false);
2350
- setTypingText(undefined);
2351
- }
2352
- },
2353
- [processEnterTextDialogResponse, clearEnterTextPending]
2354
- );
2355
-
2356
- const registerPendingEnterText = useCallback(
2357
- (correlationID: string, pending: PendingEnterText) => {
2358
- const buffered = bufferedNatsResponsesRef.current.get(correlationID);
2359
- if (buffered) {
2360
- bufferedNatsResponsesRef.current.delete(correlationID);
2361
- pendingEnterTextRef.current.set(correlationID, pending);
2362
- deliverEnterTextNatsResponse(correlationID, buffered);
2363
- return;
2364
- }
2365
-
2366
- pendingEnterTextRef.current.set(correlationID, pending);
2367
- },
2368
- [deliverEnterTextNatsResponse]
2369
- );
2370
-
2371
- const waitForEnterTextNatsResponse = useCallback(
2372
- (correlationID: string, timeoutMs = 120000) =>
2373
- new Promise<NatsDialogResponseEvent>((resolve, reject) => {
2374
- const timeoutId = setTimeout(() => {
2375
- const current = pendingEnterTextRef.current.get(correlationID);
2376
- if (current) {
2377
- clearEnterTextPending(correlationID, current);
2378
- }
2379
- logWidgetError('NATS timeout', { correlationID, timeoutMs });
2380
- reject(new Error('NATS enter-text response timeout'));
2381
- }, timeoutMs);
2382
-
2383
- registerPendingEnterText(correlationID, {
2384
- stateBeforeRequest: dialogStateFingerprint(currentDialogState),
2385
- waitForResponse: {
2386
- resolve: event => {
2387
- clearTimeout(timeoutId);
2388
- resolve(event);
2389
- },
2390
- reject: error => {
2391
- clearTimeout(timeoutId);
2392
- reject(error);
2393
- },
2394
- timeoutId,
2395
- },
2396
- });
2397
- }),
2398
- [registerPendingEnterText, clearEnterTextPending, currentDialogState]
2399
- );
2400
-
2401
- const catchUpPendingEnterText = useCallback(async () => {
2402
- if (!sessionId || pendingEnterTextRef.current.size === 0) return;
2403
-
2404
- // Async turns are registered in send order. The current session state can
2405
- // only represent the latest completed turn, so reconcile the latest one.
2406
- const latest = Array.from(pendingEnterTextRef.current.entries()).pop();
2407
- if (!latest) return;
2408
- const [correlationID, pending] = latest;
2409
-
2410
- try {
2411
- const { currentState, resultCode, resultMessage, requestID } =
2412
- await getSession(sessionId);
2413
-
2414
- if (resultCode !== 0 || !currentState) {
2415
- deliverEnterTextNatsResponse(correlationID, {
2416
- eventType: 'dialog_text_entered_response',
2417
- correlationID,
2418
- requestID,
2419
- resultCode,
2420
- resultMessage,
2421
- currentState,
2422
- });
2423
- return;
2424
- }
2425
-
2426
- if (dialogStateFingerprint(currentState) === pending.stateBeforeRequest) {
2427
- console.info('[NATS] catch-up: pending turn has not completed yet');
2428
- return;
2429
- }
2430
-
2431
- console.info('[NATS] catch-up: applying current engine session state');
2432
- deliverEnterTextNatsResponse(correlationID, {
2433
- eventType: 'dialog_text_entered_response',
2434
- correlationID,
2435
- requestID,
2436
- resultCode,
2437
- resultMessage,
2438
- currentState,
2439
- });
2440
- } catch (error) {
2441
- logWidgetError('NATS catch-up failed', error);
2442
- }
2443
- }, [sessionId, deliverEnterTextNatsResponse]);
2444
-
2445
- // NATS subscription: receives progress updates and the async enter-text response.
2446
- useNats({
2447
- baseUrl,
2448
- sessionId,
2449
- onProgress: useCallback((event: NatsProgressEvent) => {
2450
- if (event.message) {
2451
- setTypingText(event.message);
2452
- }
2453
- }, []),
2454
- onDialogResponse: useCallback(
2455
- (event: NatsDialogResponseEvent) => {
2456
- const correlationID = event.correlationID;
2457
- if (!correlationID) {
2458
- logWidgetError('NATS dialog response missing correlationID', event);
2459
- setMemoriTyping(false);
2460
- setTypingText(undefined);
2461
- return;
2462
- }
2463
-
2464
- deliverEnterTextNatsResponse(correlationID, event);
2465
- },
2466
- [deliverEnterTextNatsResponse]
2467
- ),
2468
- onError: deliverEnterTextNatsError,
2469
- onCatchUp: catchUpPendingEnterText,
2470
- });
2471
-
2472
- const focusChatInput = () => {
2473
- let textarea = document.querySelector(
2474
- '#chat-fieldset textarea'
2475
- ) as HTMLTextAreaElement | null;
2476
- if (textarea && enableFocusChatInput) {
2477
- textarea.focus();
2478
- } else {
2479
- textarea?.blur();
2480
- }
2481
- };
2482
-
2483
- /**
2484
- * Focus on the chat input on mount
2485
- */
2486
- useEffect(() => {
2487
- // focus on chat input disabled for totem layout
2488
- if (selectedLayout !== 'TOTEM') {
2489
- focusChatInput();
2490
- }
2491
- // eslint-disable-next-line react-hooks/exhaustive-deps
2492
- }, [currentDialogState?.emission]);
2493
-
2494
- const resetUIEffects = () => {
2495
- try {
2496
- clearInteractionTimeout();
2497
- setClickedStart(false);
2498
- timeoutRef.current = undefined;
2499
- ttsStop();
2500
- } catch {
2501
- // ignore reset errors
2502
- }
2503
- };
2504
- useEffect(() => {
2505
- return () => {
2506
- resetUIEffects();
2507
- };
2508
- // eslint-disable-next-line react-hooks/exhaustive-deps
2509
- }, []);
2510
- useEffect(() => {
2511
- document.addEventListener('MemoriResetUIEffects', resetUIEffects);
2512
-
2513
- return () => {
2514
- document.removeEventListener('MemoriResetUIEffects', resetUIEffects);
2515
- };
2516
- }, []);
2517
-
2518
- useEffect(() => {
2519
- // if memori is speaking, don't start listening
2520
- if (
2521
- !isPlayingAudio &&
2522
- continuousSpeech &&
2523
- (hasUserActivatedListening || !requestedListening) &&
2524
- sessionId &&
2525
- !hasUserTypedMessage // Don't start recording if user has typed a message
2526
- ) {
2527
- startRecording();
2528
- } else if (isPlayingAudio && isListening) {
2529
- stopRecording();
2530
- }
2531
- // eslint-disable-next-line react-hooks/exhaustive-deps
2532
- }, [isPlayingAudio, hasUserActivatedListening, hasUserTypedMessage]);
2533
-
2534
- useEffect(() => {
2535
- stopRecording();
2536
- // eslint-disable-next-line react-hooks/exhaustive-deps
2537
- }, [language]);
2538
-
2539
- /**
2540
- * Textarea send mode handlers
2541
- */
2542
- const [sendOnEnter, setSendOnEnter] = useState<'keypress' | 'click'>(
2543
- 'keypress'
2544
- );
2545
- useEffect(() => {
2546
- if (window.innerWidth <= 768 && hasTouchscreen()) setSendOnEnter('click');
2547
- else setSendOnEnter('keypress');
2548
- }, []);
2549
-
2550
- /**
2551
- * Attachments
2552
- */
2553
- const [attachmentsMenuOpen, setAttachmentsMenuOpen] = useState<
2554
- 'link' | 'media'
2555
- >();
2556
-
2557
- const globalBackground = integrationConfig?.globalBackground;
2558
- const globalBackgroundUrl = globalBackground
2559
- ? `url(${globalBackground})`
2560
- : null;
2561
-
2562
- const integrationProperties = (
2563
- integration
2564
- ? {
2565
- '--memori-chat-bubble-bg': '#fff',
2566
- ...(integrationConfig && !instruct
2567
- ? { '--memori-text-color': integrationConfig.textColor ?? '#000' }
2568
- : {}),
2569
- ...(integrationConfig?.buttonBgColor
2570
- ? {
2571
- '--memori-button-bg': integrationConfig.buttonBgColor,
2572
- '--memori-primary': integrationConfig.buttonBgColor,
2573
- }
2574
- : {}),
2575
- ...(integrationConfig?.buttonTextColor
2576
- ? {
2577
- '--memori-button-text': integrationConfig.buttonTextColor,
2578
- }
2579
- : {}),
2580
- ...(integrationConfig?.blurBackground
2581
- ? {
2582
- '--memori-blur-background': '5px',
2583
- }
2584
- : {
2585
- '--memori-blur-background': '0px',
2586
- }),
2587
- ...(integrationConfig?.innerBgColor
2588
- ? {
2589
- '--memori-inner-bg': `rgba(${
2590
- integrationConfig.innerBgColor === 'dark'
2591
- ? '0, 0, 0'
2592
- : '255, 255, 255'
2593
- }, ${integrationConfig.innerBgAlpha ?? 0.4})`,
2594
- '--memori-inner-content-pad': '1.5rem',
2595
- '--memori-nav-bg-image': 'none',
2596
- '--memori-nav-bg': `rgba(${
2597
- integrationConfig.innerBgColor === 'dark'
2598
- ? '0, 0, 0'
2599
- : '255, 255, 255'
2600
- }, ${integrationConfig?.innerBgAlpha ?? 0.4})`,
2601
- }
2602
- : {
2603
- '--memori-inner-content-pad': '0px',
2604
- }),
2605
- }
2606
- : {}
2607
- ) as CSSProperties;
2608
-
2609
- const integrationStylesheet = `
2610
- ${
2611
- preview ? '#preview, ' : applyVarsToRoot ? ':root, ' : ''
2612
- }memori-client, .memori-widget, .memori-drawer, .memori-modal {
2613
- ${Object.entries(integrationProperties)
2614
- .map(([key, value]) => `${key}: ${value};`)
2615
- .join('\n')}
2616
- }
2617
- `;
2618
-
2619
- const showAIicon =
2620
- integrationConfig?.showAIicon === undefined
2621
- ? true
2622
- : integrationConfig?.showAIicon;
2623
-
2624
- const enableUpload = !!(showUpload ?? integrationConfig?.showUpload);
2625
-
2626
- const enableReasoning = !!(showReasoning ?? integrationConfig?.showReasoning);
2627
- const enableMessageConsumption = !!runtimeShowMessageConsumption;
2628
-
2629
- const showWhyThisAnswer =
2630
- integrationConfig?.showWhyThisAnswer === undefined
2631
- ? true
2632
- : integrationConfig?.showWhyThisAnswer;
2633
-
2634
- // eslint-disable-next-line
2635
- const [avatar3dVisible, setAvatar3dVisible] = useState(false);
2636
- useEffect(() => {
2637
- if (
2638
- (window.innerWidth >= 768 && selectedLayout === 'FULLPAGE') ||
2639
- selectedLayout !== 'FULLPAGE'
2640
- ) {
2641
- setAvatar3dVisible(true);
2642
- }
2643
- }, []);
2644
-
2645
- // Put SEO tags in head
2646
- useEffect(() => {
2647
- if (integrationConfig?.seoTitle) {
2648
- let meta = document.createElement('meta');
2649
- meta.setAttribute('property', 'og:title');
2650
- meta.setAttribute('content', integrationConfig.seoTitle);
2651
- document.head.append(meta);
2652
- }
2653
- if (integrationConfig?.seoDescription) {
2654
- let meta = document.createElement('meta');
2655
- meta.setAttribute('property', 'og:description');
2656
- meta.setAttribute('content', integrationConfig.seoDescription);
2657
- document.head.append(meta);
2658
- }
2659
- if (integrationConfig?.seoUrl) {
2660
- let meta = document.createElement('meta');
2661
- meta.setAttribute('property', 'og:url');
2662
- meta.setAttribute('content', integrationConfig.seoUrl);
2663
- document.head.append(meta);
2664
- }
2665
- let image = ogImage || memori.avatarURL;
2666
- if (integrationConfig?.seoImageShowAvatar && image) {
2667
- let meta = document.createElement('meta');
2668
- meta.setAttribute('property', 'og:image');
2669
- meta.setAttribute('content', image);
2670
- document.head.append(meta);
2671
- }
2672
- }, [integrationConfig, memori.avatarURL, ogImage]);
2673
-
2674
- const simulateUserPrompt = (text: string, translatedText?: string) => {
2675
- ttsStop();
2676
- sendMessage(text, undefined, undefined, false, translatedText);
2677
- };
2678
-
2679
- // listen to events from browser
2680
- // to use in integrations or snippets
2681
- const memoriTextEnteredHandler = useCallback(
2682
- (e: MemoriTextEnteredEvent) => {
2683
- if (disableTextEnteredEvents) {
2684
- return;
2685
- }
2686
-
2687
- const {
2688
- text,
2689
- waitForPrevious,
2690
- hidden,
2691
- typingText,
2692
- useLoaderTextAsMsg,
2693
- hasBatchQueued,
2694
- } = e.detail;
2695
-
2696
- if (text) {
2697
- // wait to finish reading previous emission
2698
- if (
2699
- waitForPrevious &&
2700
- !speakerMuted &&
2701
- (memoriSpeaking || !!memoriTyping)
2702
- ) {
2703
- setTimeout(() => {
2704
- memoriTextEnteredHandler(e);
2705
- }, 1000);
2706
- } else {
2707
- ttsStop();
2708
- sendMessage(
2709
- text,
2710
- undefined,
2711
- undefined,
2712
- undefined,
2713
- undefined,
2714
- hidden,
2715
- typingText,
2716
- useLoaderTextAsMsg,
2717
- hasBatchQueued
2718
- );
2719
- }
2720
- }
2721
- },
2722
- [
2723
- sessionId,
2724
- isPlayingAudio,
2725
- memoriTyping,
2726
- userLang,
2727
- disableTextEnteredEvents,
2728
- speakerMuted,
2729
- ]
2730
- );
2731
- useEffect(() => {
2732
- if (!disableTextEnteredEvents) {
2733
- document.addEventListener('MemoriTextEntered', memoriTextEnteredHandler);
2734
- } else {
2735
- document.removeEventListener(
2736
- 'MemoriTextEntered',
2737
- memoriTextEnteredHandler
2738
- );
2739
- }
2740
-
2741
- return () => {
2742
- document.removeEventListener(
2743
- 'MemoriTextEntered',
2744
- memoriTextEnteredHandler
2745
- );
2746
- };
2747
- }, [sessionId, userLang, disableTextEnteredEvents]);
2748
-
2749
- /**
2750
- * Handles clicking the start button to begin or resume a session
2751
- * @param session Optional existing session with dialog state and ID
2752
- * @param initialSessionExpired Whether the initial session has expired
2753
- /**
2754
- * Handles clicking the start button to begin or resume a session
2755
- * @param session Optional existing session with dialog state and ID
2756
- * @param initialSessionExpired Whether the initial session has expired
2757
- */
2758
- const onClickStart = useCallback(
2759
- async (
2760
- session?: { dialogState: DialogState; sessionID: string },
2761
- initialSessionExpired = false,
2762
- chatLog?: ChatLog,
2763
- targetSessionID?: string
2764
- ) => {
2765
- const sessionID = chatLog ? undefined : session?.sessionID || sessionId;
2766
- const dialogState = chatLog
2767
- ? undefined
2768
- : session?.dialogState || currentDialogState;
2769
- setClickedStart(true);
2770
- setHasUserTypedMessage(false); // Reset typing flag when starting a new session
2771
-
2772
- let translatedMessages: Message[] = [];
2773
-
2774
- // Get birth date from storage or props
2775
- let storageBirthDate = getLocalConfig<string | undefined>(
2776
- 'birthDate',
2777
- undefined
2778
- );
2779
- let birth = birthDate || storageBirthDate || user?.birthDate;
2780
- if (!birth && autoStart && (initialSessionID || targetSessionID))
2781
- birth = '1970-01-01T10:24:03.845Z';
2782
-
2783
- const localPosition = getLocalConfig<Venue | undefined>(
2784
- 'position',
2785
- undefined
2786
- );
2787
- // Only check for position requirement if memori.needsPosition is true
2788
- if (autoStart && !localPosition && memori.needsPosition) {
2789
- setShowPositionDrawer(true);
2790
- return;
2791
- }
2792
-
2793
- if (!(await checkCredits({ notify: true }))) {
2794
- setClickedStart(false);
2795
- setLoading(false);
2796
- return;
2797
- }
2798
-
2799
- // Handle age verification
2800
- if (!sessionID && !!minAge && !birth) {
2801
- setShowAgeVerification(true);
2802
- setClickedStart(false);
2803
- return;
2804
- }
2805
- // Handle authentication
2806
- else if (
2807
- !sessionID &&
2808
- memori.privacyType !== 'PUBLIC' &&
2809
- !memori.secretToken &&
2810
- !memoriPwd &&
2811
- !memoriTokens
2812
- ) {
2813
- setAuthModalState('password');
2814
- setClickedStart(false);
2815
- return;
2816
- }
2817
- // Create new session if needed
2818
- else if (!sessionID || initialSessionExpired) {
2819
- if (sessionStartingRef.current) {
2820
- return;
2821
- }
2822
- sessionStartingRef.current = true;
2823
- try {
2824
- const session = await fetchSession({
2825
- memoriID: memori.engineMemoriID!,
2826
- password: secret || memoriPwd || memori.secretToken,
2827
- tag: personification?.tag,
2828
- pin: personification?.pin,
2829
- continueFromChatLogID: chatLog?.chatLogID,
2830
- initialContextVars: {
2831
- LANG: userLang,
2832
- PATHNAME: window.location.pathname?.toUpperCase(),
2833
- ROUTE:
2834
- window.location.pathname?.split('/')?.pop()?.toUpperCase() ||
2835
- '',
2836
- ...((!chatLog
2837
- ? initialContextVars
2838
- : chatLog.lines[chatLog.lines.length - 1].contextVars) || {}),
2839
- },
2840
- initialQuestion: chatLog ? undefined : initialQuestion,
2841
- birthDate: birth,
2842
- additionalInfo: {
2843
- ...(additionalInfo || {}),
2844
- loginToken: resolveLoginToken(),
2845
- language: (
2846
- userLang ??
2847
- memori.culture?.split('-')?.[0] ??
2848
- 'IT'
2849
- ).toLowerCase(),
2850
- timeZoneOffset: new Date().getTimezoneOffset().toString(),
2851
- },
2852
- });
2853
-
2854
- if (session?.dialogState) {
2855
- // reset history
2856
- if (!chatLog) {
2857
- setHistory([]);
2858
-
2859
- // Use translateAndSpeak which already handles the speaking
2860
- await translateAndSpeak(session.dialogState, userLang);
2861
- // No need for additional handleSpeak call since translateAndSpeak already handles it
2862
- setHasUserActivatedSpeak(true);
2863
- setClickedStart(false);
2864
- } else {
2865
- const messages = chatLog.lines.map(
2866
- (l, i) =>
2867
- ({
2868
- text: l.text,
2869
- media: l.media
2870
- ?.filter(m => allowedMediaTypes.includes(m.mimeType))
2871
- ?.map(m => ({
2872
- mediumID: `${i}-${m.mimeType}`,
2873
- ...m,
2874
- })),
2875
- fromUser: l.inbound,
2876
- llmUsage: (l as any).llmUsage,
2877
- timestamp: l.timestamp,
2878
- emitter: l.emitter,
2879
- initial: i === 0,
2880
- } as Message)
2881
- );
2882
-
2883
- // we dont remove the last one as it is the current state
2884
- translatedMessages = messages ?? [];
2885
- if (
2886
- language.toUpperCase() !== userLang.toUpperCase() &&
2887
- isMultilanguageEnabled
2888
- ) {
2889
- try {
2890
- translatedMessages = await Promise.all(
2891
- messages.map(async m => {
2892
- // If original text is present, the message is already translated
2893
- if ('originalText' in m && m.originalText) {
2894
- return m;
2895
- }
2896
- // Otherwise translate the message
2897
- return {
2898
- ...m,
2899
- originalText: m.text,
2900
- text: (
2901
- await getTranslation(
2902
- m.text,
2903
- userLang,
2904
- language,
2905
- baseUrl
2906
- )
2907
- ).text,
2908
- };
2909
- })
2910
- );
2911
- } catch {
2912
- // ignore translation errors
2913
- }
2914
- }
2915
-
2916
- setHistory(translatedMessages);
2917
-
2918
- translateDialogState(
2919
- session.dialogState,
2920
- userLang,
2921
- undefined,
2922
- true
2923
- ).finally(() => {
2924
- setHasUserActivatedSpeak(true);
2925
- setClickedStart(false);
2926
- });
2927
- }
2928
- } else if (session?.resultCode === 0) {
2929
- sessionStartingRef.current = false;
2930
- await onClickStart((session as any) || undefined);
2931
- } else {
2932
- setLoading(false);
2933
- setClickedStart(false);
2934
- }
2935
- } finally {
2936
- sessionStartingRef.current = false;
2937
- }
2938
-
2939
- return;
2940
- }
2941
- // Handle initial session
2942
- else if (initialSessionID || targetSessionID) {
2943
- const sessionID = targetSessionID ?? initialSessionID;
2944
- // check if session is valid and not expired
2945
- const { currentState, ...response } = await getSession(sessionID!);
2946
-
2947
- if (response.resultCode !== 0 || !currentState) {
2948
- const { chatLogs } = await getSessionChatLogs(sessionID!, sessionID!);
2949
- setSessionId(undefined);
2950
- await onClickStart(undefined, true, chatLogs?.[0]);
2951
- return;
2952
- }
2953
-
2954
- // reset history
2955
- setHistory([]);
2956
-
2957
- // Handle personification tag changes
2958
- if (
2959
- personification &&
2960
- currentState.currentTag !== personification.tag
2961
- ) {
2962
- try {
2963
- // reset tag
2964
- await changeTag(memori.engineMemoriID!, sessionID!, '-');
2965
- // change tag to receiver
2966
- const session = await changeTag(
2967
- memori.engineMemoriID!,
2968
- sessionID!,
2969
- personification.tag,
2970
- personification.pin
2971
- );
2972
-
2973
- if (session && session.resultCode === 0) {
2974
- await translateAndSpeak(session.currentState, userLang);
2975
- setClickedStart(false);
2976
- } else {
2977
- throw new Error('No session');
2978
- }
2979
- } catch {
2980
- reopenSession(
2981
- true,
2982
- memori?.secretToken,
2983
- undefined,
2984
- personification.tag,
2985
- personification.pin,
2986
- {
2987
- LANG: userLang,
2988
- PATHNAME: window.location.pathname?.toUpperCase(),
2989
- ROUTE:
2990
- window.location.pathname?.split('/')?.pop()?.toUpperCase() ||
2991
- '',
2992
- ...(initialContextVars || {}),
2993
- },
2994
- initialQuestion,
2995
- birth
2996
- ).then(() => {
2997
- setHasUserActivatedSpeak(true);
2998
- setClickedStart(false);
2999
- });
3000
- }
3001
- }
3002
- // Handle anonymous tag changes
3003
- else if (
3004
- !personification &&
3005
- currentState?.currentTag &&
3006
- currentState?.currentTag !== anonTag &&
3007
- currentState?.currentTag !== '-'
3008
- ) {
3009
- try {
3010
- // reset tag
3011
- await changeTag(memori.engineMemoriID!, sessionID!, '-');
3012
- // change tag to anonymous
3013
- const session = await changeTag(
3014
- memori.engineMemoriID!,
3015
- sessionID!,
3016
- anonTag
3017
- );
3018
-
3019
- if (session && session.resultCode === 0) {
3020
- await translateAndSpeak(session.currentState, userLang);
3021
- setClickedStart(false);
3022
- } else {
3023
- throw new Error('No session');
3024
- }
3025
- } catch (e) {
3026
- reopenSession(
3027
- true,
3028
- memori?.secretToken,
3029
- undefined,
3030
- undefined,
3031
- undefined,
3032
- {
3033
- LANG: userLang,
3034
- PATHNAME: window.location.pathname?.toUpperCase(),
3035
- ROUTE:
3036
- window.location.pathname?.split('/')?.pop()?.toUpperCase() ||
3037
- '',
3038
- ...(initialContextVars || {}),
3039
- },
3040
- initialQuestion,
3041
- birth
3042
- ).then(() => {
3043
- setHasUserActivatedSpeak(true);
3044
- setClickedStart(false);
3045
- });
3046
- }
3047
- }
3048
- // No tag changes needed
3049
- else {
3050
- try {
3051
- //This is the session id of the session that was opened before the current session
3052
- const { chatLogs } = await getSessionChatLogs(
3053
- sessionID!,
3054
- sessionID!
3055
- );
3056
-
3057
- const messages = chatLogs?.[0]?.lines.map(
3058
- (l, i) =>
3059
- ({
3060
- text: l.text,
3061
- media: l.media
3062
- ?.filter(m => allowedMediaTypes.includes(m.mimeType))
3063
- ?.map(m => ({
3064
- mediumID: `${i}-${m.mimeType}`,
3065
- ...m,
3066
- })),
3067
- fromUser: l.inbound,
3068
- llmUsage: (l as any).llmUsage,
3069
- timestamp: l.timestamp,
3070
- emitter: l.emitter,
3071
- initial: i === 0,
3072
- } as Message)
3073
- );
3074
-
3075
- // we dont remove the last one as it is the current state
3076
- translatedMessages = messages ?? [];
3077
- if (
3078
- language.toUpperCase() !== userLang.toUpperCase() &&
3079
- isMultilanguageEnabled
3080
- ) {
3081
- try {
3082
- translatedMessages = await Promise.all(
3083
- messages.map(async m => ({
3084
- ...m,
3085
- originalText: m.text,
3086
- text: (
3087
- await getTranslation(m.text, userLang, language, baseUrl)
3088
- ).text,
3089
- }))
3090
- );
3091
- } catch {
3092
- // ignore translation errors
3093
- }
3094
- }
3095
-
3096
- setHistory(translatedMessages);
3097
- } catch {
3098
- // ignore chat log retrieval errors
3099
- }
3100
-
3101
- if (
3102
- (!!translatedMessages?.length && translatedMessages.length > 1) ||
3103
- !initialQuestion
3104
- ) {
3105
- // we have a history, don't push message
3106
- setHasUserActivatedSpeak(true);
3107
- setClickedStart(false);
3108
- await translateAndSpeak(
3109
- currentState,
3110
- userLang,
3111
- undefined,
3112
- // if empty history, pick current state emission
3113
- // otherwise, don't push message
3114
- !!translatedMessages?.length
3115
- );
3116
- } else {
3117
- // remove default initial message
3118
- translatedMessages = [];
3119
- setHistory([]);
3120
-
3121
- // we have no chat history, we start by initial question
3122
- const placeSpec = getPlaceSpecForEnterText(position);
3123
- const response = await postEnterTextAsync({
3124
- sessionId: sessionID!,
3125
- text: initialQuestion,
3126
- ...(memori.needsDateTime && {
3127
- dateUTC: DateTime.utc().toISO() ?? undefined,
3128
- }),
3129
- ...(placeSpec !== undefined && { place: placeSpec }),
3130
- });
3131
- // Handle 500 error from EnterTextAsync
3132
- if (response.resultCode === 500 && response.resultMessage) {
3133
- setHistory(h => [
3134
- ...h,
3135
- {
3136
- text: 'Error: ' + response.resultMessage,
3137
- emitter: 'system',
3138
- fromUser: false,
3139
- initial: false,
3140
- contextVars: {},
3141
- date: new Date().toISOString(),
3142
- },
3143
- ]);
3144
- return;
3145
- }
3146
-
3147
- const onClickStartCorrelationID = readCorrelationID(response);
3148
- if (response.resultCode === 0 && onClickStartCorrelationID) {
3149
- setMemoriTyping(true);
3150
- try {
3151
- const natsEvent = await waitForEnterTextNatsResponse(
3152
- onClickStartCorrelationID
3153
- );
3154
- if (natsEvent.resultCode === 0 && natsEvent.currentState) {
3155
- await translateAndSpeak(
3156
- natsEvent.currentState,
3157
- userLang,
3158
- undefined,
3159
- false
3160
- );
3161
- setClickedStart(false);
3162
- }
3163
- } catch (err) {
3164
- logWidgetError('onClickStart NATS wait failed', err);
3165
- setMemoriTyping(false);
3166
- setTypingText(undefined);
3167
- }
3168
- } else if (response.resultCode === 0) {
3169
- logWidgetError('onClickStart enter-text missing correlationID', response);
3170
- }
3171
- }
3172
- }
3173
- }
3174
- // Default case - just translate and activate
3175
- else {
3176
- // reset history
3177
- setHistory([]);
3178
-
3179
- // everything is fine, just translate dialog state and activate chat
3180
- await translateAndSpeak(dialogState!, userLang);
3181
- setClickedStart(false);
3182
- }
3183
- },
3184
- [memoriPwd, memori, memoriTokens, birthDate, sessionId, userLang, position]
3185
- );
3186
-
3187
- // check if owner has enough credits
3188
- const needsCredits = tenant?.billingDelegation;
3189
- const [hasEnoughCredits, setHasEnoughCredits] = useState<boolean>(true);
3190
-
3191
- useEffect(() => {
3192
- if (
3193
- !clickedStart &&
3194
- !sessionStartingRef.current &&
3195
- !sessionId &&
3196
- autoStart &&
3197
- selectedLayout !== 'HIDDEN_CHAT' &&
3198
- (!needsCredits || hasEnoughCredits)
3199
- ) {
3200
- onClickStart();
3201
- }
3202
- }, [
3203
- clickedStart,
3204
- autoStart,
3205
- selectedLayout,
3206
- sessionId,
3207
- needsCredits,
3208
- hasEnoughCredits,
3209
- ]);
3210
-
3211
- useEffect(() => {
3212
- const targetNode =
3213
- document.querySelector(`memori-client[memoriname="${memori.name}"]`) ||
3214
- document.querySelector(`memori-client[memoriid="${memori.memoriID}"]`) ||
3215
- document.querySelector('memori-client');
3216
- if (!targetNode) {
3217
- return;
3218
- }
3219
-
3220
- const propsToken = additionalInfo?.loginToken ?? authToken;
3221
- const initialDomToken = targetNode.getAttribute('authtoken') || undefined;
3222
- if (initialDomToken && !propsToken) {
3223
- setLoginToken(initialDomToken);
3224
- userTokenRef.current = initialDomToken;
3225
- }
3226
-
3227
- const applyAuthTokenFromElement = (element: Element) => {
3228
- const clientNode =
3229
- element.nodeName === 'MEMORI-CLIENT'
3230
- ? element
3231
- : element.closest('memori-client') ?? element.parentElement;
3232
- const token = clientNode?.getAttribute('authtoken') || undefined;
3233
- setLoginToken(token);
3234
- userTokenRef.current = token;
3235
- };
3236
-
3237
- const config = { attributes: true, childList: false, subtree: false };
3238
- const callback: MutationCallback = mutationList => {
3239
- for (const mutation of mutationList) {
3240
- if (
3241
- mutation.type === 'attributes' &&
3242
- mutation.attributeName?.toLowerCase() === 'authtoken'
3243
- ) {
3244
- applyAuthTokenFromElement(mutation.target as Element);
3245
- }
3246
- }
3247
- };
3248
- const observer = new MutationObserver(callback);
3249
- observer.observe(targetNode, config);
3250
-
3251
- return () => {
3252
- observer.disconnect();
3253
- };
3254
- }, [memori.name, memori.memoriID, additionalInfo?.loginToken, authToken]);
3255
-
3256
- /**
3257
- * Experts references
3258
- */
3259
- const [experts, setExperts] = useState<ExpertReference[]>();
3260
- const fetchExperts = useCallback(async () => {
3261
- if (!sessionId || !memori?.enableBoardOfExperts) return;
3262
-
3263
- try {
3264
- const { experts, count, ...resp } = await getExpertReferences(sessionId);
3265
-
3266
- if (resp.resultCode === 0) {
3267
- setExperts(experts);
3268
- }
3269
- } catch {
3270
- // ignore expert fetch errors
3271
- }
3272
- }, [sessionId, memori?.enableBoardOfExperts]);
3273
- useEffect(() => {
3274
- fetchExperts();
3275
- }, [sessionId, fetchExperts]);
3276
-
3277
- const deepThoughtEnabled =
3278
- memori.enableDeepThought &&
3279
- !!loginToken &&
3280
- !!user?.userID &&
3281
- user?.pAndCUAccepted;
3282
-
3283
- const handleNotEnoughCredits = useCallback(() => {
3284
- setHasEnoughCredits(false);
3285
- setAuthModalState(null);
3286
- toast.error(t('notEnoughCredits'));
3287
- }, [t]);
3288
- const checkCredits = useCallback(
3289
- async (options?: { notify?: boolean }) => {
3290
- if (!tenant?.billingDelegation) return true;
3291
-
3292
- // Billing delegation is active: credits MUST be verified.
3293
- // Without either owner identifier we cannot call the API, so we fail closed
3294
- // instead of silently letting the session start unverified.
3295
- if (!ownerUserID && !ownerUserName) {
3296
- if (options?.notify) {
3297
- handleNotEnoughCredits();
3298
- } else {
3299
- setHasEnoughCredits(false);
3300
- }
3301
- return false;
3302
- }
3303
-
3304
- try {
3305
- const resp = await getCredits({
3306
- operation: deepThoughtEnabled
3307
- ? 'dt_session_creation'
3308
- : 'session_creation',
3309
- baseUrl: baseUrl,
3310
- userID: ownerUserID,
3311
- userName: ownerUserName,
3312
- tenant: tenantID,
3313
- });
3314
-
3315
- if (resp.enough) {
3316
- setHasEnoughCredits(true);
3317
- return true;
3318
- } else {
3319
- if (options?.notify) {
3320
- handleNotEnoughCredits();
3321
- } else {
3322
- setHasEnoughCredits(false);
3323
- }
3324
- return false;
3325
- }
3326
- } catch (err) {
3327
- logWidgetError('checkCredits failed', err);
3328
- return true;
3329
- }
3330
- },
3331
- [
3332
- baseUrl,
3333
- deepThoughtEnabled,
3334
- handleNotEnoughCredits,
3335
- ownerUserID,
3336
- ownerUserName,
3337
- tenant?.billingDelegation,
3338
- tenantID,
3339
- ]
3340
- );
3341
- useEffect(() => {
3342
- if (tenant?.billingDelegation) {
3343
- checkCredits();
3344
- }
3345
- }, [tenant?.billingDelegation, deepThoughtEnabled, checkCredits]);
3346
-
3347
- useEffect(() => {
3348
- if (__WEBCOMPONENT__) return;
3349
-
3350
- const closeSession = () => {
3351
- if (sessionId) {
3352
- deleteSession(sessionId);
3353
- }
3354
- };
3355
-
3356
- // delete session when the user closes the browser tab
3357
- window.addEventListener('beforeunload', closeSession);
3358
-
3359
- return () => {
3360
- window.removeEventListener('beforeunload', closeSession);
3361
- closeSession();
3362
- };
3363
- }, [sessionId]);
3364
-
3365
- const showFullHistory =
3366
- showOnlyLastMessages === undefined
3367
- ? selectedLayout !== 'TOTEM' && selectedLayout !== 'WEBSITE_ASSISTANT'
3368
- : !showOnlyLastMessages;
3369
- const canShowLoginButton =
3370
- !tenant?.ssoLogin &&
3371
- (showLogin ?? integrationConfig?.showLogin ?? memori.requireLoginToken);
3372
-
3373
- const headerProps: HeaderProps = {
3374
- memori: {
3375
- ...memori,
3376
- ownerUserID: memori.ownerUserID ?? ownerUserID ?? undefined,
3377
- },
3378
- apiClient: client,
3379
- tenant,
3380
- history,
3381
- showShare: showShare ?? integrationConfig?.showShare ?? true,
3382
- position,
3383
- layout: selectedLayout,
3384
- additionalSettings,
3385
- setShowPositionDrawer,
3386
- setShowSettingsDrawer,
3387
- setShowKnownFactsDrawer,
3388
- setShowExpertsDrawer,
3389
- enableAudio: enableAudio ?? integrationConfig?.enableAudio ?? true,
3390
- speakerMuted: speakerMuted ?? false,
3391
- setSpeakerMuted: (mute: boolean) => {
3392
- toggleMute(mute);
3393
- },
3394
- setShowChatHistoryDrawer,
3395
- showSettings: showSettings ?? integrationConfig?.showSettings ?? true,
3396
- showChatHistory:
3397
- showChatHistory ?? integrationConfig?.showChatHistory ?? true,
3398
- showMessageConsumption: enableMessageConsumption,
3399
- hasUserActivatedSpeak,
3400
- showReload: selectedLayout === 'TOTEM',
3401
- showClear: showClear ?? integrationConfig?.showClear ?? false,
3402
- clearHistory: () => setHistory(h => h.slice(-1)),
3403
- showLogin: canShowLoginButton,
3404
- setShowLoginDrawer,
3405
- loginToken,
3406
- user,
3407
- sessionID: sessionId,
3408
- baseUrl,
3409
- onLogout: () => {
3410
- if (!loginToken) return;
3411
-
3412
- client.backend.pwlUserLogout(loginToken).then(() => {
3413
- setShowLoginDrawer(false);
3414
- setUser(undefined);
3415
- setLoginToken(undefined);
3416
- userTokenRef.current = undefined;
3417
- removeLocalConfig('loginToken');
3418
- });
3419
- },
3420
- };
3421
-
3422
- const avatarProps: AvatarProps = {
3423
- memori,
3424
- integration,
3425
- integrationConfig,
3426
- tenant,
3427
- instruct,
3428
- avatar3dVisible,
3429
- setAvatar3dVisible,
3430
- hasUserActivatedSpeak,
3431
- isPlayingAudio:
3432
- isPlayingAudio &&
3433
- !speakerMuted &&
3434
- (enableAudio ?? integrationConfig?.enableAudio ?? true),
3435
- loading: !!memoriTyping,
3436
- baseUrl,
3437
- apiUrl: client.constants.BACKEND_URL,
3438
- enablePositionControls,
3439
- setEnablePositionControls,
3440
- avatarType,
3441
- };
3442
-
3443
- const startPanelProps: StartPanelProps = {
3444
- memori,
3445
- tenant: tenant,
3446
- language: language,
3447
- userLang: userLang,
3448
- setUserLang: setUserLang,
3449
- baseUrl: baseUrl,
3450
- apiUrl: client.constants.BACKEND_URL,
3451
- position: position,
3452
- openPositionDrawer: () => setShowPositionDrawer(true),
3453
- integrationConfig: integrationConfig,
3454
- instruct: instruct,
3455
- sessionId: sessionId,
3456
- clickedStart: clickedStart,
3457
- isMultilanguageEnabled: isMultilanguageEnabled,
3458
- onClickStart: onClickStart,
3459
- isUserLoggedIn: !!loginToken && !!user?.userID,
3460
- hasInitialSession: !!initialSessionID,
3461
- notEnoughCredits: needsCredits && !hasEnoughCredits,
3462
- showLogin: canShowLoginButton,
3463
- setShowLoginDrawer,
3464
- user,
3465
- };
3466
-
3467
- const chatProps: ChatProps = {
3468
- memori,
3469
- sessionID: sessionId || '',
3470
- tenant,
3471
- translateTo:
3472
- isMultilanguageEnabled &&
3473
- userLang.toUpperCase() !==
3474
- (
3475
- memori.culture?.split('-')?.[0] ??
3476
- i18n.language ??
3477
- 'IT'
3478
- )?.toUpperCase()
3479
- ? userLang
3480
- : undefined,
3481
- baseUrl,
3482
- apiUrl: client.constants.BACKEND_URL,
3483
- layout,
3484
- memoriTyping,
3485
- typingText,
3486
- showTypingText:
3487
- showTypingText ?? integrationConfig?.showTypingText ?? false,
3488
- history: showFullHistory ? history : history.slice(-2),
3489
- authToken: resolveLoginToken(),
3490
- dialogState: currentDialogState,
3491
- pushMessage,
3492
- simulateUserPrompt,
3493
- showDates,
3494
- showContextPerLine,
3495
- showMessageConsumption: enableMessageConsumption,
3496
- showAIicon,
3497
- showUpload: enableUpload,
3498
- showReasoning: enableReasoning,
3499
- showWhyThisAnswer,
3500
- showCopyButton: showCopyButton ?? integrationConfig?.showCopyButton ?? true,
3501
- showTranslationOriginal:
3502
- showTranslationOriginal ??
3503
- integrationConfig?.showTranslationOriginal ??
3504
- false,
3505
- client,
3506
- instruct,
3507
- preview,
3508
- sendOnEnter,
3509
- setSendOnEnter,
3510
- microphoneMode: continuousSpeech ? 'CONTINUOUS' : 'HOLD_TO_TALK',
3511
- attachmentsMenuOpen,
3512
- setAttachmentsMenuOpen,
3513
- showInputs,
3514
- showMicrophone:
3515
- !!ttsProvider && (enableAudio ?? integrationConfig?.enableAudio ?? true),
3516
- showFunctionCache,
3517
- userMessage,
3518
- onChangeUserMessage,
3519
- sendMessage: (msg: string, media?: (Medium & { type: string })[]) => {
3520
- ttsStop();
3521
- stopRecording();
3522
- setHasUserTypedMessage(true); // Mark that user has typed a message
3523
- sendMessage(msg, media);
3524
- setUserMessage('');
3525
- },
3526
- stopListening: stopRecording,
3527
- startListening: () => {
3528
- setHasUserTypedMessage(false); // Reset typing flag when user starts listening
3529
- startRecording();
3530
- },
3531
- stopAudio: ttsStop,
3532
- listening: isListening,
3533
- setEnableFocusChatInput,
3534
- isPlayingAudio,
3535
- customMediaRenderer,
3536
- user,
3537
- userAvatar,
3538
- experts,
3539
- useMathFormatting: applyMathFormatting,
3540
- maxTotalMessagePayload,
3541
- maxTextareaCharacters,
3542
- };
3543
-
3544
- const integrationBackground =
3545
- integration && globalBackgroundUrl ? (
3546
- <div className="memori--global-background">
3547
- <div
3548
- className="memori--global-background-image"
3549
- style={{ backgroundImage: globalBackgroundUrl }}
3550
- />
3551
- </div>
3552
- ) : (
3553
- <div className="memori--global-background no-background-image" />
3554
- );
3555
-
3556
- const integrationStyle = integration ? (
3557
- <style dangerouslySetInnerHTML={{ __html: integrationStylesheet }} />
3558
- ) : null;
3559
-
3560
- const poweredBy = (
3561
- <PoweredBy
3562
- tenant={tenant}
3563
- userLang={userLang}
3564
- integrationID={integration?.integrationID}
3565
- memoriHash={`${memori.ownerTenantName}-${memori.ownerUserName}-${memori.name}`}
3566
- />
3567
- );
3568
-
3569
- const Layout = customLayout
3570
- ? customLayout
3571
- : selectedLayout === 'TOTEM'
3572
- ? TotemLayout
3573
- : selectedLayout === 'CHAT'
3574
- ? ChatLayout
3575
- : selectedLayout === 'FULLPAGE'
3576
- ? FullPageLayout
3577
- : selectedLayout === 'WEBSITE_ASSISTANT'
3578
- ? WebsiteAssistantLayout
3579
- : selectedLayout === 'HIDDEN_CHAT'
3580
- ? HiddenChatLayout
3581
- : selectedLayout === 'ZOOMED_FULL_BODY'
3582
- ? ZoomedFullBodyLayout
3583
- : FullPageLayout;
3584
-
3585
- return (
3586
- <div
3587
- className={cx(
3588
- 'memori',
3589
- 'memori-widget',
3590
- `memori-layout-${selectedLayout.toLowerCase()}`,
3591
- `memori-controls-${controlsPosition.toLowerCase()}`,
3592
- `memori--avatar-${integrationConfig?.avatar || 'default'}`,
3593
- {
3594
- 'memori--auto-start': autoStart,
3595
- 'memori--preview': preview,
3596
- 'memori--embed': embed,
3597
- 'memori--with-integration': integration,
3598
- 'memori--with-speechkey': !!ttsProvider,
3599
- 'memori--active': hasUserActivatedSpeak,
3600
- 'memori--hide-emissions': hideEmissions,
3601
- 'memori--has-active-session': !!sessionId,
3602
- }
3603
- )}
3604
- data-memori-name={memori?.name}
3605
- data-memori-id={memori?.engineMemoriID}
3606
- data-memori-secondary-id={memori?.memoriID}
3607
- data-memori-session-id={sessionId}
3608
- data-memori-integration={integration?.integrationID}
3609
- data-memori-engine-state={JSON.stringify({
3610
- ...currentDialogState,
3611
- sessionID: sessionId,
3612
- })}
3613
- style={{ height }}
3614
- >
3615
- <Layout
3616
- Header={Header}
3617
- headerProps={headerProps}
3618
- Avatar={Avatar}
3619
- avatarProps={avatarProps}
3620
- Chat={Chat}
3621
- chatProps={chatProps}
3622
- StartPanel={StartPanel}
3623
- startPanelProps={startPanelProps}
3624
- integrationStyle={integrationStyle}
3625
- integrationBackground={integrationBackground}
3626
- poweredBy={poweredBy}
3627
- autoStart={autoStart}
3628
- sessionId={sessionId}
3629
- hasUserActivatedSpeak={hasUserActivatedSpeak}
3630
- loading={loading}
3631
- avatar3dHidden={avatar3dHidden ?? integrationConfig?.avatar_3d_hidden}
3632
- />
3633
-
3634
- <ArtifactAPIBridge
3635
- pushMessage={(message: Message) => {
3636
- setHistory(history => {
3637
- if (!history.length) return history;
3638
- const lastMessage = history[history.length - 1];
3639
- if (!lastMessage || lastMessage.fromUser) return history;
3640
- // Create a new message object with the updated text
3641
- const updatedLastMessage = {
3642
- ...lastMessage,
3643
- text: lastMessage.text + message.text,
3644
- };
3645
- return [...history.slice(0, -1), updatedLastMessage];
3646
- });
3647
- }}
3648
- />
3649
-
3650
- <audio
3651
- id="memori-audio"
3652
- style={{ display: 'none' }}
3653
- src="https://aisuru.com/intro.mp3"
3654
- />
3655
-
3656
- {isClient && (
3657
- <MemoriAuth
3658
- withModal
3659
- pwdOrTokens={authModalState}
3660
- openModal={!!authModalState}
3661
- setPwdOrTokens={setAuthModalState}
3662
- showTokens={memori.privacyType === 'SECRET'}
3663
- onFinish={(values: any) => {
3664
- if (values['password']) setMemoriPwd(values['password']);
3665
- if (values['password']) memoriPassword = values['password'];
3666
- if (values['tokens']) setMemoriTokens(values['tokens']);
3667
-
3668
- return reopenSession(
3669
- !sessionId,
3670
- values['password'],
3671
- values['tokens'],
3672
- personification?.tag,
3673
- personification?.pin,
3674
- {
3675
- LANG: userLang,
3676
- PATHNAME: window.location.pathname?.toUpperCase(),
3677
- ROUTE:
3678
- window.location.pathname?.split('/')?.pop()?.toUpperCase() ||
3679
- '',
3680
- ...(initialContextVars || {}),
3681
- },
3682
- initialQuestion,
3683
- birthDate
3684
- )
3685
- .then(state => {
3686
- if (!state?.sessionID) {
3687
- throw new Error('AUTH_FAILED');
3688
- }
3689
-
3690
- setAuthModalState(null);
3691
- // If we got a valid state from reopenSession, don't call onClickStart again
3692
- // to avoid duplicate snippet execution
3693
- if (state?.dialogState) {
3694
- setHasUserActivatedSpeak(true);
3695
- } else {
3696
- // Only call onClickStart if reopenSession didn't return a valid state
3697
- onClickStart(state);
3698
- }
3699
- })
3700
- .catch(error => {
3701
- throw error;
3702
- });
3703
- }}
3704
- minimumNumberOfRecoveryTokens={
3705
- memori?.minimumNumberOfRecoveryTokens ?? 1
3706
- }
3707
- />
3708
- )}
3709
-
3710
- {isClient && (
3711
- <AgeVerificationModal
3712
- visible={showAgeVerification}
3713
- minAge={minAge}
3714
- onClose={birthDate => {
3715
- if (birthDate) {
3716
- setBirthDate(birthDate);
3717
-
3718
- setLocalConfig('birthDate', birthDate);
3719
-
3720
- reopenSession(
3721
- !sessionId,
3722
- memoriPassword || memoriPwd || memori?.secretToken,
3723
- memoriTokens,
3724
- personification?.tag,
3725
- personification?.pin,
3726
- {
3727
- LANG: userLang,
3728
- PATHNAME: window.location.pathname?.toUpperCase(),
3729
- ROUTE:
3730
- window.location.pathname
3731
- ?.split('/')
3732
- ?.pop()
3733
- ?.toUpperCase() || '',
3734
- ...(initialContextVars || {}),
3735
- },
3736
- initialQuestion,
3737
- birthDate
3738
- )
3739
- .then(state => {
3740
- setShowAgeVerification(false);
3741
- setAuthModalState(null);
3742
- onClickStart(state || undefined);
3743
- })
3744
- .catch(() => {
3745
- setShowAgeVerification(false);
3746
- });
3747
- } else {
3748
- setShowAgeVerification(false);
3749
- setClickedStart(false);
3750
- }
3751
- }}
3752
- />
3753
- )}
3754
-
3755
- {showSettingsDrawer && (
3756
- <SettingsDrawer
3757
- layout={selectedLayout}
3758
- open={!!showSettingsDrawer}
3759
- onClose={() => setShowSettingsDrawer(false)}
3760
- microphoneMode={continuousSpeech ? 'CONTINUOUS' : 'HOLD_TO_TALK'}
3761
- continuousSpeechTimeout={continuousSpeechTimeout}
3762
- setMicrophoneMode={mode => setContinuousSpeech(mode === 'CONTINUOUS')}
3763
- setContinuousSpeechTimeout={setContinuousSpeechTimeout}
3764
- controlsPosition={controlsPosition}
3765
- setControlsPosition={setControlsPosition}
3766
- hideEmissions={hideEmissions}
3767
- setHideEmissions={setHideEmissions}
3768
- avatarType={avatarType}
3769
- setAvatarType={setAvatarType}
3770
- enablePositionControls={enablePositionControls}
3771
- setEnablePositionControls={setEnablePositionControls}
3772
- isAvatar3d={!!integrationConfig?.avatarURL}
3773
- additionalSettings={additionalSettings}
3774
- speakerMuted={speakerMuted}
3775
- />
3776
- )}
3777
-
3778
- {showChatHistoryDrawer && (
3779
- <ChatHistoryDrawer
3780
- open={!!showChatHistoryDrawer}
3781
- onClose={() => setShowChatHistoryDrawer(false)}
3782
- resumeSession={chatLog => {
3783
- setChatLogID(chatLog.chatLogID);
3784
- onClickStart(undefined, false, chatLog);
3785
- setShowChatHistoryDrawer(false);
3786
- }}
3787
- apiClient={client}
3788
- sessionId={sessionId || ''}
3789
- memori={memori}
3790
- baseUrl={baseUrl}
3791
- history={history}
3792
- apiUrl={client.constants.BACKEND_URL}
3793
- loginToken={loginToken}
3794
- language={language}
3795
- userLang={userLang}
3796
- isMultilanguageEnabled={isMultilanguageEnabled}
3797
- />
3798
- )}
3799
-
3800
- {showPositionDrawer && (
3801
- <PositionDrawer
3802
- memori={memori}
3803
- open={!!showPositionDrawer}
3804
- venue={position}
3805
- setVenue={setPosition}
3806
- onClose={() => {
3807
- setShowPositionDrawer(false);
3808
- if (autoStart) {
3809
- onClickStart();
3810
- }
3811
- }}
3812
- drawerClassName={
3813
- selectedLayout === 'WEBSITE_ASSISTANT'
3814
- ? 'memori-drawer--above-website-assistant'
3815
- : undefined
3816
- }
3817
- />
3818
- )}
3819
-
3820
- {showKnownFactsDrawer && sessionId && (
3821
- <KnownFacts
3822
- apiClient={client}
3823
- memori={memori}
3824
- sessionID={sessionId}
3825
- visible={showKnownFactsDrawer}
3826
- closeDrawer={() => setShowKnownFactsDrawer(false)}
3827
- />
3828
- )}
3829
-
3830
- {showExpertsDrawer && !!experts && (
3831
- <ExpertsDrawer
3832
- apiUrl={client.constants.BACKEND_URL}
3833
- baseUrl={baseUrl}
3834
- tenant={tenant}
3835
- experts={experts}
3836
- open={showExpertsDrawer}
3837
- onClose={() => setShowExpertsDrawer(false)}
3838
- />
3839
- )}
3840
-
3841
- {showLoginDrawer && tenant?.name && (
3842
- <LoginDrawer
3843
- tenant={tenant}
3844
- apiClient={client}
3845
- open={!!showLoginDrawer}
3846
- user={user}
3847
- loginToken={loginToken}
3848
- onClose={() => setShowLoginDrawer(false)}
3849
- drawerClassName={
3850
- selectedLayout === 'WEBSITE_ASSISTANT'
3851
- ? 'memori-drawer--above-website-assistant'
3852
- : undefined
3853
- }
3854
- onLogin={(user, token) => {
3855
- //The user is logged in, so we need to set open a new session with the new token
3856
- reopenSession(
3857
- false,
3858
- memoriPassword || memoriPwd || memori?.secretToken,
3859
- [],
3860
- personification?.tag,
3861
- personification?.pin,
3862
- {
3863
- LANG: userLang,
3864
- PATHNAME: window.location.pathname?.toUpperCase(),
3865
- ROUTE:
3866
- window.location.pathname?.split('/')?.pop()?.toUpperCase() ||
3867
- '',
3868
- ...(initialContextVars || {}),
3869
- },
3870
- undefined, // Don't send initialQuestion after login, only show the login status chip
3871
- birthDate,
3872
- { loginToken: token } as any,
3873
- undefined,
3874
- sessionId
3875
- ).then(state => {
3876
- setShowLoginDrawer(false);
3877
- setUser(user);
3878
- setLoginToken(token);
3879
- userTokenRef.current = token;
3880
- setLocalConfig('loginToken', token);
3881
- // Push a message with initial status to show status message when a new session is created after login
3882
- if (
3883
- state?.sessionID &&
3884
- state.sessionID !== sessionId &&
3885
- state?.dialogState
3886
- ) {
3887
- // Push a message with initial status message showing successful login
3888
- // Only show the chip component, not the emission text
3889
- const username = user?.userName || t('login.user');
3890
- pushMessage({
3891
- text: '', // Empty text so only the chip is visible
3892
- emitter: state.dialogState.emitter,
3893
- media:
3894
- state.dialogState.emittedMedia ??
3895
- state.dialogState.media ??
3896
- [],
3897
- fromUser: false,
3898
- initial: t('login.successfullyLoggedIn', { username }) as any,
3899
- contextVars: state.dialogState.contextVars,
3900
- date: state.dialogState.currentDate,
3901
- placeName: state.dialogState.currentPlaceName,
3902
- placeLatitude: state.dialogState.currentLatitude,
3903
- placeLongitude: state.dialogState.currentLongitude,
3904
- placeUncertaintyKm: state.dialogState.currentUncertaintyKm,
3905
- tag: state.dialogState.currentTag,
3906
- memoryTags: state.dialogState.memoryTags,
3907
- });
3908
- // Update the dialog state so the UI reflects the new session
3909
- setCurrentDialogState(state.dialogState);
3910
- }
3911
- });
3912
- }}
3913
- setUser={setUser}
3914
- onLogout={() => {
3915
- if (!loginToken) return;
3916
- client.backend.pwlUserLogout(loginToken).then(() => {
3917
- setShowLoginDrawer(false);
3918
- setUser(undefined);
3919
- setLoginToken(undefined);
3920
- userTokenRef.current = undefined;
3921
- removeLocalConfig('loginToken');
3922
- });
3923
- }}
3924
- />
3925
- )}
3926
- </div>
3927
- );
3928
- };
3929
-
3930
- export default MemoriWidget;