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