@memori.ai/memori-react 8.38.8 → 8.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/components/MemoriWidget/MemoriWidget.js +401 -87
  3. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  4. package/dist/helpers/credits.d.ts +5 -2
  5. package/dist/helpers/credits.js +5 -1
  6. package/dist/helpers/credits.js.map +1 -1
  7. package/dist/helpers/nats/getNatsConfig.d.ts +5 -0
  8. package/dist/helpers/nats/getNatsConfig.js +29 -0
  9. package/dist/helpers/nats/getNatsConfig.js.map +1 -0
  10. package/dist/helpers/nats/useNats.d.ts +12 -0
  11. package/dist/helpers/nats/useNats.js +72 -0
  12. package/dist/helpers/nats/useNats.js.map +1 -0
  13. package/dist/helpers/nats/useNatsSession.d.ts +27 -0
  14. package/dist/helpers/nats/useNatsSession.js +108 -0
  15. package/dist/helpers/nats/useNatsSession.js.map +1 -0
  16. package/dist/version.d.ts +1 -1
  17. package/dist/version.js +1 -1
  18. package/esm/components/MemoriWidget/MemoriWidget.js +401 -87
  19. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  20. package/esm/helpers/credits.d.ts +5 -2
  21. package/esm/helpers/credits.js +5 -1
  22. package/esm/helpers/credits.js.map +1 -1
  23. package/esm/helpers/nats/getNatsConfig.d.ts +5 -0
  24. package/esm/helpers/nats/getNatsConfig.js +25 -0
  25. package/esm/helpers/nats/getNatsConfig.js.map +1 -0
  26. package/esm/helpers/nats/useNats.d.ts +12 -0
  27. package/esm/helpers/nats/useNats.js +68 -0
  28. package/esm/helpers/nats/useNats.js.map +1 -0
  29. package/esm/helpers/nats/useNatsSession.d.ts +27 -0
  30. package/esm/helpers/nats/useNatsSession.js +103 -0
  31. package/esm/helpers/nats/useNatsSession.js.map +1 -0
  32. package/esm/version.d.ts +1 -1
  33. package/esm/version.js +1 -1
  34. package/package.json +3 -2
  35. package/src/components/MemoriWidget/MemoriWidget.tsx +546 -140
  36. package/src/components/StartPanel/StartPanel.stories.tsx +21 -0
  37. package/src/components/StartPanel/StartPanel.test.tsx +66 -1
  38. package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +156 -0
  39. package/src/components/layouts/layouts.stories.tsx +28 -34
  40. package/src/helpers/credits.ts +16 -1
  41. package/src/helpers/nats/getNatsConfig.ts +69 -0
  42. package/src/helpers/nats/useNats.ts +122 -0
  43. package/src/helpers/nats/useNatsSession.ts +210 -0
  44. package/src/index.stories.tsx +19 -3
  45. package/src/version.ts +1 -1
@@ -41,6 +41,7 @@ const sanitizer_1 = require("../../helpers/sanitizer");
41
41
  const useTTS_1 = require("../../helpers/tts/useTTS");
42
42
  const ChatHistory_1 = tslib_1.__importDefault(require("../ChatHistoryDrawer/ChatHistory"));
43
43
  const useSTT_1 = require("../../helpers/stt/useSTT");
44
+ const useNats_1 = require("../../helpers/nats/useNats");
44
45
  const getMemoriState = (integrationId) => {
45
46
  var _a, _b, _c, _d, _f;
46
47
  let widget = integrationId
@@ -68,6 +69,11 @@ const NULL_PLACE_SPEC = {
68
69
  longitude: null,
69
70
  uncertaintyKm: null,
70
71
  };
72
+ const ENTER_TEXT_NATS_TIMEOUT_MS = 120000;
73
+ function readCorrelationID(response) {
74
+ const value = response.correlationID;
75
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
76
+ }
71
77
  const typeMessage = (message, waitForPrevious = true, hidden = false, typingText, useLoaderTextAsMsg = false, hasBatchQueued = false) => {
72
78
  const e = new CustomEvent('MemoriTextEntered', {
73
79
  detail: {
@@ -172,7 +178,7 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
172
178
  setIsClient(true);
173
179
  }, []);
174
180
  const client = (0, memori_api_client_1.default)(apiURL, engineURL);
175
- const { initSession, deleteSession, postTextEnteredEvent, postPlaceChangedEvent, postDateChangedEvent, postTagChangedEvent, getSession, getExpertReferences, getSessionChatLogs, } = client;
181
+ const { initSession, deleteSession, postEnterTextAsync, postTextEnteredEvent, postPlaceChangedEvent, postDateChangedEvent, postTagChangedEvent, getSession, getExpertReferences, getSessionChatLogs, } = client;
176
182
  const [instruct, setInstruct] = (0, react_1.useState)(false);
177
183
  const [enableFocusChatInput, setEnableFocusChatInput] = (0, react_1.useState)(true);
178
184
  const [loginToken, setLoginToken] = (0, react_1.useState)((_a = additionalInfo === null || additionalInfo === void 0 ? void 0 : additionalInfo.loginToken) !== null && _a !== void 0 ? _a : authToken);
@@ -202,7 +208,6 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
202
208
  }, [loginToken, user === null || user === void 0 ? void 0 : user.userID]);
203
209
  const [showLoginDrawer, setShowLoginDrawer] = (0, react_1.useState)(false);
204
210
  const [clickedStart, setClickedStart] = (0, react_1.useState)(false);
205
- const [gotErrorInOpening, setGotErrorInOpening] = (0, react_1.useState)(false);
206
211
  const language = ((_d = (_c = (_b = memori.culture) === null || _b === void 0 ? void 0 : _b.split('-')) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.toUpperCase()) ||
207
212
  ((_j = (_h = (_g = (_f = memoriConfigs === null || memoriConfigs === void 0 ? void 0 : memoriConfigs.find(c => c.memoriConfigID === memori.memoriConfigurationID)) === null || _f === void 0 ? void 0 : _f.culture) === null || _g === void 0 ? void 0 : _g.split('-')) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.toUpperCase());
208
213
  const integrationConfig = (integration === null || integration === void 0 ? void 0 : integration.customData)
@@ -238,6 +243,8 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
238
243
  const [loading, setLoading] = (0, react_1.useState)(false);
239
244
  const [memoriTyping, setMemoriTyping] = (0, react_1.useState)(false);
240
245
  const [typingText, setTypingText] = (0, react_1.useState)();
246
+ const pendingEnterTextRef = (0, react_1.useRef)(new Map());
247
+ const bufferedNatsResponsesRef = (0, react_1.useRef)(new Map());
241
248
  const layoutName = typeof layout === 'string'
242
249
  ? layout
243
250
  : typeof (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.layout) === 'string'
@@ -324,14 +331,12 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
324
331
  if (!venue)
325
332
  return undefined;
326
333
  const place = {};
327
- if (venue.latitude != null &&
328
- venue.longitude != null) {
334
+ if (venue.latitude != null && venue.longitude != null) {
329
335
  place.latitude = venue.latitude;
330
336
  place.longitude = venue.longitude;
331
337
  if (venue.placeName)
332
338
  place.placeName = venue.placeName;
333
- if (venue.uncertainty != null &&
334
- venue.uncertainty > 0)
339
+ if (venue.uncertainty != null && venue.uncertainty > 0)
335
340
  place.uncertaintyKm = venue.uncertainty;
336
341
  }
337
342
  else if (venue.placeName) {
@@ -388,7 +393,7 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
388
393
  };
389
394
  const [chatLogID, setChatLogID] = (0, react_1.useState)(undefined);
390
395
  const sendMessage = async (text, media, newSessionId, translate = true, translatedText, hidden = false, typingText, useLoaderTextAsMsg = false, hasBatchQueued = false) => {
391
- var _a, _b, _c, _d, _f, _g;
396
+ var _a, _b, _c, _d;
392
397
  const sessionID = newSessionId ||
393
398
  sessionId ||
394
399
  ((_a = window.getMemoriState()) === null || _a === void 0 ? void 0 : _a.sessionID);
@@ -442,12 +447,16 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
442
447
  ? !!newSessionId && newSessionId !== sessionId
443
448
  : !!newSessionId,
444
449
  });
445
- setMemoriTyping(true);
446
- setTypingText(typingText);
447
450
  let gotError = false;
448
451
  try {
449
452
  const placeSpec = getPlaceSpecForEnterText(position);
450
- const { currentState, ...response } = await postTextEnteredEvent({
453
+ console.debug('[EnterText] sendMessage: posting', {
454
+ sessionId: sessionID,
455
+ textLength: msg.length,
456
+ hasBatchQueued,
457
+ typingText,
458
+ });
459
+ const response = await postEnterTextAsync({
451
460
  sessionId: sessionID,
452
461
  text: msg,
453
462
  ...(memori.needsDateTime && {
@@ -455,57 +464,35 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
455
464
  }),
456
465
  ...(placeSpec !== undefined && { place: placeSpec }),
457
466
  });
458
- if (response.resultCode === 0 && currentState) {
459
- setChatLogID(undefined);
460
- const emission = useLoaderTextAsMsg && typingText
461
- ? typingText
462
- : (_c = currentState.emission) !== null && _c !== void 0 ? _c : currentDialogState === null || currentDialogState === void 0 ? void 0 : currentDialogState.emission;
463
- if (userLang.toLowerCase() !== language.toLowerCase() &&
464
- emission &&
465
- isMultilanguageEnabled) {
466
- currentState.emission = emission;
467
- translateDialogState(currentState, userLang, msg).then(ts => {
468
- let text = ts.translatedEmission || ts.emission;
469
- if (text && shouldPlayAudio(text)) {
470
- handleSpeak(text);
471
- }
472
- });
473
- }
474
- else {
475
- setCurrentDialogState({
476
- ...currentState,
477
- emission,
478
- });
479
- if (emission) {
480
- pushMessage({
481
- text: emission,
482
- emitter: currentState.emitter,
483
- media: (_d = currentState.emittedMedia) !== null && _d !== void 0 ? _d : currentState.media,
484
- llmUsage: currentState.llmUsage,
485
- fromUser: false,
486
- questionAnswered: msg,
487
- generatedByAI: !!currentState.completion,
488
- contextVars: currentState.contextVars,
489
- date: currentState.currentDate,
490
- placeName: currentState.currentPlaceName,
491
- placeLatitude: currentState.currentLatitude,
492
- placeLongitude: currentState.currentLongitude,
493
- placeUncertaintyKm: currentState.currentUncertaintyKm,
494
- tag: currentState.currentTag,
495
- memoryTags: currentState.memoryTags,
496
- });
497
- if (emission && shouldPlayAudio(emission)) {
498
- handleSpeak(emission);
499
- }
500
- }
501
- }
467
+ console.debug('[EnterText] sendMessage: HTTP response', {
468
+ resultCode: response.resultCode,
469
+ correlationID: readCorrelationID(response),
470
+ resultMessage: response.resultMessage,
471
+ });
472
+ const correlationID = readCorrelationID(response);
473
+ if (response.resultCode === 0 && correlationID) {
474
+ registerPendingEnterText(correlationID, {
475
+ msg,
476
+ typingText,
477
+ useLoaderTextAsMsg,
478
+ hasBatchQueued,
479
+ });
480
+ console.info('[EnterText] sendMessage: accepted, showing typing indicator', {
481
+ correlationID: correlationID,
482
+ typingText,
483
+ });
484
+ setMemoriTyping(true);
485
+ setTypingText(typingText);
486
+ }
487
+ else if (response.resultCode === 0) {
488
+ console.error('[EnterText] sendMessage: HTTP 200 but missing correlationID — cannot match NATS response', response);
502
489
  }
503
490
  else if (response.resultCode === 404) {
504
491
  setHistory(h => [...h.slice(0, h.length - 1)]);
505
492
  reopenSession(true, memoriPwd || memori.secretToken, memoriTokens, undefined, undefined, {
506
493
  LANG: userLang,
507
494
  PATHNAME: window.location.pathname,
508
- ROUTE: ((_g = (_f = window.location.pathname) === null || _f === void 0 ? void 0 : _f.split('/')) === null || _g === void 0 ? void 0 : _g.pop()) || '',
495
+ ROUTE: ((_d = (_c = window.location.pathname) === null || _c === void 0 ? void 0 : _c.split('/')) === null || _d === void 0 ? void 0 : _d.pop()) || '',
509
496
  ...(initialContextVars || {}),
510
497
  }, initialQuestion, undefined, undefined, undefined, undefined, true).then(state => {
511
498
  console.info('session timeout');
@@ -535,16 +522,11 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
535
522
  }
536
523
  }
537
524
  catch (error) {
538
- console.log('error', error);
539
- console.error(error);
525
+ console.error('[EnterText] sendMessage: request failed', error);
540
526
  gotError = true;
541
527
  setTypingText(undefined);
542
528
  setMemoriTyping(false);
543
529
  }
544
- if (!hasBatchQueued) {
545
- setTypingText(undefined);
546
- setMemoriTyping(false);
547
- }
548
530
  };
549
531
  const translateDialogState = async (state, userLang, msg, avoidPushingMessage = false) => {
550
532
  var _a, _b, _c, _d, _f, _g;
@@ -766,6 +748,9 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
766
748
  setAuthModalState('password');
767
749
  return;
768
750
  }
751
+ if (!(await checkCredits({ notify: true }))) {
752
+ return;
753
+ }
769
754
  setLoading(true);
770
755
  try {
771
756
  let referral;
@@ -809,9 +794,8 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
809
794
  else if (session === null || session === void 0 ? void 0 : session.resultMessage.startsWith('This Memori is aged restricted')) {
810
795
  console.warn(session);
811
796
  react_hot_toast_1.default.error(t('underageTwinSession', { age: minAge }));
812
- setGotErrorInOpening(true);
813
797
  }
814
- else if ((session === null || session === void 0 ? void 0 : session.resultCode) === 403) {
798
+ else if ((session === null || session === void 0 ? void 0 : session.resultCode) === 403 && memori.privacyType !== 'PUBLIC') {
815
799
  setMemoriPwd(undefined);
816
800
  setAuthModalState('password');
817
801
  return session;
@@ -821,7 +805,6 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
821
805
  react_hot_toast_1.default.error(tst => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { children: t((0, error_1.getErrori18nKey)(session === null || session === void 0 ? void 0 : session.resultCode)) }), (0, jsx_runtime_1.jsx)(Button_1.default, { outlined: true, padded: false, onClick: () => react_hot_toast_1.default.dismiss(tst.id), icon: (0, jsx_runtime_1.jsx)(Close_1.default, {}), children: t('close') })] })), {
822
806
  duration: Infinity,
823
807
  });
824
- setGotErrorInOpening(true);
825
808
  return session;
826
809
  }
827
810
  }
@@ -848,6 +831,10 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
848
831
  setAuthModalState('password');
849
832
  return;
850
833
  }
834
+ if (!(await checkCredits({ notify: true }))) {
835
+ setLoading(false);
836
+ return null;
837
+ }
851
838
  let referral;
852
839
  try {
853
840
  referral = (() => {
@@ -941,9 +928,9 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
941
928
  else if (response === null || response === void 0 ? void 0 : response.resultMessage.startsWith('This Memori is aged restricted')) {
942
929
  console.error('[REOPEN_SESSION] Age restriction error:', response);
943
930
  react_hot_toast_1.default.error(t('underageTwinSession', { age: minAge }));
944
- setGotErrorInOpening(true);
945
931
  }
946
- else if ((response === null || response === void 0 ? void 0 : response.resultCode) === 403) {
932
+ else if ((response === null || response === void 0 ? void 0 : response.resultCode) === 403 &&
933
+ memori.privacyType !== 'PUBLIC') {
947
934
  console.error('[REOPEN_SESSION] Authentication error');
948
935
  setMemoriPwd(undefined);
949
936
  setAuthModalState('password');
@@ -951,7 +938,6 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
951
938
  else {
952
939
  console.error('[REOPEN_SESSION] Other error:', response);
953
940
  react_hot_toast_1.default.error(t((0, error_1.getErrori18nKey)(response.resultCode)));
954
- setGotErrorInOpening(true);
955
941
  }
956
942
  }
957
943
  catch (err) {
@@ -1158,6 +1144,277 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1158
1144
  setHasUserActivatedSpeak,
1159
1145
  speakerMuted,
1160
1146
  ]);
1147
+ const processEnterTextDialogResponse = (0, react_1.useCallback)((event, pending) => {
1148
+ var _a, _b;
1149
+ console.debug('[EnterText] processDialogResponse', {
1150
+ correlationID: event.correlationID,
1151
+ resultCode: event.resultCode,
1152
+ hasCurrentState: !!event.currentState,
1153
+ hasBatchQueued: pending.hasBatchQueued,
1154
+ });
1155
+ const { msg, typingText: pendingTypingText, useLoaderTextAsMsg, } = pending;
1156
+ const currentState = event.currentState;
1157
+ if (event.resultCode !== 0 || !currentState) {
1158
+ if (event.resultCode === 500 && event.resultMessage) {
1159
+ console.warn('[EnterText] processDialogResponse: server error', {
1160
+ correlationID: event.correlationID,
1161
+ resultMessage: event.resultMessage,
1162
+ });
1163
+ setHistory(h => [
1164
+ ...h,
1165
+ {
1166
+ text: 'Error: ' + event.resultMessage,
1167
+ emitter: 'system',
1168
+ fromUser: false,
1169
+ initial: false,
1170
+ contextVars: {},
1171
+ date: new Date().toISOString(),
1172
+ },
1173
+ ]);
1174
+ }
1175
+ else if (event.resultCode !== 0) {
1176
+ console.warn('[SEND_MESSAGE/NATS]', event);
1177
+ }
1178
+ return;
1179
+ }
1180
+ if (!msg) {
1181
+ console.debug('[EnterText] processDialogResponse: no msg in pending, skipping');
1182
+ return;
1183
+ }
1184
+ setChatLogID(undefined);
1185
+ const emission = useLoaderTextAsMsg && pendingTypingText
1186
+ ? pendingTypingText
1187
+ : (_a = currentState.emission) !== null && _a !== void 0 ? _a : currentDialogState === null || currentDialogState === void 0 ? void 0 : currentDialogState.emission;
1188
+ console.debug('[EnterText] processDialogResponse: rendering emission', {
1189
+ correlationID: event.correlationID,
1190
+ emissionPreview: emission === null || emission === void 0 ? void 0 : emission.slice(0, 80),
1191
+ state: currentState.state,
1192
+ });
1193
+ if (userLang.toLowerCase() !== language.toLowerCase() &&
1194
+ emission &&
1195
+ isMultilanguageEnabled) {
1196
+ currentState.emission = emission;
1197
+ translateDialogState(currentState, userLang, msg).then(ts => {
1198
+ const text = ts.translatedEmission || ts.emission;
1199
+ if (text && shouldPlayAudio(text)) {
1200
+ handleSpeak(text);
1201
+ }
1202
+ });
1203
+ }
1204
+ else {
1205
+ setCurrentDialogState({
1206
+ ...currentState,
1207
+ emission,
1208
+ });
1209
+ if (emission) {
1210
+ pushMessage({
1211
+ text: emission,
1212
+ emitter: currentState.emitter,
1213
+ media: (_b = currentState.emittedMedia) !== null && _b !== void 0 ? _b : currentState.media,
1214
+ llmUsage: currentState.llmUsage,
1215
+ fromUser: false,
1216
+ questionAnswered: msg,
1217
+ generatedByAI: !!currentState.completion,
1218
+ contextVars: currentState.contextVars,
1219
+ date: currentState.currentDate,
1220
+ placeName: currentState.currentPlaceName,
1221
+ placeLatitude: currentState.currentLatitude,
1222
+ placeLongitude: currentState.currentLongitude,
1223
+ placeUncertaintyKm: currentState.currentUncertaintyKm,
1224
+ tag: currentState.currentTag,
1225
+ memoryTags: currentState.memoryTags,
1226
+ });
1227
+ if (emission && shouldPlayAudio(emission)) {
1228
+ handleSpeak(emission);
1229
+ }
1230
+ }
1231
+ }
1232
+ }, [
1233
+ userLang,
1234
+ language,
1235
+ isMultilanguageEnabled,
1236
+ currentDialogState === null || currentDialogState === void 0 ? void 0 : currentDialogState.emission,
1237
+ translateDialogState,
1238
+ handleSpeak,
1239
+ shouldPlayAudio,
1240
+ ]);
1241
+ const clearEnterTextPending = (0, react_1.useCallback)((correlationID, pending) => {
1242
+ var _a;
1243
+ if (pending.natsTimeoutId) {
1244
+ clearTimeout(pending.natsTimeoutId);
1245
+ }
1246
+ if ((_a = pending.waitForResponse) === null || _a === void 0 ? void 0 : _a.timeoutId) {
1247
+ clearTimeout(pending.waitForResponse.timeoutId);
1248
+ }
1249
+ pendingEnterTextRef.current.delete(correlationID);
1250
+ }, []);
1251
+ const deliverEnterTextNatsError = (0, react_1.useCallback)((event) => {
1252
+ var _a, _b, _c;
1253
+ const correlationID = event.correlationID;
1254
+ const errorText = event.errorMessage
1255
+ ? `Error: ${event.errorMessage}`
1256
+ : event.errorCode
1257
+ ? `Error: ${event.errorCode}`
1258
+ : 'Error: An unexpected error occurred';
1259
+ console.error('[EnterText] NATS error event', {
1260
+ correlationID,
1261
+ errorCode: event.errorCode,
1262
+ errorMessage: event.errorMessage,
1263
+ });
1264
+ pushMessage({
1265
+ text: errorText,
1266
+ emitter: 'system',
1267
+ fromUser: false,
1268
+ initial: false,
1269
+ contextVars: {},
1270
+ date: new Date().toISOString(),
1271
+ });
1272
+ if (correlationID) {
1273
+ const pending = pendingEnterTextRef.current.get(correlationID);
1274
+ if (pending) {
1275
+ clearEnterTextPending(correlationID, pending);
1276
+ (_a = pending.waitForResponse) === null || _a === void 0 ? void 0 : _a.reject(new Error((_b = event.errorMessage) !== null && _b !== void 0 ? _b : String((_c = event.errorCode) !== null && _c !== void 0 ? _c : 'NATS error')));
1277
+ }
1278
+ }
1279
+ setMemoriTyping(false);
1280
+ setTypingText(undefined);
1281
+ }, [clearEnterTextPending]);
1282
+ const deliverEnterTextNatsResponse = (0, react_1.useCallback)((correlationID, event) => {
1283
+ const pending = pendingEnterTextRef.current.get(correlationID);
1284
+ if (!pending) {
1285
+ const pendingCorrelationIDs = [...pendingEnterTextRef.current.keys()];
1286
+ console.warn('[EnterText] NATS response buffered (no matching pending)', {
1287
+ receivedCorrelationID: correlationID,
1288
+ resultCode: event.resultCode,
1289
+ pendingCorrelationIDs,
1290
+ hint: pendingCorrelationIDs.length > 0
1291
+ ? 'Use one of pendingCorrelationIDs in your nats pub correlation_id'
1292
+ : 'Send a message in the widget first, then copy correlationID from HTTP response logs',
1293
+ });
1294
+ bufferedNatsResponsesRef.current.set(correlationID, event);
1295
+ return;
1296
+ }
1297
+ clearEnterTextPending(correlationID, pending);
1298
+ if (pending.waitForResponse) {
1299
+ console.info('[EnterText] NATS response delivered to waiter', {
1300
+ correlationID,
1301
+ resultCode: event.resultCode,
1302
+ });
1303
+ pending.waitForResponse.resolve(event);
1304
+ setMemoriTyping(false);
1305
+ setTypingText(undefined);
1306
+ return;
1307
+ }
1308
+ processEnterTextDialogResponse(event, pending);
1309
+ if (!pending.hasBatchQueued) {
1310
+ console.info('[EnterText] typing indicator cleared', { correlationID });
1311
+ setMemoriTyping(false);
1312
+ setTypingText(undefined);
1313
+ }
1314
+ else {
1315
+ console.debug('[EnterText] typing kept (batch queued)', {
1316
+ correlationID,
1317
+ });
1318
+ }
1319
+ }, [processEnterTextDialogResponse, clearEnterTextPending]);
1320
+ const registerPendingEnterText = (0, react_1.useCallback)((correlationID, pending) => {
1321
+ const buffered = bufferedNatsResponsesRef.current.get(correlationID);
1322
+ if (buffered) {
1323
+ console.info('[EnterText] replaying buffered NATS response', {
1324
+ correlationID,
1325
+ waitForResponse: !!pending.waitForResponse,
1326
+ });
1327
+ bufferedNatsResponsesRef.current.delete(correlationID);
1328
+ pendingEnterTextRef.current.set(correlationID, pending);
1329
+ deliverEnterTextNatsResponse(correlationID, buffered);
1330
+ return;
1331
+ }
1332
+ if (!pending.waitForResponse && !pending.natsTimeoutId) {
1333
+ pending.natsTimeoutId = setTimeout(() => {
1334
+ var _a;
1335
+ const current = pendingEnterTextRef.current.get(correlationID);
1336
+ if (!current)
1337
+ return;
1338
+ clearEnterTextPending(correlationID, current);
1339
+ console.error('[EnterText] NATS response timeout', {
1340
+ correlationID,
1341
+ timeoutMs: ENTER_TEXT_NATS_TIMEOUT_MS,
1342
+ });
1343
+ if (!current.hasBatchQueued) {
1344
+ setMemoriTyping(false);
1345
+ setTypingText(undefined);
1346
+ }
1347
+ (_a = current.waitForResponse) === null || _a === void 0 ? void 0 : _a.reject(new Error('NATS enter-text response timeout'));
1348
+ }, ENTER_TEXT_NATS_TIMEOUT_MS);
1349
+ }
1350
+ console.debug('[EnterText] pending registered', {
1351
+ correlationID,
1352
+ waitForResponse: !!pending.waitForResponse,
1353
+ hasBatchQueued: pending.hasBatchQueued,
1354
+ });
1355
+ pendingEnterTextRef.current.set(correlationID, pending);
1356
+ }, [deliverEnterTextNatsResponse, clearEnterTextPending]);
1357
+ const waitForEnterTextNatsResponse = (0, react_1.useCallback)((correlationID, timeoutMs = 120000) => new Promise((resolve, reject) => {
1358
+ console.debug('[EnterText] waiting for NATS response', {
1359
+ correlationID,
1360
+ timeoutMs,
1361
+ });
1362
+ const timeoutId = setTimeout(() => {
1363
+ const current = pendingEnterTextRef.current.get(correlationID);
1364
+ if (current) {
1365
+ clearEnterTextPending(correlationID, current);
1366
+ }
1367
+ console.error('[EnterText] NATS response timeout', {
1368
+ correlationID,
1369
+ timeoutMs,
1370
+ });
1371
+ reject(new Error('NATS enter-text response timeout'));
1372
+ }, timeoutMs);
1373
+ registerPendingEnterText(correlationID, {
1374
+ waitForResponse: {
1375
+ resolve: event => {
1376
+ clearTimeout(timeoutId);
1377
+ resolve(event);
1378
+ },
1379
+ reject: error => {
1380
+ clearTimeout(timeoutId);
1381
+ reject(error);
1382
+ },
1383
+ timeoutId,
1384
+ },
1385
+ });
1386
+ }), [registerPendingEnterText, clearEnterTextPending]);
1387
+ (0, useNats_1.useNats)({
1388
+ baseUrl,
1389
+ sessionId,
1390
+ onProgress: (0, react_1.useCallback)((event) => {
1391
+ console.debug('[EnterText] NATS progress', {
1392
+ correlationID: event.correlationID,
1393
+ step: event.currentStep,
1394
+ finalStep: event.finalStep,
1395
+ message: event.message,
1396
+ });
1397
+ if (event.message) {
1398
+ setTypingText(event.message);
1399
+ }
1400
+ }, []),
1401
+ onDialogResponse: (0, react_1.useCallback)((event) => {
1402
+ const correlationID = event.correlationID;
1403
+ console.debug('[EnterText] NATS dialog.text_entered_response received', {
1404
+ correlationID,
1405
+ resultCode: event.resultCode,
1406
+ requestID: event.requestID,
1407
+ });
1408
+ if (!correlationID) {
1409
+ console.warn('[EnterText] dialog_text_entered_response without correlationID', event);
1410
+ setMemoriTyping(false);
1411
+ setTypingText(undefined);
1412
+ return;
1413
+ }
1414
+ deliverEnterTextNatsResponse(correlationID, event);
1415
+ }, [deliverEnterTextNatsResponse]),
1416
+ onError: deliverEnterTextNatsError,
1417
+ });
1161
1418
  const focusChatInput = () => {
1162
1419
  let textarea = document.querySelector('#chat-fieldset textarea');
1163
1420
  if (textarea && enableFocusChatInput) {
@@ -1352,7 +1609,7 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1352
1609
  };
1353
1610
  }, [sessionId, userLang, disableTextEnteredEvents]);
1354
1611
  const onClickStart = (0, react_1.useCallback)(async (session, initialSessionExpired = false, chatLog, targetSessionID) => {
1355
- var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
1612
+ var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
1356
1613
  const sessionID = chatLog ? undefined : (session === null || session === void 0 ? void 0 : session.sessionID) || sessionId;
1357
1614
  const dialogState = chatLog
1358
1615
  ? undefined
@@ -1369,23 +1626,26 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1369
1626
  setShowPositionDrawer(true);
1370
1627
  return;
1371
1628
  }
1629
+ if (!(await checkCredits({ notify: true }))) {
1630
+ setClickedStart(false);
1631
+ setLoading(false);
1632
+ return;
1633
+ }
1372
1634
  if (!sessionID && !!minAge && !birth) {
1373
1635
  setShowAgeVerification(true);
1374
1636
  setClickedStart(false);
1375
1637
  }
1376
- else if ((!sessionID &&
1638
+ else if (!sessionID &&
1377
1639
  memori.privacyType !== 'PUBLIC' &&
1378
1640
  !memori.secretToken &&
1379
1641
  !memoriPwd &&
1380
- !memoriTokens) ||
1381
- (!sessionID && gotErrorInOpening)) {
1642
+ !memoriTokens) {
1382
1643
  setAuthModalState('password');
1383
1644
  setClickedStart(false);
1384
1645
  return;
1385
1646
  }
1386
1647
  else if (!sessionID || initialSessionExpired) {
1387
1648
  setClickedStart(false);
1388
- setGotErrorInOpening(false);
1389
1649
  const session = await fetchSession({
1390
1650
  memoriID: memori.engineMemoriID,
1391
1651
  password: secret || memoriPwd || memori.secretToken,
@@ -1469,7 +1729,6 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1469
1729
  const { currentState, ...response } = await getSession(sessionID);
1470
1730
  if (response.resultCode !== 0 || !currentState) {
1471
1731
  const { chatLogs } = await getSessionChatLogs(sessionID, sessionID);
1472
- setGotErrorInOpening(true);
1473
1732
  setSessionId(undefined);
1474
1733
  setClickedStart(false);
1475
1734
  await onClickStart(undefined, true, chatLogs === null || chatLogs === void 0 ? void 0 : chatLogs[0]);
@@ -1573,9 +1832,11 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1573
1832
  console.log('[onClickStart] Starting with initial question');
1574
1833
  translatedMessages = [];
1575
1834
  setHistory([]);
1576
- setMemoriTyping(true);
1577
1835
  const placeSpec = getPlaceSpecForEnterText(position);
1578
- const response = await postTextEnteredEvent({
1836
+ console.debug('[EnterText] onClickStart: posting initial question', {
1837
+ sessionId: sessionID,
1838
+ });
1839
+ const response = await postEnterTextAsync({
1579
1840
  sessionId: sessionID,
1580
1841
  text: initialQuestion,
1581
1842
  ...(memori.needsDateTime && {
@@ -1583,6 +1844,10 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1583
1844
  }),
1584
1845
  ...(placeSpec !== undefined && { place: placeSpec }),
1585
1846
  });
1847
+ console.debug('[EnterText] onClickStart: HTTP response', {
1848
+ resultCode: response.resultCode,
1849
+ correlationID: readCorrelationID(response),
1850
+ });
1586
1851
  if (response.resultCode === 500 && response.resultMessage) {
1587
1852
  setHistory(h => [
1588
1853
  ...h,
@@ -1595,10 +1860,33 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1595
1860
  date: new Date().toISOString(),
1596
1861
  },
1597
1862
  ]);
1598
- setMemoriTyping(false);
1599
1863
  return;
1600
1864
  }
1601
- await translateAndSpeak((_w = response.currentState) !== null && _w !== void 0 ? _w : currentState, userLang, undefined, false);
1865
+ const onClickStartCorrelationID = readCorrelationID(response);
1866
+ if (response.resultCode === 0 && onClickStartCorrelationID) {
1867
+ console.info('[EnterText] onClickStart: accepted, showing typing indicator', {
1868
+ correlationID: onClickStartCorrelationID,
1869
+ });
1870
+ setMemoriTyping(true);
1871
+ try {
1872
+ const natsEvent = await waitForEnterTextNatsResponse(onClickStartCorrelationID);
1873
+ console.info('[EnterText] onClickStart: NATS response received', {
1874
+ correlationID: onClickStartCorrelationID,
1875
+ resultCode: natsEvent.resultCode,
1876
+ });
1877
+ if (natsEvent.resultCode === 0 && natsEvent.currentState) {
1878
+ await translateAndSpeak(natsEvent.currentState, userLang, undefined, false);
1879
+ }
1880
+ }
1881
+ catch (e) {
1882
+ console.error('[EnterText] onClickStart: NATS wait failed', e);
1883
+ setMemoriTyping(false);
1884
+ setTypingText(undefined);
1885
+ }
1886
+ }
1887
+ else if (response.resultCode === 0) {
1888
+ console.error('[EnterText] onClickStart: HTTP 200 but missing correlationID', response);
1889
+ }
1602
1890
  }
1603
1891
  }
1604
1892
  }
@@ -1669,9 +1957,24 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1669
1957
  (user === null || user === void 0 ? void 0 : user.pAndCUAccepted);
1670
1958
  const needsCredits = tenant === null || tenant === void 0 ? void 0 : tenant.billingDelegation;
1671
1959
  const [hasEnoughCredits, setHasEnoughCredits] = (0, react_1.useState)(true);
1672
- const checkCredits = (0, react_1.useCallback)(async () => {
1960
+ const handleNotEnoughCredits = (0, react_1.useCallback)(() => {
1961
+ setHasEnoughCredits(false);
1962
+ setAuthModalState(null);
1963
+ react_hot_toast_1.default.error(t('notEnoughCredits'));
1964
+ }, [t]);
1965
+ const checkCredits = (0, react_1.useCallback)(async (options) => {
1673
1966
  if (!(tenant === null || tenant === void 0 ? void 0 : tenant.billingDelegation))
1674
- return;
1967
+ return true;
1968
+ if (!ownerUserID && !ownerUserName) {
1969
+ console.warn('Cannot verify credits: missing owner identifier');
1970
+ if (options === null || options === void 0 ? void 0 : options.notify) {
1971
+ handleNotEnoughCredits();
1972
+ }
1973
+ else {
1974
+ setHasEnoughCredits(false);
1975
+ }
1976
+ return false;
1977
+ }
1675
1978
  try {
1676
1979
  const resp = await (0, credits_1.getCredits)({
1677
1980
  operation: deepThoughtEnabled
@@ -1684,22 +1987,38 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1684
1987
  });
1685
1988
  if (resp.enough) {
1686
1989
  setHasEnoughCredits(true);
1990
+ return true;
1687
1991
  }
1688
1992
  else {
1689
- setHasEnoughCredits(false);
1690
1993
  console.warn('Not enough credits. Required:', resp.required);
1994
+ if (options === null || options === void 0 ? void 0 : options.notify) {
1995
+ handleNotEnoughCredits();
1996
+ }
1997
+ else {
1998
+ setHasEnoughCredits(false);
1999
+ }
2000
+ return false;
1691
2001
  }
1692
2002
  }
1693
2003
  catch (e) {
1694
2004
  let err = e;
1695
2005
  console.debug(err);
2006
+ return true;
1696
2007
  }
1697
- }, [tenant === null || tenant === void 0 ? void 0 : tenant.billingDelegation, deepThoughtEnabled]);
2008
+ }, [
2009
+ baseUrl,
2010
+ deepThoughtEnabled,
2011
+ handleNotEnoughCredits,
2012
+ ownerUserID,
2013
+ ownerUserName,
2014
+ tenant === null || tenant === void 0 ? void 0 : tenant.billingDelegation,
2015
+ tenantID,
2016
+ ]);
1698
2017
  (0, react_1.useEffect)(() => {
1699
2018
  if (tenant === null || tenant === void 0 ? void 0 : tenant.billingDelegation) {
1700
2019
  checkCredits();
1701
2020
  }
1702
- }, [tenant === null || tenant === void 0 ? void 0 : tenant.billingDelegation, deepThoughtEnabled]);
2021
+ }, [tenant === null || tenant === void 0 ? void 0 : tenant.billingDelegation, deepThoughtEnabled, checkCredits]);
1703
2022
  (0, react_1.useEffect)(() => {
1704
2023
  if (__WEBCOMPONENT__)
1705
2024
  return;
@@ -1945,10 +2264,6 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1945
2264
  }
1946
2265
  })
1947
2266
  .catch(error => {
1948
- if (!(error instanceof Error) ||
1949
- error.message !== 'AUTH_FAILED') {
1950
- setGotErrorInOpening(true);
1951
- }
1952
2267
  throw error;
1953
2268
  });
1954
2269
  }, minimumNumberOfRecoveryTokens: (_18 = memori === null || memori === void 0 ? void 0 : memori.minimumNumberOfRecoveryTokens) !== null && _18 !== void 0 ? _18 : 1 })), isClient && ((0, jsx_runtime_1.jsx)(AgeVerificationModal_1.default, { visible: showAgeVerification, minAge: minAge, onClose: birthDate => {
@@ -1969,7 +2284,6 @@ const MemoriWidget = ({ memori, memoriConfigs, ownerUserID, ownerUserName, tenan
1969
2284
  })
1970
2285
  .catch(() => {
1971
2286
  setShowAgeVerification(false);
1972
- setGotErrorInOpening(true);
1973
2287
  });
1974
2288
  }
1975
2289
  else {