impact-chatbot 2.3.61 → 2.3.63
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/dist/index.cjs.js +335 -74
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +335 -74
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -5203,9 +5203,28 @@ const sseevent = (message, messageToStoreRef) => {
|
|
|
5203
5203
|
messageToStoreRef.current.navSessionId = parsedData.session_id;
|
|
5204
5204
|
}
|
|
5205
5205
|
if (parsedData?.is_error) {
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5206
|
+
// Only append the error message once, even if multiple is_error chunks arrive.
|
|
5207
|
+
// Append as a widget item to appendedData so it renders AFTER any existing
|
|
5208
|
+
// widgets (tables, graphs, text widgets) rather than at the top of the response.
|
|
5209
|
+
if (!messageToStoreRef.current.hasErrorMessage) {
|
|
5210
|
+
messageToStoreRef.current.hasErrorMessage = true;
|
|
5211
|
+
const errorMsg = parsedData?.message && parsedData.message !== "[DONE]"
|
|
5212
|
+
? parsedData.message
|
|
5213
|
+
: "There is an error, please reach out to IA with this use case.";
|
|
5214
|
+
const errorWidget = { type: "text", response: errorMsg };
|
|
5215
|
+
const prevAppended = messageToStoreRef.current.appendedData;
|
|
5216
|
+
if (Array.isArray(prevAppended) && prevAppended.length > 0) {
|
|
5217
|
+
messageToStoreRef.current.appendedData = [...prevAppended, errorWidget];
|
|
5218
|
+
}
|
|
5219
|
+
else if (prevAppended && typeof prevAppended === "object" && Object.keys(prevAppended).length > 0) {
|
|
5220
|
+
messageToStoreRef.current.appendedData = [prevAppended, errorWidget];
|
|
5221
|
+
}
|
|
5222
|
+
else {
|
|
5223
|
+
// No existing widgets — put on chatData.response instead
|
|
5224
|
+
messageToStoreRef.current.chatData.response =
|
|
5225
|
+
messageToStoreRef.current.chatData.response + errorMsg;
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
5209
5228
|
// Still process completed/follow-up status even on error chunks
|
|
5210
5229
|
// so that initValue is set correctly and dummyButton is suppressed
|
|
5211
5230
|
if (parsedData?.status === "completed" ||
|
|
@@ -5387,6 +5406,7 @@ const AxiosSource = (url, opts, messageToStoreRef) => {
|
|
|
5387
5406
|
})
|
|
5388
5407
|
.catch((error) => {
|
|
5389
5408
|
let reason = "Network request failed";
|
|
5409
|
+
let statusCode = 0;
|
|
5390
5410
|
console.error("[AxiosSource] Request failed:", error);
|
|
5391
5411
|
// Determine specific error reasons
|
|
5392
5412
|
if (axios.isCancel(error)) {
|
|
@@ -5395,8 +5415,15 @@ const AxiosSource = (url, opts, messageToStoreRef) => {
|
|
|
5395
5415
|
else if (error.code === "ECONNABORTED") {
|
|
5396
5416
|
reason = "Network request timed out";
|
|
5397
5417
|
}
|
|
5398
|
-
//
|
|
5399
|
-
|
|
5418
|
+
// Capture HTTP status code from the error response (e.g. 503)
|
|
5419
|
+
if (error.response && error.response.status) {
|
|
5420
|
+
statusCode = error.response.status;
|
|
5421
|
+
reason = `HTTP ${statusCode}: ${reason}`;
|
|
5422
|
+
}
|
|
5423
|
+
// Emit error event with the specific reason and status code
|
|
5424
|
+
const errorEvent = new CloseEvent("error", { reason });
|
|
5425
|
+
errorEvent.statusCode = statusCode;
|
|
5426
|
+
eventTarget.dispatchEvent(errorEvent);
|
|
5400
5427
|
});
|
|
5401
5428
|
// Add method to manually close the connection
|
|
5402
5429
|
eventTarget.close = () => {
|
|
@@ -5437,10 +5464,15 @@ const useStyles$6 = makeStyles((theme) => ({
|
|
|
5437
5464
|
marginTop: pxToRem(16),
|
|
5438
5465
|
}
|
|
5439
5466
|
}));
|
|
5467
|
+
const MAX_RETRY_COUNT_BUTTON = 1;
|
|
5468
|
+
const RETRY_COUNTDOWN_SECONDS_BUTTON = 15;
|
|
5440
5469
|
const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = false, isFormValid = true }) => {
|
|
5441
5470
|
const classes = useStyles$6();
|
|
5442
5471
|
const dispatch = useDispatch();
|
|
5443
5472
|
const sourceRef = useRef(null);
|
|
5473
|
+
const retryCountRef = useRef(0);
|
|
5474
|
+
const retryTimerRef = useRef(null);
|
|
5475
|
+
const retryPayloadRef = useRef(null);
|
|
5444
5476
|
const messageStoreRef = useRef({
|
|
5445
5477
|
currentMode: "agent",
|
|
5446
5478
|
chatData: {
|
|
@@ -5484,6 +5516,8 @@ const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = fa
|
|
|
5484
5516
|
sendButton.click();
|
|
5485
5517
|
};
|
|
5486
5518
|
const callInitApiStream = (userInput) => {
|
|
5519
|
+
// Store payload for potential retries
|
|
5520
|
+
retryPayloadRef.current = userInput;
|
|
5487
5521
|
// Prefer module-level values (set synchronously by StreamedContent), fall back to sessionStorage
|
|
5488
5522
|
// If a previous completed/follow-up response updated the state, use those values.
|
|
5489
5523
|
// Read from module-level stepFormStreamControl (survives remounts) instead of local messageStoreRef.
|
|
@@ -5683,12 +5717,65 @@ const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = fa
|
|
|
5683
5717
|
console.error("[ButtonContent] SSE parse error:", e);
|
|
5684
5718
|
}
|
|
5685
5719
|
});
|
|
5686
|
-
sourceRef.current.addEventListener("error", () => {
|
|
5687
|
-
// On error, still dispatch whatever we collected
|
|
5720
|
+
sourceRef.current.addEventListener("error", (event) => {
|
|
5688
5721
|
stepFormStreamControl.isStreaming = false;
|
|
5689
5722
|
stepFormStreamControl.abort = null;
|
|
5690
5723
|
window.dispatchEvent(new CustomEvent("stepFormStreamEnd"));
|
|
5691
|
-
|
|
5724
|
+
// Don't retry if the user manually aborted the request
|
|
5725
|
+
const isUserAbort = event.reason === "Network request aborted";
|
|
5726
|
+
// Check if we can auto-retry (abrupt close or 503, but not user abort)
|
|
5727
|
+
if (!isUserAbort && retryCountRef.current < MAX_RETRY_COUNT_BUTTON) {
|
|
5728
|
+
// Dispatch a "retrying" status so TabularContent can show countdown UI
|
|
5729
|
+
dispatch(setStepFormStreamData({ status: "retrying", chunks: [...chunksRef], sessionId, countdown: RETRY_COUNTDOWN_SECONDS_BUTTON }));
|
|
5730
|
+
// Start countdown and auto-retry
|
|
5731
|
+
let countdown = RETRY_COUNTDOWN_SECONDS_BUTTON;
|
|
5732
|
+
retryTimerRef.current = setInterval(() => {
|
|
5733
|
+
countdown -= 1;
|
|
5734
|
+
if (countdown <= 0) {
|
|
5735
|
+
clearInterval(retryTimerRef.current);
|
|
5736
|
+
retryTimerRef.current = null;
|
|
5737
|
+
retryCountRef.current += 1;
|
|
5738
|
+
// Re-invoke with same payload
|
|
5739
|
+
callInitApiStream(retryPayloadRef.current);
|
|
5740
|
+
}
|
|
5741
|
+
else {
|
|
5742
|
+
// Update countdown in Redux for UI
|
|
5743
|
+
dispatch(setStepFormStreamData({ status: "retrying", chunks: [...chunksRef], sessionId, countdown }));
|
|
5744
|
+
}
|
|
5745
|
+
}, 1000);
|
|
5746
|
+
}
|
|
5747
|
+
else {
|
|
5748
|
+
// Retries exhausted or user-aborted — show final message
|
|
5749
|
+
chunksRef.push({ status: "content", message: "Please reach out to IA for this, as this didn't work even after retry" });
|
|
5750
|
+
dispatch(setStepFormStreamData({ status: "error", chunks: [...chunksRef], sessionId }));
|
|
5751
|
+
}
|
|
5752
|
+
});
|
|
5753
|
+
sourceRef.current.addEventListener("close", () => {
|
|
5754
|
+
if (stepFormStreamControl.isStreaming) {
|
|
5755
|
+
stepFormStreamControl.isStreaming = false;
|
|
5756
|
+
stepFormStreamControl.abort = null;
|
|
5757
|
+
window.dispatchEvent(new CustomEvent("stepFormStreamEnd"));
|
|
5758
|
+
if (retryCountRef.current < MAX_RETRY_COUNT_BUTTON) {
|
|
5759
|
+
dispatch(setStepFormStreamData({ status: "retrying", chunks: [...chunksRef], sessionId, countdown: RETRY_COUNTDOWN_SECONDS_BUTTON }));
|
|
5760
|
+
let countdown = RETRY_COUNTDOWN_SECONDS_BUTTON;
|
|
5761
|
+
retryTimerRef.current = setInterval(() => {
|
|
5762
|
+
countdown -= 1;
|
|
5763
|
+
if (countdown <= 0) {
|
|
5764
|
+
clearInterval(retryTimerRef.current);
|
|
5765
|
+
retryTimerRef.current = null;
|
|
5766
|
+
retryCountRef.current += 1;
|
|
5767
|
+
callInitApiStream(retryPayloadRef.current);
|
|
5768
|
+
}
|
|
5769
|
+
else {
|
|
5770
|
+
dispatch(setStepFormStreamData({ status: "retrying", chunks: [...chunksRef], sessionId, countdown }));
|
|
5771
|
+
}
|
|
5772
|
+
}, 1000);
|
|
5773
|
+
}
|
|
5774
|
+
else {
|
|
5775
|
+
chunksRef.push({ status: "content", message: "Please reach out to IA for this, as this didn't work even after retry" });
|
|
5776
|
+
dispatch(setStepFormStreamData({ status: "error", chunks: [...chunksRef], sessionId }));
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5692
5779
|
});
|
|
5693
5780
|
};
|
|
5694
5781
|
const renderButtons = () => {
|
|
@@ -5994,6 +6081,15 @@ const useStyles$5 = makeStyles((theme) => ({
|
|
|
5994
6081
|
position: "relative",
|
|
5995
6082
|
background: colours.white,
|
|
5996
6083
|
},
|
|
6084
|
+
retryContainer: {
|
|
6085
|
+
marginTop: pxToRem(8),
|
|
6086
|
+
},
|
|
6087
|
+
retryMessage: {
|
|
6088
|
+
fontFamily: "Manrope",
|
|
6089
|
+
fontSize: pxToRem(13),
|
|
6090
|
+
fontWeight: 500,
|
|
6091
|
+
color: "#374151",
|
|
6092
|
+
},
|
|
5997
6093
|
}));
|
|
5998
6094
|
|
|
5999
6095
|
var _path$i, _path2$2, _defs$5;
|
|
@@ -7698,8 +7794,20 @@ const AgentResponse$1 = (props) => {
|
|
|
7698
7794
|
};
|
|
7699
7795
|
|
|
7700
7796
|
const StepsResponseTab = (props) => {
|
|
7701
|
-
const { steps, setSteps, stepsDone, setStepsDone, finalStepDone, setFinalStepDone, content, isStreaming, stepChange, currentMode, questions, questionsStepsMap, stepFormDataMap, isFormDisabled, streamingWidgetData, } = props;
|
|
7797
|
+
const { steps, setSteps, stepsDone, setStepsDone, finalStepDone, setFinalStepDone, content, isStreaming, stepChange, currentMode, questions, questionsStepsMap, stepFormDataMap, isFormDisabled, streamingWidgetData, isRetrying, } = props;
|
|
7702
7798
|
const [tabValue, setTabValue] = useState("steps");
|
|
7799
|
+
// When retry countdown starts, switch to agent_response to show it.
|
|
7800
|
+
// When retry actually fires (isRetrying goes false + isStreaming goes true), switch back to steps.
|
|
7801
|
+
useEffect(() => {
|
|
7802
|
+
if (isRetrying) {
|
|
7803
|
+
setTabValue("agent_response");
|
|
7804
|
+
}
|
|
7805
|
+
}, [isRetrying]);
|
|
7806
|
+
useEffect(() => {
|
|
7807
|
+
if (isStreaming && !isRetrying) {
|
|
7808
|
+
setTabValue("steps");
|
|
7809
|
+
}
|
|
7810
|
+
}, [isStreaming, isRetrying]);
|
|
7703
7811
|
const handleChangeTabValue = (_event, newValue) => {
|
|
7704
7812
|
setTabValue(newValue);
|
|
7705
7813
|
};
|
|
@@ -7929,6 +8037,14 @@ const StreamedContent = ({ botData }) => {
|
|
|
7929
8037
|
const [finalStepDone, setFinalStepDone] = useState(false);
|
|
7930
8038
|
const [wasStreamingAborted, setWasStreamingAborted] = useState(false);
|
|
7931
8039
|
const wasStreamingAbortedRef = useRef(false);
|
|
8040
|
+
// Auto-retry state
|
|
8041
|
+
const MAX_RETRY_COUNT = 1; // Number of auto-retries allowed
|
|
8042
|
+
const RETRY_COUNTDOWN_SECONDS = 15; // Countdown duration before auto-retry
|
|
8043
|
+
const [isRetrying, setIsRetrying] = useState(false);
|
|
8044
|
+
const [retryCountdown, setRetryCountdown] = useState(0);
|
|
8045
|
+
const retryCountRef = useRef(0); // How many retries have been attempted
|
|
8046
|
+
const retryTimerRef = useRef(null); // Interval ref for countdown
|
|
8047
|
+
const retryCountdownRef = useRef(0); // Non-state countdown for interval callback
|
|
7932
8048
|
const thinkingContentRef = useRef("");
|
|
7933
8049
|
useSelector((state) => {
|
|
7934
8050
|
return state.smartBotReducer.thinkingContext;
|
|
@@ -8048,6 +8164,94 @@ const StreamedContent = ({ botData }) => {
|
|
|
8048
8164
|
}));
|
|
8049
8165
|
}
|
|
8050
8166
|
}, [stepChange, questions, questionsStepsMap, stepsDone, isStreamingDone]);
|
|
8167
|
+
/**
|
|
8168
|
+
* Starts a countdown for auto-retry. Shows countdown in UI, then calls retryStream.
|
|
8169
|
+
*/
|
|
8170
|
+
const startRetryCountdown = () => {
|
|
8171
|
+
setIsRetrying(true);
|
|
8172
|
+
retryCountdownRef.current = RETRY_COUNTDOWN_SECONDS;
|
|
8173
|
+
setRetryCountdown(RETRY_COUNTDOWN_SECONDS);
|
|
8174
|
+
retryTimerRef.current = setInterval(() => {
|
|
8175
|
+
retryCountdownRef.current -= 1;
|
|
8176
|
+
setRetryCountdown(retryCountdownRef.current);
|
|
8177
|
+
if (retryCountdownRef.current <= 0) {
|
|
8178
|
+
clearInterval(retryTimerRef.current);
|
|
8179
|
+
retryTimerRef.current = null;
|
|
8180
|
+
retryStream();
|
|
8181
|
+
}
|
|
8182
|
+
}, 1000);
|
|
8183
|
+
};
|
|
8184
|
+
/**
|
|
8185
|
+
* Retries the stream by resetting state and calling processStream again.
|
|
8186
|
+
*/
|
|
8187
|
+
const retryStream = () => {
|
|
8188
|
+
// Clear countdown state
|
|
8189
|
+
if (retryTimerRef.current) {
|
|
8190
|
+
clearInterval(retryTimerRef.current);
|
|
8191
|
+
retryTimerRef.current = null;
|
|
8192
|
+
}
|
|
8193
|
+
setIsRetrying(false);
|
|
8194
|
+
setRetryCountdown(0);
|
|
8195
|
+
retryCountRef.current += 1;
|
|
8196
|
+
// Reset streaming state for a fresh start
|
|
8197
|
+
setContent("");
|
|
8198
|
+
setIsStreaming(true);
|
|
8199
|
+
setIsStreamingDone(false);
|
|
8200
|
+
setStepsDone(false);
|
|
8201
|
+
setFinalStepDone(false);
|
|
8202
|
+
setWasStreamingAborted(false);
|
|
8203
|
+
wasStreamingAbortedRef.current = false;
|
|
8204
|
+
setSteps([{ header: "Processing Request", sub_header: "Analyzing the current request", step_status: "not-completed" }]);
|
|
8205
|
+
stepRef.current = [{ header: "Processing Request", sub_header: "Analyzing the current request", step_status: "not-completed" }];
|
|
8206
|
+
setQuestions([]);
|
|
8207
|
+
questionsRef.current = [];
|
|
8208
|
+
setQuestionsStepsMap({});
|
|
8209
|
+
questionsStepsMapRef.current = {};
|
|
8210
|
+
setStepFormDataMap({});
|
|
8211
|
+
stepFormDataMapRef.current = {};
|
|
8212
|
+
setStreamingWidgetData([]);
|
|
8213
|
+
streamingDoneProcessedRef.current = false;
|
|
8214
|
+
// Reset messageToStoreRef for fresh accumulation
|
|
8215
|
+
messageToStoreRef.current = {
|
|
8216
|
+
status: "",
|
|
8217
|
+
currentMode: currentMode,
|
|
8218
|
+
chatData: {
|
|
8219
|
+
response: "",
|
|
8220
|
+
response_heading: "",
|
|
8221
|
+
thinkingResponse: {
|
|
8222
|
+
thinkingContent: jsx(ThinkingIndicator, { thinkingContent: thinkingContent }),
|
|
8223
|
+
thinkingStream: "",
|
|
8224
|
+
thinkingTime: 0,
|
|
8225
|
+
thinkingHeading: null,
|
|
8226
|
+
},
|
|
8227
|
+
},
|
|
8228
|
+
appendedData: {},
|
|
8229
|
+
appendedDataFromLastChunk: {},
|
|
8230
|
+
initValue: false,
|
|
8231
|
+
sessionId: "",
|
|
8232
|
+
navSessionId: "",
|
|
8233
|
+
uniqueChatId: "",
|
|
8234
|
+
additionalArgs: {},
|
|
8235
|
+
};
|
|
8236
|
+
// Reset stream state map entry and invalidate old listeners
|
|
8237
|
+
const state = streamStateMap.get(streamKey);
|
|
8238
|
+
if (state) {
|
|
8239
|
+
state.messageStore = messageToStoreRef.current;
|
|
8240
|
+
state.completed = false;
|
|
8241
|
+
// Increment listenerGeneration so any in-flight events from the old source are ignored
|
|
8242
|
+
state.listenerGeneration = (state.listenerGeneration || 0) + 1;
|
|
8243
|
+
// Close the old source if it's still open
|
|
8244
|
+
if (state.source && typeof state.source.close === "function") {
|
|
8245
|
+
try {
|
|
8246
|
+
state.source.close();
|
|
8247
|
+
}
|
|
8248
|
+
catch (e) { /* ignore */ }
|
|
8249
|
+
}
|
|
8250
|
+
state.source = null;
|
|
8251
|
+
}
|
|
8252
|
+
// Re-initiate the stream
|
|
8253
|
+
processStream();
|
|
8254
|
+
};
|
|
8051
8255
|
/**
|
|
8052
8256
|
* Main effect to initialize and handle the streaming connection
|
|
8053
8257
|
* Sets up the SSE connection and event listeners for message processing
|
|
@@ -8153,6 +8357,26 @@ const StreamedContent = ({ botData }) => {
|
|
|
8153
8357
|
}));
|
|
8154
8358
|
return;
|
|
8155
8359
|
}
|
|
8360
|
+
// Skip is_error chunks — already handled in AxiosEventSource sseevent()
|
|
8361
|
+
if (data?.is_error) {
|
|
8362
|
+
// Trigger completion if status is "completed"
|
|
8363
|
+
if (data?.status === "completed") {
|
|
8364
|
+
setStepsDone(true);
|
|
8365
|
+
setIsStreamingDone(true);
|
|
8366
|
+
const doneState = streamStateMap.get(streamKey);
|
|
8367
|
+
if (doneState)
|
|
8368
|
+
doneState.completed = true;
|
|
8369
|
+
dispatch(setMinimizedStreamData({
|
|
8370
|
+
isStreaming: false,
|
|
8371
|
+
stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Completed",
|
|
8372
|
+
stepSubHeader: "Done",
|
|
8373
|
+
stepStatus: "completed",
|
|
8374
|
+
streamStartTime: streamStartTimeRef.current,
|
|
8375
|
+
actionCount: questionsRef.current.length || 1,
|
|
8376
|
+
}));
|
|
8377
|
+
}
|
|
8378
|
+
return;
|
|
8379
|
+
}
|
|
8156
8380
|
if (data?.message || data?.status === "step" || data?.status === "step_form" || data?.status === "thinking" || data?.status === "questions" || data?.status === "widget") {
|
|
8157
8381
|
if (data.status === "questions") {
|
|
8158
8382
|
const incomingQuestions = data.widget_data?.[0]?.questions || [];
|
|
@@ -8422,64 +8646,36 @@ const StreamedContent = ({ botData }) => {
|
|
|
8422
8646
|
const errState = streamStateMap.get(streamKey);
|
|
8423
8647
|
if (!errState || errState.listenerGeneration !== generation)
|
|
8424
8648
|
return;
|
|
8425
|
-
console.error("Stream error:", event.reason);
|
|
8649
|
+
console.error("Stream error:", event.reason, "statusCode:", event.statusCode);
|
|
8426
8650
|
setIsStreaming(false);
|
|
8427
8651
|
errState.completed = true;
|
|
8652
|
+
// Check if this is a user-initiated abort (should not trigger retry)
|
|
8653
|
+
const isUserAbort = event.reason === "Network request aborted";
|
|
8654
|
+
const is503 = event.statusCode === 503;
|
|
8428
8655
|
if (isThinking) {
|
|
8429
8656
|
setIsThinking(false);
|
|
8430
8657
|
const endTime = Date.now();
|
|
8431
8658
|
const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
|
|
8432
|
-
const finalThinkingTime = Math.max(duration, 1);
|
|
8659
|
+
const finalThinkingTime = Math.max(duration, 1);
|
|
8433
8660
|
setThinkingTime(finalThinkingTime);
|
|
8434
8661
|
setShowThoughtDropdown(true);
|
|
8435
8662
|
thinkingStartTimeRef.current = finalThinkingTime;
|
|
8436
|
-
// Store thinking time in messageToStoreRef for persistence
|
|
8437
8663
|
messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
|
|
8438
8664
|
}
|
|
8439
|
-
//
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
}
|
|
8452
|
-
// Set the response message for the agent_response tab
|
|
8453
|
-
const abruptCloseMessage = "The requested Task has ended unexpectedly, please retry again";
|
|
8454
|
-
messageToStoreRef.current.chatData.response = abruptCloseMessage;
|
|
8455
|
-
setContent(abruptCloseMessage);
|
|
8456
|
-
// Dispatch minimized widget data
|
|
8457
|
-
dispatch(setMinimizedStreamData({
|
|
8458
|
-
isStreaming: false,
|
|
8459
|
-
stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
|
|
8460
|
-
stepSubHeader: "Connection closed",
|
|
8461
|
-
stepStatus: "completed",
|
|
8462
|
-
streamStartTime: streamStartTimeRef.current,
|
|
8463
|
-
actionCount: questionsRef.current.length || 1,
|
|
8464
|
-
}));
|
|
8465
|
-
// Switch to agent_response tab and trigger completion flow
|
|
8466
|
-
setStepsDone(true);
|
|
8467
|
-
setIsStreamingDone(true);
|
|
8468
|
-
});
|
|
8469
|
-
// Handle stream closure
|
|
8470
|
-
source.addEventListener("close", () => {
|
|
8471
|
-
const closeState = streamStateMap.get(streamKey);
|
|
8472
|
-
if (!closeState || closeState.listenerGeneration !== generation)
|
|
8473
|
-
return;
|
|
8474
|
-
setIsStreaming(false);
|
|
8475
|
-
// If closeState.completed is already true, this is a normal close after [DONE].
|
|
8476
|
-
// If it's still false, the backend closed the connection abruptly.
|
|
8477
|
-
if (!closeState.completed) {
|
|
8478
|
-
closeState.completed = true;
|
|
8479
|
-
// Mark as aborted so the completion effect skips the Submit button
|
|
8665
|
+
// Check if we can auto-retry (abrupt close or 503, but not user abort)
|
|
8666
|
+
if (!isUserAbort && retryCountRef.current < MAX_RETRY_COUNT) {
|
|
8667
|
+
// Still have retries left — start countdown instead of showing final error
|
|
8668
|
+
setStepsDone(true); // Switch to agent_response tab to show countdown
|
|
8669
|
+
const retryMessage = is503
|
|
8670
|
+
? "Service temporarily unavailable (503), auto re-trying..."
|
|
8671
|
+
: "The service is temporarily unavailable because of an unusual load. Please wait a moment and try again.";
|
|
8672
|
+
setContent(retryMessage);
|
|
8673
|
+
startRetryCountdown();
|
|
8674
|
+
}
|
|
8675
|
+
else {
|
|
8676
|
+
// Retries exhausted — show final message
|
|
8480
8677
|
setWasStreamingAborted(true);
|
|
8481
8678
|
wasStreamingAbortedRef.current = true;
|
|
8482
|
-
// Mark the last in-progress step as completed
|
|
8483
8679
|
const currentSteps = stepRef.current;
|
|
8484
8680
|
if (currentSteps.length > 0) {
|
|
8485
8681
|
const lastStep = currentSteps[currentSteps.length - 1];
|
|
@@ -8489,21 +8685,9 @@ const StreamedContent = ({ botData }) => {
|
|
|
8489
8685
|
stepRef.current = [...currentSteps];
|
|
8490
8686
|
setSteps([...currentSteps]);
|
|
8491
8687
|
}
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
setContent(abruptCloseMessage);
|
|
8496
|
-
// Stop thinking if in progress
|
|
8497
|
-
if (isThinking) {
|
|
8498
|
-
setIsThinking(false);
|
|
8499
|
-
const endTime = Date.now();
|
|
8500
|
-
const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
|
|
8501
|
-
const finalThinkingTime = Math.max(duration, 1);
|
|
8502
|
-
setThinkingTime(finalThinkingTime);
|
|
8503
|
-
setShowThoughtDropdown(true);
|
|
8504
|
-
messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
|
|
8505
|
-
}
|
|
8506
|
-
// Dispatch minimized widget data
|
|
8688
|
+
const finalMessage = "Please reach out to IA for this, as this didn't work even after retry";
|
|
8689
|
+
messageToStoreRef.current.chatData.response = finalMessage;
|
|
8690
|
+
setContent(finalMessage);
|
|
8507
8691
|
dispatch(setMinimizedStreamData({
|
|
8508
8692
|
isStreaming: false,
|
|
8509
8693
|
stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
|
|
@@ -8512,10 +8696,65 @@ const StreamedContent = ({ botData }) => {
|
|
|
8512
8696
|
streamStartTime: streamStartTimeRef.current,
|
|
8513
8697
|
actionCount: questionsRef.current.length || 1,
|
|
8514
8698
|
}));
|
|
8515
|
-
// Switch to agent_response tab and trigger completion flow
|
|
8516
8699
|
setStepsDone(true);
|
|
8517
8700
|
setIsStreamingDone(true);
|
|
8518
8701
|
}
|
|
8702
|
+
});
|
|
8703
|
+
// Handle stream closure
|
|
8704
|
+
source.addEventListener("close", () => {
|
|
8705
|
+
const closeState = streamStateMap.get(streamKey);
|
|
8706
|
+
if (!closeState || closeState.listenerGeneration !== generation)
|
|
8707
|
+
return;
|
|
8708
|
+
setIsStreaming(false);
|
|
8709
|
+
// If closeState.completed is already true, this is a normal close after [DONE].
|
|
8710
|
+
// If it's still false, the backend closed the connection abruptly.
|
|
8711
|
+
if (!closeState.completed) {
|
|
8712
|
+
closeState.completed = true;
|
|
8713
|
+
// Stop thinking if in progress
|
|
8714
|
+
if (isThinking) {
|
|
8715
|
+
setIsThinking(false);
|
|
8716
|
+
const endTime = Date.now();
|
|
8717
|
+
const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
|
|
8718
|
+
const finalThinkingTime = Math.max(duration, 1);
|
|
8719
|
+
setThinkingTime(finalThinkingTime);
|
|
8720
|
+
setShowThoughtDropdown(true);
|
|
8721
|
+
messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
|
|
8722
|
+
}
|
|
8723
|
+
// Check if we can auto-retry
|
|
8724
|
+
if (retryCountRef.current < MAX_RETRY_COUNT) {
|
|
8725
|
+
// Still have retries left — start countdown instead of showing final error
|
|
8726
|
+
setStepsDone(true); // Switch to agent_response tab to show countdown
|
|
8727
|
+
setContent("The service is temporarily unavailable because of an unusual load. Please wait a moment and try again.");
|
|
8728
|
+
startRetryCountdown();
|
|
8729
|
+
}
|
|
8730
|
+
else {
|
|
8731
|
+
// Retries exhausted — show final elegant message
|
|
8732
|
+
setWasStreamingAborted(true);
|
|
8733
|
+
wasStreamingAbortedRef.current = true;
|
|
8734
|
+
const currentSteps = stepRef.current;
|
|
8735
|
+
if (currentSteps.length > 0) {
|
|
8736
|
+
const lastStep = currentSteps[currentSteps.length - 1];
|
|
8737
|
+
if (lastStep.step_status === "not-completed") {
|
|
8738
|
+
lastStep.step_status = "completed";
|
|
8739
|
+
}
|
|
8740
|
+
stepRef.current = [...currentSteps];
|
|
8741
|
+
setSteps([...currentSteps]);
|
|
8742
|
+
}
|
|
8743
|
+
const finalMessage = "Please reach out to IA for this, as this didn't work even after retry";
|
|
8744
|
+
messageToStoreRef.current.chatData.response = finalMessage;
|
|
8745
|
+
setContent(finalMessage);
|
|
8746
|
+
dispatch(setMinimizedStreamData({
|
|
8747
|
+
isStreaming: false,
|
|
8748
|
+
stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
|
|
8749
|
+
stepSubHeader: "Connection closed",
|
|
8750
|
+
stepStatus: "completed",
|
|
8751
|
+
streamStartTime: streamStartTimeRef.current,
|
|
8752
|
+
actionCount: questionsRef.current.length || 1,
|
|
8753
|
+
}));
|
|
8754
|
+
setStepsDone(true);
|
|
8755
|
+
setIsStreamingDone(true);
|
|
8756
|
+
}
|
|
8757
|
+
}
|
|
8519
8758
|
// else: closeState.completed is already true — normal close after [DONE], nothing to do.
|
|
8520
8759
|
});
|
|
8521
8760
|
};
|
|
@@ -8819,6 +9058,14 @@ const StreamedContent = ({ botData }) => {
|
|
|
8819
9058
|
* Aborts the current streaming connection
|
|
8820
9059
|
*/
|
|
8821
9060
|
const abortStreaming = () => {
|
|
9061
|
+
// If a retry countdown is running, clear it and prevent further retries
|
|
9062
|
+
if (retryTimerRef.current) {
|
|
9063
|
+
clearInterval(retryTimerRef.current);
|
|
9064
|
+
retryTimerRef.current = null;
|
|
9065
|
+
setIsRetrying(false);
|
|
9066
|
+
setRetryCountdown(0);
|
|
9067
|
+
}
|
|
9068
|
+
retryCountRef.current = MAX_RETRY_COUNT; // Exhaust retries so no further auto-retries happen
|
|
8822
9069
|
if (sourceRef.current && isStreaming) {
|
|
8823
9070
|
setWasStreamingAborted(true);
|
|
8824
9071
|
wasStreamingAbortedRef.current = true;
|
|
@@ -8866,9 +9113,9 @@ const StreamedContent = ({ botData }) => {
|
|
|
8866
9113
|
// If no content yet, show aborted message
|
|
8867
9114
|
// if (isEmpty(messageToStoreRef.current.chatData?.response)) {
|
|
8868
9115
|
messageToStoreRef.current.chatData.response =
|
|
8869
|
-
messageToStoreRef.current.chatData.response + "\n\
|
|
9116
|
+
messageToStoreRef.current.chatData.response + "\n\nYou stopped this response. You can ask a new question whenever you're ready";
|
|
8870
9117
|
setContent((prev) => {
|
|
8871
|
-
return prev + "\n\
|
|
9118
|
+
return prev + "\n\nYou stopped this response. You can ask a new question whenever you're ready";
|
|
8872
9119
|
});
|
|
8873
9120
|
// }
|
|
8874
9121
|
}
|
|
@@ -8890,10 +9137,16 @@ const StreamedContent = ({ botData }) => {
|
|
|
8890
9137
|
* Cleanup on unmount: Do NOT close the stream here.
|
|
8891
9138
|
* The stream connection is kept alive in streamStateMap so it survives
|
|
8892
9139
|
* tab-switch remounts. It is only closed by explicit abortStreaming() or new chat.
|
|
9140
|
+
* Also clean up retry timer if running.
|
|
8893
9141
|
*/
|
|
8894
9142
|
useEffect(() => {
|
|
8895
9143
|
return () => {
|
|
8896
9144
|
// Intentionally not closing the stream - it persists in streamStateMap
|
|
9145
|
+
// Clean up retry timer
|
|
9146
|
+
if (retryTimerRef.current) {
|
|
9147
|
+
clearInterval(retryTimerRef.current);
|
|
9148
|
+
retryTimerRef.current = null;
|
|
9149
|
+
}
|
|
8897
9150
|
};
|
|
8898
9151
|
}, []);
|
|
8899
9152
|
/**
|
|
@@ -8945,7 +9198,7 @@ const StreamedContent = ({ botData }) => {
|
|
|
8945
9198
|
* @returns {JSX.Element} Rendered content with optional blinking cursor
|
|
8946
9199
|
*/
|
|
8947
9200
|
const renderContent = () => {
|
|
8948
|
-
return (
|
|
9201
|
+
return (jsxs("div", { className: classes.streamContainer, children: [jsx(StepsResponseTab, { steps: steps, stepChange: stepChange, setSteps: setSteps, stepsDone: stepsDone, setStepsDone: setStepsDone, finalStepDone: finalStepDone, setFinalStepDone: setFinalStepDone, content: content, isStreaming: isStreaming, currentMode: currentMode, questions: questions, questionsStepsMap: questionsStepsMap, stepFormDataMap: stepFormDataMap, isFormDisabled: botData?.isFormDisabled || false, streamingWidgetData: streamingWidgetData, isRetrying: isRetrying }), isRetrying && (jsx("div", { className: classes.retryContainer, children: jsxs("span", { className: classes.retryMessage, children: ["Auto re-trying in ", retryCountdown, "s"] }) }))] }));
|
|
8949
9202
|
};
|
|
8950
9203
|
if (currentMode === "agent") {
|
|
8951
9204
|
return renderContent();
|
|
@@ -9237,6 +9490,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
|
|
|
9237
9490
|
const [stepFormSubmitted, setStepFormSubmitted] = useState(false);
|
|
9238
9491
|
const [hasNewStepFormFromRestream, setHasNewStepFormFromRestream] = useState(false);
|
|
9239
9492
|
const [activeFormIntent, setActiveFormIntent] = useState(null);
|
|
9493
|
+
const [retryCountdown, setRetryCountdown] = useState(0);
|
|
9240
9494
|
// Stable unique instance ID for this TabularContent mount
|
|
9241
9495
|
const instanceIdRef = useRef(null);
|
|
9242
9496
|
if (instanceIdRef.current === null) {
|
|
@@ -9302,10 +9556,15 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
|
|
|
9302
9556
|
const payload = stepFormStreamData;
|
|
9303
9557
|
// Clear Redux immediately
|
|
9304
9558
|
dispatch(setStepFormStreamData(null));
|
|
9559
|
+
if (payload.status === "retrying") {
|
|
9560
|
+
setRetryCountdown(payload.countdown || 0);
|
|
9561
|
+
return;
|
|
9562
|
+
}
|
|
9305
9563
|
if (payload.status === "streaming_start") {
|
|
9306
9564
|
setIsRestreaming(true);
|
|
9307
9565
|
setStepFormSubmitted(true);
|
|
9308
9566
|
setHasNewStepFormFromRestream(false);
|
|
9567
|
+
setRetryCountdown(0);
|
|
9309
9568
|
setTabValue("steps");
|
|
9310
9569
|
return;
|
|
9311
9570
|
}
|
|
@@ -9445,6 +9704,8 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
|
|
|
9445
9704
|
}
|
|
9446
9705
|
return;
|
|
9447
9706
|
}
|
|
9707
|
+
// Clear retry countdown when final processing starts
|
|
9708
|
+
setRetryCountdown(0);
|
|
9448
9709
|
// Process all collected chunks at once (done or error)
|
|
9449
9710
|
const chunks = payload.chunks || [];
|
|
9450
9711
|
let newSteps = cloneDeep(stepsRef.current);
|
|
@@ -9593,7 +9854,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
|
|
|
9593
9854
|
},
|
|
9594
9855
|
], tabPanels: [
|
|
9595
9856
|
jsx(Steps, { steps: stepsState, questions: questionsState, questionsStepsMap: questionsStepsMapState, stepFormDataMap: stepFormDataMapState, isFormDisabled: (isFormDisabled && !isRestreaming) || stepFormSubmitted, activeFormIntent: hasNewStepFormFromRestream ? activeFormIntent : null, isRestreaming: isRestreaming }),
|
|
9596
|
-
jsxs(AgentResponse, { children: [children, renderedWidgets.length > 0 && (jsx("div", { className: "restream-widget-content", children: renderedWidgets }))] }),
|
|
9857
|
+
jsxs(AgentResponse, { children: [children, renderedWidgets.length > 0 && (jsx("div", { className: "restream-widget-content", children: renderedWidgets })), retryCountdown > 0 && (jsx("div", { style: { marginTop: "8px" }, children: jsxs("span", { style: { fontFamily: "Manrope", fontSize: "13px", fontWeight: 500, color: "#374151" }, children: ["Auto re-trying in ", retryCountdown, "s"] }) }))] }),
|
|
9597
9858
|
], value: tabValue }) }));
|
|
9598
9859
|
};
|
|
9599
9860
|
/** Reset the active instance tracker (call when a new conversation starts from the input field) */
|