impact-chatbot 2.3.62 → 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.esm.js CHANGED
@@ -5406,6 +5406,7 @@ const AxiosSource = (url, opts, messageToStoreRef) => {
5406
5406
  })
5407
5407
  .catch((error) => {
5408
5408
  let reason = "Network request failed";
5409
+ let statusCode = 0;
5409
5410
  console.error("[AxiosSource] Request failed:", error);
5410
5411
  // Determine specific error reasons
5411
5412
  if (axios.isCancel(error)) {
@@ -5414,8 +5415,15 @@ const AxiosSource = (url, opts, messageToStoreRef) => {
5414
5415
  else if (error.code === "ECONNABORTED") {
5415
5416
  reason = "Network request timed out";
5416
5417
  }
5417
- // Emit error event with the specific reason
5418
- eventTarget.dispatchEvent(new CloseEvent("error", { reason }));
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);
5419
5427
  });
5420
5428
  // Add method to manually close the connection
5421
5429
  eventTarget.close = () => {
@@ -5456,10 +5464,15 @@ const useStyles$6 = makeStyles((theme) => ({
5456
5464
  marginTop: pxToRem(16),
5457
5465
  }
5458
5466
  }));
5467
+ const MAX_RETRY_COUNT_BUTTON = 1;
5468
+ const RETRY_COUNTDOWN_SECONDS_BUTTON = 15;
5459
5469
  const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = false, isFormValid = true }) => {
5460
5470
  const classes = useStyles$6();
5461
5471
  const dispatch = useDispatch();
5462
5472
  const sourceRef = useRef(null);
5473
+ const retryCountRef = useRef(0);
5474
+ const retryTimerRef = useRef(null);
5475
+ const retryPayloadRef = useRef(null);
5463
5476
  const messageStoreRef = useRef({
5464
5477
  currentMode: "agent",
5465
5478
  chatData: {
@@ -5503,6 +5516,8 @@ const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = fa
5503
5516
  sendButton.click();
5504
5517
  };
5505
5518
  const callInitApiStream = (userInput) => {
5519
+ // Store payload for potential retries
5520
+ retryPayloadRef.current = userInput;
5506
5521
  // Prefer module-level values (set synchronously by StreamedContent), fall back to sessionStorage
5507
5522
  // If a previous completed/follow-up response updated the state, use those values.
5508
5523
  // Read from module-level stepFormStreamControl (survives remounts) instead of local messageStoreRef.
@@ -5702,33 +5717,64 @@ const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = fa
5702
5717
  console.error("[ButtonContent] SSE parse error:", e);
5703
5718
  }
5704
5719
  });
5705
- sourceRef.current.addEventListener("error", () => {
5706
- // On error, still dispatch whatever we collected
5720
+ sourceRef.current.addEventListener("error", (event) => {
5707
5721
  stepFormStreamControl.isStreaming = false;
5708
5722
  stepFormStreamControl.abort = null;
5709
5723
  window.dispatchEvent(new CustomEvent("stepFormStreamEnd"));
5710
- dispatch(setStepFormStreamData({ status: "error", chunks: chunksRef, sessionId }));
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
+ }
5711
5752
  });
5712
- // Handle stream closure — fires when the HTTP request completes successfully.
5713
- // If [DONE] was received, the "message" handler already dispatched status:"done"
5714
- // and set stepFormStreamControl.isStreaming = false.
5715
- // If [DONE] was NOT received, the connection closed abruptly mid-processing.
5716
5753
  sourceRef.current.addEventListener("close", () => {
5717
5754
  if (stepFormStreamControl.isStreaming) {
5718
5755
  stepFormStreamControl.isStreaming = false;
5719
5756
  stepFormStreamControl.abort = null;
5720
5757
  window.dispatchEvent(new CustomEvent("stepFormStreamEnd"));
5721
- // Append the abrupt close message as a content chunk so TabularContent
5722
- // renders it at the bottom (after any already-received widgets/steps)
5723
- chunksRef.push({
5724
- status: "content",
5725
- message: "The requested Task has ended unexpectedly, please retry again",
5726
- });
5727
- dispatch(setStepFormStreamData({
5728
- status: "error",
5729
- chunks: [...chunksRef],
5730
- sessionId,
5731
- }));
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
+ }
5732
5778
  }
5733
5779
  });
5734
5780
  };
@@ -6035,6 +6081,15 @@ const useStyles$5 = makeStyles((theme) => ({
6035
6081
  position: "relative",
6036
6082
  background: colours.white,
6037
6083
  },
6084
+ retryContainer: {
6085
+ marginTop: pxToRem(8),
6086
+ },
6087
+ retryMessage: {
6088
+ fontFamily: "Manrope",
6089
+ fontSize: pxToRem(13),
6090
+ fontWeight: 500,
6091
+ color: "#374151",
6092
+ },
6038
6093
  }));
6039
6094
 
6040
6095
  var _path$i, _path2$2, _defs$5;
@@ -7739,8 +7794,20 @@ const AgentResponse$1 = (props) => {
7739
7794
  };
7740
7795
 
7741
7796
  const StepsResponseTab = (props) => {
7742
- 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;
7743
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]);
7744
7811
  const handleChangeTabValue = (_event, newValue) => {
7745
7812
  setTabValue(newValue);
7746
7813
  };
@@ -7970,6 +8037,14 @@ const StreamedContent = ({ botData }) => {
7970
8037
  const [finalStepDone, setFinalStepDone] = useState(false);
7971
8038
  const [wasStreamingAborted, setWasStreamingAborted] = useState(false);
7972
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
7973
8048
  const thinkingContentRef = useRef("");
7974
8049
  useSelector((state) => {
7975
8050
  return state.smartBotReducer.thinkingContext;
@@ -8089,6 +8164,94 @@ const StreamedContent = ({ botData }) => {
8089
8164
  }));
8090
8165
  }
8091
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
+ };
8092
8255
  /**
8093
8256
  * Main effect to initialize and handle the streaming connection
8094
8257
  * Sets up the SSE connection and event listeners for message processing
@@ -8483,66 +8646,59 @@ const StreamedContent = ({ botData }) => {
8483
8646
  const errState = streamStateMap.get(streamKey);
8484
8647
  if (!errState || errState.listenerGeneration !== generation)
8485
8648
  return;
8486
- console.error("Stream error:", event.reason);
8649
+ console.error("Stream error:", event.reason, "statusCode:", event.statusCode);
8487
8650
  setIsStreaming(false);
8488
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;
8489
8655
  if (isThinking) {
8490
8656
  setIsThinking(false);
8491
8657
  const endTime = Date.now();
8492
8658
  const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
8493
- const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
8659
+ const finalThinkingTime = Math.max(duration, 1);
8494
8660
  setThinkingTime(finalThinkingTime);
8495
8661
  setShowThoughtDropdown(true);
8496
8662
  thinkingStartTimeRef.current = finalThinkingTime;
8497
- // Store thinking time in messageToStoreRef for persistence
8498
8663
  messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
8499
8664
  }
8500
- // Mark as aborted so the completion effect skips the Submit button
8501
- setWasStreamingAborted(true);
8502
- wasStreamingAbortedRef.current = true;
8503
- // Mark the last in-progress step as completed
8504
- const currentSteps = stepRef.current;
8505
- if (currentSteps.length > 0) {
8506
- const lastStep = currentSteps[currentSteps.length - 1];
8507
- if (lastStep.step_status === "not-completed") {
8508
- lastStep.step_status = "completed";
8509
- }
8510
- stepRef.current = [...currentSteps];
8511
- setSteps([...currentSteps]);
8512
- }
8513
- // Show the abrupt close message at the bottom of any existing content.
8514
- // If widgets already exist in appendedData, append the error as the last widget
8515
- // so it renders AFTER the already-received content (not at the top).
8516
- const abruptCloseMessage = "The requested Task has ended unexpectedly, please retry again";
8517
- const hasExistingWidgetsOnErr = messageToStoreRef.current.appendedData &&
8518
- (isArray(messageToStoreRef.current.appendedData)
8519
- ? messageToStoreRef.current.appendedData.length > 0
8520
- : Object.keys(messageToStoreRef.current.appendedData).length > 0);
8521
- if (hasExistingWidgetsOnErr) {
8522
- const errorWidget = { type: "text", response: abruptCloseMessage };
8523
- if (isArray(messageToStoreRef.current.appendedData)) {
8524
- messageToStoreRef.current.appendedData.push(errorWidget);
8525
- }
8526
- else {
8527
- messageToStoreRef.current.appendedData = [messageToStoreRef.current.appendedData, errorWidget];
8528
- }
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();
8529
8674
  }
8530
8675
  else {
8531
- messageToStoreRef.current.chatData.response = abruptCloseMessage;
8676
+ // Retries exhausted — show final message
8677
+ setWasStreamingAborted(true);
8678
+ wasStreamingAbortedRef.current = true;
8679
+ const currentSteps = stepRef.current;
8680
+ if (currentSteps.length > 0) {
8681
+ const lastStep = currentSteps[currentSteps.length - 1];
8682
+ if (lastStep.step_status === "not-completed") {
8683
+ lastStep.step_status = "completed";
8684
+ }
8685
+ stepRef.current = [...currentSteps];
8686
+ setSteps([...currentSteps]);
8687
+ }
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);
8691
+ dispatch(setMinimizedStreamData({
8692
+ isStreaming: false,
8693
+ stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8694
+ stepSubHeader: "Connection closed",
8695
+ stepStatus: "completed",
8696
+ streamStartTime: streamStartTimeRef.current,
8697
+ actionCount: questionsRef.current.length || 1,
8698
+ }));
8699
+ setStepsDone(true);
8700
+ setIsStreamingDone(true);
8532
8701
  }
8533
- setContent(abruptCloseMessage);
8534
- // Dispatch minimized widget data
8535
- dispatch(setMinimizedStreamData({
8536
- isStreaming: false,
8537
- stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8538
- stepSubHeader: "Connection closed",
8539
- stepStatus: "completed",
8540
- streamStartTime: streamStartTimeRef.current,
8541
- actionCount: questionsRef.current.length || 1,
8542
- }));
8543
- // Switch to agent_response tab and trigger completion flow
8544
- setStepsDone(true);
8545
- setIsStreamingDone(true);
8546
8702
  });
8547
8703
  // Handle stream closure
8548
8704
  source.addEventListener("close", () => {
@@ -8554,44 +8710,6 @@ const StreamedContent = ({ botData }) => {
8554
8710
  // If it's still false, the backend closed the connection abruptly.
8555
8711
  if (!closeState.completed) {
8556
8712
  closeState.completed = true;
8557
- // Mark as aborted so the completion effect skips the Submit button
8558
- setWasStreamingAborted(true);
8559
- wasStreamingAbortedRef.current = true;
8560
- // Mark the last in-progress step as completed
8561
- const currentSteps = stepRef.current;
8562
- if (currentSteps.length > 0) {
8563
- const lastStep = currentSteps[currentSteps.length - 1];
8564
- if (lastStep.step_status === "not-completed") {
8565
- lastStep.step_status = "completed";
8566
- }
8567
- stepRef.current = [...currentSteps];
8568
- setSteps([...currentSteps]);
8569
- }
8570
- // Show the abrupt close message. The final response array is structured as:
8571
- // [textResponseTobeParsed, ...appendedData] — so chatData.response renders FIRST,
8572
- // then widget items from appendedData render below it.
8573
- // To ensure the error message appears AFTER already-rendered widgets, we append it
8574
- // as a text widget item to appendedData instead of setting it on chatData.response.
8575
- const abruptCloseMessage = "The requested Task has ended unexpectedly, please retry again";
8576
- const hasExistingWidgets = messageToStoreRef.current.appendedData &&
8577
- (isArray(messageToStoreRef.current.appendedData)
8578
- ? messageToStoreRef.current.appendedData.length > 0
8579
- : Object.keys(messageToStoreRef.current.appendedData).length > 0);
8580
- if (hasExistingWidgets) {
8581
- // Widgets already exist — append error as the last widget so it renders at the bottom
8582
- const errorWidget = { type: "text", response: abruptCloseMessage };
8583
- if (isArray(messageToStoreRef.current.appendedData)) {
8584
- messageToStoreRef.current.appendedData.push(errorWidget);
8585
- }
8586
- else {
8587
- messageToStoreRef.current.appendedData = [messageToStoreRef.current.appendedData, errorWidget];
8588
- }
8589
- }
8590
- else {
8591
- // No widgets — set on chatData.response (will be the only rendered text)
8592
- messageToStoreRef.current.chatData.response = abruptCloseMessage;
8593
- }
8594
- setContent(abruptCloseMessage);
8595
8713
  // Stop thinking if in progress
8596
8714
  if (isThinking) {
8597
8715
  setIsThinking(false);
@@ -8602,18 +8720,40 @@ const StreamedContent = ({ botData }) => {
8602
8720
  setShowThoughtDropdown(true);
8603
8721
  messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
8604
8722
  }
8605
- // Dispatch minimized widget data
8606
- dispatch(setMinimizedStreamData({
8607
- isStreaming: false,
8608
- stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8609
- stepSubHeader: "Connection closed",
8610
- stepStatus: "completed",
8611
- streamStartTime: streamStartTimeRef.current,
8612
- actionCount: questionsRef.current.length || 1,
8613
- }));
8614
- // Switch to agent_response tab and trigger completion flow
8615
- setStepsDone(true);
8616
- setIsStreamingDone(true);
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
+ }
8617
8757
  }
8618
8758
  // else: closeState.completed is already true — normal close after [DONE], nothing to do.
8619
8759
  });
@@ -8918,6 +9058,14 @@ const StreamedContent = ({ botData }) => {
8918
9058
  * Aborts the current streaming connection
8919
9059
  */
8920
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
8921
9069
  if (sourceRef.current && isStreaming) {
8922
9070
  setWasStreamingAborted(true);
8923
9071
  wasStreamingAbortedRef.current = true;
@@ -8965,9 +9113,9 @@ const StreamedContent = ({ botData }) => {
8965
9113
  // If no content yet, show aborted message
8966
9114
  // if (isEmpty(messageToStoreRef.current.chatData?.response)) {
8967
9115
  messageToStoreRef.current.chatData.response =
8968
- messageToStoreRef.current.chatData.response + "\n\nStream was stopped by user.";
9116
+ messageToStoreRef.current.chatData.response + "\n\nYou stopped this response. You can ask a new question whenever you're ready";
8969
9117
  setContent((prev) => {
8970
- return prev + "\n\nStream was stopped by user.";
9118
+ return prev + "\n\nYou stopped this response. You can ask a new question whenever you're ready";
8971
9119
  });
8972
9120
  // }
8973
9121
  }
@@ -8989,10 +9137,16 @@ const StreamedContent = ({ botData }) => {
8989
9137
  * Cleanup on unmount: Do NOT close the stream here.
8990
9138
  * The stream connection is kept alive in streamStateMap so it survives
8991
9139
  * tab-switch remounts. It is only closed by explicit abortStreaming() or new chat.
9140
+ * Also clean up retry timer if running.
8992
9141
  */
8993
9142
  useEffect(() => {
8994
9143
  return () => {
8995
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
+ }
8996
9150
  };
8997
9151
  }, []);
8998
9152
  /**
@@ -9044,7 +9198,7 @@ const StreamedContent = ({ botData }) => {
9044
9198
  * @returns {JSX.Element} Rendered content with optional blinking cursor
9045
9199
  */
9046
9200
  const renderContent = () => {
9047
- return (jsx("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 }) }));
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"] }) }))] }));
9048
9202
  };
9049
9203
  if (currentMode === "agent") {
9050
9204
  return renderContent();
@@ -9336,6 +9490,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9336
9490
  const [stepFormSubmitted, setStepFormSubmitted] = useState(false);
9337
9491
  const [hasNewStepFormFromRestream, setHasNewStepFormFromRestream] = useState(false);
9338
9492
  const [activeFormIntent, setActiveFormIntent] = useState(null);
9493
+ const [retryCountdown, setRetryCountdown] = useState(0);
9339
9494
  // Stable unique instance ID for this TabularContent mount
9340
9495
  const instanceIdRef = useRef(null);
9341
9496
  if (instanceIdRef.current === null) {
@@ -9401,10 +9556,15 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9401
9556
  const payload = stepFormStreamData;
9402
9557
  // Clear Redux immediately
9403
9558
  dispatch(setStepFormStreamData(null));
9559
+ if (payload.status === "retrying") {
9560
+ setRetryCountdown(payload.countdown || 0);
9561
+ return;
9562
+ }
9404
9563
  if (payload.status === "streaming_start") {
9405
9564
  setIsRestreaming(true);
9406
9565
  setStepFormSubmitted(true);
9407
9566
  setHasNewStepFormFromRestream(false);
9567
+ setRetryCountdown(0);
9408
9568
  setTabValue("steps");
9409
9569
  return;
9410
9570
  }
@@ -9544,6 +9704,8 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9544
9704
  }
9545
9705
  return;
9546
9706
  }
9707
+ // Clear retry countdown when final processing starts
9708
+ setRetryCountdown(0);
9547
9709
  // Process all collected chunks at once (done or error)
9548
9710
  const chunks = payload.chunks || [];
9549
9711
  let newSteps = cloneDeep(stepsRef.current);
@@ -9692,7 +9854,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9692
9854
  },
9693
9855
  ], tabPanels: [
9694
9856
  jsx(Steps, { steps: stepsState, questions: questionsState, questionsStepsMap: questionsStepsMapState, stepFormDataMap: stepFormDataMapState, isFormDisabled: (isFormDisabled && !isRestreaming) || stepFormSubmitted, activeFormIntent: hasNewStepFormFromRestream ? activeFormIntent : null, isRestreaming: isRestreaming }),
9695
- 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"] }) }))] }),
9696
9858
  ], value: tabValue }) }));
9697
9859
  };
9698
9860
  /** Reset the active instance tracker (call when a new conversation starts from the input field) */