@memori.ai/memori-react 9.0.0-alpha.5 → 9.0.0-alpha.7

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 (286) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/components/AgeVerificationModal/AgeVerificationModal.js +8 -3
  3. package/dist/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  4. package/dist/components/Auth/Auth.js +3 -1
  5. package/dist/components/Auth/Auth.js.map +1 -1
  6. package/dist/components/Avatar/Avatar.js +5 -10
  7. package/dist/components/Avatar/Avatar.js.map +1 -1
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +8 -1
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +1 -1
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +2 -0
  13. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js +14 -5
  14. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  15. package/dist/components/Chat/Chat.js +3 -1
  16. package/dist/components/Chat/Chat.js.map +1 -1
  17. package/dist/components/ChatBubble/ChatBubble.js +21 -15
  18. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  19. package/dist/components/ChatHistoryDrawer/ChatHistory.js +3 -1
  20. package/dist/components/ChatHistoryDrawer/ChatHistory.js.map +1 -1
  21. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js +6 -3
  22. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  23. package/dist/components/ContentPreviewModal/ContentPreviewModal.js +3 -1
  24. package/dist/components/ContentPreviewModal/ContentPreviewModal.js.map +1 -1
  25. package/dist/components/DateSelector/DateSelector.d.ts +2 -1
  26. package/dist/components/DateSelector/DateSelector.js +17 -7
  27. package/dist/components/DateSelector/DateSelector.js.map +1 -1
  28. package/dist/components/ExpertsDrawer/ExpertsDrawer.js +3 -1
  29. package/dist/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
  30. package/dist/components/FilePreview/FilePreview.js +22 -5
  31. package/dist/components/FilePreview/FilePreview.js.map +1 -1
  32. package/dist/components/KnownFacts/KnownFacts.js +5 -3
  33. package/dist/components/KnownFacts/KnownFacts.js.map +1 -1
  34. package/dist/components/LoginDrawer/LoginDrawer.js +3 -1
  35. package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -1
  36. package/dist/components/MediaWidget/MediaItemWidget.js +14 -12
  37. package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
  38. package/dist/components/MediaWidget/MediaItemWidget.utils.d.ts +3 -2
  39. package/dist/components/MediaWidget/MediaItemWidget.utils.js +28 -6
  40. package/dist/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  41. package/dist/components/MediaWidget/MediaPreviewModal.js +6 -7
  42. package/dist/components/MediaWidget/MediaPreviewModal.js.map +1 -1
  43. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +19 -5
  44. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +4 -1
  45. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  46. package/dist/components/MemoriWidget/MemoriWidget.css +4 -2
  47. package/dist/components/MemoriWidget/MemoriWidget.js +760 -404
  48. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  49. package/dist/components/SettingsDrawer/SettingsDrawer.js +3 -1
  50. package/dist/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  51. package/dist/components/StartPanel/StartPanel.js +3 -1
  52. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  53. package/dist/components/UploadButton/UploadButton.js +21 -5
  54. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  55. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +126 -60
  56. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  57. package/dist/components/UploadButton/UploadImages/UploadImages.js +3 -1
  58. package/dist/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
  59. package/dist/components/WhyThisAnswer/WhyThisAnswer.js +3 -1
  60. package/dist/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
  61. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js +3 -1
  62. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
  63. package/dist/components/layouts/WebsiteAssistant/website-assistant.css +21 -1
  64. package/dist/components/layouts/chat.css +2 -1
  65. package/dist/components/layouts/fullpage.css +31 -8
  66. package/dist/components/layouts/hidden-chat.css +1 -0
  67. package/dist/components/layouts/totem.css +2 -1
  68. package/dist/components/layouts/zoomed-full-body.css +32 -7
  69. package/dist/context/widgetSurfaceContext.d.ts +3 -0
  70. package/dist/context/widgetSurfaceContext.js +11 -0
  71. package/dist/context/widgetSurfaceContext.js.map +1 -0
  72. package/dist/helpers/constants.d.ts +3 -0
  73. package/dist/helpers/constants.js +27 -1
  74. package/dist/helpers/constants.js.map +1 -1
  75. package/dist/helpers/credits.d.ts +5 -2
  76. package/dist/helpers/credits.js +5 -1
  77. package/dist/helpers/credits.js.map +1 -1
  78. package/dist/helpers/message.d.ts +1 -1
  79. package/dist/helpers/message.js +4 -4
  80. package/dist/helpers/message.js.map +1 -1
  81. package/dist/helpers/nats/getNatsConfig.d.ts +9 -0
  82. package/dist/helpers/nats/getNatsConfig.js +65 -0
  83. package/dist/helpers/nats/getNatsConfig.js.map +1 -0
  84. package/dist/helpers/nats/isSessionExpiredError.d.ts +3 -0
  85. package/dist/helpers/nats/isSessionExpiredError.js +36 -0
  86. package/dist/helpers/nats/isSessionExpiredError.js.map +1 -0
  87. package/dist/helpers/nats/useNats.d.ts +12 -0
  88. package/dist/helpers/nats/useNats.js +75 -0
  89. package/dist/helpers/nats/useNats.js.map +1 -0
  90. package/dist/helpers/nats/useNatsSession.d.ts +28 -0
  91. package/dist/helpers/nats/useNatsSession.js +212 -0
  92. package/dist/helpers/nats/useNatsSession.js.map +1 -0
  93. package/dist/helpers/utils.d.ts +21 -0
  94. package/dist/helpers/utils.js +75 -4
  95. package/dist/helpers/utils.js.map +1 -1
  96. package/dist/locales/de.json +1 -0
  97. package/dist/locales/en.json +1 -0
  98. package/dist/locales/es.json +1 -0
  99. package/dist/locales/fr.json +1 -0
  100. package/dist/locales/it.json +1 -0
  101. package/dist/styles.css +1 -1
  102. package/dist/types/integration.d.ts +3 -0
  103. package/dist/types/integration.js +16 -0
  104. package/dist/types/integration.js.map +1 -0
  105. package/dist/version.d.ts +1 -1
  106. package/dist/version.js +1 -1
  107. package/esm/components/AgeVerificationModal/AgeVerificationModal.js +8 -3
  108. package/esm/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  109. package/esm/components/Auth/Auth.js +3 -1
  110. package/esm/components/Auth/Auth.js.map +1 -1
  111. package/esm/components/Avatar/Avatar.js +5 -10
  112. package/esm/components/Avatar/Avatar.js.map +1 -1
  113. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +9 -2
  114. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  115. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +2 -2
  116. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  117. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +2 -0
  118. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js +13 -4
  119. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  120. package/esm/components/Chat/Chat.js +3 -1
  121. package/esm/components/Chat/Chat.js.map +1 -1
  122. package/esm/components/ChatBubble/ChatBubble.js +21 -15
  123. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  124. package/esm/components/ChatHistoryDrawer/ChatHistory.js +3 -1
  125. package/esm/components/ChatHistoryDrawer/ChatHistory.js.map +1 -1
  126. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js +6 -3
  127. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  128. package/esm/components/ContentPreviewModal/ContentPreviewModal.js +3 -1
  129. package/esm/components/ContentPreviewModal/ContentPreviewModal.js.map +1 -1
  130. package/esm/components/DateSelector/DateSelector.d.ts +2 -1
  131. package/esm/components/DateSelector/DateSelector.js +17 -7
  132. package/esm/components/DateSelector/DateSelector.js.map +1 -1
  133. package/esm/components/ExpertsDrawer/ExpertsDrawer.js +3 -1
  134. package/esm/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
  135. package/esm/components/FilePreview/FilePreview.js +24 -7
  136. package/esm/components/FilePreview/FilePreview.js.map +1 -1
  137. package/esm/components/KnownFacts/KnownFacts.js +5 -3
  138. package/esm/components/KnownFacts/KnownFacts.js.map +1 -1
  139. package/esm/components/LoginDrawer/LoginDrawer.js +3 -1
  140. package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -1
  141. package/esm/components/MediaWidget/MediaItemWidget.js +16 -14
  142. package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
  143. package/esm/components/MediaWidget/MediaItemWidget.utils.d.ts +3 -2
  144. package/esm/components/MediaWidget/MediaItemWidget.utils.js +27 -6
  145. package/esm/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  146. package/esm/components/MediaWidget/MediaPreviewModal.js +6 -7
  147. package/esm/components/MediaWidget/MediaPreviewModal.js.map +1 -1
  148. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +19 -5
  149. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +4 -1
  150. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  151. package/esm/components/MemoriWidget/MemoriWidget.css +4 -2
  152. package/esm/components/MemoriWidget/MemoriWidget.js +760 -404
  153. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  154. package/esm/components/SettingsDrawer/SettingsDrawer.js +3 -1
  155. package/esm/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  156. package/esm/components/StartPanel/StartPanel.js +3 -1
  157. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  158. package/esm/components/UploadButton/UploadButton.js +21 -5
  159. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  160. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +126 -60
  161. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  162. package/esm/components/UploadButton/UploadImages/UploadImages.js +3 -1
  163. package/esm/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
  164. package/esm/components/WhyThisAnswer/WhyThisAnswer.js +3 -1
  165. package/esm/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
  166. package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js +3 -1
  167. package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
  168. package/esm/components/layouts/WebsiteAssistant/website-assistant.css +21 -1
  169. package/esm/components/layouts/chat.css +2 -1
  170. package/esm/components/layouts/fullpage.css +31 -8
  171. package/esm/components/layouts/hidden-chat.css +1 -0
  172. package/esm/components/layouts/totem.css +2 -1
  173. package/esm/components/layouts/zoomed-full-body.css +32 -7
  174. package/esm/context/widgetSurfaceContext.d.ts +3 -0
  175. package/esm/context/widgetSurfaceContext.js +7 -0
  176. package/esm/context/widgetSurfaceContext.js.map +1 -0
  177. package/esm/helpers/constants.d.ts +3 -0
  178. package/esm/helpers/constants.js +26 -0
  179. package/esm/helpers/constants.js.map +1 -1
  180. package/esm/helpers/credits.d.ts +5 -2
  181. package/esm/helpers/credits.js +5 -1
  182. package/esm/helpers/credits.js.map +1 -1
  183. package/esm/helpers/message.d.ts +1 -1
  184. package/esm/helpers/message.js +2 -2
  185. package/esm/helpers/message.js.map +1 -1
  186. package/esm/helpers/nats/getNatsConfig.d.ts +9 -0
  187. package/esm/helpers/nats/getNatsConfig.js +61 -0
  188. package/esm/helpers/nats/getNatsConfig.js.map +1 -0
  189. package/esm/helpers/nats/isSessionExpiredError.d.ts +3 -0
  190. package/esm/helpers/nats/isSessionExpiredError.js +32 -0
  191. package/esm/helpers/nats/isSessionExpiredError.js.map +1 -0
  192. package/esm/helpers/nats/useNats.d.ts +12 -0
  193. package/esm/helpers/nats/useNats.js +72 -0
  194. package/esm/helpers/nats/useNats.js.map +1 -0
  195. package/esm/helpers/nats/useNatsSession.d.ts +28 -0
  196. package/esm/helpers/nats/useNatsSession.js +208 -0
  197. package/esm/helpers/nats/useNatsSession.js.map +1 -0
  198. package/esm/helpers/utils.d.ts +21 -0
  199. package/esm/helpers/utils.js +68 -3
  200. package/esm/helpers/utils.js.map +1 -1
  201. package/esm/locales/de.json +1 -0
  202. package/esm/locales/en.json +1 -0
  203. package/esm/locales/es.json +1 -0
  204. package/esm/locales/fr.json +1 -0
  205. package/esm/locales/it.json +1 -0
  206. package/esm/styles.css +1 -1
  207. package/esm/types/integration.d.ts +3 -0
  208. package/esm/types/integration.js +11 -0
  209. package/esm/types/integration.js.map +1 -0
  210. package/esm/version.d.ts +1 -1
  211. package/esm/version.js +1 -1
  212. package/package.json +5 -3
  213. package/src/components/AgeVerificationModal/AgeVerificationModal.tsx +9 -1
  214. package/src/components/Auth/Auth.tsx +3 -0
  215. package/src/components/Avatar/Avatar.stories.tsx +45 -28
  216. package/src/components/Avatar/Avatar.test.tsx +54 -0
  217. package/src/components/Avatar/Avatar.tsx +8 -12
  218. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +12 -5
  219. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +2 -1
  220. package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +16 -4
  221. package/src/components/Avatar/__snapshots__/Avatar.test.tsx.snap +164 -0
  222. package/src/components/Chat/Chat.tsx +3 -0
  223. package/src/components/ChatBubble/ChatBubble.tsx +40 -12
  224. package/src/components/ChatHistoryDrawer/ChatHistory.tsx +3 -0
  225. package/src/components/CompletionProviderStatus/CompletionProviderStatus.tsx +8 -4
  226. package/src/components/ContentPreviewModal/ContentPreviewModal.tsx +3 -0
  227. package/src/components/DateSelector/DateSelector.stories.tsx +11 -2
  228. package/src/components/DateSelector/DateSelector.tsx +28 -9
  229. package/src/components/ExpertsDrawer/ExpertsDrawer.tsx +3 -0
  230. package/src/components/FilePreview/FilePreview.tsx +28 -7
  231. package/src/components/KnownFacts/KnownFacts.tsx +5 -0
  232. package/src/components/LoginDrawer/LoginDrawer.tsx +3 -0
  233. package/src/components/MediaWidget/MediaItemWidget.tsx +26 -13
  234. package/src/components/MediaWidget/MediaItemWidget.utils.test.ts +45 -2
  235. package/src/components/MediaWidget/MediaItemWidget.utils.ts +39 -6
  236. package/src/components/MediaWidget/MediaPreviewModal.tsx +9 -7
  237. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +19 -5
  238. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +4 -0
  239. package/src/components/MemoriWidget/MemoriWidget.css +4 -2
  240. package/src/components/MemoriWidget/MemoriWidget.tsx +818 -349
  241. package/src/components/SettingsDrawer/SettingsDrawer.tsx +3 -0
  242. package/src/components/StartPanel/StartPanel.stories.tsx +22 -0
  243. package/src/components/StartPanel/StartPanel.test.tsx +64 -1
  244. package/src/components/StartPanel/StartPanel.tsx +3 -0
  245. package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +261 -0
  246. package/src/components/UploadButton/UploadButton.tsx +30 -11
  247. package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +151 -61
  248. package/src/components/UploadButton/UploadImages/UploadImages.tsx +3 -0
  249. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +2 -2
  250. package/src/components/WhyThisAnswer/WhyThisAnswer.tsx +3 -0
  251. package/src/components/layouts/WebsiteAssistant/WebsiteAssistant.tsx +4 -0
  252. package/src/components/layouts/WebsiteAssistant/__snapshots__/WebsiteAssistant.test.tsx.snap +48 -44
  253. package/src/components/layouts/WebsiteAssistant/website-assistant.css +21 -1
  254. package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +533 -529
  255. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +1058 -1050
  256. package/src/components/layouts/__snapshots__/HiddenChat.test.tsx.snap +443 -439
  257. package/src/components/layouts/__snapshots__/Totem.test.tsx.snap +584 -580
  258. package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +528 -524
  259. package/src/components/layouts/chat.css +2 -1
  260. package/src/components/layouts/fullpage.css +31 -8
  261. package/src/components/layouts/hidden-chat.css +1 -0
  262. package/src/components/layouts/layouts.stories.tsx +131 -12
  263. package/src/components/layouts/totem.css +2 -1
  264. package/src/components/layouts/zoomed-full-body.css +32 -7
  265. package/src/context/widgetSurfaceContext.tsx +14 -0
  266. package/src/helpers/constants.ts +32 -1
  267. package/src/helpers/credits.ts +16 -1
  268. package/src/helpers/message.test.ts +28 -1
  269. package/src/helpers/message.ts +4 -4
  270. package/src/helpers/nats/getNatsConfig.ts +145 -0
  271. package/src/helpers/nats/isSessionExpiredError.test.ts +38 -0
  272. package/src/helpers/nats/isSessionExpiredError.ts +51 -0
  273. package/src/helpers/nats/useNats.ts +124 -0
  274. package/src/helpers/nats/useNatsSession.ts +394 -0
  275. package/src/helpers/utils.test.ts +162 -0
  276. package/src/helpers/utils.ts +108 -3
  277. package/src/index.stories.tsx +32 -0
  278. package/src/locales/de.json +1 -0
  279. package/src/locales/en.json +1 -0
  280. package/src/locales/es.json +1 -0
  281. package/src/locales/fr.json +1 -0
  282. package/src/locales/it.json +1 -0
  283. package/src/styles.css +61 -1
  284. package/src/types/integration.test.ts +32 -0
  285. package/src/types/integration.ts +31 -0
  286. package/src/version.ts +1 -1
@@ -44,6 +44,7 @@ import {
44
44
  createAlertOptions,
45
45
  MemoriUIProvider,
46
46
  } from '@memori.ai/ui';
47
+ import { WidgetSurfaceProvider } from '../../context/widgetSurfaceContext';
47
48
 
48
49
  // Components
49
50
  import MemoriAuth from '../Auth/Auth';
@@ -92,6 +93,16 @@ import { sanitizeText } from '../../helpers/sanitizer';
92
93
  import { TTSConfig, useTTS } from '../../helpers/tts/useTTS';
93
94
  import ChatHistoryDrawer from '../ChatHistoryDrawer/ChatHistory';
94
95
  import { STTConfig, useSTT } from '../../helpers/stt/useSTT';
96
+ import { useNats } from '../../helpers/nats/useNats';
97
+ import {
98
+ NatsProgressEvent,
99
+ NatsDialogResponseEvent,
100
+ NatsErrorEvent,
101
+ } from '../../helpers/nats/useNatsSession';
102
+ import {
103
+ isSessionExpiredNatsError,
104
+ isSessionExpiredNatsResponse,
105
+ } from '../../helpers/nats/isSessionExpiredError';
95
106
 
96
107
  // Widget utilities and helpers
97
108
  const getMemoriState = (integrationId?: string): object | null => {
@@ -122,7 +133,21 @@ const getMemoriState = (integrationId?: string): object | null => {
122
133
  };
123
134
  };
124
135
 
125
- /** Place spec with all nulls for postTextEnteredEvent when position is not set or user chose "I don't want to provide my position". */
136
+ /** Unified login token resolution: React state > ref > call-site override > props. */
137
+ const resolveLoginTokenValue = (
138
+ loginToken: string | undefined,
139
+ userTokenRef: string | undefined,
140
+ callAdditionalInfoLoginToken: string | undefined,
141
+ additionalInfoLoginToken: string | undefined,
142
+ authTokenProp: string | undefined
143
+ ): string | undefined =>
144
+ loginToken ??
145
+ userTokenRef ??
146
+ callAdditionalInfoLoginToken ??
147
+ additionalInfoLoginToken ??
148
+ authTokenProp;
149
+
150
+ /** Place spec with all nulls for postEnterTextAsync when position is not set or user chose "I don't want to provide my position". */
126
151
  const NULL_PLACE_SPEC = {
127
152
  placeName: null,
128
153
  latitude: null,
@@ -130,6 +155,18 @@ const NULL_PLACE_SPEC = {
130
155
  uncertaintyKm: null,
131
156
  } as const;
132
157
 
158
+ const logWidgetError = (context: string, detail?: unknown) => {
159
+ console.error(`[MemoriWidget] ${context}`, detail ?? '');
160
+ };
161
+
162
+ /** Reads correlation id from HTTP async response (supports camelCase / snake_case). */
163
+ function readCorrelationID(response: {
164
+ correlationID?: string;
165
+ }): string | undefined {
166
+ const value = response.correlationID;
167
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
168
+ }
169
+
133
170
  type MemoriTextEnteredEvent = CustomEvent<{
134
171
  text: string;
135
172
  waitForPrevious?: boolean;
@@ -363,7 +400,6 @@ window.typeBatchMessages = typeBatchMessages;
363
400
  let audioContext: IAudioContext;
364
401
 
365
402
  let memoriPassword: string | undefined;
366
- let userToken: string | undefined;
367
403
 
368
404
  export interface LayoutProps {
369
405
  Header?: typeof Header;
@@ -580,6 +616,7 @@ const MemoriWidget = ({
580
616
  const {
581
617
  initSession,
582
618
  deleteSession,
619
+ postEnterTextAsync,
583
620
  postTextEnteredEvent,
584
621
  postPlaceChangedEvent,
585
622
  postDateChangedEvent,
@@ -597,6 +634,11 @@ const MemoriWidget = ({
597
634
  // UI primitives via `MemoriUIProvider`). `useRef` would not trigger a
598
635
  // re-render and would leave the value `null` on the first pass.
599
636
  const [widgetRootEl, setWidgetRootEl] = useState<HTMLDivElement | null>(null);
637
+ // Inner surface establishes `contain: layout` for Drawers/Modals. Kept separate
638
+ // from widgetRootEl so Floating UI menus can portal outside containment.
639
+ const [widgetSurfaceEl, setWidgetSurfaceEl] = useState<HTMLDivElement | null>(
640
+ null
641
+ );
600
642
  const [isDarkTheme, setIsDarkTheme] = useState(false);
601
643
  useEffect(() => {
602
644
  if (!widgetRootEl) return;
@@ -617,36 +659,85 @@ const MemoriWidget = ({
617
659
  const [loginToken, setLoginToken] = useState<string | undefined>(
618
660
  additionalInfo?.loginToken ?? authToken
619
661
  );
662
+ const userTokenRef = useRef<string | undefined>(
663
+ additionalInfo?.loginToken ?? authToken
664
+ );
665
+ const prevPropsLoginTokenRef = useRef<string | undefined>(
666
+ additionalInfo?.loginToken ?? authToken
667
+ );
668
+
620
669
  const [user, setUser] = useState<User | undefined>({
621
670
  avatarURL: typeof userAvatar === 'string' ? userAvatar : undefined,
622
671
  } as User);
672
+
673
+ const resolveLoginToken = useCallback(
674
+ (callAdditionalInfoLoginToken?: string) =>
675
+ resolveLoginTokenValue(
676
+ loginToken,
677
+ userTokenRef.current,
678
+ callAdditionalInfoLoginToken,
679
+ additionalInfo?.loginToken,
680
+ authToken
681
+ ),
682
+ [loginToken, additionalInfo?.loginToken, authToken]
683
+ );
684
+
685
+ // Sync loginToken when props change (including explicit removal from parent)
623
686
  useEffect(() => {
624
- if (
625
- loginToken &&
626
- !user?.userID &&
627
- (showLogin || memori.requireLoginToken)
628
- ) {
629
- client.backend
630
- .pwlGetCurrentUser(loginToken)
631
- .then(({ user, resultCode }) => {
632
- if (user && resultCode === 0) {
633
- setUser(user);
634
- setLocalConfig('loginToken', loginToken);
635
-
636
- if (!birthDate && user.birthDate) {
637
- setBirthDate(user.birthDate);
638
- setLocalConfig('birthDate', user.birthDate);
639
- }
640
- } else {
641
- removeLocalConfig('loginToken');
642
- }
643
- });
687
+ const incomingToken = additionalInfo?.loginToken ?? authToken;
688
+ const prevPropsToken = prevPropsLoginTokenRef.current;
689
+ prevPropsLoginTokenRef.current = incomingToken;
690
+
691
+ if (incomingToken !== prevPropsToken) {
692
+ setLoginToken(incomingToken);
693
+ userTokenRef.current = incomingToken;
694
+ if (!incomingToken) {
695
+ removeLocalConfig('loginToken');
696
+ setUser(undefined);
697
+ }
698
+ }
699
+ }, [additionalInfo?.loginToken, authToken]);
700
+
701
+ useEffect(() => {
702
+ if (!loginToken || !(showLogin || memori.requireLoginToken)) {
703
+ return;
644
704
  }
645
- }, [loginToken, user?.userID]);
705
+
706
+ let cancelled = false;
707
+
708
+ client.backend
709
+ .pwlGetCurrentUser(loginToken)
710
+ .then(({ user: fetchedUser, resultCode }) => {
711
+ if (cancelled) return;
712
+
713
+ if (fetchedUser && resultCode === 0) {
714
+ setUser(fetchedUser);
715
+ setLocalConfig('loginToken', loginToken);
716
+ userTokenRef.current = loginToken;
717
+
718
+ if (!birthDate && fetchedUser.birthDate) {
719
+ setBirthDate(fetchedUser.birthDate);
720
+ setLocalConfig('birthDate', fetchedUser.birthDate);
721
+ }
722
+ } else {
723
+ setLoginToken(undefined);
724
+ userTokenRef.current = undefined;
725
+ setUser(undefined);
726
+ removeLocalConfig('loginToken');
727
+ }
728
+ });
729
+
730
+ return () => {
731
+ cancelled = true;
732
+ };
733
+ }, [loginToken, showLogin, memori.requireLoginToken]);
646
734
  const [showLoginDrawer, setShowLoginDrawer] = useState(false);
647
735
 
648
736
  const [clickedStart, setClickedStart] = useState(false);
649
737
  const [gotErrorInOpening, setGotErrorInOpening] = useState(false);
738
+ const sessionStartingRef = useRef(false);
739
+ const needsCredits = tenant?.billingDelegation;
740
+ const [hasEnoughCredits, setHasEnoughCredits] = useState<boolean>(true);
650
741
 
651
742
  const language =
652
743
  memori.culture?.split('-')?.[0]?.toUpperCase()! ||
@@ -708,7 +799,33 @@ const MemoriWidget = ({
708
799
  const [memoriTyping, setMemoriTyping] = useState<boolean>(false);
709
800
  const [typingText, setTypingText] = useState<string>();
710
801
 
711
- // Layout: prop can be string or object { name, piiDetection } (e.g. from integration customData when PII is enabled).
802
+ type EnterTextRetryParams = {
803
+ text?: string;
804
+ media?: Medium[];
805
+ translate?: boolean;
806
+ translatedText?: string;
807
+ hidden?: boolean;
808
+ typingText?: string;
809
+ useLoaderTextAsMsg?: boolean;
810
+ hasBatchQueued?: boolean;
811
+ expiredSessionID?: string;
812
+ continueFromChatLogID?: string;
813
+ };
814
+
815
+ type PendingEnterText = EnterTextRetryParams & {
816
+ msg?: string;
817
+ waitForResponse?: {
818
+ resolve: (event: NatsDialogResponseEvent) => void;
819
+ reject: (error: Error) => void;
820
+ timeoutId: ReturnType<typeof setTimeout>;
821
+ };
822
+ };
823
+ const pendingEnterTextRef = useRef<Map<string, PendingEnterText>>(new Map());
824
+ const bufferedNatsResponsesRef = useRef<Map<string, NatsDialogResponseEvent>>(
825
+ new Map()
826
+ );
827
+
828
+ // Layout: from prop (string only) or integrationConfig. PII detection is only from integrationConfig (customData.layout as object with piiDetection).
712
829
  const layoutName =
713
830
  typeof layout === 'string'
714
831
  ? layout
@@ -825,7 +942,7 @@ const MemoriWidget = ({
825
942
 
826
943
  if (!additionalInfo?.loginToken && !authToken) {
827
944
  setLoginToken(getLocalConfig<typeof loginToken>('loginToken', undefined));
828
- userToken = getLocalConfig<typeof loginToken>('loginToken', undefined);
945
+ userTokenRef.current = getLocalConfig<typeof loginToken>('loginToken', undefined);
829
946
 
830
947
  setBirthDate(getLocalConfig<string | undefined>('birthDate', undefined));
831
948
  }
@@ -900,7 +1017,7 @@ const MemoriWidget = ({
900
1017
  return Object.keys(place).length > 0 ? place : undefined;
901
1018
  }, []);
902
1019
 
903
- /** Place to send with postTextEnteredEvent: real place, nulls when no/declined position, or undefined when position not needed. */
1020
+ /** Place to send with postEnterTextAsync: real place, nulls when no/declined position, or undefined when position not needed. */
904
1021
  const getPlaceSpecForEnterText = useCallback(
905
1022
  (venue: Venue | undefined) => {
906
1023
  if (!memori.needsPosition) return undefined;
@@ -974,6 +1091,7 @@ const MemoriWidget = ({
974
1091
  * @param typingText Optional custom typing indicator text
975
1092
  * @param useLoaderTextAsMsg Whether to use the loader text as the message (default false)
976
1093
  * @param hasBatchQueued Whether there are more messages queued to be sent (default false)
1094
+ * @param skipHistoryPush Skip adding the user message to history (e.g. session-expired retry)
977
1095
  */
978
1096
  const sendMessage = async (
979
1097
  text: string,
@@ -984,7 +1102,8 @@ const MemoriWidget = ({
984
1102
  hidden: boolean = false,
985
1103
  typingText?: string,
986
1104
  useLoaderTextAsMsg = false,
987
- hasBatchQueued = false
1105
+ hasBatchQueued = false,
1106
+ skipHistoryPush = false
988
1107
  ) => {
989
1108
  // Get the session ID from params or global state
990
1109
  const sessionID =
@@ -1051,7 +1170,7 @@ const MemoriWidget = ({
1051
1170
  }
1052
1171
 
1053
1172
  // Add user message to chat history if not hidden
1054
- if (!hidden)
1173
+ if (!hidden && !skipHistoryPush)
1055
1174
  pushMessage({
1056
1175
  text: text,
1057
1176
  translatedText,
@@ -1062,27 +1181,12 @@ const MemoriWidget = ({
1062
1181
  : !!newSessionId,
1063
1182
  });
1064
1183
 
1065
- // Show typing indicator
1066
- setMemoriTyping(true);
1067
- setTypingText(typingText);
1068
-
1184
+ // Show typing indicator after the async enter-text request is accepted (HTTP 200).
1069
1185
  let gotError = false;
1070
1186
 
1071
1187
  try {
1072
- // Add chat reference link to the message if it exists
1073
- // if (chatLogID) {
1074
- // msg =
1075
- // msg +
1076
- // ' \n\n' +
1077
- // '<chat-reference session-id="' +
1078
- // sessionID +
1079
- // '" event-log-id="' +
1080
- // chatLogID +
1081
- // '"></chat-reference>';
1082
- // }
1083
-
1084
1188
  const placeSpec = getPlaceSpecForEnterText(position);
1085
- const { currentState, ...response } = await postTextEnteredEvent({
1189
+ const response = await postEnterTextAsync({
1086
1190
  sessionId: sessionID,
1087
1191
  text: msg,
1088
1192
  ...(memori.needsDateTime && {
@@ -1090,85 +1194,35 @@ const MemoriWidget = ({
1090
1194
  }),
1091
1195
  ...(placeSpec !== undefined && { place: placeSpec }),
1092
1196
  });
1093
- if (response.resultCode === 0 && currentState) {
1094
- setChatLogID(undefined);
1095
- const emission =
1096
- useLoaderTextAsMsg && typingText
1097
- ? typingText
1098
- : currentState.emission ?? currentDialogState?.emission;
1099
-
1100
- if (
1101
- userLang.toLowerCase() !== language.toLowerCase() &&
1102
- emission &&
1103
- isMultilanguageEnabled
1104
- ) {
1105
- currentState.emission = emission;
1106
-
1107
- translateDialogState(currentState, userLang, msg).then(ts => {
1108
- let text = ts.translatedEmission || ts.emission;
1109
- if (text && shouldPlayAudio(text)) {
1110
- handleSpeak(text);
1111
- }
1112
- });
1113
- } else {
1114
- setCurrentDialogState({
1115
- ...currentState,
1116
- emission,
1117
- });
1118
-
1119
- if (emission) {
1120
- pushMessage({
1121
- text: emission,
1122
- emitter: currentState.emitter,
1123
- media: currentState.emittedMedia ?? currentState.media,
1124
- llmUsage: (currentState as any).llmUsage,
1125
- fromUser: false,
1126
- questionAnswered: msg,
1127
- generatedByAI: !!currentState.completion,
1128
- contextVars: currentState.contextVars,
1129
- date: currentState.currentDate,
1130
- placeName: currentState.currentPlaceName,
1131
- placeLatitude: currentState.currentLatitude,
1132
- placeLongitude: currentState.currentLongitude,
1133
- placeUncertaintyKm: currentState.currentUncertaintyKm,
1134
- tag: currentState.currentTag,
1135
- memoryTags: currentState.memoryTags,
1136
- } as any);
1137
- if (emission && shouldPlayAudio(emission)) {
1138
- handleSpeak(emission);
1139
- }
1140
- }
1141
- }
1197
+ const correlationID = readCorrelationID(response);
1198
+ if (response.resultCode === 0 && correlationID) {
1199
+ registerPendingEnterText(correlationID, {
1200
+ msg,
1201
+ text,
1202
+ media,
1203
+ translate,
1204
+ translatedText,
1205
+ hidden,
1206
+ typingText,
1207
+ useLoaderTextAsMsg,
1208
+ hasBatchQueued,
1209
+ });
1210
+ setMemoriTyping(true);
1211
+ setTypingText(typingText);
1212
+ } else if (response.resultCode === 0) {
1213
+ logWidgetError('enter-text missing correlationID', response);
1142
1214
  } else if (response.resultCode === 404) {
1143
- // Handle expired session
1144
- // remove last sent message, will set it as initial
1145
- setHistory(h => [...h.slice(0, h.length - 1)]);
1146
-
1147
- reopenSession(
1148
- true,
1149
- memoriPwd || memori.secretToken,
1150
- memoriTokens,
1151
- undefined,
1152
- undefined,
1153
- {
1154
- LANG: userLang,
1155
- PATHNAME: window.location.pathname,
1156
- ROUTE: window.location.pathname?.split('/')?.pop() || '',
1157
- ...(initialContextVars || {}),
1158
- },
1159
- initialQuestion,
1160
- undefined,
1161
- undefined,
1162
- undefined,
1163
- undefined,
1164
- true // isSessionExpired
1165
- ).then(state => {
1166
- console.info('session timeout');
1167
- if (state?.sessionID) {
1168
- setTimeout(() => {
1169
- sendMessage(text, media, state?.sessionID);
1170
- }, 500);
1171
- }
1215
+ retryAfterExpiredSessionRef.current({
1216
+ text,
1217
+ media,
1218
+ translate,
1219
+ translatedText,
1220
+ hidden,
1221
+ typingText,
1222
+ useLoaderTextAsMsg,
1223
+ hasBatchQueued,
1224
+ expiredSessionID: sessionID,
1225
+ continueFromChatLogID: chatLogID,
1172
1226
  });
1173
1227
  } else if (response.resultCode === 500 && response.resultMessage) {
1174
1228
  setHistory(h => [
@@ -1183,22 +1237,15 @@ const MemoriWidget = ({
1183
1237
  },
1184
1238
  ]);
1185
1239
  } else {
1186
- console.warn('[SEND_MESSAGE]', response);
1187
1240
  return Promise.reject(response);
1188
1241
  }
1189
1242
  } catch (error) {
1190
- console.log('error', error);
1191
- console.error(error);
1192
1243
  gotError = true;
1244
+ logWidgetError('sendMessage failed', error);
1193
1245
 
1194
1246
  setTypingText(undefined);
1195
1247
  setMemoriTyping(false);
1196
1248
  }
1197
-
1198
- if (!hasBatchQueued) {
1199
- setTypingText(undefined);
1200
- setMemoriTyping(false);
1201
- }
1202
1249
  };
1203
1250
 
1204
1251
  /**
@@ -1211,12 +1258,6 @@ const MemoriWidget = ({
1211
1258
  msg?: string,
1212
1259
  avoidPushingMessage: boolean = false
1213
1260
  ) => {
1214
- // console.log('[TRANSLATE] Starting translation with params:', {
1215
- // state,
1216
- // userLang,
1217
- // msg
1218
- // });
1219
-
1220
1261
  const emission = state?.emission ?? currentDialogState?.emission;
1221
1262
 
1222
1263
  let translatedState = { ...state };
@@ -1229,7 +1270,6 @@ const MemoriWidget = ({
1229
1270
  !isMultilanguageEnabled ||
1230
1271
  avoidPushingMessage
1231
1272
  ) {
1232
- // console.log('[TRANSLATE] Skipping translation - using original emission');
1233
1273
  translatedState = { ...state, emission };
1234
1274
  if (emission) {
1235
1275
  translatedMsg = {
@@ -1251,12 +1291,10 @@ const MemoriWidget = ({
1251
1291
  }
1252
1292
  } else {
1253
1293
  try {
1254
- // console.log('[TRANSLATE] Translating emission');
1255
1294
  const t = await getTranslation(emission, userLang, language, baseUrl);
1256
1295
 
1257
1296
  // Handle hints translation if present
1258
1297
  if (state.hints && state.hints.length > 0) {
1259
- // console.log('[TRANSLATE] Translating hints');
1260
1298
  const translatedHints = await Promise.all(
1261
1299
  (state.hints ?? []).map(async hint => {
1262
1300
  const tHint = await getTranslation(
@@ -1288,7 +1326,6 @@ const MemoriWidget = ({
1288
1326
  }
1289
1327
 
1290
1328
  if (t.text.length > 0) {
1291
- // console.log('[TRANSLATE] Creating translated message');
1292
1329
  translatedMsg = {
1293
1330
  text: emission,
1294
1331
  translatedText: t.text,
@@ -1309,7 +1346,6 @@ const MemoriWidget = ({
1309
1346
  };
1310
1347
  }
1311
1348
  } catch (error) {
1312
- console.error('[TRANSLATE] Error during translation:', error);
1313
1349
  translatedState = { ...state, emission };
1314
1350
  translatedMsg = {
1315
1351
  text: emission,
@@ -1330,10 +1366,8 @@ const MemoriWidget = ({
1330
1366
  }
1331
1367
  }
1332
1368
 
1333
- // console.log('[TRANSLATE] Setting translated state and message');
1334
1369
  setCurrentDialogState(translatedState);
1335
1370
  if (!avoidPushingMessage && translatedMsg) {
1336
- // console.log('[TRANSLATE] Pushing translated message', translatedMsg);
1337
1371
  pushMessage(translatedMsg);
1338
1372
  }
1339
1373
 
@@ -1439,7 +1473,6 @@ const MemoriWidget = ({
1439
1473
  executableSnippets?.forEach(s => {
1440
1474
  try {
1441
1475
  setTimeout(() => {
1442
- console.log('snippet', s);
1443
1476
  // eslint-disable-next-line no-new-func
1444
1477
  new Function(s.content ?? '')();
1445
1478
 
@@ -1453,8 +1486,8 @@ const MemoriWidget = ({
1453
1486
  );
1454
1487
  }, 400);
1455
1488
  }, 1000);
1456
- } catch (e) {
1457
- console.warn(e);
1489
+ } catch {
1490
+ // ignore snippet execution errors
1458
1491
  }
1459
1492
  });
1460
1493
  };
@@ -1505,6 +1538,10 @@ const MemoriWidget = ({
1505
1538
  return;
1506
1539
  }
1507
1540
 
1541
+ if (!(await checkCredits({ notify: true }))) {
1542
+ return;
1543
+ }
1544
+
1508
1545
  setLoading(true);
1509
1546
 
1510
1547
  try {
@@ -1526,8 +1563,8 @@ const MemoriWidget = ({
1526
1563
  referral = (() => {
1527
1564
  return window.location.href;
1528
1565
  })();
1529
- } catch (err) {
1530
- console.debug(err);
1566
+ } catch {
1567
+ // ignore referral URL errors
1531
1568
  }
1532
1569
 
1533
1570
  // Initialize session with parameters
@@ -1538,12 +1575,7 @@ const MemoriWidget = ({
1538
1575
  pin: params.pin ?? personification?.pin,
1539
1576
  additionalInfo: {
1540
1577
  ...(params.additionalInfo || additionalInfo || {}),
1541
- loginToken:
1542
- userToken ??
1543
- loginToken ??
1544
- params.additionalInfo?.loginToken ??
1545
- additionalInfo?.loginToken ??
1546
- authToken,
1578
+ loginToken: resolveLoginToken(params.additionalInfo?.loginToken),
1547
1579
  language: (
1548
1580
  userLang ??
1549
1581
  memori.culture?.split('-')?.[0] ??
@@ -1589,7 +1621,7 @@ const MemoriWidget = ({
1589
1621
  setGotErrorInOpening(true);
1590
1622
  }
1591
1623
  // Handle authentication error
1592
- else if (session?.resultCode === 403) {
1624
+ else if (session?.resultCode === 403 && memori.privacyType !== 'PUBLIC') {
1593
1625
  setMemoriPwd(undefined);
1594
1626
  setAuthModalState('password');
1595
1627
  return session;
@@ -1614,11 +1646,10 @@ const MemoriWidget = ({
1614
1646
  duration: 0,
1615
1647
  })
1616
1648
  );
1617
- setGotErrorInOpening(true);
1618
1649
  return session;
1619
1650
  }
1620
1651
  } catch (err) {
1621
- console.error(err);
1652
+ logWidgetError('fetchSession failed', err);
1622
1653
  }
1623
1654
  };
1624
1655
 
@@ -1646,7 +1677,8 @@ const MemoriWidget = ({
1646
1677
  additionalInfoProp?: { [key: string]: string | undefined },
1647
1678
  continueFromChatLogID?: string,
1648
1679
  continueFromSessionID?: string,
1649
- isSessionExpired?: boolean
1680
+ isSessionExpired?: boolean,
1681
+ suppressHistoryUpdate?: boolean
1650
1682
  ) => {
1651
1683
  // Set loading state while reopening session
1652
1684
  setLoading(true);
@@ -1657,12 +1689,10 @@ const MemoriWidget = ({
1657
1689
  undefined
1658
1690
  );
1659
1691
  let userBirthDate = birthDate ?? storageBirthDate;
1660
- // console.log('[REOPEN_SESSION] Using birth date:', userBirthDate);
1661
1692
 
1662
1693
  try {
1663
1694
  // Show age verification if required and birth date not provided
1664
1695
  if (!userBirthDate && !!minAge) {
1665
- // console.log('[REOPEN_SESSION] Age verification required, showing modal');
1666
1696
  setShowAgeVerification(true);
1667
1697
  return;
1668
1698
  }
@@ -1676,24 +1706,26 @@ const MemoriWidget = ({
1676
1706
  !recoveryTokens &&
1677
1707
  !memoriTokens
1678
1708
  ) {
1679
- // console.log('[REOPEN_SESSION] Authentication required, showing modal');
1680
1709
  setAuthModalState('password');
1681
1710
  return;
1682
1711
  }
1683
1712
 
1713
+ if (!(await checkCredits({ notify: true }))) {
1714
+ setLoading(false);
1715
+ return null;
1716
+ }
1717
+
1684
1718
  // Get current URL as referral
1685
1719
  let referral;
1686
1720
  try {
1687
1721
  referral = (() => {
1688
1722
  return window.location.href;
1689
1723
  })();
1690
- // console.log('[REOPEN_SESSION] Got referral:', referral);
1691
- } catch (err) {
1692
- console.debug('[REOPEN_SESSION] Error getting referral:', err);
1724
+ } catch {
1725
+ // ignore referral URL errors
1693
1726
  }
1694
1727
 
1695
1728
  // Initialize session with provided parameters
1696
- // console.log('[REOPEN_SESSION] Initializing session...');
1697
1729
  const { sessionID, currentState, ...response } = await initSession({
1698
1730
  memoriID: memori.engineMemoriID ?? '',
1699
1731
  password: password || memoriPwd || memori.secretToken,
@@ -1712,12 +1744,7 @@ const MemoriWidget = ({
1712
1744
  birthDate: userBirthDate,
1713
1745
  additionalInfo: {
1714
1746
  ...(additionalInfoProp || additionalInfo || {}),
1715
- loginToken:
1716
- userToken ??
1717
- loginToken ??
1718
- additionalInfoProp?.loginToken ??
1719
- additionalInfo?.loginToken ??
1720
- authToken,
1747
+ loginToken: resolveLoginToken(additionalInfoProp?.loginToken),
1721
1748
  language: (
1722
1749
  userLang ??
1723
1750
  memori.culture?.split('-')?.[0] ??
@@ -1730,27 +1757,34 @@ const MemoriWidget = ({
1730
1757
 
1731
1758
  // Handle successful session initialization
1732
1759
  if (sessionID && currentState && response.resultCode === 0) {
1733
- console.log(
1734
- '[REOPEN_SESSION] Session initialized successfully:',
1735
- sessionID
1736
- );
1737
1760
  setSessionId(sessionID);
1738
1761
 
1739
1762
  // Update dialog state and history if requested
1740
1763
  if (updateDialogState) {
1741
- // console.log('[REOPEN_SESSION] Updating dialog state');
1742
1764
  setCurrentDialogState(currentState);
1743
1765
 
1744
- if (currentState.emission) {
1745
- console.log(
1746
- '[REOPEN_SESSION] Processing emission:',
1747
- currentState.emission
1748
- );
1766
+ const sessionExpiredStatus =
1767
+ isSessionExpired && history.length > 1
1768
+ ? t('sessionExpiredReopening')
1769
+ : null;
1770
+
1771
+ if (sessionExpiredStatus && suppressHistoryUpdate) {
1772
+ pushMessage({
1773
+ text: '',
1774
+ emitter: 'system',
1775
+ fromUser: false,
1776
+ initial: sessionExpiredStatus as any,
1777
+ contextVars: {},
1778
+ date: new Date().toISOString(),
1779
+ });
1780
+ }
1781
+
1782
+ if (currentState.emission && !suppressHistoryUpdate) {
1749
1783
  // Determine initial status message based on context
1750
1784
  // Show status message only if session expired and there's existing history
1751
1785
  const initialStatus =
1752
- isSessionExpired && history.length > 1
1753
- ? 'Session Expired, reopening session'
1786
+ sessionExpiredStatus
1787
+ ? sessionExpiredStatus
1754
1788
  : history.length <= 1
1755
1789
  ? true
1756
1790
  : undefined;
@@ -1816,8 +1850,10 @@ const MemoriWidget = ({
1816
1850
  setGotErrorInOpening(true);
1817
1851
  }
1818
1852
  // Handle authentication error
1819
- else if (response?.resultCode === 403) {
1820
- console.error('[REOPEN_SESSION] Authentication error');
1853
+ else if (
1854
+ response?.resultCode === 403 &&
1855
+ memori.privacyType !== 'PUBLIC'
1856
+ ) {
1821
1857
  setMemoriPwd(undefined);
1822
1858
  setAuthModalState('password');
1823
1859
  }
@@ -1833,7 +1869,7 @@ const MemoriWidget = ({
1833
1869
  setGotErrorInOpening(true);
1834
1870
  }
1835
1871
  } catch (err) {
1836
- console.error('[REOPEN_SESSION] Caught error:', err);
1872
+ logWidgetError('reopenSession failed', err);
1837
1873
  }
1838
1874
  // Reset loading state
1839
1875
  setLoading(false);
@@ -1841,6 +1877,99 @@ const MemoriWidget = ({
1841
1877
  return null;
1842
1878
  };
1843
1879
 
1880
+ const retryAfterExpiredSessionRef = useRef<
1881
+ (params: EnterTextRetryParams) => ReturnType<typeof reopenSession>
1882
+ >(() => Promise.resolve(null));
1883
+
1884
+ retryAfterExpiredSessionRef.current = (params: EnterTextRetryParams) => {
1885
+ const {
1886
+ text,
1887
+ media,
1888
+ translate = true,
1889
+ translatedText,
1890
+ hidden = false,
1891
+ typingText,
1892
+ useLoaderTextAsMsg = false,
1893
+ hasBatchQueued = false,
1894
+ expiredSessionID,
1895
+ continueFromChatLogID: continueFromChatLogIDParam,
1896
+ } = params;
1897
+
1898
+ const continueFromSessionID = expiredSessionID ?? sessionId;
1899
+ const continueFromChatLogID = continueFromChatLogIDParam ?? chatLogID;
1900
+
1901
+ const reopenAfterExpiry = () =>
1902
+ reopenSession(
1903
+ true,
1904
+ memoriPwd || memori.secretToken,
1905
+ memoriTokens,
1906
+ undefined,
1907
+ undefined,
1908
+ {
1909
+ LANG: userLang,
1910
+ PATHNAME: window.location.pathname,
1911
+ ROUTE: window.location.pathname?.split('/')?.pop() || '',
1912
+ ...(initialContextVars || {}),
1913
+ },
1914
+ initialQuestion,
1915
+ undefined,
1916
+ undefined,
1917
+ continueFromChatLogID,
1918
+ continueFromSessionID,
1919
+ true,
1920
+ true
1921
+ );
1922
+
1923
+ const scheduleRetry = (newSessionID: string) => {
1924
+ setTimeout(() => {
1925
+ sendMessage(
1926
+ text!,
1927
+ media,
1928
+ newSessionID,
1929
+ translate,
1930
+ translatedText,
1931
+ hidden,
1932
+ typingText,
1933
+ useLoaderTextAsMsg,
1934
+ hasBatchQueued,
1935
+ true
1936
+ );
1937
+ }, 500);
1938
+ };
1939
+
1940
+ const handleReopenFailure = (state: Awaited<ReturnType<typeof reopenSession>>) => {
1941
+ setMemoriTyping(false);
1942
+ setTypingText(undefined);
1943
+ // `null` = explicit failure; `undefined` = auth/age modal opened (message kept in history)
1944
+ if (state === null && text && !hidden) {
1945
+ add(
1946
+ createAlertOptions({
1947
+ description: t('errors.SESSION_EXPIRED'),
1948
+ severity: 'error',
1949
+ })
1950
+ );
1951
+ }
1952
+ };
1953
+
1954
+ if (!text) {
1955
+ return reopenAfterExpiry().then(state => {
1956
+ if (!state?.sessionID) {
1957
+ handleReopenFailure(state);
1958
+ }
1959
+ return state;
1960
+ });
1961
+ }
1962
+
1963
+ return reopenAfterExpiry().then(state => {
1964
+ if (state?.sessionID) {
1965
+ scheduleRetry(state.sessionID);
1966
+ } else {
1967
+ handleReopenFailure(state);
1968
+ }
1969
+ return state;
1970
+ });
1971
+ };
1972
+
1844
1973
  const changeTag = async (
1845
1974
  memoriId: string,
1846
1975
  sessionId: string,
@@ -1848,7 +1977,6 @@ const MemoriWidget = ({
1848
1977
  pin?: string
1849
1978
  ) => {
1850
1979
  if (!memoriId || !sessionId) {
1851
- console.error('CHANGETAG/Session not found');
1852
1980
  return Promise.reject('Session not found');
1853
1981
  }
1854
1982
 
@@ -1888,7 +2016,6 @@ const MemoriWidget = ({
1888
2016
  };
1889
2017
  }
1890
2018
  } else if ([400, 401, 403, 404, 500].includes(resultCode)) {
1891
- console.warn('[APPCONTEXT/CHANGETAG]', resultCode);
1892
2019
  let storageBirthDate = getLocalConfig<string | undefined>(
1893
2020
  'birthDate',
1894
2021
  undefined
@@ -1899,8 +2026,8 @@ const MemoriWidget = ({
1899
2026
  referral = (() => {
1900
2027
  return window.location.href;
1901
2028
  })();
1902
- } catch (err) {
1903
- console.debug(err);
2029
+ } catch {
2030
+ // ignore referral URL errors
1904
2031
  }
1905
2032
 
1906
2033
  fetchSession({
@@ -1918,11 +2045,7 @@ const MemoriWidget = ({
1918
2045
  birthDate: birthDate || storageBirthDate || undefined,
1919
2046
  additionalInfo: {
1920
2047
  ...(additionalInfo || {}),
1921
- loginToken:
1922
- userToken ??
1923
- loginToken ??
1924
- additionalInfo?.loginToken ??
1925
- authToken,
2048
+ loginToken: resolveLoginToken(),
1926
2049
  language: (
1927
2050
  userLang ??
1928
2051
  memori.culture?.split('-')?.[0] ??
@@ -1942,7 +2065,6 @@ const MemoriWidget = ({
1942
2065
  }
1943
2066
  } catch (_e) {
1944
2067
  let err = _e as Error;
1945
- console.warn('[APPCONTEXT/CHANGETAG]', err);
1946
2068
  return Promise.reject(err);
1947
2069
  }
1948
2070
 
@@ -1969,6 +2091,7 @@ const MemoriWidget = ({
1969
2091
  return () => {
1970
2092
  setHasUserActivatedSpeak(false);
1971
2093
  setClickedStart(false);
2094
+ sessionStartingRef.current = false;
1972
2095
  clearInteractionTimeout();
1973
2096
  timeoutRef.current = undefined;
1974
2097
  };
@@ -1981,8 +2104,6 @@ const MemoriWidget = ({
1981
2104
  const [requestedListening, setRequestedListening] = useState(false);
1982
2105
  const startListeningRef = useRef<(() => Promise<void>) | null>(null);
1983
2106
 
1984
- // console.log('tenantID', tenantID);
1985
-
1986
2107
  // Define TTS configuration
1987
2108
  const ttsConfig = useMemo(
1988
2109
  () => ({
@@ -2037,7 +2158,6 @@ const MemoriWidget = ({
2037
2158
  'muteSpeaker',
2038
2159
  !defaultEnableAudio
2039
2160
  );
2040
- console.log('[MemoriWidget] shouldPlayAudio', currentSpeakerMuted);
2041
2161
  return (
2042
2162
  text &&
2043
2163
  text.trim() &&
@@ -2049,7 +2169,6 @@ const MemoriWidget = ({
2049
2169
 
2050
2170
  // Create a single, centralized function to process and send messages
2051
2171
  const processSpeechAndSendMessage = (text: string) => {
2052
- // console.log('processSpeechAndSendMessage', text);
2053
2172
  // Skip if already processing or no text
2054
2173
  if (!text || text.trim().length === 0) {
2055
2174
  return;
@@ -2058,17 +2177,15 @@ const MemoriWidget = ({
2058
2177
  try {
2059
2178
  // Process the text
2060
2179
  const message = stripDuplicates(text);
2061
- console.debug('Processing speech message:', message);
2062
2180
 
2063
2181
  if (message.length > 0) {
2064
2182
  setUserMessage('');
2065
2183
 
2066
2184
  // Send the message
2067
- console.debug('Sending message:', message);
2068
2185
  sendMessage(message);
2069
2186
  }
2070
- } catch (error) {
2071
- console.error('Error in processSpeechAndSendMessage:', error);
2187
+ } catch {
2188
+ // ignore speech processing errors
2072
2189
  }
2073
2190
  };
2074
2191
 
@@ -2148,8 +2265,7 @@ const MemoriWidget = ({
2148
2265
  }
2149
2266
 
2150
2267
  return translatedState;
2151
- } catch (error) {
2152
- console.error('Error in translateAndSpeak:', error);
2268
+ } catch {
2153
2269
  // Still update activation state even if there's an error
2154
2270
  if (!hasUserActivatedSpeak) {
2155
2271
  setHasUserActivatedSpeak(true);
@@ -2166,6 +2282,277 @@ const MemoriWidget = ({
2166
2282
  ]
2167
2283
  );
2168
2284
 
2285
+ const processEnterTextDialogResponse = useCallback(
2286
+ (event: NatsDialogResponseEvent, pending: PendingEnterText) => {
2287
+ const {
2288
+ msg,
2289
+ typingText: pendingTypingText,
2290
+ useLoaderTextAsMsg,
2291
+ } = pending;
2292
+ const currentState = event.currentState;
2293
+
2294
+ if (event.resultCode !== 0 || !currentState) {
2295
+ if (isSessionExpiredNatsResponse(event) && pending.text) {
2296
+ setMemoriTyping(false);
2297
+ setTypingText(undefined);
2298
+ retryAfterExpiredSessionRef.current({
2299
+ text: pending.text,
2300
+ media: pending.media,
2301
+ translate: pending.translate,
2302
+ translatedText: pending.translatedText,
2303
+ hidden: pending.hidden,
2304
+ typingText: pending.typingText,
2305
+ useLoaderTextAsMsg: pending.useLoaderTextAsMsg,
2306
+ hasBatchQueued: pending.hasBatchQueued,
2307
+ expiredSessionID: sessionId,
2308
+ continueFromChatLogID: chatLogID,
2309
+ });
2310
+ return;
2311
+ }
2312
+
2313
+ if (event.resultCode === 500 && event.resultMessage) {
2314
+ setHistory(h => [
2315
+ ...h,
2316
+ {
2317
+ text: 'Error: ' + event.resultMessage,
2318
+ emitter: 'system',
2319
+ fromUser: false,
2320
+ initial: false,
2321
+ contextVars: {},
2322
+ date: new Date().toISOString(),
2323
+ },
2324
+ ]);
2325
+ }
2326
+ return;
2327
+ }
2328
+
2329
+ if (!msg) {
2330
+ return;
2331
+ }
2332
+
2333
+ setChatLogID(undefined);
2334
+ const emission =
2335
+ useLoaderTextAsMsg && pendingTypingText
2336
+ ? pendingTypingText
2337
+ : currentState.emission ?? currentDialogState?.emission;
2338
+
2339
+ if (
2340
+ userLang.toLowerCase() !== language.toLowerCase() &&
2341
+ emission &&
2342
+ isMultilanguageEnabled
2343
+ ) {
2344
+ currentState.emission = emission;
2345
+
2346
+ translateDialogState(currentState, userLang, msg).then(ts => {
2347
+ const text = ts.translatedEmission || ts.emission;
2348
+ if (text && shouldPlayAudio(text)) {
2349
+ handleSpeak(text);
2350
+ }
2351
+ });
2352
+ } else {
2353
+ setCurrentDialogState({
2354
+ ...currentState,
2355
+ emission,
2356
+ });
2357
+
2358
+ if (emission) {
2359
+ pushMessage({
2360
+ text: emission,
2361
+ emitter: currentState.emitter,
2362
+ media: currentState.emittedMedia ?? currentState.media,
2363
+ llmUsage: (currentState as any).llmUsage,
2364
+ fromUser: false,
2365
+ questionAnswered: msg,
2366
+ generatedByAI: !!currentState.completion,
2367
+ contextVars: currentState.contextVars,
2368
+ date: currentState.currentDate,
2369
+ placeName: currentState.currentPlaceName,
2370
+ placeLatitude: currentState.currentLatitude,
2371
+ placeLongitude: currentState.currentLongitude,
2372
+ placeUncertaintyKm: currentState.currentUncertaintyKm,
2373
+ tag: currentState.currentTag,
2374
+ memoryTags: currentState.memoryTags,
2375
+ } as any);
2376
+ if (emission && shouldPlayAudio(emission)) {
2377
+ handleSpeak(emission);
2378
+ }
2379
+ }
2380
+ }
2381
+ },
2382
+ [
2383
+ userLang,
2384
+ language,
2385
+ isMultilanguageEnabled,
2386
+ currentDialogState?.emission,
2387
+ translateDialogState,
2388
+ handleSpeak,
2389
+ shouldPlayAudio,
2390
+ ]
2391
+ );
2392
+
2393
+ const clearEnterTextPending = useCallback(
2394
+ (correlationID: string, pending: PendingEnterText) => {
2395
+ if (pending.waitForResponse?.timeoutId) {
2396
+ clearTimeout(pending.waitForResponse.timeoutId);
2397
+ }
2398
+ pendingEnterTextRef.current.delete(correlationID);
2399
+ },
2400
+ []
2401
+ );
2402
+
2403
+ const deliverEnterTextNatsError = useCallback(
2404
+ (event: NatsErrorEvent) => {
2405
+ const correlationID = event.correlationID;
2406
+ let pending: PendingEnterText | undefined;
2407
+
2408
+ if (correlationID) {
2409
+ pending = pendingEnterTextRef.current.get(correlationID);
2410
+ if (pending) {
2411
+ clearEnterTextPending(correlationID, pending);
2412
+ pending.waitForResponse?.reject(
2413
+ new Error(
2414
+ event.errorMessage ?? String(event.errorCode ?? 'NATS error')
2415
+ )
2416
+ );
2417
+ }
2418
+ }
2419
+
2420
+ if (isSessionExpiredNatsError(event) && pending?.text) {
2421
+ setMemoriTyping(false);
2422
+ setTypingText(undefined);
2423
+ retryAfterExpiredSessionRef.current({
2424
+ text: pending.text,
2425
+ media: pending.media,
2426
+ translate: pending.translate,
2427
+ translatedText: pending.translatedText,
2428
+ hidden: pending.hidden,
2429
+ typingText: pending.typingText,
2430
+ useLoaderTextAsMsg: pending.useLoaderTextAsMsg,
2431
+ hasBatchQueued: pending.hasBatchQueued,
2432
+ expiredSessionID: sessionId,
2433
+ continueFromChatLogID: chatLogID,
2434
+ });
2435
+ return;
2436
+ }
2437
+
2438
+ const errorText = event.errorMessage
2439
+ ? `Error: ${event.errorMessage}`
2440
+ : event.errorCode
2441
+ ? `Error: ${event.errorCode}`
2442
+ : 'Error: An unexpected error occurred';
2443
+
2444
+ pushMessage({
2445
+ text: errorText,
2446
+ emitter: 'system',
2447
+ fromUser: false,
2448
+ initial: false,
2449
+ contextVars: {},
2450
+ date: new Date().toISOString(),
2451
+ });
2452
+
2453
+ setMemoriTyping(false);
2454
+ setTypingText(undefined);
2455
+ },
2456
+ [clearEnterTextPending]
2457
+ );
2458
+
2459
+ const deliverEnterTextNatsResponse = useCallback(
2460
+ (correlationID: string, event: NatsDialogResponseEvent) => {
2461
+ const pending = pendingEnterTextRef.current.get(correlationID);
2462
+ if (!pending) {
2463
+ bufferedNatsResponsesRef.current.set(correlationID, event);
2464
+ return;
2465
+ }
2466
+
2467
+ clearEnterTextPending(correlationID, pending);
2468
+
2469
+ if (pending.waitForResponse) {
2470
+ pending.waitForResponse.resolve(event);
2471
+ setMemoriTyping(false);
2472
+ setTypingText(undefined);
2473
+ return;
2474
+ }
2475
+
2476
+ processEnterTextDialogResponse(event, pending);
2477
+
2478
+ if (!pending.hasBatchQueued) {
2479
+ setMemoriTyping(false);
2480
+ setTypingText(undefined);
2481
+ }
2482
+ },
2483
+ [processEnterTextDialogResponse, clearEnterTextPending]
2484
+ );
2485
+
2486
+ const registerPendingEnterText = useCallback(
2487
+ (correlationID: string, pending: PendingEnterText) => {
2488
+ const buffered = bufferedNatsResponsesRef.current.get(correlationID);
2489
+ if (buffered) {
2490
+ bufferedNatsResponsesRef.current.delete(correlationID);
2491
+ pendingEnterTextRef.current.set(correlationID, pending);
2492
+ deliverEnterTextNatsResponse(correlationID, buffered);
2493
+ return;
2494
+ }
2495
+
2496
+ pendingEnterTextRef.current.set(correlationID, pending);
2497
+ },
2498
+ [deliverEnterTextNatsResponse]
2499
+ );
2500
+
2501
+ const waitForEnterTextNatsResponse = useCallback(
2502
+ (correlationID: string, timeoutMs = 120000) =>
2503
+ new Promise<NatsDialogResponseEvent>((resolve, reject) => {
2504
+ const timeoutId = setTimeout(() => {
2505
+ const current = pendingEnterTextRef.current.get(correlationID);
2506
+ if (current) {
2507
+ clearEnterTextPending(correlationID, current);
2508
+ }
2509
+ logWidgetError('NATS timeout', { correlationID, timeoutMs });
2510
+ reject(new Error('NATS enter-text response timeout'));
2511
+ }, timeoutMs);
2512
+
2513
+ registerPendingEnterText(correlationID, {
2514
+ waitForResponse: {
2515
+ resolve: event => {
2516
+ clearTimeout(timeoutId);
2517
+ resolve(event);
2518
+ },
2519
+ reject: error => {
2520
+ clearTimeout(timeoutId);
2521
+ reject(error);
2522
+ },
2523
+ timeoutId,
2524
+ },
2525
+ });
2526
+ }),
2527
+ [registerPendingEnterText, clearEnterTextPending]
2528
+ );
2529
+
2530
+ // NATS subscription: receives progress updates and the async enter-text response.
2531
+ useNats({
2532
+ baseUrl,
2533
+ sessionId,
2534
+ onProgress: useCallback((event: NatsProgressEvent) => {
2535
+ if (event.message) {
2536
+ setTypingText(event.message);
2537
+ }
2538
+ }, []),
2539
+ onDialogResponse: useCallback(
2540
+ (event: NatsDialogResponseEvent) => {
2541
+ const correlationID = event.correlationID;
2542
+ if (!correlationID) {
2543
+ logWidgetError('NATS dialog response missing correlationID', event);
2544
+ setMemoriTyping(false);
2545
+ setTypingText(undefined);
2546
+ return;
2547
+ }
2548
+
2549
+ deliverEnterTextNatsResponse(correlationID, event);
2550
+ },
2551
+ [deliverEnterTextNatsResponse]
2552
+ ),
2553
+ onError: deliverEnterTextNatsError,
2554
+ });
2555
+
2169
2556
  const focusChatInput = () => {
2170
2557
  let textarea = document.querySelector(
2171
2558
  '#chat-fieldset textarea'
@@ -2194,8 +2581,8 @@ const MemoriWidget = ({
2194
2581
  setClickedStart(false);
2195
2582
  timeoutRef.current = undefined;
2196
2583
  ttsStop();
2197
- } catch (e) {
2198
- // console.log('Error: resetUIEffects', e);
2584
+ } catch {
2585
+ // ignore reset errors
2199
2586
  }
2200
2587
  };
2201
2588
  useEffect(() => {
@@ -2451,12 +2838,6 @@ const MemoriWidget = ({
2451
2838
  chatLog?: ChatLog,
2452
2839
  targetSessionID?: string
2453
2840
  ) => {
2454
- // console.log('[onClickStart] Starting with params:', {
2455
- // session,
2456
- // initialSessionExpired,
2457
- // chatLog
2458
- // });
2459
-
2460
2841
  const sessionID = chatLog ? undefined : session?.sessionID || sessionId;
2461
2842
  const dialogState = chatLog
2462
2843
  ? undefined
@@ -2485,10 +2866,17 @@ const MemoriWidget = ({
2485
2866
  return;
2486
2867
  }
2487
2868
 
2869
+ if (!(await checkCredits({ notify: true }))) {
2870
+ setClickedStart(false);
2871
+ setLoading(false);
2872
+ return;
2873
+ }
2874
+
2488
2875
  // Handle age verification
2489
2876
  if (!sessionID && !!minAge && !birth) {
2490
2877
  setShowAgeVerification(true);
2491
2878
  setClickedStart(false);
2879
+ return;
2492
2880
  }
2493
2881
  // Handle authentication
2494
2882
  else if (
@@ -2505,42 +2893,43 @@ const MemoriWidget = ({
2505
2893
  }
2506
2894
  // Create new session if needed
2507
2895
  else if (!sessionID || initialSessionExpired) {
2508
- setClickedStart(false);
2509
2896
  setGotErrorInOpening(false);
2510
- const session = await fetchSession({
2511
- memoriID: memori.engineMemoriID!,
2512
- password: secret || memoriPwd || memori.secretToken,
2513
- tag: personification?.tag,
2514
- pin: personification?.pin,
2515
- continueFromChatLogID: chatLog?.chatLogID,
2516
- initialContextVars: {
2517
- LANG: userLang,
2518
- PATHNAME: window.location.pathname?.toUpperCase(),
2519
- ROUTE:
2520
- window.location.pathname?.split('/')?.pop()?.toUpperCase() || '',
2521
- ...((!chatLog
2522
- ? initialContextVars
2523
- : chatLog.lines[chatLog.lines.length - 1].contextVars) || {}),
2524
- },
2525
- initialQuestion: chatLog ? undefined : initialQuestion,
2526
- birthDate: birth,
2527
- additionalInfo: {
2528
- ...(additionalInfo || {}),
2529
- loginToken:
2530
- userToken ??
2531
- loginToken ??
2532
- additionalInfo?.loginToken ??
2533
- authToken,
2534
- language: (
2535
- userLang ??
2536
- memori.culture?.split('-')?.[0] ??
2537
- 'IT'
2538
- ).toLowerCase(),
2539
- timeZoneOffset: new Date().getTimezoneOffset().toString(),
2540
- },
2541
- });
2897
+ if (sessionStartingRef.current) {
2898
+ return;
2899
+ }
2900
+ sessionStartingRef.current = true;
2901
+ try {
2902
+ const session = await fetchSession({
2903
+ memoriID: memori.engineMemoriID!,
2904
+ password: secret || memoriPwd || memori.secretToken,
2905
+ tag: personification?.tag,
2906
+ pin: personification?.pin,
2907
+ continueFromChatLogID: chatLog?.chatLogID,
2908
+ initialContextVars: {
2909
+ LANG: userLang,
2910
+ PATHNAME: window.location.pathname?.toUpperCase(),
2911
+ ROUTE:
2912
+ window.location.pathname?.split('/')?.pop()?.toUpperCase() ||
2913
+ '',
2914
+ ...((!chatLog
2915
+ ? initialContextVars
2916
+ : chatLog.lines[chatLog.lines.length - 1].contextVars) || {}),
2917
+ },
2918
+ initialQuestion: chatLog ? undefined : initialQuestion,
2919
+ birthDate: birth,
2920
+ additionalInfo: {
2921
+ ...(additionalInfo || {}),
2922
+ loginToken: resolveLoginToken(),
2923
+ language: (
2924
+ userLang ??
2925
+ memori.culture?.split('-')?.[0] ??
2926
+ 'IT'
2927
+ ).toLowerCase(),
2928
+ timeZoneOffset: new Date().getTimezoneOffset().toString(),
2929
+ },
2930
+ });
2542
2931
 
2543
- if (session?.dialogState) {
2932
+ if (session?.dialogState) {
2544
2933
  // reset history
2545
2934
  if (!chatLog) {
2546
2935
  setHistory([]);
@@ -2549,6 +2938,7 @@ const MemoriWidget = ({
2549
2938
  await translateAndSpeak(session.dialogState, userLang);
2550
2939
  // No need for additional handleSpeak call since translateAndSpeak already handles it
2551
2940
  setHasUserActivatedSpeak(true);
2941
+ setClickedStart(false);
2552
2942
  } else {
2553
2943
  const messages = chatLog.lines.map(
2554
2944
  (l, i) =>
@@ -2596,8 +2986,8 @@ const MemoriWidget = ({
2596
2986
  };
2597
2987
  })
2598
2988
  );
2599
- } catch (e) {
2600
- console.error('[onClickStart] Error translating messages:', e);
2989
+ } catch {
2990
+ // ignore translation errors
2601
2991
  }
2602
2992
  }
2603
2993
 
@@ -2610,12 +3000,18 @@ const MemoriWidget = ({
2610
3000
  true
2611
3001
  ).finally(() => {
2612
3002
  setHasUserActivatedSpeak(true);
3003
+ setClickedStart(false);
2613
3004
  });
2614
3005
  }
2615
- } else if (session?.resultCode === 0) {
2616
- await onClickStart((session as any) || undefined);
2617
- } else {
2618
- setLoading(false);
3006
+ } else if (session?.resultCode === 0) {
3007
+ sessionStartingRef.current = false;
3008
+ await onClickStart((session as any) || undefined);
3009
+ } else {
3010
+ setLoading(false);
3011
+ setClickedStart(false);
3012
+ }
3013
+ } finally {
3014
+ sessionStartingRef.current = false;
2619
3015
  }
2620
3016
 
2621
3017
  return;
@@ -2628,9 +3024,7 @@ const MemoriWidget = ({
2628
3024
 
2629
3025
  if (response.resultCode !== 0 || !currentState) {
2630
3026
  const { chatLogs } = await getSessionChatLogs(sessionID!, sessionID!);
2631
- setGotErrorInOpening(true);
2632
3027
  setSessionId(undefined);
2633
- setClickedStart(false);
2634
3028
  await onClickStart(undefined, true, chatLogs?.[0]);
2635
3029
  return;
2636
3030
  }
@@ -2656,11 +3050,11 @@ const MemoriWidget = ({
2656
3050
 
2657
3051
  if (session && session.resultCode === 0) {
2658
3052
  await translateAndSpeak(session.currentState, userLang);
3053
+ setClickedStart(false);
2659
3054
  } else {
2660
3055
  throw new Error('No session');
2661
3056
  }
2662
- } catch (e) {
2663
- console.error('[onClickStart] Error changing tag:', e);
3057
+ } catch {
2664
3058
  reopenSession(
2665
3059
  true,
2666
3060
  memori?.secretToken,
@@ -2679,6 +3073,7 @@ const MemoriWidget = ({
2679
3073
  birth
2680
3074
  ).then(() => {
2681
3075
  setHasUserActivatedSpeak(true);
3076
+ setClickedStart(false);
2682
3077
  });
2683
3078
  }
2684
3079
  }
@@ -2701,6 +3096,7 @@ const MemoriWidget = ({
2701
3096
 
2702
3097
  if (session && session.resultCode === 0) {
2703
3098
  await translateAndSpeak(session.currentState, userLang);
3099
+ setClickedStart(false);
2704
3100
  } else {
2705
3101
  throw new Error('No session');
2706
3102
  }
@@ -2723,6 +3119,7 @@ const MemoriWidget = ({
2723
3119
  birth
2724
3120
  ).then(() => {
2725
3121
  setHasUserActivatedSpeak(true);
3122
+ setClickedStart(false);
2726
3123
  });
2727
3124
  }
2728
3125
  }
@@ -2769,14 +3166,14 @@ const MemoriWidget = ({
2769
3166
  ).text,
2770
3167
  }))
2771
3168
  );
2772
- } catch (e) {
2773
- console.error('[onClickStart] Error translating messages:', e);
3169
+ } catch {
3170
+ // ignore translation errors
2774
3171
  }
2775
3172
  }
2776
3173
 
2777
3174
  setHistory(translatedMessages);
2778
- } catch (e) {
2779
- console.error('[onClickStart] Error retrieving chat logs:', e);
3175
+ } catch {
3176
+ // ignore chat log retrieval errors
2780
3177
  }
2781
3178
 
2782
3179
  if (
@@ -2785,6 +3182,7 @@ const MemoriWidget = ({
2785
3182
  ) {
2786
3183
  // we have a history, don't push message
2787
3184
  setHasUserActivatedSpeak(true);
3185
+ setClickedStart(false);
2788
3186
  await translateAndSpeak(
2789
3187
  currentState,
2790
3188
  userLang,
@@ -2794,16 +3192,13 @@ const MemoriWidget = ({
2794
3192
  !!translatedMessages?.length
2795
3193
  );
2796
3194
  } else {
2797
- console.log('[onClickStart] Starting with initial question');
2798
3195
  // remove default initial message
2799
3196
  translatedMessages = [];
2800
3197
  setHistory([]);
2801
3198
 
2802
- setMemoriTyping(true);
2803
-
2804
3199
  // we have no chat history, we start by initial question
2805
3200
  const placeSpec = getPlaceSpecForEnterText(position);
2806
- const response = await postTextEnteredEvent({
3201
+ const response = await postEnterTextAsync({
2807
3202
  sessionId: sessionID!,
2808
3203
  text: initialQuestion,
2809
3204
  ...(memori.needsDateTime && {
@@ -2811,8 +3206,7 @@ const MemoriWidget = ({
2811
3206
  }),
2812
3207
  ...(placeSpec !== undefined && { place: placeSpec }),
2813
3208
  });
2814
-
2815
- // Handle 500 error from TextEnteredEvent
3209
+ // Handle 500 error from EnterTextAsync
2816
3210
  if (response.resultCode === 500 && response.resultMessage) {
2817
3211
  setHistory(h => [
2818
3212
  ...h,
@@ -2825,16 +3219,33 @@ const MemoriWidget = ({
2825
3219
  date: new Date().toISOString(),
2826
3220
  },
2827
3221
  ]);
2828
- setMemoriTyping(false);
2829
3222
  return;
2830
3223
  }
2831
3224
 
2832
- await translateAndSpeak(
2833
- response.currentState ?? currentState,
2834
- userLang,
2835
- undefined,
2836
- false
2837
- );
3225
+ const onClickStartCorrelationID = readCorrelationID(response);
3226
+ if (response.resultCode === 0 && onClickStartCorrelationID) {
3227
+ setMemoriTyping(true);
3228
+ try {
3229
+ const natsEvent = await waitForEnterTextNatsResponse(
3230
+ onClickStartCorrelationID
3231
+ );
3232
+ if (natsEvent.resultCode === 0 && natsEvent.currentState) {
3233
+ await translateAndSpeak(
3234
+ natsEvent.currentState,
3235
+ userLang,
3236
+ undefined,
3237
+ false
3238
+ );
3239
+ setClickedStart(false);
3240
+ }
3241
+ } catch (err) {
3242
+ logWidgetError('onClickStart NATS wait failed', err);
3243
+ setMemoriTyping(false);
3244
+ setTypingText(undefined);
3245
+ }
3246
+ } else if (response.resultCode === 0) {
3247
+ logWidgetError('onClickStart enter-text missing correlationID', response);
3248
+ }
2838
3249
  }
2839
3250
  }
2840
3251
  }
@@ -2845,6 +3256,7 @@ const MemoriWidget = ({
2845
3256
 
2846
3257
  // everything is fine, just translate dialog state and activate chat
2847
3258
  await translateAndSpeak(dialogState!, userLang);
3259
+ setClickedStart(false);
2848
3260
  }
2849
3261
  },
2850
3262
  [memoriPwd, memori, memoriTokens, birthDate, sessionId, userLang, position]
@@ -2861,10 +3273,24 @@ const MemoriWidget = ({
2861
3273
  );
2862
3274
 
2863
3275
  useEffect(() => {
2864
- if (!clickedStart && autoStart && selectedLayout !== 'HIDDEN_CHAT') {
3276
+ if (
3277
+ !clickedStart &&
3278
+ !sessionStartingRef.current &&
3279
+ !sessionId &&
3280
+ autoStart &&
3281
+ selectedLayout !== 'HIDDEN_CHAT' &&
3282
+ (!needsCredits || hasEnoughCredits)
3283
+ ) {
2865
3284
  onClickStart();
2866
3285
  }
2867
- }, [clickedStart, autoStart, selectedLayout]);
3286
+ }, [
3287
+ clickedStart,
3288
+ autoStart,
3289
+ selectedLayout,
3290
+ sessionId,
3291
+ needsCredits,
3292
+ hasEnoughCredits,
3293
+ ]);
2868
3294
 
2869
3295
  useEffect(() => {
2870
3296
  const targetNode =
@@ -2875,29 +3301,31 @@ const MemoriWidget = ({
2875
3301
  return;
2876
3302
  }
2877
3303
 
3304
+ const propsToken = additionalInfo?.loginToken ?? authToken;
3305
+ const initialDomToken = targetNode.getAttribute('authtoken') || undefined;
3306
+ if (initialDomToken && !propsToken) {
3307
+ setLoginToken(initialDomToken);
3308
+ userTokenRef.current = initialDomToken;
3309
+ }
3310
+
3311
+ const applyAuthTokenFromElement = (element: Element) => {
3312
+ const clientNode =
3313
+ element.nodeName === 'MEMORI-CLIENT'
3314
+ ? element
3315
+ : element.closest('memori-client') ?? element.parentElement;
3316
+ const token = clientNode?.getAttribute('authtoken') || undefined;
3317
+ setLoginToken(token);
3318
+ userTokenRef.current = token;
3319
+ };
3320
+
2878
3321
  const config = { attributes: true, childList: false, subtree: false };
2879
- const callback: MutationCallback = (mutationList, _observer) => {
3322
+ const callback: MutationCallback = mutationList => {
2880
3323
  for (const mutation of mutationList) {
2881
3324
  if (
2882
3325
  mutation.type === 'attributes' &&
2883
3326
  mutation.attributeName?.toLowerCase() === 'authtoken'
2884
3327
  ) {
2885
- if (mutation.target.nodeName === 'MEMORI-CLIENT') {
2886
- setLoginToken(
2887
- // @ts-ignore
2888
- mutation.target.getAttribute('authtoken') || undefined
2889
- );
2890
- // @ts-ignore
2891
- userToken = mutation.target.getAttribute('authtoken') || undefined;
2892
- } else {
2893
- // @ts-ignore
2894
- setLoginToken(
2895
- mutation.target?.parentElement?.getAttribute('authtoken') ||
2896
- undefined
2897
- );
2898
- // @ts-ignore
2899
- userToken = mutation.target.getAttribute('authtoken') || undefined;
2900
- }
3328
+ applyAuthTokenFromElement(mutation.target as Element);
2901
3329
  }
2902
3330
  }
2903
3331
  };
@@ -2907,7 +3335,7 @@ const MemoriWidget = ({
2907
3335
  return () => {
2908
3336
  observer.disconnect();
2909
3337
  };
2910
- }, []);
3338
+ }, [memori.name, memori.memoriID, additionalInfo?.loginToken, authToken]);
2911
3339
 
2912
3340
  /**
2913
3341
  * Experts references
@@ -2921,11 +3349,9 @@ const MemoriWidget = ({
2921
3349
 
2922
3350
  if (resp.resultCode === 0) {
2923
3351
  setExperts(experts);
2924
- } else {
2925
- console.warn('Error fetching experts', resp);
2926
3352
  }
2927
- } catch (err) {
2928
- console.warn(err);
3353
+ } catch {
3354
+ // ignore expert fetch errors
2929
3355
  }
2930
3356
  }, [sessionId, memori?.enableBoardOfExperts]);
2931
3357
  useEffect(() => {
@@ -2938,39 +3364,74 @@ const MemoriWidget = ({
2938
3364
  !!user?.userID &&
2939
3365
  user?.pAndCUAccepted;
2940
3366
 
2941
- // check if owner has enough credits
2942
- const needsCredits = tenant?.billingDelegation;
2943
- const [hasEnoughCredits, setHasEnoughCredits] = useState<boolean>(true);
2944
- const checkCredits = useCallback(async () => {
2945
- if (!tenant?.billingDelegation) return;
3367
+ const handleNotEnoughCredits = useCallback(() => {
3368
+ setHasEnoughCredits(false);
3369
+ setAuthModalState(null);
3370
+ add(
3371
+ createAlertOptions({
3372
+ description: t('notEnoughCredits'),
3373
+ severity: 'error',
3374
+ })
3375
+ );
3376
+ }, [add, t]);
3377
+ const checkCredits = useCallback(
3378
+ async (options?: { notify?: boolean }) => {
3379
+ if (!tenant?.billingDelegation) return true;
3380
+
3381
+ // Billing delegation is active: credits MUST be verified.
3382
+ // Without either owner identifier we cannot call the API, so we fail closed
3383
+ // instead of silently letting the session start unverified.
3384
+ if (!ownerUserID && !ownerUserName) {
3385
+ if (options?.notify) {
3386
+ handleNotEnoughCredits();
3387
+ } else {
3388
+ setHasEnoughCredits(false);
3389
+ }
3390
+ return false;
3391
+ }
2946
3392
 
2947
- try {
2948
- const resp = await getCredits({
2949
- operation: deepThoughtEnabled
2950
- ? 'dt_session_creation'
2951
- : 'session_creation',
2952
- baseUrl: baseUrl,
2953
- userID: ownerUserID,
2954
- userName: ownerUserName,
2955
- tenant: tenantID,
2956
- });
3393
+ try {
3394
+ const resp = await getCredits({
3395
+ operation: deepThoughtEnabled
3396
+ ? 'dt_session_creation'
3397
+ : 'session_creation',
3398
+ baseUrl: baseUrl,
3399
+ userID: ownerUserID,
3400
+ userName: ownerUserName,
3401
+ tenant: tenantID,
3402
+ });
2957
3403
 
2958
- if (resp.enough) {
2959
- setHasEnoughCredits(true);
2960
- } else {
2961
- setHasEnoughCredits(false);
2962
- console.warn('Not enough credits. Required:', resp.required);
3404
+ if (resp.enough) {
3405
+ setHasEnoughCredits(true);
3406
+ return true;
3407
+ } else {
3408
+ if (options?.notify) {
3409
+ handleNotEnoughCredits();
3410
+ } else {
3411
+ setHasEnoughCredits(false);
3412
+ }
3413
+ return false;
3414
+ }
3415
+ } catch (err) {
3416
+ logWidgetError('checkCredits failed', err);
3417
+ return true;
2963
3418
  }
2964
- } catch (e) {
2965
- let err = e as Error;
2966
- console.debug(err);
2967
- }
2968
- }, [tenant?.billingDelegation, deepThoughtEnabled]);
3419
+ },
3420
+ [
3421
+ baseUrl,
3422
+ deepThoughtEnabled,
3423
+ handleNotEnoughCredits,
3424
+ ownerUserID,
3425
+ ownerUserName,
3426
+ tenant?.billingDelegation,
3427
+ tenantID,
3428
+ ]
3429
+ );
2969
3430
  useEffect(() => {
2970
3431
  if (tenant?.billingDelegation) {
2971
3432
  checkCredits();
2972
3433
  }
2973
- }, [tenant?.billingDelegation, deepThoughtEnabled]);
3434
+ }, [tenant?.billingDelegation, deepThoughtEnabled, checkCredits]);
2974
3435
 
2975
3436
  useEffect(() => {
2976
3437
  if (__WEBCOMPONENT__) return;
@@ -3043,7 +3504,7 @@ const MemoriWidget = ({
3043
3504
  setShowLoginDrawer(false);
3044
3505
  setUser(undefined);
3045
3506
  setLoginToken(undefined);
3046
- userToken = undefined;
3507
+ userTokenRef.current = undefined;
3047
3508
  removeLocalConfig('loginToken');
3048
3509
  });
3049
3510
  },
@@ -3120,8 +3581,7 @@ const MemoriWidget = ({
3120
3581
  showTypingText:
3121
3582
  showTypingText ?? integrationConfig?.showTypingText ?? false,
3122
3583
  history: showFullHistory ? history : history.slice(-2),
3123
- authToken:
3124
- loginToken ?? userToken ?? additionalInfo?.loginToken ?? authToken,
3584
+ authToken: resolveLoginToken(),
3125
3585
  dialogState: currentDialogState,
3126
3586
  pushMessage,
3127
3587
  simulateUserPrompt,
@@ -3225,9 +3685,12 @@ const MemoriWidget = ({
3225
3685
 
3226
3686
  // Resolve the widget theme once so both `data-theme` on the root and the
3227
3687
  // `MemoriUIProvider` (which stamps portaled popups) stay in sync.
3688
+ // Prefer integration config; fall back to host dark mode when unset.
3228
3689
  const widgetTheme: 'light' | 'dark' | undefined =
3229
3690
  integrationConfig?.theme === 'light' || integrationConfig?.theme === 'dark'
3230
3691
  ? integrationConfig.theme
3692
+ : isDarkTheme
3693
+ ? 'dark'
3231
3694
  : undefined;
3232
3695
 
3233
3696
  return (
@@ -3263,24 +3726,28 @@ const MemoriWidget = ({
3263
3726
  style={{ height }}
3264
3727
  >
3265
3728
  <MemoriUIProvider container={widgetRootEl} theme={widgetTheme}>
3266
- <Layout
3267
- Header={Header}
3268
- headerProps={headerProps}
3269
- Avatar={Avatar}
3270
- avatarProps={avatarProps}
3271
- Chat={Chat}
3272
- chatProps={chatProps}
3273
- StartPanel={StartPanel}
3274
- startPanelProps={startPanelProps}
3275
- integrationStyle={integrationStyle}
3276
- integrationBackground={integrationBackground}
3277
- poweredBy={poweredBy}
3278
- autoStart={autoStart}
3279
- sessionId={sessionId}
3280
- hasUserActivatedSpeak={hasUserActivatedSpeak}
3281
- loading={loading}
3282
- avatar3dHidden={avatar3dHidden ?? integrationConfig?.avatar_3d_hidden}
3283
- />
3729
+ <WidgetSurfaceProvider value={widgetSurfaceEl}>
3730
+ <div ref={setWidgetSurfaceEl} className="memori-widget__surface">
3731
+ <Layout
3732
+ Header={Header}
3733
+ headerProps={headerProps}
3734
+ Avatar={Avatar}
3735
+ avatarProps={avatarProps}
3736
+ Chat={Chat}
3737
+ chatProps={chatProps}
3738
+ StartPanel={StartPanel}
3739
+ startPanelProps={startPanelProps}
3740
+ integrationStyle={integrationStyle}
3741
+ integrationBackground={integrationBackground}
3742
+ poweredBy={poweredBy}
3743
+ autoStart={autoStart}
3744
+ sessionId={sessionId}
3745
+ hasUserActivatedSpeak={hasUserActivatedSpeak}
3746
+ loading={loading}
3747
+ avatar3dHidden={
3748
+ avatar3dHidden ?? integrationConfig?.avatar_3d_hidden
3749
+ }
3750
+ />
3284
3751
 
3285
3752
  <ArtifactAPIBridge
3286
3753
  pushMessage={(message: Message) => {
@@ -3520,7 +3987,7 @@ const MemoriWidget = ({
3520
3987
  setShowLoginDrawer(false);
3521
3988
  setUser(user);
3522
3989
  setLoginToken(token);
3523
- userToken = token;
3990
+ userTokenRef.current = token;
3524
3991
  setLocalConfig('loginToken', token);
3525
3992
  // Push a message with initial status to show status message when a new session is created after login
3526
3993
  if (
@@ -3563,12 +4030,14 @@ const MemoriWidget = ({
3563
4030
  setShowLoginDrawer(false);
3564
4031
  setUser(undefined);
3565
4032
  setLoginToken(undefined);
3566
- userToken = undefined;
4033
+ userTokenRef.current = undefined;
3567
4034
  removeLocalConfig('loginToken');
3568
4035
  });
3569
4036
  }}
3570
4037
  />
3571
4038
  )}
4039
+ </div>
4040
+ </WidgetSurfaceProvider>
3572
4041
  </MemoriUIProvider>
3573
4042
  </div>
3574
4043
  );