@memori.ai/memori-react 8.45.1 → 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 (511) hide show
  1. package/CHANGELOG.md +27 -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 +4 -1
  5. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js +7 -57
  6. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -1
  7. package/dist/components/IconButton/IconButton.d.ts +1 -1
  8. package/dist/components/MemoriWidget/MemoriWidget.d.ts +1 -1
  9. package/dist/components/MemoriWidget/MemoriWidget.js +2 -1
  10. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  11. package/dist/components/PositionPopover/PositionPopover.js +38 -25
  12. package/dist/components/PositionPopover/PositionPopover.js.map +1 -1
  13. package/dist/components/UploadButton/UploadButton.js +3 -0
  14. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  15. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +14 -2
  16. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  17. package/dist/components/layouts/chat.css +27 -4
  18. package/dist/context/widgetSurfaceContext.js +2 -2
  19. package/dist/context/widgetSurfaceContext.js.map +1 -1
  20. package/dist/helpers/constants.d.ts +3 -2
  21. package/dist/helpers/constants.js +19 -20
  22. package/dist/helpers/constants.js.map +1 -1
  23. package/dist/helpers/positionPopover.d.ts +2 -1
  24. package/dist/helpers/positionPopover.js +12 -3
  25. package/dist/helpers/positionPopover.js.map +1 -1
  26. package/dist/helpers/tts/muteSpeaker.js +4 -3
  27. package/dist/helpers/tts/muteSpeaker.js.map +1 -1
  28. package/dist/helpers/usePressTooltip.js +2 -1
  29. package/dist/helpers/usePressTooltip.js.map +1 -1
  30. package/dist/helpers/utils.d.ts +2 -1
  31. package/dist/helpers/utils.js +7 -1
  32. package/dist/helpers/utils.js.map +1 -1
  33. package/dist/icons/MemoriIcon.js +1 -1
  34. package/dist/icons/MemoriIcon.js.map +1 -1
  35. package/dist/styles.css +1 -2
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/esm/components/Chat/Chat.js +4 -1
  39. package/esm/components/Chat/Chat.js.map +1 -1
  40. package/esm/components/MemoriWidget/MemoriWidget.d.ts +1 -1
  41. package/esm/components/MemoriWidget/MemoriWidget.js +2 -1
  42. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  43. package/esm/components/UploadButton/UploadButton.js +4 -1
  44. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  45. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +16 -4
  46. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  47. package/esm/components/layouts/chat.css +27 -4
  48. package/esm/helpers/constants.d.ts +3 -2
  49. package/esm/helpers/constants.js +18 -19
  50. package/esm/helpers/constants.js.map +1 -1
  51. package/esm/helpers/utils.d.ts +2 -1
  52. package/esm/helpers/utils.js +6 -1
  53. package/esm/helpers/utils.js.map +1 -1
  54. package/esm/styles.css +1 -2
  55. package/esm/version.d.ts +1 -1
  56. package/esm/version.js +1 -1
  57. package/package.json +6 -7
  58. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.d.ts +0 -3
  59. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.js +0 -64
  60. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.js.map +0 -1
  61. package/dist/testUtils.d.ts +0 -5
  62. package/dist/testUtils.js +0 -18
  63. package/dist/testUtils.js.map +0 -1
  64. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.d.ts +0 -3
  65. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.js +0 -62
  66. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.js.map +0 -1
  67. package/src/I18nWrapper.tsx +0 -13
  68. package/src/__snapshots__/index.test.tsx.snap +0 -96
  69. package/src/components/AgeVerificationModal/AgeVerificationModal.css +0 -95
  70. package/src/components/AgeVerificationModal/AgeVerificationModal.stories.tsx +0 -46
  71. package/src/components/AgeVerificationModal/AgeVerificationModal.test.tsx +0 -33
  72. package/src/components/AgeVerificationModal/AgeVerificationModal.tsx +0 -99
  73. package/src/components/AgeVerificationModal/__snapshots__/AgeVerificationModal.test.tsx.snap +0 -19
  74. package/src/components/Auth/Auth.css +0 -57
  75. package/src/components/Auth/Auth.stories.tsx +0 -90
  76. package/src/components/Auth/Auth.test.tsx +0 -63
  77. package/src/components/Auth/Auth.tsx +0 -201
  78. package/src/components/Auth/__snapshots__/Auth.test.tsx.snap +0 -143
  79. package/src/components/Avatar/Avatar.css +0 -151
  80. package/src/components/Avatar/Avatar.stories.tsx +0 -160
  81. package/src/components/Avatar/Avatar.test.tsx +0 -158
  82. package/src/components/Avatar/Avatar.tsx +0 -245
  83. package/src/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.tsx +0 -103
  84. package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +0 -105
  85. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +0 -338
  86. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/types.ts +0 -44
  87. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.ts +0 -1091
  88. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarPositionController.ts +0 -83
  89. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +0 -301
  90. package/src/components/Avatar/AvatarView/AvatarComponent/components/controls.tsx +0 -100
  91. package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +0 -152
  92. package/src/components/Avatar/AvatarView/AvatarComponent/components/loader.tsx +0 -31
  93. package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +0 -141
  94. package/src/components/Avatar/AvatarView/AvatarComponent/lights/Lights.tsx +0 -145
  95. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +0 -111
  96. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.tsx +0 -201
  97. package/src/components/Avatar/AvatarView/AvatarView.stories.tsx +0 -254
  98. package/src/components/Avatar/AvatarView/index.tsx +0 -194
  99. package/src/components/Avatar/AvatarView/utils/hideHands.ts +0 -11
  100. package/src/components/Avatar/AvatarView/utils/useEyeBlink.ts +0 -100
  101. package/src/components/Avatar/AvatarView/utils/useHeadMovement.ts +0 -72
  102. package/src/components/Avatar/AvatarView/utils/useLoadingMorphAnim.ts +0 -38
  103. package/src/components/Avatar/AvatarView/utils/useMouthAnimation.ts +0 -93
  104. package/src/components/Avatar/AvatarView/utils/useSmile.ts +0 -39
  105. package/src/components/Avatar/AvatarView/utils/utils.ts +0 -55
  106. package/src/components/Avatar/__snapshots__/Avatar.test.tsx.snap +0 -246
  107. package/src/components/Blob/Blob.css +0 -286
  108. package/src/components/Blob/Blob.stories.tsx +0 -53
  109. package/src/components/Blob/Blob.test.tsx +0 -18
  110. package/src/components/Blob/Blob.tsx +0 -26
  111. package/src/components/Blob/__snapshots__/Blob.test.tsx.snap +0 -80
  112. package/src/components/BlockedMemoriBadge/BlockedMemoriBadge.css +0 -40
  113. package/src/components/BlockedMemoriBadge/BlockedMemoriBadge.stories.tsx +0 -58
  114. package/src/components/BlockedMemoriBadge/BlockedMemoriBadge.test.tsx +0 -53
  115. package/src/components/BlockedMemoriBadge/BlockedMemoriBadge.tsx +0 -74
  116. package/src/components/BlockedMemoriBadge/__snapshots__/BlockedMemoriBadge.test.tsx.snap +0 -181
  117. package/src/components/ChangeMode/ChangeMode.css +0 -22
  118. package/src/components/ChangeMode/ChangeMode.stories.tsx +0 -50
  119. package/src/components/ChangeMode/ChangeMode.test.tsx +0 -24
  120. package/src/components/ChangeMode/ChangeMode.tsx +0 -51
  121. package/src/components/ChangeMode/__snapshots__/ChangeMode.test.tsx.snap +0 -107
  122. package/src/components/Chat/Chat.css +0 -285
  123. package/src/components/Chat/Chat.stories.tsx +0 -1684
  124. package/src/components/Chat/Chat.test.tsx +0 -372
  125. package/src/components/Chat/Chat.tsx +0 -778
  126. package/src/components/Chat/__snapshots__/Chat.test.tsx.snap +0 -6816
  127. package/src/components/ChatBubble/ChatBubble.css +0 -430
  128. package/src/components/ChatBubble/ChatBubble.stories.tsx +0 -1192
  129. package/src/components/ChatBubble/ChatBubble.test.tsx +0 -481
  130. package/src/components/ChatBubble/ChatBubble.tsx +0 -776
  131. package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +0 -8734
  132. package/src/components/ChatHistoryDrawer/ChatHistory.css +0 -587
  133. package/src/components/ChatHistoryDrawer/ChatHistory.stories.tsx +0 -410
  134. package/src/components/ChatHistoryDrawer/ChatHistory.test.tsx +0 -107
  135. package/src/components/ChatHistoryDrawer/ChatHistory.tsx +0 -1298
  136. package/src/components/ChatHistoryDrawer/__snapshots__/ChatHistory.test.tsx.snap +0 -27
  137. package/src/components/ChatInputs/ChatInputs.css +0 -340
  138. package/src/components/ChatInputs/ChatInputs.stories.tsx +0 -251
  139. package/src/components/ChatInputs/ChatInputs.tsx +0 -487
  140. package/src/components/ChatTextArea/ChatTextArea.css +0 -185
  141. package/src/components/ChatTextArea/ChatTextArea.stories.tsx +0 -55
  142. package/src/components/ChatTextArea/ChatTextArea.test.tsx +0 -28
  143. package/src/components/ChatTextArea/ChatTextArea.tsx +0 -171
  144. package/src/components/ChatTextArea/__snapshots__/ChatTextArea.test.tsx.snap +0 -68
  145. package/src/components/CompletionProviderStatus/CompletionProviderStatus.css +0 -36
  146. package/src/components/CompletionProviderStatus/CompletionProviderStatus.stories.tsx +0 -296
  147. package/src/components/CompletionProviderStatus/CompletionProviderStatus.test.tsx +0 -31
  148. package/src/components/CompletionProviderStatus/CompletionProviderStatus.tsx +0 -232
  149. package/src/components/CompletionProviderStatus/__snapshots__/CompletionProviderStatus.test.tsx.snap +0 -96
  150. package/src/components/ContentPreviewModal/ContentPreviewModal.css +0 -128
  151. package/src/components/ContentPreviewModal/ContentPreviewModal.tsx +0 -67
  152. package/src/components/ContentPreviewModal/index.ts +0 -2
  153. package/src/components/CustomGLBModelViewer/ModelViewer.css +0 -11
  154. package/src/components/CustomGLBModelViewer/ModelViewer.stories.tsx +0 -58
  155. package/src/components/CustomGLBModelViewer/ModelViewer.test.tsx +0 -13
  156. package/src/components/CustomGLBModelViewer/ModelViewer.tsx +0 -37
  157. package/src/components/CustomGLBModelViewer/__snapshots__/ModelViewer.test.tsx.snap +0 -20
  158. package/src/components/DateSelector/DateSelector.css +0 -158
  159. package/src/components/DateSelector/DateSelector.stories.tsx +0 -47
  160. package/src/components/DateSelector/DateSelector.test.tsx +0 -149
  161. package/src/components/DateSelector/DateSelector.tsx +0 -251
  162. package/src/components/DateSelector/__snapshots__/DateSelector.test.tsx.snap +0 -1954
  163. package/src/components/ErrorBoundary/ErrorBoundary.tsx +0 -54
  164. package/src/components/ExpertsDrawer/ExpertsDrawer.css +0 -67
  165. package/src/components/ExpertsDrawer/ExpertsDrawer.stories.tsx +0 -54
  166. package/src/components/ExpertsDrawer/ExpertsDrawer.test.tsx +0 -53
  167. package/src/components/ExpertsDrawer/ExpertsDrawer.tsx +0 -69
  168. package/src/components/ExpertsDrawer/__snapshots__/ExpertsDrawer.test.tsx.snap +0 -11
  169. package/src/components/ExportHistoryButton/ExportHistoryButton.css +0 -22
  170. package/src/components/FeedbackButtons/FeedbackButtons.css +0 -107
  171. package/src/components/FeedbackButtons/FeedbackButtons.stories.tsx +0 -52
  172. package/src/components/FeedbackButtons/FeedbackButtons.test.tsx +0 -11
  173. package/src/components/FeedbackButtons/FeedbackButtons.tsx +0 -152
  174. package/src/components/FeedbackButtons/__snapshots__/FeedbackButtons.test.tsx.snap +0 -42
  175. package/src/components/FilePreview/FilePreview.css +0 -358
  176. package/src/components/FilePreview/FilePreview.stories.tsx +0 -63
  177. package/src/components/FilePreview/FilePreview.test.tsx +0 -26
  178. package/src/components/FilePreview/FilePreview.tsx +0 -277
  179. package/src/components/FilePreview/__snapshots__/FilePreview.test.tsx.snap +0 -185
  180. package/src/components/Header/ChatConsumptionDropdown.test.tsx +0 -117
  181. package/src/components/Header/ChatConsumptionDropdown.tsx +0 -275
  182. package/src/components/Header/Header.css +0 -282
  183. package/src/components/Header/Header.stories.tsx +0 -338
  184. package/src/components/Header/Header.test.tsx +0 -371
  185. package/src/components/Header/Header.tsx +0 -442
  186. package/src/components/Header/__snapshots__/Header.test.tsx.snap +0 -964
  187. package/src/components/KnownFacts/KnownFacts.css +0 -19
  188. package/src/components/KnownFacts/KnownFacts.stories.tsx +0 -80
  189. package/src/components/KnownFacts/KnownFacts.test.tsx +0 -57
  190. package/src/components/KnownFacts/KnownFacts.tsx +0 -376
  191. package/src/components/KnownFacts/__snapshots__/KnownFacts.test.tsx.snap +0 -19
  192. package/src/components/LoginDrawer/LoginDrawer.css +0 -1616
  193. package/src/components/LoginDrawer/LoginDrawer.stories.tsx +0 -102
  194. package/src/components/LoginDrawer/LoginDrawer.test.tsx +0 -199
  195. package/src/components/LoginDrawer/LoginDrawer.tsx +0 -600
  196. package/src/components/LoginDrawer/__snapshots__/LoginDrawer.test.tsx.snap +0 -91
  197. package/src/components/MediaWidget/DocumentCard.test.tsx +0 -45
  198. package/src/components/MediaWidget/DocumentCard.tsx +0 -19
  199. package/src/components/MediaWidget/MediaItemWidget.css +0 -1398
  200. package/src/components/MediaWidget/MediaItemWidget.stories.tsx +0 -423
  201. package/src/components/MediaWidget/MediaItemWidget.test.tsx +0 -248
  202. package/src/components/MediaWidget/MediaItemWidget.tsx +0 -997
  203. package/src/components/MediaWidget/MediaItemWidget.types.ts +0 -65
  204. package/src/components/MediaWidget/MediaItemWidget.utils.test.ts +0 -367
  205. package/src/components/MediaWidget/MediaItemWidget.utils.ts +0 -227
  206. package/src/components/MediaWidget/MediaPreviewModal.test.tsx +0 -271
  207. package/src/components/MediaWidget/MediaPreviewModal.tsx +0 -425
  208. package/src/components/MediaWidget/MediaWidget.css +0 -131
  209. package/src/components/MediaWidget/MediaWidget.stories.tsx +0 -319
  210. package/src/components/MediaWidget/MediaWidget.test.tsx +0 -41
  211. package/src/components/MediaWidget/MediaWidget.tsx +0 -107
  212. package/src/components/MediaWidget/__snapshots__/DocumentCard.test.tsx.snap +0 -24
  213. package/src/components/MediaWidget/__snapshots__/MediaItemWidget.test.tsx.snap +0 -791
  214. package/src/components/MediaWidget/__snapshots__/MediaWidget.test.tsx.snap +0 -154
  215. package/src/components/MemoriArtifactSystem/ArtifactDrawer.stories.tsx +0 -2299
  216. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.css +0 -163
  217. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.tsx +0 -250
  218. package/src/components/MemoriArtifactSystem/components/ArtifactActions/CopyButton.stories.tsx +0 -30
  219. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.tsx +0 -467
  220. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.tsx +0 -89
  221. package/src/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.ts +0 -619
  222. package/src/components/MemoriArtifactSystem/components/ArtifactActions/index.ts +0 -9
  223. package/src/components/MemoriArtifactSystem/components/ArtifactActions/styles.css +0 -489
  224. package/src/components/MemoriArtifactSystem/components/ArtifactActions/test/CopyButtonTest.tsx +0 -90
  225. package/src/components/MemoriArtifactSystem/components/ArtifactActions/types.ts +0 -73
  226. package/src/components/MemoriArtifactSystem/components/ArtifactActions/utils/PDFExporter.ts +0 -454
  227. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +0 -1059
  228. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +0 -431
  229. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.css +0 -150
  230. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/components/TabSwitch.tsx +0 -79
  231. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.css +0 -253
  232. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.tsx +0 -561
  233. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +0 -355
  234. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.tsx +0 -160
  235. package/src/components/MemoriArtifactSystem/context/ArtifactContext.tsx +0 -167
  236. package/src/components/MemoriArtifactSystem/types/artifact.types.ts +0 -35
  237. package/src/components/MemoriArtifactSystem/utils/ArtifactAPI.test.tsx +0 -308
  238. package/src/components/MemoriArtifactSystem/utils/ArtifactAPI.tsx +0 -379
  239. package/src/components/MemoriArtifactSystem/utils/applyEdits.test.ts +0 -85
  240. package/src/components/MemoriArtifactSystem/utils/applyEdits.ts +0 -173
  241. package/src/components/MemoriWidget/MemoriWidget.css +0 -220
  242. package/src/components/MemoriWidget/MemoriWidget.stories.tsx +0 -274
  243. package/src/components/MemoriWidget/MemoriWidget.tsx +0 -3930
  244. package/src/components/MicrophoneButton/MicrophoneButton.css +0 -142
  245. package/src/components/MicrophoneButton/MicrophoneButton.stories.tsx +0 -52
  246. package/src/components/MicrophoneButton/MicrophoneButton.tsx +0 -128
  247. package/src/components/PositionDrawer/PositionDrawer.stories.tsx +0 -51
  248. package/src/components/PositionDrawer/PositionDrawer.test.tsx +0 -39
  249. package/src/components/PositionDrawer/PositionDrawer.tsx +0 -50
  250. package/src/components/PositionDrawer/__snapshots__/PositionDrawer.test.tsx.snap +0 -17
  251. package/src/components/PoweredBy/PoweredBy.css +0 -47
  252. package/src/components/PoweredBy/PoweredBy.stories.tsx +0 -75
  253. package/src/components/PoweredBy/PoweredBy.test.tsx +0 -15
  254. package/src/components/PoweredBy/PoweredBy.tsx +0 -44
  255. package/src/components/PoweredBy/__snapshots__/PoweredBy.test.tsx.snap +0 -55
  256. package/src/components/SendOnEnterMenu/SendOnEnterMenu.css +0 -47
  257. package/src/components/SendOnEnterMenu/SendOnEnterMenu.stories.tsx +0 -58
  258. package/src/components/SendOnEnterMenu/SendOnEnterMenu.test.tsx +0 -17
  259. package/src/components/SendOnEnterMenu/SendOnEnterMenu.tsx +0 -82
  260. package/src/components/SendOnEnterMenu/__snapshots__/SendOnEnterMenu.test.tsx.snap +0 -57
  261. package/src/components/SettingsDrawer/SettingsDrawer.css +0 -24
  262. package/src/components/SettingsDrawer/SettingsDrawer.stories.tsx +0 -94
  263. package/src/components/SettingsDrawer/SettingsDrawer.test.tsx +0 -202
  264. package/src/components/SettingsDrawer/SettingsDrawer.tsx +0 -255
  265. package/src/components/SettingsDrawer/__snapshots__/SettingsDrawer.test.tsx.snap +0 -59
  266. package/src/components/ShareButton/ShareButton.css +0 -73
  267. package/src/components/ShareButton/ShareButton.stories.tsx +0 -98
  268. package/src/components/ShareButton/ShareButton.test.tsx +0 -53
  269. package/src/components/ShareButton/ShareButton.tsx +0 -395
  270. package/src/components/ShareButton/__snapshots__/ShareButton.test.tsx.snap +0 -316
  271. package/src/components/Snippet/Snippet.css +0 -142
  272. package/src/components/Snippet/Snippet.stories.tsx +0 -286
  273. package/src/components/Snippet/Snippet.test.tsx +0 -176
  274. package/src/components/Snippet/Snippet.tsx +0 -148
  275. package/src/components/Snippet/__snapshots__/Snippet.test.tsx.snap +0 -603
  276. package/src/components/StartPanel/StartPanel.css +0 -273
  277. package/src/components/StartPanel/StartPanel.stories.tsx +0 -404
  278. package/src/components/StartPanel/StartPanel.test.tsx +0 -360
  279. package/src/components/StartPanel/StartPanel.tsx +0 -426
  280. package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +0 -1986
  281. package/src/components/Typing/Typing.stories.tsx +0 -130
  282. package/src/components/Typing/Typing.test.tsx +0 -62
  283. package/src/components/Typing/Typing.tsx +0 -154
  284. package/src/components/Typing/__snapshots__/Typing.test.tsx.snap +0 -133
  285. package/src/components/UploadButton/UploadButton.css +0 -542
  286. package/src/components/UploadButton/UploadButton.stories.tsx +0 -376
  287. package/src/components/UploadButton/UploadButton.test.tsx +0 -11
  288. package/src/components/UploadButton/UploadButton.tsx +0 -684
  289. package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +0 -405
  290. package/src/components/UploadButton/UploadImages/UploadImages.tsx +0 -538
  291. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +0 -165
  292. package/src/components/VenueWidget/VenueWidget.css +0 -163
  293. package/src/components/VenueWidget/VenueWidget.stories.tsx +0 -66
  294. package/src/components/VenueWidget/VenueWidget.test.tsx +0 -49
  295. package/src/components/VenueWidget/VenueWidget.tsx +0 -458
  296. package/src/components/VenueWidget/__snapshots__/VenueWidget.test.tsx.snap +0 -370
  297. package/src/components/WhyThisAnswer/WhyThisAnswer.css +0 -171
  298. package/src/components/WhyThisAnswer/WhyThisAnswer.stories.tsx +0 -285
  299. package/src/components/WhyThisAnswer/WhyThisAnswer.test.tsx +0 -245
  300. package/src/components/WhyThisAnswer/WhyThisAnswer.tsx +0 -233
  301. package/src/components/WhyThisAnswer/__snapshots__/WhyThisAnswer.test.tsx.snap +0 -27
  302. package/src/components/icons/AI.tsx +0 -34
  303. package/src/components/icons/Alert.tsx +0 -31
  304. package/src/components/icons/ArrowUp.tsx +0 -28
  305. package/src/components/icons/Bug.tsx +0 -81
  306. package/src/components/icons/Chat.tsx +0 -30
  307. package/src/components/icons/ChevronDown.tsx +0 -26
  308. package/src/components/icons/ChevronLeft.tsx +0 -28
  309. package/src/components/icons/ChevronRight.tsx +0 -28
  310. package/src/components/icons/ChevronUp.tsx +0 -24
  311. package/src/components/icons/Clear.tsx +0 -23
  312. package/src/components/icons/Close.tsx +0 -30
  313. package/src/components/icons/Code.tsx +0 -24
  314. package/src/components/icons/Copy.tsx +0 -23
  315. package/src/components/icons/DeepThought.tsx +0 -38
  316. package/src/components/icons/Delete.tsx +0 -23
  317. package/src/components/icons/Document.tsx +0 -50
  318. package/src/components/icons/Download.tsx +0 -23
  319. package/src/components/icons/Edit.tsx +0 -17
  320. package/src/components/icons/Expand.tsx +0 -23
  321. package/src/components/icons/Eye.tsx +0 -17
  322. package/src/components/icons/EyeInvisible.tsx +0 -24
  323. package/src/components/icons/Facebook.tsx +0 -23
  324. package/src/components/icons/Feedback.tsx +0 -24
  325. package/src/components/icons/File.tsx +0 -17
  326. package/src/components/icons/FileExcel.tsx +0 -23
  327. package/src/components/icons/FilePdf.tsx +0 -23
  328. package/src/components/icons/FileWord.tsx +0 -23
  329. package/src/components/icons/Fullscreen.tsx +0 -23
  330. package/src/components/icons/FullscreenExit.tsx +0 -23
  331. package/src/components/icons/GasStation.tsx +0 -36
  332. package/src/components/icons/Group.tsx +0 -30
  333. package/src/components/icons/History.tsx +0 -33
  334. package/src/components/icons/Image.tsx +0 -37
  335. package/src/components/icons/Info.tsx +0 -31
  336. package/src/components/icons/Link.tsx +0 -17
  337. package/src/components/icons/Linkedin.tsx +0 -23
  338. package/src/components/icons/Loading.tsx +0 -28
  339. package/src/components/icons/Logout.tsx +0 -27
  340. package/src/components/icons/Mail.tsx +0 -17
  341. package/src/components/icons/MapMarker.tsx +0 -26
  342. package/src/components/icons/MenuHorizontal.tsx +0 -29
  343. package/src/components/icons/MenuVertical.tsx +0 -29
  344. package/src/components/icons/Message.tsx +0 -23
  345. package/src/components/icons/Microphone.tsx +0 -23
  346. package/src/components/icons/Minus.tsx +0 -23
  347. package/src/components/icons/MinusCircle.tsx +0 -24
  348. package/src/components/icons/PaperClip.tsx +0 -23
  349. package/src/components/icons/Picture.tsx +0 -23
  350. package/src/components/icons/Plus.tsx +0 -18
  351. package/src/components/icons/Preview.tsx +0 -29
  352. package/src/components/icons/Print.tsx +0 -38
  353. package/src/components/icons/QuestionHelp.tsx +0 -30
  354. package/src/components/icons/Refresh.tsx +0 -30
  355. package/src/components/icons/SelectIcon.tsx +0 -28
  356. package/src/components/icons/Send.tsx +0 -17
  357. package/src/components/icons/Setting.tsx +0 -23
  358. package/src/components/icons/Share.tsx +0 -23
  359. package/src/components/icons/Sound.tsx +0 -23
  360. package/src/components/icons/SoundDeactivated.tsx +0 -27
  361. package/src/components/icons/Telegram.tsx +0 -26
  362. package/src/components/icons/ThumbDown.tsx +0 -29
  363. package/src/components/icons/ThumbUp.tsx +0 -29
  364. package/src/components/icons/Translation.tsx +0 -24
  365. package/src/components/icons/Twitter.tsx +0 -23
  366. package/src/components/icons/Upload.tsx +0 -34
  367. package/src/components/icons/User.tsx +0 -17
  368. package/src/components/icons/Warning.tsx +0 -24
  369. package/src/components/icons/WhatsApp.tsx +0 -24
  370. package/src/components/icons/icons.stories.css +0 -5
  371. package/src/components/icons/icons.stories.tsx +0 -81
  372. package/src/components/icons/loading.css +0 -14
  373. package/src/components/layouts/Chat.test.tsx +0 -40
  374. package/src/components/layouts/Chat.tsx +0 -69
  375. package/src/components/layouts/FullBody/FullBody.stories.tsx +0 -110
  376. package/src/components/layouts/FullPage.test.tsx +0 -65
  377. package/src/components/layouts/FullPage.tsx +0 -80
  378. package/src/components/layouts/HiddenChat.test.tsx +0 -40
  379. package/src/components/layouts/HiddenChat.tsx +0 -233
  380. package/src/components/layouts/Totem/Totem.stories.tsx +0 -131
  381. package/src/components/layouts/Totem.test.tsx +0 -40
  382. package/src/components/layouts/Totem.tsx +0 -49
  383. package/src/components/layouts/WebsiteAssistant.test.tsx +0 -40
  384. package/src/components/layouts/WebsiteAssistant.tsx +0 -132
  385. package/src/components/layouts/ZoomedFullBody/ZoomedFullBody.stories.tsx +0 -131
  386. package/src/components/layouts/ZoomedFullBody.test.tsx +0 -40
  387. package/src/components/layouts/ZoomedFullBody.tsx +0 -81
  388. package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +0 -457
  389. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +0 -1045
  390. package/src/components/layouts/__snapshots__/HiddenChat.test.tsx.snap +0 -460
  391. package/src/components/layouts/__snapshots__/Totem.test.tsx.snap +0 -566
  392. package/src/components/layouts/__snapshots__/WebsiteAssistant.test.tsx.snap +0 -59
  393. package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +0 -523
  394. package/src/components/layouts/chat.css +0 -451
  395. package/src/components/layouts/hidden-chat.css +0 -276
  396. package/src/components/layouts/layouts.stories.tsx +0 -231
  397. package/src/components/layouts/totem.css +0 -221
  398. package/src/components/layouts/website-assistant.css +0 -677
  399. package/src/components/layouts/zoomed-full-body.css +0 -18
  400. package/src/components/ui/Alert.css +0 -93
  401. package/src/components/ui/Alert.stories.tsx +0 -137
  402. package/src/components/ui/Alert.test.tsx +0 -129
  403. package/src/components/ui/Alert.tsx +0 -123
  404. package/src/components/ui/Button.css +0 -167
  405. package/src/components/ui/Button.stories.tsx +0 -156
  406. package/src/components/ui/Button.test.tsx +0 -74
  407. package/src/components/ui/Button.tsx +0 -107
  408. package/src/components/ui/Card.css +0 -65
  409. package/src/components/ui/Card.stories.tsx +0 -95
  410. package/src/components/ui/Card.test.tsx +0 -47
  411. package/src/components/ui/Card.tsx +0 -48
  412. package/src/components/ui/Checkbox.css +0 -116
  413. package/src/components/ui/Checkbox.stories.tsx +0 -74
  414. package/src/components/ui/Checkbox.test.tsx +0 -28
  415. package/src/components/ui/Checkbox.tsx +0 -58
  416. package/src/components/ui/ConfirmDialog.css +0 -42
  417. package/src/components/ui/ConfirmDialog.stories.tsx +0 -216
  418. package/src/components/ui/ConfirmDialog.test.tsx +0 -124
  419. package/src/components/ui/ConfirmDialog.tsx +0 -58
  420. package/src/components/ui/Details.css +0 -66
  421. package/src/components/ui/Drawer.css +0 -270
  422. package/src/components/ui/Drawer.stories.tsx +0 -267
  423. package/src/components/ui/Drawer.test.tsx +0 -130
  424. package/src/components/ui/Drawer.tsx +0 -293
  425. package/src/components/ui/Dropdown.css +0 -173
  426. package/src/components/ui/Dropdown.tsx +0 -63
  427. package/src/components/ui/Expandable.css +0 -15
  428. package/src/components/ui/Expandable.stories.tsx +0 -180
  429. package/src/components/ui/Expandable.test.tsx +0 -107
  430. package/src/components/ui/Expandable.tsx +0 -121
  431. package/src/components/ui/Modal.css +0 -98
  432. package/src/components/ui/Modal.stories.tsx +0 -171
  433. package/src/components/ui/Modal.test.tsx +0 -111
  434. package/src/components/ui/Modal.tsx +0 -117
  435. package/src/components/ui/Select.css +0 -138
  436. package/src/components/ui/Select.stories.tsx +0 -79
  437. package/src/components/ui/Select.test.tsx +0 -84
  438. package/src/components/ui/Select.tsx +0 -79
  439. package/src/components/ui/Slider.css +0 -192
  440. package/src/components/ui/Slider.stories.tsx +0 -63
  441. package/src/components/ui/Slider.tsx +0 -142
  442. package/src/components/ui/Spin.css +0 -31
  443. package/src/components/ui/Spin.stories.tsx +0 -64
  444. package/src/components/ui/Spin.test.tsx +0 -41
  445. package/src/components/ui/Spin.tsx +0 -31
  446. package/src/components/ui/Table.css +0 -132
  447. package/src/components/ui/Tooltip.css +0 -171
  448. package/src/components/ui/Tooltip.stories.tsx +0 -109
  449. package/src/components/ui/Tooltip.test.tsx +0 -102
  450. package/src/components/ui/Tooltip.tsx +0 -163
  451. package/src/components/ui/__snapshots__/Alert.test.tsx.snap +0 -59
  452. package/src/components/ui/__snapshots__/Button.test.tsx.snap +0 -204
  453. package/src/components/ui/__snapshots__/Card.test.tsx.snap +0 -321
  454. package/src/components/ui/__snapshots__/Checkbox.test.tsx.snap +0 -131
  455. package/src/components/ui/__snapshots__/ConfirmDialog.test.tsx.snap +0 -35
  456. package/src/components/ui/__snapshots__/Drawer.test.tsx.snap +0 -67
  457. package/src/components/ui/__snapshots__/Expandable.test.tsx.snap +0 -159
  458. package/src/components/ui/__snapshots__/Modal.test.tsx.snap +0 -59
  459. package/src/components/ui/__snapshots__/Select.test.tsx.snap +0 -254
  460. package/src/components/ui/__snapshots__/Spin.test.tsx.snap +0 -193
  461. package/src/components/ui/__snapshots__/Tooltip.test.tsx.snap +0 -113
  462. package/src/components/ui/definitions.stories.tsx +0 -139
  463. package/src/context/visemeContext.tsx +0 -384
  464. package/src/helpers/chatPdfExport.ts +0 -544
  465. package/src/helpers/configuration.ts +0 -42
  466. package/src/helpers/constants.ts +0 -267
  467. package/src/helpers/convertDocument.test.ts +0 -67
  468. package/src/helpers/convertDocument.ts +0 -35
  469. package/src/helpers/credits.ts +0 -54
  470. package/src/helpers/error.test.ts +0 -29
  471. package/src/helpers/error.ts +0 -278
  472. package/src/helpers/imageCompression.ts +0 -230
  473. package/src/helpers/llmUsage.ts +0 -328
  474. package/src/helpers/markedExtendedTables.js +0 -217
  475. package/src/helpers/media.test.ts +0 -169
  476. package/src/helpers/media.ts +0 -81
  477. package/src/helpers/message.test.ts +0 -126
  478. package/src/helpers/message.ts +0 -219
  479. package/src/helpers/nats/getNatsConfig.ts +0 -148
  480. package/src/helpers/nats/isSessionExpiredError.test.ts +0 -38
  481. package/src/helpers/nats/isSessionExpiredError.ts +0 -51
  482. package/src/helpers/nats/useNats.test.tsx +0 -81
  483. package/src/helpers/nats/useNats.ts +0 -204
  484. package/src/helpers/nats/useNatsSession.test.ts +0 -16
  485. package/src/helpers/nats/useNatsSession.ts +0 -460
  486. package/src/helpers/piiDetection.test.ts +0 -186
  487. package/src/helpers/piiDetection.ts +0 -91
  488. package/src/helpers/sanitizer.ts +0 -17
  489. package/src/helpers/statistics.ts +0 -58
  490. package/src/helpers/stt/useSTT.ts +0 -515
  491. package/src/helpers/translations.ts +0 -39
  492. package/src/helpers/tts/ttsVoiceUtility.ts +0 -275
  493. package/src/helpers/tts/useTTS.ts +0 -621
  494. package/src/helpers/utils.test.ts +0 -321
  495. package/src/helpers/utils.ts +0 -586
  496. package/src/helpers/venue.ts +0 -36
  497. package/src/i18n.ts +0 -39
  498. package/src/index.stories.tsx +0 -220
  499. package/src/index.test.tsx +0 -156
  500. package/src/index.tsx +0 -592
  501. package/src/locales/de.json +0 -680
  502. package/src/locales/en.json +0 -754
  503. package/src/locales/es.json +0 -682
  504. package/src/locales/fr.json +0 -682
  505. package/src/locales/it.json +0 -750
  506. package/src/mocks/data.ts +0 -790
  507. package/src/styles.css +0 -126
  508. package/src/types/integration.test.ts +0 -32
  509. package/src/types/integration.ts +0 -31
  510. package/src/types/layout.ts +0 -50
  511. 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;