@memori.ai/memori-react 8.39.0 → 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 +13 -0
- package/dist/components/MemoriWidget/MemoriWidget.d.ts +2 -1
- package/dist/components/MemoriWidget/MemoriWidget.js +357 -73
- 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 +357 -73
- 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 +475 -108
- package/src/components/layouts/layouts.stories.tsx +28 -34
- 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,
|
|
@@ -626,6 +643,23 @@ const MemoriWidget = ({
|
|
|
626
643
|
const [memoriTyping, setMemoriTyping] = useState<boolean>(false);
|
|
627
644
|
const [typingText, setTypingText] = useState<string>();
|
|
628
645
|
|
|
646
|
+
type PendingEnterText = {
|
|
647
|
+
msg?: string;
|
|
648
|
+
typingText?: string;
|
|
649
|
+
useLoaderTextAsMsg?: boolean;
|
|
650
|
+
hasBatchQueued?: boolean;
|
|
651
|
+
natsTimeoutId?: ReturnType<typeof setTimeout>;
|
|
652
|
+
waitForResponse?: {
|
|
653
|
+
resolve: (event: NatsDialogResponseEvent) => void;
|
|
654
|
+
reject: (error: Error) => void;
|
|
655
|
+
timeoutId: ReturnType<typeof setTimeout>;
|
|
656
|
+
};
|
|
657
|
+
};
|
|
658
|
+
const pendingEnterTextRef = useRef<Map<string, PendingEnterText>>(new Map());
|
|
659
|
+
const bufferedNatsResponsesRef = useRef<Map<string, NatsDialogResponseEvent>>(
|
|
660
|
+
new Map()
|
|
661
|
+
);
|
|
662
|
+
|
|
629
663
|
// Layout: from prop (string only) or integrationConfig. PII detection is only from integrationConfig (customData.layout as object with piiDetection).
|
|
630
664
|
const layoutName =
|
|
631
665
|
typeof layout === 'string'
|
|
@@ -783,7 +817,7 @@ const MemoriWidget = ({
|
|
|
783
817
|
return Object.keys(place).length > 0 ? place : undefined;
|
|
784
818
|
}, []);
|
|
785
819
|
|
|
786
|
-
/** Place to send with
|
|
820
|
+
/** Place to send with postEnterTextAsync: real place, nulls when no/declined position, or undefined when position not needed. */
|
|
787
821
|
const getPlaceSpecForEnterText = useCallback(
|
|
788
822
|
(venue: Venue | undefined) => {
|
|
789
823
|
if (!memori.needsPosition) return undefined;
|
|
@@ -945,27 +979,18 @@ const MemoriWidget = ({
|
|
|
945
979
|
: !!newSessionId,
|
|
946
980
|
});
|
|
947
981
|
|
|
948
|
-
// Show typing indicator
|
|
949
|
-
setMemoriTyping(true);
|
|
950
|
-
setTypingText(typingText);
|
|
951
|
-
|
|
982
|
+
// Show typing indicator after the async enter-text request is accepted (HTTP 200).
|
|
952
983
|
let gotError = false;
|
|
953
984
|
|
|
954
985
|
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
986
|
const placeSpec = getPlaceSpecForEnterText(position);
|
|
968
|
-
|
|
987
|
+
console.debug('[EnterText] sendMessage: posting', {
|
|
988
|
+
sessionId: sessionID,
|
|
989
|
+
textLength: msg.length,
|
|
990
|
+
hasBatchQueued,
|
|
991
|
+
typingText,
|
|
992
|
+
});
|
|
993
|
+
const response = await postEnterTextAsync({
|
|
969
994
|
sessionId: sessionID,
|
|
970
995
|
text: msg,
|
|
971
996
|
...(memori.needsDateTime && {
|
|
@@ -973,55 +998,33 @@ const MemoriWidget = ({
|
|
|
973
998
|
}),
|
|
974
999
|
...(placeSpec !== undefined && { place: placeSpec }),
|
|
975
1000
|
});
|
|
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
|
-
}
|
|
1001
|
+
console.debug('[EnterText] sendMessage: HTTP response', {
|
|
1002
|
+
resultCode: response.resultCode,
|
|
1003
|
+
correlationID: readCorrelationID(response),
|
|
1004
|
+
resultMessage: response.resultMessage,
|
|
1005
|
+
});
|
|
1006
|
+
const correlationID = readCorrelationID(response);
|
|
1007
|
+
if (response.resultCode === 0 && correlationID) {
|
|
1008
|
+
registerPendingEnterText(correlationID, {
|
|
1009
|
+
msg,
|
|
1010
|
+
typingText,
|
|
1011
|
+
useLoaderTextAsMsg,
|
|
1012
|
+
hasBatchQueued,
|
|
1013
|
+
});
|
|
1014
|
+
console.info(
|
|
1015
|
+
'[EnterText] sendMessage: accepted, showing typing indicator',
|
|
1016
|
+
{
|
|
1017
|
+
correlationID: correlationID,
|
|
1018
|
+
typingText,
|
|
1023
1019
|
}
|
|
1024
|
-
|
|
1020
|
+
);
|
|
1021
|
+
setMemoriTyping(true);
|
|
1022
|
+
setTypingText(typingText);
|
|
1023
|
+
} else if (response.resultCode === 0) {
|
|
1024
|
+
console.error(
|
|
1025
|
+
'[EnterText] sendMessage: HTTP 200 but missing correlationID — cannot match NATS response',
|
|
1026
|
+
response
|
|
1027
|
+
);
|
|
1025
1028
|
} else if (response.resultCode === 404) {
|
|
1026
1029
|
// Handle expired session
|
|
1027
1030
|
// remove last sent message, will set it as initial
|
|
@@ -1070,18 +1073,12 @@ const MemoriWidget = ({
|
|
|
1070
1073
|
return Promise.reject(response);
|
|
1071
1074
|
}
|
|
1072
1075
|
} catch (error) {
|
|
1073
|
-
console.
|
|
1074
|
-
console.error(error);
|
|
1076
|
+
console.error('[EnterText] sendMessage: request failed', error);
|
|
1075
1077
|
gotError = true;
|
|
1076
1078
|
|
|
1077
1079
|
setTypingText(undefined);
|
|
1078
1080
|
setMemoriTyping(false);
|
|
1079
1081
|
}
|
|
1080
|
-
|
|
1081
|
-
if (!hasBatchQueued) {
|
|
1082
|
-
setTypingText(undefined);
|
|
1083
|
-
setMemoriTyping(false);
|
|
1084
|
-
}
|
|
1085
1082
|
};
|
|
1086
1083
|
|
|
1087
1084
|
/**
|
|
@@ -1388,7 +1385,7 @@ const MemoriWidget = ({
|
|
|
1388
1385
|
return;
|
|
1389
1386
|
}
|
|
1390
1387
|
|
|
1391
|
-
if (!(await checkCredits({ notify: true
|
|
1388
|
+
if (!(await checkCredits({ notify: true }))) {
|
|
1392
1389
|
return;
|
|
1393
1390
|
}
|
|
1394
1391
|
|
|
@@ -1562,7 +1559,7 @@ const MemoriWidget = ({
|
|
|
1562
1559
|
return;
|
|
1563
1560
|
}
|
|
1564
1561
|
|
|
1565
|
-
if (!(await checkCredits({ notify: true
|
|
1562
|
+
if (!(await checkCredits({ notify: true }))) {
|
|
1566
1563
|
setLoading(false);
|
|
1567
1564
|
return null;
|
|
1568
1565
|
}
|
|
@@ -2046,6 +2043,341 @@ const MemoriWidget = ({
|
|
|
2046
2043
|
]
|
|
2047
2044
|
);
|
|
2048
2045
|
|
|
2046
|
+
const processEnterTextDialogResponse = useCallback(
|
|
2047
|
+
(event: NatsDialogResponseEvent, pending: PendingEnterText) => {
|
|
2048
|
+
console.debug('[EnterText] processDialogResponse', {
|
|
2049
|
+
correlationID: event.correlationID,
|
|
2050
|
+
resultCode: event.resultCode,
|
|
2051
|
+
hasCurrentState: !!event.currentState,
|
|
2052
|
+
hasBatchQueued: pending.hasBatchQueued,
|
|
2053
|
+
});
|
|
2054
|
+
const {
|
|
2055
|
+
msg,
|
|
2056
|
+
typingText: pendingTypingText,
|
|
2057
|
+
useLoaderTextAsMsg,
|
|
2058
|
+
} = pending;
|
|
2059
|
+
const currentState = event.currentState;
|
|
2060
|
+
|
|
2061
|
+
if (event.resultCode !== 0 || !currentState) {
|
|
2062
|
+
if (event.resultCode === 500 && event.resultMessage) {
|
|
2063
|
+
console.warn('[EnterText] processDialogResponse: server error', {
|
|
2064
|
+
correlationID: event.correlationID,
|
|
2065
|
+
resultMessage: event.resultMessage,
|
|
2066
|
+
});
|
|
2067
|
+
setHistory(h => [
|
|
2068
|
+
...h,
|
|
2069
|
+
{
|
|
2070
|
+
text: 'Error: ' + event.resultMessage,
|
|
2071
|
+
emitter: 'system',
|
|
2072
|
+
fromUser: false,
|
|
2073
|
+
initial: false,
|
|
2074
|
+
contextVars: {},
|
|
2075
|
+
date: new Date().toISOString(),
|
|
2076
|
+
},
|
|
2077
|
+
]);
|
|
2078
|
+
} else if (event.resultCode !== 0) {
|
|
2079
|
+
console.warn('[SEND_MESSAGE/NATS]', event);
|
|
2080
|
+
}
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
if (!msg) {
|
|
2085
|
+
console.debug(
|
|
2086
|
+
'[EnterText] processDialogResponse: no msg in pending, skipping'
|
|
2087
|
+
);
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
setChatLogID(undefined);
|
|
2092
|
+
const emission =
|
|
2093
|
+
useLoaderTextAsMsg && pendingTypingText
|
|
2094
|
+
? pendingTypingText
|
|
2095
|
+
: currentState.emission ?? currentDialogState?.emission;
|
|
2096
|
+
|
|
2097
|
+
console.debug('[EnterText] processDialogResponse: rendering emission', {
|
|
2098
|
+
correlationID: event.correlationID,
|
|
2099
|
+
emissionPreview: emission?.slice(0, 80),
|
|
2100
|
+
state: currentState.state,
|
|
2101
|
+
});
|
|
2102
|
+
|
|
2103
|
+
if (
|
|
2104
|
+
userLang.toLowerCase() !== language.toLowerCase() &&
|
|
2105
|
+
emission &&
|
|
2106
|
+
isMultilanguageEnabled
|
|
2107
|
+
) {
|
|
2108
|
+
currentState.emission = emission;
|
|
2109
|
+
|
|
2110
|
+
translateDialogState(currentState, userLang, msg).then(ts => {
|
|
2111
|
+
const text = ts.translatedEmission || ts.emission;
|
|
2112
|
+
if (text && shouldPlayAudio(text)) {
|
|
2113
|
+
handleSpeak(text);
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
} else {
|
|
2117
|
+
setCurrentDialogState({
|
|
2118
|
+
...currentState,
|
|
2119
|
+
emission,
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
if (emission) {
|
|
2123
|
+
pushMessage({
|
|
2124
|
+
text: emission,
|
|
2125
|
+
emitter: currentState.emitter,
|
|
2126
|
+
media: currentState.emittedMedia ?? currentState.media,
|
|
2127
|
+
llmUsage: (currentState as any).llmUsage,
|
|
2128
|
+
fromUser: false,
|
|
2129
|
+
questionAnswered: msg,
|
|
2130
|
+
generatedByAI: !!currentState.completion,
|
|
2131
|
+
contextVars: currentState.contextVars,
|
|
2132
|
+
date: currentState.currentDate,
|
|
2133
|
+
placeName: currentState.currentPlaceName,
|
|
2134
|
+
placeLatitude: currentState.currentLatitude,
|
|
2135
|
+
placeLongitude: currentState.currentLongitude,
|
|
2136
|
+
placeUncertaintyKm: currentState.currentUncertaintyKm,
|
|
2137
|
+
tag: currentState.currentTag,
|
|
2138
|
+
memoryTags: currentState.memoryTags,
|
|
2139
|
+
} as any);
|
|
2140
|
+
if (emission && shouldPlayAudio(emission)) {
|
|
2141
|
+
handleSpeak(emission);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
},
|
|
2146
|
+
[
|
|
2147
|
+
userLang,
|
|
2148
|
+
language,
|
|
2149
|
+
isMultilanguageEnabled,
|
|
2150
|
+
currentDialogState?.emission,
|
|
2151
|
+
translateDialogState,
|
|
2152
|
+
handleSpeak,
|
|
2153
|
+
shouldPlayAudio,
|
|
2154
|
+
]
|
|
2155
|
+
);
|
|
2156
|
+
|
|
2157
|
+
const clearEnterTextPending = useCallback(
|
|
2158
|
+
(correlationID: string, pending: PendingEnterText) => {
|
|
2159
|
+
if (pending.natsTimeoutId) {
|
|
2160
|
+
clearTimeout(pending.natsTimeoutId);
|
|
2161
|
+
}
|
|
2162
|
+
if (pending.waitForResponse?.timeoutId) {
|
|
2163
|
+
clearTimeout(pending.waitForResponse.timeoutId);
|
|
2164
|
+
}
|
|
2165
|
+
pendingEnterTextRef.current.delete(correlationID);
|
|
2166
|
+
},
|
|
2167
|
+
[]
|
|
2168
|
+
);
|
|
2169
|
+
|
|
2170
|
+
const deliverEnterTextNatsError = useCallback(
|
|
2171
|
+
(event: NatsErrorEvent) => {
|
|
2172
|
+
const correlationID = event.correlationID;
|
|
2173
|
+
const errorText = event.errorMessage
|
|
2174
|
+
? `Error: ${event.errorMessage}`
|
|
2175
|
+
: event.errorCode
|
|
2176
|
+
? `Error: ${event.errorCode}`
|
|
2177
|
+
: 'Error: An unexpected error occurred';
|
|
2178
|
+
|
|
2179
|
+
console.error('[EnterText] NATS error event', {
|
|
2180
|
+
correlationID,
|
|
2181
|
+
errorCode: event.errorCode,
|
|
2182
|
+
errorMessage: event.errorMessage,
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2185
|
+
pushMessage({
|
|
2186
|
+
text: errorText,
|
|
2187
|
+
emitter: 'system',
|
|
2188
|
+
fromUser: false,
|
|
2189
|
+
initial: false,
|
|
2190
|
+
contextVars: {},
|
|
2191
|
+
date: new Date().toISOString(),
|
|
2192
|
+
});
|
|
2193
|
+
|
|
2194
|
+
if (correlationID) {
|
|
2195
|
+
const pending = pendingEnterTextRef.current.get(correlationID);
|
|
2196
|
+
if (pending) {
|
|
2197
|
+
clearEnterTextPending(correlationID, pending);
|
|
2198
|
+
pending.waitForResponse?.reject(
|
|
2199
|
+
new Error(event.errorMessage ?? String(event.errorCode ?? 'NATS error'))
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
setMemoriTyping(false);
|
|
2205
|
+
setTypingText(undefined);
|
|
2206
|
+
},
|
|
2207
|
+
[clearEnterTextPending]
|
|
2208
|
+
);
|
|
2209
|
+
|
|
2210
|
+
const deliverEnterTextNatsResponse = useCallback(
|
|
2211
|
+
(correlationID: string, event: NatsDialogResponseEvent) => {
|
|
2212
|
+
const pending = pendingEnterTextRef.current.get(correlationID);
|
|
2213
|
+
if (!pending) {
|
|
2214
|
+
const pendingCorrelationIDs = [...pendingEnterTextRef.current.keys()];
|
|
2215
|
+
console.warn(
|
|
2216
|
+
'[EnterText] NATS response buffered (no matching pending)',
|
|
2217
|
+
{
|
|
2218
|
+
receivedCorrelationID: correlationID,
|
|
2219
|
+
resultCode: event.resultCode,
|
|
2220
|
+
pendingCorrelationIDs,
|
|
2221
|
+
hint:
|
|
2222
|
+
pendingCorrelationIDs.length > 0
|
|
2223
|
+
? 'Use one of pendingCorrelationIDs in your nats pub correlation_id'
|
|
2224
|
+
: 'Send a message in the widget first, then copy correlationID from HTTP response logs',
|
|
2225
|
+
}
|
|
2226
|
+
);
|
|
2227
|
+
bufferedNatsResponsesRef.current.set(correlationID, event);
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
clearEnterTextPending(correlationID, pending);
|
|
2232
|
+
|
|
2233
|
+
if (pending.waitForResponse) {
|
|
2234
|
+
console.info('[EnterText] NATS response delivered to waiter', {
|
|
2235
|
+
correlationID,
|
|
2236
|
+
resultCode: event.resultCode,
|
|
2237
|
+
});
|
|
2238
|
+
pending.waitForResponse.resolve(event);
|
|
2239
|
+
setMemoriTyping(false);
|
|
2240
|
+
setTypingText(undefined);
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
processEnterTextDialogResponse(event, pending);
|
|
2245
|
+
|
|
2246
|
+
if (!pending.hasBatchQueued) {
|
|
2247
|
+
console.info('[EnterText] typing indicator cleared', { correlationID });
|
|
2248
|
+
setMemoriTyping(false);
|
|
2249
|
+
setTypingText(undefined);
|
|
2250
|
+
} else {
|
|
2251
|
+
console.debug('[EnterText] typing kept (batch queued)', {
|
|
2252
|
+
correlationID,
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
},
|
|
2256
|
+
[processEnterTextDialogResponse, clearEnterTextPending]
|
|
2257
|
+
);
|
|
2258
|
+
|
|
2259
|
+
const registerPendingEnterText = useCallback(
|
|
2260
|
+
(correlationID: string, pending: PendingEnterText) => {
|
|
2261
|
+
const buffered = bufferedNatsResponsesRef.current.get(correlationID);
|
|
2262
|
+
if (buffered) {
|
|
2263
|
+
console.info('[EnterText] replaying buffered NATS response', {
|
|
2264
|
+
correlationID,
|
|
2265
|
+
waitForResponse: !!pending.waitForResponse,
|
|
2266
|
+
});
|
|
2267
|
+
bufferedNatsResponsesRef.current.delete(correlationID);
|
|
2268
|
+
pendingEnterTextRef.current.set(correlationID, pending);
|
|
2269
|
+
deliverEnterTextNatsResponse(correlationID, buffered);
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
if (!pending.waitForResponse && !pending.natsTimeoutId) {
|
|
2274
|
+
pending.natsTimeoutId = setTimeout(() => {
|
|
2275
|
+
const current = pendingEnterTextRef.current.get(correlationID);
|
|
2276
|
+
if (!current) return;
|
|
2277
|
+
clearEnterTextPending(correlationID, current);
|
|
2278
|
+
console.error('[EnterText] NATS response timeout', {
|
|
2279
|
+
correlationID,
|
|
2280
|
+
timeoutMs: ENTER_TEXT_NATS_TIMEOUT_MS,
|
|
2281
|
+
});
|
|
2282
|
+
if (!current.hasBatchQueued) {
|
|
2283
|
+
setMemoriTyping(false);
|
|
2284
|
+
setTypingText(undefined);
|
|
2285
|
+
}
|
|
2286
|
+
current.waitForResponse?.reject(
|
|
2287
|
+
new Error('NATS enter-text response timeout')
|
|
2288
|
+
);
|
|
2289
|
+
}, ENTER_TEXT_NATS_TIMEOUT_MS);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
console.debug('[EnterText] pending registered', {
|
|
2293
|
+
correlationID,
|
|
2294
|
+
waitForResponse: !!pending.waitForResponse,
|
|
2295
|
+
hasBatchQueued: pending.hasBatchQueued,
|
|
2296
|
+
});
|
|
2297
|
+
pendingEnterTextRef.current.set(correlationID, pending);
|
|
2298
|
+
},
|
|
2299
|
+
[deliverEnterTextNatsResponse, clearEnterTextPending]
|
|
2300
|
+
);
|
|
2301
|
+
|
|
2302
|
+
const waitForEnterTextNatsResponse = useCallback(
|
|
2303
|
+
(correlationID: string, timeoutMs = 120000) =>
|
|
2304
|
+
new Promise<NatsDialogResponseEvent>((resolve, reject) => {
|
|
2305
|
+
console.debug('[EnterText] waiting for NATS response', {
|
|
2306
|
+
correlationID,
|
|
2307
|
+
timeoutMs,
|
|
2308
|
+
});
|
|
2309
|
+
const timeoutId = setTimeout(() => {
|
|
2310
|
+
const current = pendingEnterTextRef.current.get(correlationID);
|
|
2311
|
+
if (current) {
|
|
2312
|
+
clearEnterTextPending(correlationID, current);
|
|
2313
|
+
}
|
|
2314
|
+
console.error('[EnterText] NATS response timeout', {
|
|
2315
|
+
correlationID,
|
|
2316
|
+
timeoutMs,
|
|
2317
|
+
});
|
|
2318
|
+
reject(new Error('NATS enter-text response timeout'));
|
|
2319
|
+
}, timeoutMs);
|
|
2320
|
+
|
|
2321
|
+
registerPendingEnterText(correlationID, {
|
|
2322
|
+
waitForResponse: {
|
|
2323
|
+
resolve: event => {
|
|
2324
|
+
clearTimeout(timeoutId);
|
|
2325
|
+
resolve(event);
|
|
2326
|
+
},
|
|
2327
|
+
reject: error => {
|
|
2328
|
+
clearTimeout(timeoutId);
|
|
2329
|
+
reject(error);
|
|
2330
|
+
},
|
|
2331
|
+
timeoutId,
|
|
2332
|
+
},
|
|
2333
|
+
});
|
|
2334
|
+
}),
|
|
2335
|
+
[registerPendingEnterText, clearEnterTextPending]
|
|
2336
|
+
);
|
|
2337
|
+
|
|
2338
|
+
// NATS subscription: receives progress updates and the async enter-text response.
|
|
2339
|
+
useNats({
|
|
2340
|
+
baseUrl,
|
|
2341
|
+
sessionId,
|
|
2342
|
+
onProgress: useCallback((event: NatsProgressEvent) => {
|
|
2343
|
+
console.debug('[EnterText] NATS progress', {
|
|
2344
|
+
correlationID: event.correlationID,
|
|
2345
|
+
step: event.currentStep,
|
|
2346
|
+
finalStep: event.finalStep,
|
|
2347
|
+
message: event.message,
|
|
2348
|
+
});
|
|
2349
|
+
if (event.message) {
|
|
2350
|
+
setTypingText(event.message);
|
|
2351
|
+
}
|
|
2352
|
+
}, []),
|
|
2353
|
+
onDialogResponse: useCallback(
|
|
2354
|
+
(event: NatsDialogResponseEvent) => {
|
|
2355
|
+
const correlationID = event.correlationID;
|
|
2356
|
+
console.debug(
|
|
2357
|
+
'[EnterText] NATS dialog.text_entered_response received',
|
|
2358
|
+
{
|
|
2359
|
+
correlationID,
|
|
2360
|
+
resultCode: event.resultCode,
|
|
2361
|
+
requestID: event.requestID,
|
|
2362
|
+
}
|
|
2363
|
+
);
|
|
2364
|
+
if (!correlationID) {
|
|
2365
|
+
console.warn(
|
|
2366
|
+
'[EnterText] dialog_text_entered_response without correlationID',
|
|
2367
|
+
event
|
|
2368
|
+
);
|
|
2369
|
+
setMemoriTyping(false);
|
|
2370
|
+
setTypingText(undefined);
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
deliverEnterTextNatsResponse(correlationID, event);
|
|
2375
|
+
},
|
|
2376
|
+
[deliverEnterTextNatsResponse]
|
|
2377
|
+
),
|
|
2378
|
+
onError: deliverEnterTextNatsError,
|
|
2379
|
+
});
|
|
2380
|
+
|
|
2049
2381
|
const focusChatInput = () => {
|
|
2050
2382
|
let textarea = document.querySelector(
|
|
2051
2383
|
'#chat-fieldset textarea'
|
|
@@ -2373,7 +2705,7 @@ const MemoriWidget = ({
|
|
|
2373
2705
|
return;
|
|
2374
2706
|
}
|
|
2375
2707
|
|
|
2376
|
-
if (!(await checkCredits({ notify: true
|
|
2708
|
+
if (!(await checkCredits({ notify: true }))) {
|
|
2377
2709
|
setClickedStart(false);
|
|
2378
2710
|
setLoading(false);
|
|
2379
2711
|
return;
|
|
@@ -2690,11 +3022,15 @@ const MemoriWidget = ({
|
|
|
2690
3022
|
translatedMessages = [];
|
|
2691
3023
|
setHistory([]);
|
|
2692
3024
|
|
|
2693
|
-
setMemoriTyping(true);
|
|
2694
|
-
|
|
2695
3025
|
// we have no chat history, we start by initial question
|
|
2696
3026
|
const placeSpec = getPlaceSpecForEnterText(position);
|
|
2697
|
-
|
|
3027
|
+
console.debug(
|
|
3028
|
+
'[EnterText] onClickStart: posting initial question',
|
|
3029
|
+
{
|
|
3030
|
+
sessionId: sessionID,
|
|
3031
|
+
}
|
|
3032
|
+
);
|
|
3033
|
+
const response = await postEnterTextAsync({
|
|
2698
3034
|
sessionId: sessionID!,
|
|
2699
3035
|
text: initialQuestion,
|
|
2700
3036
|
...(memori.needsDateTime && {
|
|
@@ -2702,8 +3038,12 @@ const MemoriWidget = ({
|
|
|
2702
3038
|
}),
|
|
2703
3039
|
...(placeSpec !== undefined && { place: placeSpec }),
|
|
2704
3040
|
});
|
|
3041
|
+
console.debug('[EnterText] onClickStart: HTTP response', {
|
|
3042
|
+
resultCode: response.resultCode,
|
|
3043
|
+
correlationID: readCorrelationID(response),
|
|
3044
|
+
});
|
|
2705
3045
|
|
|
2706
|
-
// Handle 500 error from
|
|
3046
|
+
// Handle 500 error from EnterTextAsync
|
|
2707
3047
|
if (response.resultCode === 500 && response.resultMessage) {
|
|
2708
3048
|
setHistory(h => [
|
|
2709
3049
|
...h,
|
|
@@ -2716,16 +3056,48 @@ const MemoriWidget = ({
|
|
|
2716
3056
|
date: new Date().toISOString(),
|
|
2717
3057
|
},
|
|
2718
3058
|
]);
|
|
2719
|
-
setMemoriTyping(false);
|
|
2720
3059
|
return;
|
|
2721
3060
|
}
|
|
2722
3061
|
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
3062
|
+
const onClickStartCorrelationID = readCorrelationID(response);
|
|
3063
|
+
if (response.resultCode === 0 && onClickStartCorrelationID) {
|
|
3064
|
+
console.info(
|
|
3065
|
+
'[EnterText] onClickStart: accepted, showing typing indicator',
|
|
3066
|
+
{
|
|
3067
|
+
correlationID: onClickStartCorrelationID,
|
|
3068
|
+
}
|
|
3069
|
+
);
|
|
3070
|
+
setMemoriTyping(true);
|
|
3071
|
+
try {
|
|
3072
|
+
const natsEvent = await waitForEnterTextNatsResponse(
|
|
3073
|
+
onClickStartCorrelationID
|
|
3074
|
+
);
|
|
3075
|
+
console.info(
|
|
3076
|
+
'[EnterText] onClickStart: NATS response received',
|
|
3077
|
+
{
|
|
3078
|
+
correlationID: onClickStartCorrelationID,
|
|
3079
|
+
resultCode: natsEvent.resultCode,
|
|
3080
|
+
}
|
|
3081
|
+
);
|
|
3082
|
+
if (natsEvent.resultCode === 0 && natsEvent.currentState) {
|
|
3083
|
+
await translateAndSpeak(
|
|
3084
|
+
natsEvent.currentState,
|
|
3085
|
+
userLang,
|
|
3086
|
+
undefined,
|
|
3087
|
+
false
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3090
|
+
} catch (e) {
|
|
3091
|
+
console.error('[EnterText] onClickStart: NATS wait failed', e);
|
|
3092
|
+
setMemoriTyping(false);
|
|
3093
|
+
setTypingText(undefined);
|
|
3094
|
+
}
|
|
3095
|
+
} else if (response.resultCode === 0) {
|
|
3096
|
+
console.error(
|
|
3097
|
+
'[EnterText] onClickStart: HTTP 200 but missing correlationID',
|
|
3098
|
+
response
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
2729
3101
|
}
|
|
2730
3102
|
}
|
|
2731
3103
|
}
|
|
@@ -2822,29 +3194,22 @@ const MemoriWidget = ({
|
|
|
2822
3194
|
// check if owner has enough credits
|
|
2823
3195
|
const needsCredits = tenant?.billingDelegation;
|
|
2824
3196
|
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
|
-
);
|
|
3197
|
+
const handleNotEnoughCredits = useCallback(() => {
|
|
3198
|
+
setHasEnoughCredits(false);
|
|
3199
|
+
setAuthModalState(null);
|
|
3200
|
+
toast.error(t('notEnoughCredits'));
|
|
3201
|
+
}, [t]);
|
|
2837
3202
|
const checkCredits = useCallback(
|
|
2838
|
-
async (options?: { notify?: boolean
|
|
3203
|
+
async (options?: { notify?: boolean }) => {
|
|
2839
3204
|
if (!tenant?.billingDelegation) return true;
|
|
2840
3205
|
|
|
2841
3206
|
// Billing delegation is active: credits MUST be verified.
|
|
2842
|
-
// Without
|
|
3207
|
+
// Without either owner identifier we cannot call the API, so we fail closed
|
|
2843
3208
|
// instead of silently letting the session start unverified.
|
|
2844
|
-
if (!ownerUserID) {
|
|
2845
|
-
console.warn('Cannot verify credits: missing
|
|
3209
|
+
if (!ownerUserID && !ownerUserName) {
|
|
3210
|
+
console.warn('Cannot verify credits: missing owner identifier');
|
|
2846
3211
|
if (options?.notify) {
|
|
2847
|
-
handleNotEnoughCredits(
|
|
3212
|
+
handleNotEnoughCredits();
|
|
2848
3213
|
} else {
|
|
2849
3214
|
setHasEnoughCredits(false);
|
|
2850
3215
|
}
|
|
@@ -2858,6 +3223,7 @@ const MemoriWidget = ({
|
|
|
2858
3223
|
: 'session_creation',
|
|
2859
3224
|
baseUrl: baseUrl,
|
|
2860
3225
|
userID: ownerUserID,
|
|
3226
|
+
userName: ownerUserName,
|
|
2861
3227
|
tenant: tenantID,
|
|
2862
3228
|
});
|
|
2863
3229
|
|
|
@@ -2867,7 +3233,7 @@ const MemoriWidget = ({
|
|
|
2867
3233
|
} else {
|
|
2868
3234
|
console.warn('Not enough credits. Required:', resp.required);
|
|
2869
3235
|
if (options?.notify) {
|
|
2870
|
-
handleNotEnoughCredits(
|
|
3236
|
+
handleNotEnoughCredits();
|
|
2871
3237
|
} else {
|
|
2872
3238
|
setHasEnoughCredits(false);
|
|
2873
3239
|
}
|
|
@@ -2884,6 +3250,7 @@ const MemoriWidget = ({
|
|
|
2884
3250
|
deepThoughtEnabled,
|
|
2885
3251
|
handleNotEnoughCredits,
|
|
2886
3252
|
ownerUserID,
|
|
3253
|
+
ownerUserName,
|
|
2887
3254
|
tenant?.billingDelegation,
|
|
2888
3255
|
tenantID,
|
|
2889
3256
|
]
|