@memori.ai/memori-react 8.39.0 → 8.40.1
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.d.ts +2 -1
- package/dist/components/MemoriWidget/MemoriWidget.js +450 -146
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/helpers/credits.d.ts +3 -2
- package/dist/helpers/credits.js +4 -3
- 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/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.d.ts +2 -1
- package/esm/components/MemoriWidget/MemoriWidget.js +453 -147
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/helpers/credits.d.ts +3 -2
- package/esm/helpers/credits.js +4 -3
- 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/index.js +1 -1
- package/esm/index.js.map +1 -1
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/package.json +3 -2
- package/src/components/MemoriWidget/MemoriWidget.tsx +543 -149
- package/src/components/layouts/layouts.stories.tsx +29 -35
- package/src/helpers/credits.ts +6 -3
- 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/index.tsx +1 -0
- package/src/version.ts +1 -1
|
@@ -85,6 +85,12 @@ import { sanitizeText } from '../../helpers/sanitizer';
|
|
|
85
85
|
import { TTSConfig, useTTS } from '../../helpers/tts/useTTS';
|
|
86
86
|
import ChatHistoryDrawer from '../ChatHistoryDrawer/ChatHistory';
|
|
87
87
|
import { STTConfig, useSTT } from '../../helpers/stt/useSTT';
|
|
88
|
+
import { useNats } from '../../helpers/nats/useNats';
|
|
89
|
+
import {
|
|
90
|
+
NatsProgressEvent,
|
|
91
|
+
NatsDialogResponseEvent,
|
|
92
|
+
NatsErrorEvent,
|
|
93
|
+
} from '../../helpers/nats/useNatsSession';
|
|
88
94
|
|
|
89
95
|
// Widget utilities and helpers
|
|
90
96
|
const getMemoriState = (integrationId?: string): object | null => {
|
|
@@ -115,7 +121,7 @@ const getMemoriState = (integrationId?: string): object | null => {
|
|
|
115
121
|
};
|
|
116
122
|
};
|
|
117
123
|
|
|
118
|
-
/** Place spec with all nulls for
|
|
124
|
+
/** Place spec with all nulls for postEnterTextAsync when position is not set or user chose "I don't want to provide my position". */
|
|
119
125
|
const NULL_PLACE_SPEC = {
|
|
120
126
|
placeName: null,
|
|
121
127
|
latitude: null,
|
|
@@ -123,6 +129,16 @@ const NULL_PLACE_SPEC = {
|
|
|
123
129
|
uncertaintyKm: null,
|
|
124
130
|
} as const;
|
|
125
131
|
|
|
132
|
+
const ENTER_TEXT_NATS_TIMEOUT_MS = 120_000;
|
|
133
|
+
|
|
134
|
+
/** Reads correlation id from HTTP async response (supports camelCase / snake_case). */
|
|
135
|
+
function readCorrelationID(response: {
|
|
136
|
+
correlationID?: string;
|
|
137
|
+
}): string | undefined {
|
|
138
|
+
const value = response.correlationID;
|
|
139
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
126
142
|
type MemoriTextEnteredEvent = CustomEvent<{
|
|
127
143
|
text: string;
|
|
128
144
|
waitForPrevious?: boolean;
|
|
@@ -381,7 +397,7 @@ export interface LayoutProps {
|
|
|
381
397
|
|
|
382
398
|
export interface Props {
|
|
383
399
|
memori: Memori;
|
|
384
|
-
|
|
400
|
+
ownerUserName?: string | null;
|
|
385
401
|
ownerUserID?: string | null;
|
|
386
402
|
tenantID: string;
|
|
387
403
|
memoriConfigs?: MemoriConfig[];
|
|
@@ -453,7 +469,7 @@ const MemoriWidget = ({
|
|
|
453
469
|
memori,
|
|
454
470
|
memoriConfigs,
|
|
455
471
|
ownerUserID,
|
|
456
|
-
|
|
472
|
+
ownerUserName,
|
|
457
473
|
tenantID,
|
|
458
474
|
memoriLang,
|
|
459
475
|
uiLang,
|
|
@@ -521,6 +537,7 @@ const MemoriWidget = ({
|
|
|
521
537
|
const {
|
|
522
538
|
initSession,
|
|
523
539
|
deleteSession,
|
|
540
|
+
postEnterTextAsync,
|
|
524
541
|
postTextEnteredEvent,
|
|
525
542
|
postPlaceChangedEvent,
|
|
526
543
|
postDateChangedEvent,
|
|
@@ -565,6 +582,7 @@ const MemoriWidget = ({
|
|
|
565
582
|
const [showLoginDrawer, setShowLoginDrawer] = useState(false);
|
|
566
583
|
|
|
567
584
|
const [clickedStart, setClickedStart] = useState(false);
|
|
585
|
+
const sessionStartingRef = useRef(false);
|
|
568
586
|
|
|
569
587
|
const language =
|
|
570
588
|
memori.culture?.split('-')?.[0]?.toUpperCase()! ||
|
|
@@ -626,6 +644,23 @@ const MemoriWidget = ({
|
|
|
626
644
|
const [memoriTyping, setMemoriTyping] = useState<boolean>(false);
|
|
627
645
|
const [typingText, setTypingText] = useState<string>();
|
|
628
646
|
|
|
647
|
+
type PendingEnterText = {
|
|
648
|
+
msg?: string;
|
|
649
|
+
typingText?: string;
|
|
650
|
+
useLoaderTextAsMsg?: boolean;
|
|
651
|
+
hasBatchQueued?: boolean;
|
|
652
|
+
natsTimeoutId?: ReturnType<typeof setTimeout>;
|
|
653
|
+
waitForResponse?: {
|
|
654
|
+
resolve: (event: NatsDialogResponseEvent) => void;
|
|
655
|
+
reject: (error: Error) => void;
|
|
656
|
+
timeoutId: ReturnType<typeof setTimeout>;
|
|
657
|
+
};
|
|
658
|
+
};
|
|
659
|
+
const pendingEnterTextRef = useRef<Map<string, PendingEnterText>>(new Map());
|
|
660
|
+
const bufferedNatsResponsesRef = useRef<Map<string, NatsDialogResponseEvent>>(
|
|
661
|
+
new Map()
|
|
662
|
+
);
|
|
663
|
+
|
|
629
664
|
// Layout: from prop (string only) or integrationConfig. PII detection is only from integrationConfig (customData.layout as object with piiDetection).
|
|
630
665
|
const layoutName =
|
|
631
666
|
typeof layout === 'string'
|
|
@@ -783,7 +818,7 @@ const MemoriWidget = ({
|
|
|
783
818
|
return Object.keys(place).length > 0 ? place : undefined;
|
|
784
819
|
}, []);
|
|
785
820
|
|
|
786
|
-
/** Place to send with
|
|
821
|
+
/** Place to send with postEnterTextAsync: real place, nulls when no/declined position, or undefined when position not needed. */
|
|
787
822
|
const getPlaceSpecForEnterText = useCallback(
|
|
788
823
|
(venue: Venue | undefined) => {
|
|
789
824
|
if (!memori.needsPosition) return undefined;
|
|
@@ -945,27 +980,18 @@ const MemoriWidget = ({
|
|
|
945
980
|
: !!newSessionId,
|
|
946
981
|
});
|
|
947
982
|
|
|
948
|
-
// Show typing indicator
|
|
949
|
-
setMemoriTyping(true);
|
|
950
|
-
setTypingText(typingText);
|
|
951
|
-
|
|
983
|
+
// Show typing indicator after the async enter-text request is accepted (HTTP 200).
|
|
952
984
|
let gotError = false;
|
|
953
985
|
|
|
954
986
|
try {
|
|
955
|
-
// Add chat reference link to the message if it exists
|
|
956
|
-
// if (chatLogID) {
|
|
957
|
-
// msg =
|
|
958
|
-
// msg +
|
|
959
|
-
// ' \n\n' +
|
|
960
|
-
// '<chat-reference session-id="' +
|
|
961
|
-
// sessionID +
|
|
962
|
-
// '" event-log-id="' +
|
|
963
|
-
// chatLogID +
|
|
964
|
-
// '"></chat-reference>';
|
|
965
|
-
// }
|
|
966
|
-
|
|
967
987
|
const placeSpec = getPlaceSpecForEnterText(position);
|
|
968
|
-
|
|
988
|
+
console.debug('[EnterText] sendMessage: posting', {
|
|
989
|
+
sessionId: sessionID,
|
|
990
|
+
textLength: msg.length,
|
|
991
|
+
hasBatchQueued,
|
|
992
|
+
typingText,
|
|
993
|
+
});
|
|
994
|
+
const response = await postEnterTextAsync({
|
|
969
995
|
sessionId: sessionID,
|
|
970
996
|
text: msg,
|
|
971
997
|
...(memori.needsDateTime && {
|
|
@@ -973,55 +999,33 @@ const MemoriWidget = ({
|
|
|
973
999
|
}),
|
|
974
1000
|
...(placeSpec !== undefined && { place: placeSpec }),
|
|
975
1001
|
});
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
}
|
|
995
|
-
});
|
|
996
|
-
} else {
|
|
997
|
-
setCurrentDialogState({
|
|
998
|
-
...currentState,
|
|
999
|
-
emission,
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
if (emission) {
|
|
1003
|
-
pushMessage({
|
|
1004
|
-
text: emission,
|
|
1005
|
-
emitter: currentState.emitter,
|
|
1006
|
-
media: currentState.emittedMedia ?? currentState.media,
|
|
1007
|
-
llmUsage: (currentState as any).llmUsage,
|
|
1008
|
-
fromUser: false,
|
|
1009
|
-
questionAnswered: msg,
|
|
1010
|
-
generatedByAI: !!currentState.completion,
|
|
1011
|
-
contextVars: currentState.contextVars,
|
|
1012
|
-
date: currentState.currentDate,
|
|
1013
|
-
placeName: currentState.currentPlaceName,
|
|
1014
|
-
placeLatitude: currentState.currentLatitude,
|
|
1015
|
-
placeLongitude: currentState.currentLongitude,
|
|
1016
|
-
placeUncertaintyKm: currentState.currentUncertaintyKm,
|
|
1017
|
-
tag: currentState.currentTag,
|
|
1018
|
-
memoryTags: currentState.memoryTags,
|
|
1019
|
-
} as any);
|
|
1020
|
-
if (emission && shouldPlayAudio(emission)) {
|
|
1021
|
-
handleSpeak(emission);
|
|
1022
|
-
}
|
|
1002
|
+
console.debug('[EnterText] sendMessage: HTTP response', {
|
|
1003
|
+
resultCode: response.resultCode,
|
|
1004
|
+
correlationID: readCorrelationID(response),
|
|
1005
|
+
resultMessage: response.resultMessage,
|
|
1006
|
+
});
|
|
1007
|
+
const correlationID = readCorrelationID(response);
|
|
1008
|
+
if (response.resultCode === 0 && correlationID) {
|
|
1009
|
+
registerPendingEnterText(correlationID, {
|
|
1010
|
+
msg,
|
|
1011
|
+
typingText,
|
|
1012
|
+
useLoaderTextAsMsg,
|
|
1013
|
+
hasBatchQueued,
|
|
1014
|
+
});
|
|
1015
|
+
console.info(
|
|
1016
|
+
'[EnterText] sendMessage: accepted, showing typing indicator',
|
|
1017
|
+
{
|
|
1018
|
+
correlationID: correlationID,
|
|
1019
|
+
typingText,
|
|
1023
1020
|
}
|
|
1024
|
-
|
|
1021
|
+
);
|
|
1022
|
+
setMemoriTyping(true);
|
|
1023
|
+
setTypingText(typingText);
|
|
1024
|
+
} else if (response.resultCode === 0) {
|
|
1025
|
+
console.error(
|
|
1026
|
+
'[EnterText] sendMessage: HTTP 200 but missing correlationID — cannot match NATS response',
|
|
1027
|
+
response
|
|
1028
|
+
);
|
|
1025
1029
|
} else if (response.resultCode === 404) {
|
|
1026
1030
|
// Handle expired session
|
|
1027
1031
|
// remove last sent message, will set it as initial
|
|
@@ -1070,18 +1074,12 @@ const MemoriWidget = ({
|
|
|
1070
1074
|
return Promise.reject(response);
|
|
1071
1075
|
}
|
|
1072
1076
|
} catch (error) {
|
|
1073
|
-
console.
|
|
1074
|
-
console.error(error);
|
|
1077
|
+
console.error('[EnterText] sendMessage: request failed', error);
|
|
1075
1078
|
gotError = true;
|
|
1076
1079
|
|
|
1077
1080
|
setTypingText(undefined);
|
|
1078
1081
|
setMemoriTyping(false);
|
|
1079
1082
|
}
|
|
1080
|
-
|
|
1081
|
-
if (!hasBatchQueued) {
|
|
1082
|
-
setTypingText(undefined);
|
|
1083
|
-
setMemoriTyping(false);
|
|
1084
|
-
}
|
|
1085
1083
|
};
|
|
1086
1084
|
|
|
1087
1085
|
/**
|
|
@@ -1388,7 +1386,7 @@ const MemoriWidget = ({
|
|
|
1388
1386
|
return;
|
|
1389
1387
|
}
|
|
1390
1388
|
|
|
1391
|
-
if (!(await checkCredits({ notify: true
|
|
1389
|
+
if (!(await checkCredits({ notify: true }))) {
|
|
1392
1390
|
return;
|
|
1393
1391
|
}
|
|
1394
1392
|
|
|
@@ -1562,7 +1560,7 @@ const MemoriWidget = ({
|
|
|
1562
1560
|
return;
|
|
1563
1561
|
}
|
|
1564
1562
|
|
|
1565
|
-
if (!(await checkCredits({ notify: true
|
|
1563
|
+
if (!(await checkCredits({ notify: true }))) {
|
|
1566
1564
|
setLoading(false);
|
|
1567
1565
|
return null;
|
|
1568
1566
|
}
|
|
@@ -1846,6 +1844,7 @@ const MemoriWidget = ({
|
|
|
1846
1844
|
return () => {
|
|
1847
1845
|
setHasUserActivatedSpeak(false);
|
|
1848
1846
|
setClickedStart(false);
|
|
1847
|
+
sessionStartingRef.current = false;
|
|
1849
1848
|
clearInteractionTimeout();
|
|
1850
1849
|
timeoutRef.current = undefined;
|
|
1851
1850
|
};
|
|
@@ -2046,6 +2045,341 @@ const MemoriWidget = ({
|
|
|
2046
2045
|
]
|
|
2047
2046
|
);
|
|
2048
2047
|
|
|
2048
|
+
const processEnterTextDialogResponse = useCallback(
|
|
2049
|
+
(event: NatsDialogResponseEvent, pending: PendingEnterText) => {
|
|
2050
|
+
console.debug('[EnterText] processDialogResponse', {
|
|
2051
|
+
correlationID: event.correlationID,
|
|
2052
|
+
resultCode: event.resultCode,
|
|
2053
|
+
hasCurrentState: !!event.currentState,
|
|
2054
|
+
hasBatchQueued: pending.hasBatchQueued,
|
|
2055
|
+
});
|
|
2056
|
+
const {
|
|
2057
|
+
msg,
|
|
2058
|
+
typingText: pendingTypingText,
|
|
2059
|
+
useLoaderTextAsMsg,
|
|
2060
|
+
} = pending;
|
|
2061
|
+
const currentState = event.currentState;
|
|
2062
|
+
|
|
2063
|
+
if (event.resultCode !== 0 || !currentState) {
|
|
2064
|
+
if (event.resultCode === 500 && event.resultMessage) {
|
|
2065
|
+
console.warn('[EnterText] processDialogResponse: server error', {
|
|
2066
|
+
correlationID: event.correlationID,
|
|
2067
|
+
resultMessage: event.resultMessage,
|
|
2068
|
+
});
|
|
2069
|
+
setHistory(h => [
|
|
2070
|
+
...h,
|
|
2071
|
+
{
|
|
2072
|
+
text: 'Error: ' + event.resultMessage,
|
|
2073
|
+
emitter: 'system',
|
|
2074
|
+
fromUser: false,
|
|
2075
|
+
initial: false,
|
|
2076
|
+
contextVars: {},
|
|
2077
|
+
date: new Date().toISOString(),
|
|
2078
|
+
},
|
|
2079
|
+
]);
|
|
2080
|
+
} else if (event.resultCode !== 0) {
|
|
2081
|
+
console.warn('[SEND_MESSAGE/NATS]', event);
|
|
2082
|
+
}
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
if (!msg) {
|
|
2087
|
+
console.debug(
|
|
2088
|
+
'[EnterText] processDialogResponse: no msg in pending, skipping'
|
|
2089
|
+
);
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
setChatLogID(undefined);
|
|
2094
|
+
const emission =
|
|
2095
|
+
useLoaderTextAsMsg && pendingTypingText
|
|
2096
|
+
? pendingTypingText
|
|
2097
|
+
: currentState.emission ?? currentDialogState?.emission;
|
|
2098
|
+
|
|
2099
|
+
console.debug('[EnterText] processDialogResponse: rendering emission', {
|
|
2100
|
+
correlationID: event.correlationID,
|
|
2101
|
+
emissionPreview: emission?.slice(0, 80),
|
|
2102
|
+
state: currentState.state,
|
|
2103
|
+
});
|
|
2104
|
+
|
|
2105
|
+
if (
|
|
2106
|
+
userLang.toLowerCase() !== language.toLowerCase() &&
|
|
2107
|
+
emission &&
|
|
2108
|
+
isMultilanguageEnabled
|
|
2109
|
+
) {
|
|
2110
|
+
currentState.emission = emission;
|
|
2111
|
+
|
|
2112
|
+
translateDialogState(currentState, userLang, msg).then(ts => {
|
|
2113
|
+
const text = ts.translatedEmission || ts.emission;
|
|
2114
|
+
if (text && shouldPlayAudio(text)) {
|
|
2115
|
+
handleSpeak(text);
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
} else {
|
|
2119
|
+
setCurrentDialogState({
|
|
2120
|
+
...currentState,
|
|
2121
|
+
emission,
|
|
2122
|
+
});
|
|
2123
|
+
|
|
2124
|
+
if (emission) {
|
|
2125
|
+
pushMessage({
|
|
2126
|
+
text: emission,
|
|
2127
|
+
emitter: currentState.emitter,
|
|
2128
|
+
media: currentState.emittedMedia ?? currentState.media,
|
|
2129
|
+
llmUsage: (currentState as any).llmUsage,
|
|
2130
|
+
fromUser: false,
|
|
2131
|
+
questionAnswered: msg,
|
|
2132
|
+
generatedByAI: !!currentState.completion,
|
|
2133
|
+
contextVars: currentState.contextVars,
|
|
2134
|
+
date: currentState.currentDate,
|
|
2135
|
+
placeName: currentState.currentPlaceName,
|
|
2136
|
+
placeLatitude: currentState.currentLatitude,
|
|
2137
|
+
placeLongitude: currentState.currentLongitude,
|
|
2138
|
+
placeUncertaintyKm: currentState.currentUncertaintyKm,
|
|
2139
|
+
tag: currentState.currentTag,
|
|
2140
|
+
memoryTags: currentState.memoryTags,
|
|
2141
|
+
} as any);
|
|
2142
|
+
if (emission && shouldPlayAudio(emission)) {
|
|
2143
|
+
handleSpeak(emission);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
},
|
|
2148
|
+
[
|
|
2149
|
+
userLang,
|
|
2150
|
+
language,
|
|
2151
|
+
isMultilanguageEnabled,
|
|
2152
|
+
currentDialogState?.emission,
|
|
2153
|
+
translateDialogState,
|
|
2154
|
+
handleSpeak,
|
|
2155
|
+
shouldPlayAudio,
|
|
2156
|
+
]
|
|
2157
|
+
);
|
|
2158
|
+
|
|
2159
|
+
const clearEnterTextPending = useCallback(
|
|
2160
|
+
(correlationID: string, pending: PendingEnterText) => {
|
|
2161
|
+
if (pending.natsTimeoutId) {
|
|
2162
|
+
clearTimeout(pending.natsTimeoutId);
|
|
2163
|
+
}
|
|
2164
|
+
if (pending.waitForResponse?.timeoutId) {
|
|
2165
|
+
clearTimeout(pending.waitForResponse.timeoutId);
|
|
2166
|
+
}
|
|
2167
|
+
pendingEnterTextRef.current.delete(correlationID);
|
|
2168
|
+
},
|
|
2169
|
+
[]
|
|
2170
|
+
);
|
|
2171
|
+
|
|
2172
|
+
const deliverEnterTextNatsError = useCallback(
|
|
2173
|
+
(event: NatsErrorEvent) => {
|
|
2174
|
+
const correlationID = event.correlationID;
|
|
2175
|
+
const errorText = event.errorMessage
|
|
2176
|
+
? `Error: ${event.errorMessage}`
|
|
2177
|
+
: event.errorCode
|
|
2178
|
+
? `Error: ${event.errorCode}`
|
|
2179
|
+
: 'Error: An unexpected error occurred';
|
|
2180
|
+
|
|
2181
|
+
console.error('[EnterText] NATS error event', {
|
|
2182
|
+
correlationID,
|
|
2183
|
+
errorCode: event.errorCode,
|
|
2184
|
+
errorMessage: event.errorMessage,
|
|
2185
|
+
});
|
|
2186
|
+
|
|
2187
|
+
pushMessage({
|
|
2188
|
+
text: errorText,
|
|
2189
|
+
emitter: 'system',
|
|
2190
|
+
fromUser: false,
|
|
2191
|
+
initial: false,
|
|
2192
|
+
contextVars: {},
|
|
2193
|
+
date: new Date().toISOString(),
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
if (correlationID) {
|
|
2197
|
+
const pending = pendingEnterTextRef.current.get(correlationID);
|
|
2198
|
+
if (pending) {
|
|
2199
|
+
clearEnterTextPending(correlationID, pending);
|
|
2200
|
+
pending.waitForResponse?.reject(
|
|
2201
|
+
new Error(event.errorMessage ?? String(event.errorCode ?? 'NATS error'))
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
setMemoriTyping(false);
|
|
2207
|
+
setTypingText(undefined);
|
|
2208
|
+
},
|
|
2209
|
+
[clearEnterTextPending]
|
|
2210
|
+
);
|
|
2211
|
+
|
|
2212
|
+
const deliverEnterTextNatsResponse = useCallback(
|
|
2213
|
+
(correlationID: string, event: NatsDialogResponseEvent) => {
|
|
2214
|
+
const pending = pendingEnterTextRef.current.get(correlationID);
|
|
2215
|
+
if (!pending) {
|
|
2216
|
+
const pendingCorrelationIDs = [...pendingEnterTextRef.current.keys()];
|
|
2217
|
+
console.warn(
|
|
2218
|
+
'[EnterText] NATS response buffered (no matching pending)',
|
|
2219
|
+
{
|
|
2220
|
+
receivedCorrelationID: correlationID,
|
|
2221
|
+
resultCode: event.resultCode,
|
|
2222
|
+
pendingCorrelationIDs,
|
|
2223
|
+
hint:
|
|
2224
|
+
pendingCorrelationIDs.length > 0
|
|
2225
|
+
? 'Use one of pendingCorrelationIDs in your nats pub correlation_id'
|
|
2226
|
+
: 'Send a message in the widget first, then copy correlationID from HTTP response logs',
|
|
2227
|
+
}
|
|
2228
|
+
);
|
|
2229
|
+
bufferedNatsResponsesRef.current.set(correlationID, event);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
clearEnterTextPending(correlationID, pending);
|
|
2234
|
+
|
|
2235
|
+
if (pending.waitForResponse) {
|
|
2236
|
+
console.info('[EnterText] NATS response delivered to waiter', {
|
|
2237
|
+
correlationID,
|
|
2238
|
+
resultCode: event.resultCode,
|
|
2239
|
+
});
|
|
2240
|
+
pending.waitForResponse.resolve(event);
|
|
2241
|
+
setMemoriTyping(false);
|
|
2242
|
+
setTypingText(undefined);
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
processEnterTextDialogResponse(event, pending);
|
|
2247
|
+
|
|
2248
|
+
if (!pending.hasBatchQueued) {
|
|
2249
|
+
console.info('[EnterText] typing indicator cleared', { correlationID });
|
|
2250
|
+
setMemoriTyping(false);
|
|
2251
|
+
setTypingText(undefined);
|
|
2252
|
+
} else {
|
|
2253
|
+
console.debug('[EnterText] typing kept (batch queued)', {
|
|
2254
|
+
correlationID,
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
},
|
|
2258
|
+
[processEnterTextDialogResponse, clearEnterTextPending]
|
|
2259
|
+
);
|
|
2260
|
+
|
|
2261
|
+
const registerPendingEnterText = useCallback(
|
|
2262
|
+
(correlationID: string, pending: PendingEnterText) => {
|
|
2263
|
+
const buffered = bufferedNatsResponsesRef.current.get(correlationID);
|
|
2264
|
+
if (buffered) {
|
|
2265
|
+
console.info('[EnterText] replaying buffered NATS response', {
|
|
2266
|
+
correlationID,
|
|
2267
|
+
waitForResponse: !!pending.waitForResponse,
|
|
2268
|
+
});
|
|
2269
|
+
bufferedNatsResponsesRef.current.delete(correlationID);
|
|
2270
|
+
pendingEnterTextRef.current.set(correlationID, pending);
|
|
2271
|
+
deliverEnterTextNatsResponse(correlationID, buffered);
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
if (!pending.waitForResponse && !pending.natsTimeoutId) {
|
|
2276
|
+
pending.natsTimeoutId = setTimeout(() => {
|
|
2277
|
+
const current = pendingEnterTextRef.current.get(correlationID);
|
|
2278
|
+
if (!current) return;
|
|
2279
|
+
clearEnterTextPending(correlationID, current);
|
|
2280
|
+
console.error('[EnterText] NATS response timeout', {
|
|
2281
|
+
correlationID,
|
|
2282
|
+
timeoutMs: ENTER_TEXT_NATS_TIMEOUT_MS,
|
|
2283
|
+
});
|
|
2284
|
+
if (!current.hasBatchQueued) {
|
|
2285
|
+
setMemoriTyping(false);
|
|
2286
|
+
setTypingText(undefined);
|
|
2287
|
+
}
|
|
2288
|
+
current.waitForResponse?.reject(
|
|
2289
|
+
new Error('NATS enter-text response timeout')
|
|
2290
|
+
);
|
|
2291
|
+
}, ENTER_TEXT_NATS_TIMEOUT_MS);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
console.debug('[EnterText] pending registered', {
|
|
2295
|
+
correlationID,
|
|
2296
|
+
waitForResponse: !!pending.waitForResponse,
|
|
2297
|
+
hasBatchQueued: pending.hasBatchQueued,
|
|
2298
|
+
});
|
|
2299
|
+
pendingEnterTextRef.current.set(correlationID, pending);
|
|
2300
|
+
},
|
|
2301
|
+
[deliverEnterTextNatsResponse, clearEnterTextPending]
|
|
2302
|
+
);
|
|
2303
|
+
|
|
2304
|
+
const waitForEnterTextNatsResponse = useCallback(
|
|
2305
|
+
(correlationID: string, timeoutMs = 120000) =>
|
|
2306
|
+
new Promise<NatsDialogResponseEvent>((resolve, reject) => {
|
|
2307
|
+
console.debug('[EnterText] waiting for NATS response', {
|
|
2308
|
+
correlationID,
|
|
2309
|
+
timeoutMs,
|
|
2310
|
+
});
|
|
2311
|
+
const timeoutId = setTimeout(() => {
|
|
2312
|
+
const current = pendingEnterTextRef.current.get(correlationID);
|
|
2313
|
+
if (current) {
|
|
2314
|
+
clearEnterTextPending(correlationID, current);
|
|
2315
|
+
}
|
|
2316
|
+
console.error('[EnterText] NATS response timeout', {
|
|
2317
|
+
correlationID,
|
|
2318
|
+
timeoutMs,
|
|
2319
|
+
});
|
|
2320
|
+
reject(new Error('NATS enter-text response timeout'));
|
|
2321
|
+
}, timeoutMs);
|
|
2322
|
+
|
|
2323
|
+
registerPendingEnterText(correlationID, {
|
|
2324
|
+
waitForResponse: {
|
|
2325
|
+
resolve: event => {
|
|
2326
|
+
clearTimeout(timeoutId);
|
|
2327
|
+
resolve(event);
|
|
2328
|
+
},
|
|
2329
|
+
reject: error => {
|
|
2330
|
+
clearTimeout(timeoutId);
|
|
2331
|
+
reject(error);
|
|
2332
|
+
},
|
|
2333
|
+
timeoutId,
|
|
2334
|
+
},
|
|
2335
|
+
});
|
|
2336
|
+
}),
|
|
2337
|
+
[registerPendingEnterText, clearEnterTextPending]
|
|
2338
|
+
);
|
|
2339
|
+
|
|
2340
|
+
// NATS subscription: receives progress updates and the async enter-text response.
|
|
2341
|
+
useNats({
|
|
2342
|
+
baseUrl,
|
|
2343
|
+
sessionId,
|
|
2344
|
+
onProgress: useCallback((event: NatsProgressEvent) => {
|
|
2345
|
+
console.debug('[EnterText] NATS progress', {
|
|
2346
|
+
correlationID: event.correlationID,
|
|
2347
|
+
step: event.currentStep,
|
|
2348
|
+
finalStep: event.finalStep,
|
|
2349
|
+
message: event.message,
|
|
2350
|
+
});
|
|
2351
|
+
if (event.message) {
|
|
2352
|
+
setTypingText(event.message);
|
|
2353
|
+
}
|
|
2354
|
+
}, []),
|
|
2355
|
+
onDialogResponse: useCallback(
|
|
2356
|
+
(event: NatsDialogResponseEvent) => {
|
|
2357
|
+
const correlationID = event.correlationID;
|
|
2358
|
+
console.debug(
|
|
2359
|
+
'[EnterText] NATS dialog.text_entered_response received',
|
|
2360
|
+
{
|
|
2361
|
+
correlationID,
|
|
2362
|
+
resultCode: event.resultCode,
|
|
2363
|
+
requestID: event.requestID,
|
|
2364
|
+
}
|
|
2365
|
+
);
|
|
2366
|
+
if (!correlationID) {
|
|
2367
|
+
console.warn(
|
|
2368
|
+
'[EnterText] dialog_text_entered_response without correlationID',
|
|
2369
|
+
event
|
|
2370
|
+
);
|
|
2371
|
+
setMemoriTyping(false);
|
|
2372
|
+
setTypingText(undefined);
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
deliverEnterTextNatsResponse(correlationID, event);
|
|
2377
|
+
},
|
|
2378
|
+
[deliverEnterTextNatsResponse]
|
|
2379
|
+
),
|
|
2380
|
+
onError: deliverEnterTextNatsError,
|
|
2381
|
+
});
|
|
2382
|
+
|
|
2049
2383
|
const focusChatInput = () => {
|
|
2050
2384
|
let textarea = document.querySelector(
|
|
2051
2385
|
'#chat-fieldset textarea'
|
|
@@ -2373,7 +2707,7 @@ const MemoriWidget = ({
|
|
|
2373
2707
|
return;
|
|
2374
2708
|
}
|
|
2375
2709
|
|
|
2376
|
-
if (!(await checkCredits({ notify: true
|
|
2710
|
+
if (!(await checkCredits({ notify: true }))) {
|
|
2377
2711
|
setClickedStart(false);
|
|
2378
2712
|
setLoading(false);
|
|
2379
2713
|
return;
|
|
@@ -2383,6 +2717,7 @@ const MemoriWidget = ({
|
|
|
2383
2717
|
if (!sessionID && !!minAge && !birth) {
|
|
2384
2718
|
setShowAgeVerification(true);
|
|
2385
2719
|
setClickedStart(false);
|
|
2720
|
+
return;
|
|
2386
2721
|
}
|
|
2387
2722
|
// Handle authentication
|
|
2388
2723
|
else if (
|
|
@@ -2398,41 +2733,46 @@ const MemoriWidget = ({
|
|
|
2398
2733
|
}
|
|
2399
2734
|
// Create new session if needed
|
|
2400
2735
|
else if (!sessionID || initialSessionExpired) {
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
:
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2736
|
+
if (sessionStartingRef.current) {
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
sessionStartingRef.current = true;
|
|
2740
|
+
try {
|
|
2741
|
+
const session = await fetchSession({
|
|
2742
|
+
memoriID: memori.engineMemoriID!,
|
|
2743
|
+
password: secret || memoriPwd || memori.secretToken,
|
|
2744
|
+
tag: personification?.tag,
|
|
2745
|
+
pin: personification?.pin,
|
|
2746
|
+
continueFromChatLogID: chatLog?.chatLogID,
|
|
2747
|
+
initialContextVars: {
|
|
2748
|
+
LANG: userLang,
|
|
2749
|
+
PATHNAME: window.location.pathname?.toUpperCase(),
|
|
2750
|
+
ROUTE:
|
|
2751
|
+
window.location.pathname?.split('/')?.pop()?.toUpperCase() ||
|
|
2752
|
+
'',
|
|
2753
|
+
...((!chatLog
|
|
2754
|
+
? initialContextVars
|
|
2755
|
+
: chatLog.lines[chatLog.lines.length - 1].contextVars) || {}),
|
|
2756
|
+
},
|
|
2757
|
+
initialQuestion: chatLog ? undefined : initialQuestion,
|
|
2758
|
+
birthDate: birth,
|
|
2759
|
+
additionalInfo: {
|
|
2760
|
+
...(additionalInfo || {}),
|
|
2761
|
+
loginToken:
|
|
2762
|
+
userToken ??
|
|
2763
|
+
loginToken ??
|
|
2764
|
+
additionalInfo?.loginToken ??
|
|
2765
|
+
authToken,
|
|
2766
|
+
language: (
|
|
2767
|
+
userLang ??
|
|
2768
|
+
memori.culture?.split('-')?.[0] ??
|
|
2769
|
+
'IT'
|
|
2770
|
+
).toLowerCase(),
|
|
2771
|
+
timeZoneOffset: new Date().getTimezoneOffset().toString(),
|
|
2772
|
+
},
|
|
2773
|
+
});
|
|
2434
2774
|
|
|
2435
|
-
|
|
2775
|
+
if (session?.dialogState) {
|
|
2436
2776
|
// reset history
|
|
2437
2777
|
if (!chatLog) {
|
|
2438
2778
|
setHistory([]);
|
|
@@ -2441,6 +2781,7 @@ const MemoriWidget = ({
|
|
|
2441
2781
|
await translateAndSpeak(session.dialogState, userLang);
|
|
2442
2782
|
// No need for additional handleSpeak call since translateAndSpeak already handles it
|
|
2443
2783
|
setHasUserActivatedSpeak(true);
|
|
2784
|
+
setClickedStart(false);
|
|
2444
2785
|
} else {
|
|
2445
2786
|
const messages = chatLog.lines.map(
|
|
2446
2787
|
(l, i) =>
|
|
@@ -2502,12 +2843,18 @@ const MemoriWidget = ({
|
|
|
2502
2843
|
true
|
|
2503
2844
|
).finally(() => {
|
|
2504
2845
|
setHasUserActivatedSpeak(true);
|
|
2846
|
+
setClickedStart(false);
|
|
2505
2847
|
});
|
|
2506
2848
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2849
|
+
} else if (session?.resultCode === 0) {
|
|
2850
|
+
sessionStartingRef.current = false;
|
|
2851
|
+
await onClickStart((session as any) || undefined);
|
|
2852
|
+
} else {
|
|
2853
|
+
setLoading(false);
|
|
2854
|
+
setClickedStart(false);
|
|
2855
|
+
}
|
|
2856
|
+
} finally {
|
|
2857
|
+
sessionStartingRef.current = false;
|
|
2511
2858
|
}
|
|
2512
2859
|
|
|
2513
2860
|
return;
|
|
@@ -2521,7 +2868,6 @@ const MemoriWidget = ({
|
|
|
2521
2868
|
if (response.resultCode !== 0 || !currentState) {
|
|
2522
2869
|
const { chatLogs } = await getSessionChatLogs(sessionID!, sessionID!);
|
|
2523
2870
|
setSessionId(undefined);
|
|
2524
|
-
setClickedStart(false);
|
|
2525
2871
|
await onClickStart(undefined, true, chatLogs?.[0]);
|
|
2526
2872
|
return;
|
|
2527
2873
|
}
|
|
@@ -2547,6 +2893,7 @@ const MemoriWidget = ({
|
|
|
2547
2893
|
|
|
2548
2894
|
if (session && session.resultCode === 0) {
|
|
2549
2895
|
await translateAndSpeak(session.currentState, userLang);
|
|
2896
|
+
setClickedStart(false);
|
|
2550
2897
|
} else {
|
|
2551
2898
|
throw new Error('No session');
|
|
2552
2899
|
}
|
|
@@ -2570,6 +2917,7 @@ const MemoriWidget = ({
|
|
|
2570
2917
|
birth
|
|
2571
2918
|
).then(() => {
|
|
2572
2919
|
setHasUserActivatedSpeak(true);
|
|
2920
|
+
setClickedStart(false);
|
|
2573
2921
|
});
|
|
2574
2922
|
}
|
|
2575
2923
|
}
|
|
@@ -2592,6 +2940,7 @@ const MemoriWidget = ({
|
|
|
2592
2940
|
|
|
2593
2941
|
if (session && session.resultCode === 0) {
|
|
2594
2942
|
await translateAndSpeak(session.currentState, userLang);
|
|
2943
|
+
setClickedStart(false);
|
|
2595
2944
|
} else {
|
|
2596
2945
|
throw new Error('No session');
|
|
2597
2946
|
}
|
|
@@ -2614,6 +2963,7 @@ const MemoriWidget = ({
|
|
|
2614
2963
|
birth
|
|
2615
2964
|
).then(() => {
|
|
2616
2965
|
setHasUserActivatedSpeak(true);
|
|
2966
|
+
setClickedStart(false);
|
|
2617
2967
|
});
|
|
2618
2968
|
}
|
|
2619
2969
|
}
|
|
@@ -2676,6 +3026,7 @@ const MemoriWidget = ({
|
|
|
2676
3026
|
) {
|
|
2677
3027
|
// we have a history, don't push message
|
|
2678
3028
|
setHasUserActivatedSpeak(true);
|
|
3029
|
+
setClickedStart(false);
|
|
2679
3030
|
await translateAndSpeak(
|
|
2680
3031
|
currentState,
|
|
2681
3032
|
userLang,
|
|
@@ -2690,11 +3041,15 @@ const MemoriWidget = ({
|
|
|
2690
3041
|
translatedMessages = [];
|
|
2691
3042
|
setHistory([]);
|
|
2692
3043
|
|
|
2693
|
-
setMemoriTyping(true);
|
|
2694
|
-
|
|
2695
3044
|
// we have no chat history, we start by initial question
|
|
2696
3045
|
const placeSpec = getPlaceSpecForEnterText(position);
|
|
2697
|
-
|
|
3046
|
+
console.debug(
|
|
3047
|
+
'[EnterText] onClickStart: posting initial question',
|
|
3048
|
+
{
|
|
3049
|
+
sessionId: sessionID,
|
|
3050
|
+
}
|
|
3051
|
+
);
|
|
3052
|
+
const response = await postEnterTextAsync({
|
|
2698
3053
|
sessionId: sessionID!,
|
|
2699
3054
|
text: initialQuestion,
|
|
2700
3055
|
...(memori.needsDateTime && {
|
|
@@ -2702,8 +3057,12 @@ const MemoriWidget = ({
|
|
|
2702
3057
|
}),
|
|
2703
3058
|
...(placeSpec !== undefined && { place: placeSpec }),
|
|
2704
3059
|
});
|
|
3060
|
+
console.debug('[EnterText] onClickStart: HTTP response', {
|
|
3061
|
+
resultCode: response.resultCode,
|
|
3062
|
+
correlationID: readCorrelationID(response),
|
|
3063
|
+
});
|
|
2705
3064
|
|
|
2706
|
-
// Handle 500 error from
|
|
3065
|
+
// Handle 500 error from EnterTextAsync
|
|
2707
3066
|
if (response.resultCode === 500 && response.resultMessage) {
|
|
2708
3067
|
setHistory(h => [
|
|
2709
3068
|
...h,
|
|
@@ -2716,16 +3075,49 @@ const MemoriWidget = ({
|
|
|
2716
3075
|
date: new Date().toISOString(),
|
|
2717
3076
|
},
|
|
2718
3077
|
]);
|
|
2719
|
-
setMemoriTyping(false);
|
|
2720
3078
|
return;
|
|
2721
3079
|
}
|
|
2722
3080
|
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
3081
|
+
const onClickStartCorrelationID = readCorrelationID(response);
|
|
3082
|
+
if (response.resultCode === 0 && onClickStartCorrelationID) {
|
|
3083
|
+
console.info(
|
|
3084
|
+
'[EnterText] onClickStart: accepted, showing typing indicator',
|
|
3085
|
+
{
|
|
3086
|
+
correlationID: onClickStartCorrelationID,
|
|
3087
|
+
}
|
|
3088
|
+
);
|
|
3089
|
+
setMemoriTyping(true);
|
|
3090
|
+
try {
|
|
3091
|
+
const natsEvent = await waitForEnterTextNatsResponse(
|
|
3092
|
+
onClickStartCorrelationID
|
|
3093
|
+
);
|
|
3094
|
+
console.info(
|
|
3095
|
+
'[EnterText] onClickStart: NATS response received',
|
|
3096
|
+
{
|
|
3097
|
+
correlationID: onClickStartCorrelationID,
|
|
3098
|
+
resultCode: natsEvent.resultCode,
|
|
3099
|
+
}
|
|
3100
|
+
);
|
|
3101
|
+
if (natsEvent.resultCode === 0 && natsEvent.currentState) {
|
|
3102
|
+
await translateAndSpeak(
|
|
3103
|
+
natsEvent.currentState,
|
|
3104
|
+
userLang,
|
|
3105
|
+
undefined,
|
|
3106
|
+
false
|
|
3107
|
+
);
|
|
3108
|
+
setClickedStart(false);
|
|
3109
|
+
}
|
|
3110
|
+
} catch (e) {
|
|
3111
|
+
console.error('[EnterText] onClickStart: NATS wait failed', e);
|
|
3112
|
+
setMemoriTyping(false);
|
|
3113
|
+
setTypingText(undefined);
|
|
3114
|
+
}
|
|
3115
|
+
} else if (response.resultCode === 0) {
|
|
3116
|
+
console.error(
|
|
3117
|
+
'[EnterText] onClickStart: HTTP 200 but missing correlationID',
|
|
3118
|
+
response
|
|
3119
|
+
);
|
|
3120
|
+
}
|
|
2729
3121
|
}
|
|
2730
3122
|
}
|
|
2731
3123
|
}
|
|
@@ -2736,16 +3128,23 @@ const MemoriWidget = ({
|
|
|
2736
3128
|
|
|
2737
3129
|
// everything is fine, just translate dialog state and activate chat
|
|
2738
3130
|
await translateAndSpeak(dialogState!, userLang);
|
|
3131
|
+
setClickedStart(false);
|
|
2739
3132
|
}
|
|
2740
3133
|
},
|
|
2741
3134
|
[memoriPwd, memori, memoriTokens, birthDate, sessionId, userLang, position]
|
|
2742
3135
|
);
|
|
2743
3136
|
|
|
2744
3137
|
useEffect(() => {
|
|
2745
|
-
if (
|
|
3138
|
+
if (
|
|
3139
|
+
!clickedStart &&
|
|
3140
|
+
!sessionStartingRef.current &&
|
|
3141
|
+
!sessionId &&
|
|
3142
|
+
autoStart &&
|
|
3143
|
+
selectedLayout !== 'HIDDEN_CHAT'
|
|
3144
|
+
) {
|
|
2746
3145
|
onClickStart();
|
|
2747
3146
|
}
|
|
2748
|
-
}, [clickedStart, autoStart, selectedLayout]);
|
|
3147
|
+
}, [clickedStart, autoStart, selectedLayout, sessionId]);
|
|
2749
3148
|
|
|
2750
3149
|
useEffect(() => {
|
|
2751
3150
|
const targetNode =
|
|
@@ -2822,29 +3221,22 @@ const MemoriWidget = ({
|
|
|
2822
3221
|
// check if owner has enough credits
|
|
2823
3222
|
const needsCredits = tenant?.billingDelegation;
|
|
2824
3223
|
const [hasEnoughCredits, setHasEnoughCredits] = useState<boolean>(true);
|
|
2825
|
-
const handleNotEnoughCredits = useCallback(
|
|
2826
|
-
(
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
if (goBack && window.history.length > 1) {
|
|
2832
|
-
window.history.back();
|
|
2833
|
-
}
|
|
2834
|
-
},
|
|
2835
|
-
[t]
|
|
2836
|
-
);
|
|
3224
|
+
const handleNotEnoughCredits = useCallback(() => {
|
|
3225
|
+
setHasEnoughCredits(false);
|
|
3226
|
+
setAuthModalState(null);
|
|
3227
|
+
toast.error(t('notEnoughCredits'));
|
|
3228
|
+
}, [t]);
|
|
2837
3229
|
const checkCredits = useCallback(
|
|
2838
|
-
async (options?: { notify?: boolean
|
|
3230
|
+
async (options?: { notify?: boolean }) => {
|
|
2839
3231
|
if (!tenant?.billingDelegation) return true;
|
|
2840
3232
|
|
|
2841
3233
|
// Billing delegation is active: credits MUST be verified.
|
|
2842
|
-
// Without
|
|
3234
|
+
// Without either owner identifier we cannot call the API, so we fail closed
|
|
2843
3235
|
// instead of silently letting the session start unverified.
|
|
2844
|
-
if (!ownerUserID) {
|
|
2845
|
-
console.warn('Cannot verify credits: missing
|
|
3236
|
+
if (!ownerUserID && !ownerUserName) {
|
|
3237
|
+
console.warn('Cannot verify credits: missing owner identifier');
|
|
2846
3238
|
if (options?.notify) {
|
|
2847
|
-
handleNotEnoughCredits(
|
|
3239
|
+
handleNotEnoughCredits();
|
|
2848
3240
|
} else {
|
|
2849
3241
|
setHasEnoughCredits(false);
|
|
2850
3242
|
}
|
|
@@ -2858,6 +3250,7 @@ const MemoriWidget = ({
|
|
|
2858
3250
|
: 'session_creation',
|
|
2859
3251
|
baseUrl: baseUrl,
|
|
2860
3252
|
userID: ownerUserID,
|
|
3253
|
+
userName: ownerUserName,
|
|
2861
3254
|
tenant: tenantID,
|
|
2862
3255
|
});
|
|
2863
3256
|
|
|
@@ -2867,7 +3260,7 @@ const MemoriWidget = ({
|
|
|
2867
3260
|
} else {
|
|
2868
3261
|
console.warn('Not enough credits. Required:', resp.required);
|
|
2869
3262
|
if (options?.notify) {
|
|
2870
|
-
handleNotEnoughCredits(
|
|
3263
|
+
handleNotEnoughCredits();
|
|
2871
3264
|
} else {
|
|
2872
3265
|
setHasEnoughCredits(false);
|
|
2873
3266
|
}
|
|
@@ -2884,6 +3277,7 @@ const MemoriWidget = ({
|
|
|
2884
3277
|
deepThoughtEnabled,
|
|
2885
3278
|
handleNotEnoughCredits,
|
|
2886
3279
|
ownerUserID,
|
|
3280
|
+
ownerUserName,
|
|
2887
3281
|
tenant?.billingDelegation,
|
|
2888
3282
|
tenantID,
|
|
2889
3283
|
]
|