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.cjs.js CHANGED
@@ -5428,6 +5428,7 @@ const AxiosSource = (url, opts, messageToStoreRef) => {
5428
5428
  })
5429
5429
  .catch((error) => {
5430
5430
  let reason = "Network request failed";
5431
+ let statusCode = 0;
5431
5432
  console.error("[AxiosSource] Request failed:", error);
5432
5433
  // Determine specific error reasons
5433
5434
  if (axios.isCancel(error)) {
@@ -5436,8 +5437,15 @@ const AxiosSource = (url, opts, messageToStoreRef) => {
5436
5437
  else if (error.code === "ECONNABORTED") {
5437
5438
  reason = "Network request timed out";
5438
5439
  }
5439
- // Emit error event with the specific reason
5440
- eventTarget.dispatchEvent(new CloseEvent("error", { reason }));
5440
+ // Capture HTTP status code from the error response (e.g. 503)
5441
+ if (error.response && error.response.status) {
5442
+ statusCode = error.response.status;
5443
+ reason = `HTTP ${statusCode}: ${reason}`;
5444
+ }
5445
+ // Emit error event with the specific reason and status code
5446
+ const errorEvent = new CloseEvent("error", { reason });
5447
+ errorEvent.statusCode = statusCode;
5448
+ eventTarget.dispatchEvent(errorEvent);
5441
5449
  });
5442
5450
  // Add method to manually close the connection
5443
5451
  eventTarget.close = () => {
@@ -5478,10 +5486,15 @@ const useStyles$6 = styles.makeStyles((theme) => ({
5478
5486
  marginTop: pxToRem(16),
5479
5487
  }
5480
5488
  }));
5489
+ const MAX_RETRY_COUNT_BUTTON = 1;
5490
+ const RETRY_COUNTDOWN_SECONDS_BUTTON = 15;
5481
5491
  const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = false, isFormValid = true }) => {
5482
5492
  const classes = useStyles$6();
5483
5493
  const dispatch = reactRedux.useDispatch();
5484
5494
  const sourceRef = React.useRef(null);
5495
+ const retryCountRef = React.useRef(0);
5496
+ const retryTimerRef = React.useRef(null);
5497
+ const retryPayloadRef = React.useRef(null);
5485
5498
  const messageStoreRef = React.useRef({
5486
5499
  currentMode: "agent",
5487
5500
  chatData: {
@@ -5525,6 +5538,8 @@ const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = fa
5525
5538
  sendButton.click();
5526
5539
  };
5527
5540
  const callInitApiStream = (userInput) => {
5541
+ // Store payload for potential retries
5542
+ retryPayloadRef.current = userInput;
5528
5543
  // Prefer module-level values (set synchronously by StreamedContent), fall back to sessionStorage
5529
5544
  // If a previous completed/follow-up response updated the state, use those values.
5530
5545
  // Read from module-level stepFormStreamControl (survives remounts) instead of local messageStoreRef.
@@ -5724,33 +5739,64 @@ const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = fa
5724
5739
  console.error("[ButtonContent] SSE parse error:", e);
5725
5740
  }
5726
5741
  });
5727
- sourceRef.current.addEventListener("error", () => {
5728
- // On error, still dispatch whatever we collected
5742
+ sourceRef.current.addEventListener("error", (event) => {
5729
5743
  stepFormStreamControl.isStreaming = false;
5730
5744
  stepFormStreamControl.abort = null;
5731
5745
  window.dispatchEvent(new CustomEvent("stepFormStreamEnd"));
5732
- dispatch(smartBotActions.setStepFormStreamData({ status: "error", chunks: chunksRef, sessionId }));
5746
+ // Don't retry if the user manually aborted the request
5747
+ const isUserAbort = event.reason === "Network request aborted";
5748
+ // Check if we can auto-retry (abrupt close or 503, but not user abort)
5749
+ if (!isUserAbort && retryCountRef.current < MAX_RETRY_COUNT_BUTTON) {
5750
+ // Dispatch a "retrying" status so TabularContent can show countdown UI
5751
+ dispatch(smartBotActions.setStepFormStreamData({ status: "retrying", chunks: [...chunksRef], sessionId, countdown: RETRY_COUNTDOWN_SECONDS_BUTTON }));
5752
+ // Start countdown and auto-retry
5753
+ let countdown = RETRY_COUNTDOWN_SECONDS_BUTTON;
5754
+ retryTimerRef.current = setInterval(() => {
5755
+ countdown -= 1;
5756
+ if (countdown <= 0) {
5757
+ clearInterval(retryTimerRef.current);
5758
+ retryTimerRef.current = null;
5759
+ retryCountRef.current += 1;
5760
+ // Re-invoke with same payload
5761
+ callInitApiStream(retryPayloadRef.current);
5762
+ }
5763
+ else {
5764
+ // Update countdown in Redux for UI
5765
+ dispatch(smartBotActions.setStepFormStreamData({ status: "retrying", chunks: [...chunksRef], sessionId, countdown }));
5766
+ }
5767
+ }, 1000);
5768
+ }
5769
+ else {
5770
+ // Retries exhausted or user-aborted — show final message
5771
+ chunksRef.push({ status: "content", message: "Please reach out to IA for this, as this didn't work even after retry" });
5772
+ dispatch(smartBotActions.setStepFormStreamData({ status: "error", chunks: [...chunksRef], sessionId }));
5773
+ }
5733
5774
  });
5734
- // Handle stream closure — fires when the HTTP request completes successfully.
5735
- // If [DONE] was received, the "message" handler already dispatched status:"done"
5736
- // and set stepFormStreamControl.isStreaming = false.
5737
- // If [DONE] was NOT received, the connection closed abruptly mid-processing.
5738
5775
  sourceRef.current.addEventListener("close", () => {
5739
5776
  if (stepFormStreamControl.isStreaming) {
5740
5777
  stepFormStreamControl.isStreaming = false;
5741
5778
  stepFormStreamControl.abort = null;
5742
5779
  window.dispatchEvent(new CustomEvent("stepFormStreamEnd"));
5743
- // Append the abrupt close message as a content chunk so TabularContent
5744
- // renders it at the bottom (after any already-received widgets/steps)
5745
- chunksRef.push({
5746
- status: "content",
5747
- message: "The requested Task has ended unexpectedly, please retry again",
5748
- });
5749
- dispatch(smartBotActions.setStepFormStreamData({
5750
- status: "error",
5751
- chunks: [...chunksRef],
5752
- sessionId,
5753
- }));
5780
+ if (retryCountRef.current < MAX_RETRY_COUNT_BUTTON) {
5781
+ dispatch(smartBotActions.setStepFormStreamData({ status: "retrying", chunks: [...chunksRef], sessionId, countdown: RETRY_COUNTDOWN_SECONDS_BUTTON }));
5782
+ let countdown = RETRY_COUNTDOWN_SECONDS_BUTTON;
5783
+ retryTimerRef.current = setInterval(() => {
5784
+ countdown -= 1;
5785
+ if (countdown <= 0) {
5786
+ clearInterval(retryTimerRef.current);
5787
+ retryTimerRef.current = null;
5788
+ retryCountRef.current += 1;
5789
+ callInitApiStream(retryPayloadRef.current);
5790
+ }
5791
+ else {
5792
+ dispatch(smartBotActions.setStepFormStreamData({ status: "retrying", chunks: [...chunksRef], sessionId, countdown }));
5793
+ }
5794
+ }, 1000);
5795
+ }
5796
+ else {
5797
+ chunksRef.push({ status: "content", message: "Please reach out to IA for this, as this didn't work even after retry" });
5798
+ dispatch(smartBotActions.setStepFormStreamData({ status: "error", chunks: [...chunksRef], sessionId }));
5799
+ }
5754
5800
  }
5755
5801
  });
5756
5802
  };
@@ -6057,6 +6103,15 @@ const useStyles$5 = styles.makeStyles((theme) => ({
6057
6103
  position: "relative",
6058
6104
  background: colours.white,
6059
6105
  },
6106
+ retryContainer: {
6107
+ marginTop: pxToRem(8),
6108
+ },
6109
+ retryMessage: {
6110
+ fontFamily: "Manrope",
6111
+ fontSize: pxToRem(13),
6112
+ fontWeight: 500,
6113
+ color: "#374151",
6114
+ },
6060
6115
  }));
6061
6116
 
6062
6117
  var _path$i, _path2$2, _defs$5;
@@ -7761,8 +7816,20 @@ const AgentResponse$1 = (props) => {
7761
7816
  };
7762
7817
 
7763
7818
  const StepsResponseTab = (props) => {
7764
- const { steps, setSteps, stepsDone, setStepsDone, finalStepDone, setFinalStepDone, content, isStreaming, stepChange, currentMode, questions, questionsStepsMap, stepFormDataMap, isFormDisabled, streamingWidgetData, } = props;
7819
+ const { steps, setSteps, stepsDone, setStepsDone, finalStepDone, setFinalStepDone, content, isStreaming, stepChange, currentMode, questions, questionsStepsMap, stepFormDataMap, isFormDisabled, streamingWidgetData, isRetrying, } = props;
7765
7820
  const [tabValue, setTabValue] = React.useState("steps");
7821
+ // When retry countdown starts, switch to agent_response to show it.
7822
+ // When retry actually fires (isRetrying goes false + isStreaming goes true), switch back to steps.
7823
+ React.useEffect(() => {
7824
+ if (isRetrying) {
7825
+ setTabValue("agent_response");
7826
+ }
7827
+ }, [isRetrying]);
7828
+ React.useEffect(() => {
7829
+ if (isStreaming && !isRetrying) {
7830
+ setTabValue("steps");
7831
+ }
7832
+ }, [isStreaming, isRetrying]);
7766
7833
  const handleChangeTabValue = (_event, newValue) => {
7767
7834
  setTabValue(newValue);
7768
7835
  };
@@ -7992,6 +8059,14 @@ const StreamedContent = ({ botData }) => {
7992
8059
  const [finalStepDone, setFinalStepDone] = React.useState(false);
7993
8060
  const [wasStreamingAborted, setWasStreamingAborted] = React.useState(false);
7994
8061
  const wasStreamingAbortedRef = React.useRef(false);
8062
+ // Auto-retry state
8063
+ const MAX_RETRY_COUNT = 1; // Number of auto-retries allowed
8064
+ const RETRY_COUNTDOWN_SECONDS = 15; // Countdown duration before auto-retry
8065
+ const [isRetrying, setIsRetrying] = React.useState(false);
8066
+ const [retryCountdown, setRetryCountdown] = React.useState(0);
8067
+ const retryCountRef = React.useRef(0); // How many retries have been attempted
8068
+ const retryTimerRef = React.useRef(null); // Interval ref for countdown
8069
+ const retryCountdownRef = React.useRef(0); // Non-state countdown for interval callback
7995
8070
  const thinkingContentRef = React.useRef("");
7996
8071
  reactRedux.useSelector((state) => {
7997
8072
  return state.smartBotReducer.thinkingContext;
@@ -8111,6 +8186,94 @@ const StreamedContent = ({ botData }) => {
8111
8186
  }));
8112
8187
  }
8113
8188
  }, [stepChange, questions, questionsStepsMap, stepsDone, isStreamingDone]);
8189
+ /**
8190
+ * Starts a countdown for auto-retry. Shows countdown in UI, then calls retryStream.
8191
+ */
8192
+ const startRetryCountdown = () => {
8193
+ setIsRetrying(true);
8194
+ retryCountdownRef.current = RETRY_COUNTDOWN_SECONDS;
8195
+ setRetryCountdown(RETRY_COUNTDOWN_SECONDS);
8196
+ retryTimerRef.current = setInterval(() => {
8197
+ retryCountdownRef.current -= 1;
8198
+ setRetryCountdown(retryCountdownRef.current);
8199
+ if (retryCountdownRef.current <= 0) {
8200
+ clearInterval(retryTimerRef.current);
8201
+ retryTimerRef.current = null;
8202
+ retryStream();
8203
+ }
8204
+ }, 1000);
8205
+ };
8206
+ /**
8207
+ * Retries the stream by resetting state and calling processStream again.
8208
+ */
8209
+ const retryStream = () => {
8210
+ // Clear countdown state
8211
+ if (retryTimerRef.current) {
8212
+ clearInterval(retryTimerRef.current);
8213
+ retryTimerRef.current = null;
8214
+ }
8215
+ setIsRetrying(false);
8216
+ setRetryCountdown(0);
8217
+ retryCountRef.current += 1;
8218
+ // Reset streaming state for a fresh start
8219
+ setContent("");
8220
+ setIsStreaming(true);
8221
+ setIsStreamingDone(false);
8222
+ setStepsDone(false);
8223
+ setFinalStepDone(false);
8224
+ setWasStreamingAborted(false);
8225
+ wasStreamingAbortedRef.current = false;
8226
+ setSteps([{ header: "Processing Request", sub_header: "Analyzing the current request", step_status: "not-completed" }]);
8227
+ stepRef.current = [{ header: "Processing Request", sub_header: "Analyzing the current request", step_status: "not-completed" }];
8228
+ setQuestions([]);
8229
+ questionsRef.current = [];
8230
+ setQuestionsStepsMap({});
8231
+ questionsStepsMapRef.current = {};
8232
+ setStepFormDataMap({});
8233
+ stepFormDataMapRef.current = {};
8234
+ setStreamingWidgetData([]);
8235
+ streamingDoneProcessedRef.current = false;
8236
+ // Reset messageToStoreRef for fresh accumulation
8237
+ messageToStoreRef.current = {
8238
+ status: "",
8239
+ currentMode: currentMode,
8240
+ chatData: {
8241
+ response: "",
8242
+ response_heading: "",
8243
+ thinkingResponse: {
8244
+ thinkingContent: jsxRuntime.jsx(ThinkingIndicator, { thinkingContent: thinkingContent }),
8245
+ thinkingStream: "",
8246
+ thinkingTime: 0,
8247
+ thinkingHeading: null,
8248
+ },
8249
+ },
8250
+ appendedData: {},
8251
+ appendedDataFromLastChunk: {},
8252
+ initValue: false,
8253
+ sessionId: "",
8254
+ navSessionId: "",
8255
+ uniqueChatId: "",
8256
+ additionalArgs: {},
8257
+ };
8258
+ // Reset stream state map entry and invalidate old listeners
8259
+ const state = streamStateMap.get(streamKey);
8260
+ if (state) {
8261
+ state.messageStore = messageToStoreRef.current;
8262
+ state.completed = false;
8263
+ // Increment listenerGeneration so any in-flight events from the old source are ignored
8264
+ state.listenerGeneration = (state.listenerGeneration || 0) + 1;
8265
+ // Close the old source if it's still open
8266
+ if (state.source && typeof state.source.close === "function") {
8267
+ try {
8268
+ state.source.close();
8269
+ }
8270
+ catch (e) { /* ignore */ }
8271
+ }
8272
+ state.source = null;
8273
+ }
8274
+ // Re-initiate the stream
8275
+ processStream();
8276
+ };
8114
8277
  /**
8115
8278
  * Main effect to initialize and handle the streaming connection
8116
8279
  * Sets up the SSE connection and event listeners for message processing
@@ -8505,66 +8668,59 @@ const StreamedContent = ({ botData }) => {
8505
8668
  const errState = streamStateMap.get(streamKey);
8506
8669
  if (!errState || errState.listenerGeneration !== generation)
8507
8670
  return;
8508
- console.error("Stream error:", event.reason);
8671
+ console.error("Stream error:", event.reason, "statusCode:", event.statusCode);
8509
8672
  setIsStreaming(false);
8510
8673
  errState.completed = true;
8674
+ // Check if this is a user-initiated abort (should not trigger retry)
8675
+ const isUserAbort = event.reason === "Network request aborted";
8676
+ const is503 = event.statusCode === 503;
8511
8677
  if (isThinking) {
8512
8678
  setIsThinking(false);
8513
8679
  const endTime = Date.now();
8514
8680
  const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
8515
- const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
8681
+ const finalThinkingTime = Math.max(duration, 1);
8516
8682
  setThinkingTime(finalThinkingTime);
8517
8683
  setShowThoughtDropdown(true);
8518
8684
  thinkingStartTimeRef.current = finalThinkingTime;
8519
- // Store thinking time in messageToStoreRef for persistence
8520
8685
  messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
8521
8686
  }
8522
- // Mark as aborted so the completion effect skips the Submit button
8523
- setWasStreamingAborted(true);
8524
- wasStreamingAbortedRef.current = true;
8525
- // Mark the last in-progress step as completed
8526
- const currentSteps = stepRef.current;
8527
- if (currentSteps.length > 0) {
8528
- const lastStep = currentSteps[currentSteps.length - 1];
8529
- if (lastStep.step_status === "not-completed") {
8530
- lastStep.step_status = "completed";
8531
- }
8532
- stepRef.current = [...currentSteps];
8533
- setSteps([...currentSteps]);
8534
- }
8535
- // Show the abrupt close message at the bottom of any existing content.
8536
- // If widgets already exist in appendedData, append the error as the last widget
8537
- // so it renders AFTER the already-received content (not at the top).
8538
- const abruptCloseMessage = "The requested Task has ended unexpectedly, please retry again";
8539
- const hasExistingWidgetsOnErr = messageToStoreRef.current.appendedData &&
8540
- (isArray(messageToStoreRef.current.appendedData)
8541
- ? messageToStoreRef.current.appendedData.length > 0
8542
- : Object.keys(messageToStoreRef.current.appendedData).length > 0);
8543
- if (hasExistingWidgetsOnErr) {
8544
- const errorWidget = { type: "text", response: abruptCloseMessage };
8545
- if (isArray(messageToStoreRef.current.appendedData)) {
8546
- messageToStoreRef.current.appendedData.push(errorWidget);
8547
- }
8548
- else {
8549
- messageToStoreRef.current.appendedData = [messageToStoreRef.current.appendedData, errorWidget];
8550
- }
8687
+ // Check if we can auto-retry (abrupt close or 503, but not user abort)
8688
+ if (!isUserAbort && retryCountRef.current < MAX_RETRY_COUNT) {
8689
+ // Still have retries left — start countdown instead of showing final error
8690
+ setStepsDone(true); // Switch to agent_response tab to show countdown
8691
+ const retryMessage = is503
8692
+ ? "Service temporarily unavailable (503), auto re-trying..."
8693
+ : "The service is temporarily unavailable because of an unusual load. Please wait a moment and try again.";
8694
+ setContent(retryMessage);
8695
+ startRetryCountdown();
8551
8696
  }
8552
8697
  else {
8553
- messageToStoreRef.current.chatData.response = abruptCloseMessage;
8698
+ // Retries exhausted — show final message
8699
+ setWasStreamingAborted(true);
8700
+ wasStreamingAbortedRef.current = true;
8701
+ const currentSteps = stepRef.current;
8702
+ if (currentSteps.length > 0) {
8703
+ const lastStep = currentSteps[currentSteps.length - 1];
8704
+ if (lastStep.step_status === "not-completed") {
8705
+ lastStep.step_status = "completed";
8706
+ }
8707
+ stepRef.current = [...currentSteps];
8708
+ setSteps([...currentSteps]);
8709
+ }
8710
+ const finalMessage = "Please reach out to IA for this, as this didn't work even after retry";
8711
+ messageToStoreRef.current.chatData.response = finalMessage;
8712
+ setContent(finalMessage);
8713
+ dispatch(smartBotActions.setMinimizedStreamData({
8714
+ isStreaming: false,
8715
+ stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8716
+ stepSubHeader: "Connection closed",
8717
+ stepStatus: "completed",
8718
+ streamStartTime: streamStartTimeRef.current,
8719
+ actionCount: questionsRef.current.length || 1,
8720
+ }));
8721
+ setStepsDone(true);
8722
+ setIsStreamingDone(true);
8554
8723
  }
8555
- setContent(abruptCloseMessage);
8556
- // Dispatch minimized widget data
8557
- dispatch(smartBotActions.setMinimizedStreamData({
8558
- isStreaming: false,
8559
- stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8560
- stepSubHeader: "Connection closed",
8561
- stepStatus: "completed",
8562
- streamStartTime: streamStartTimeRef.current,
8563
- actionCount: questionsRef.current.length || 1,
8564
- }));
8565
- // Switch to agent_response tab and trigger completion flow
8566
- setStepsDone(true);
8567
- setIsStreamingDone(true);
8568
8724
  });
8569
8725
  // Handle stream closure
8570
8726
  source.addEventListener("close", () => {
@@ -8576,44 +8732,6 @@ const StreamedContent = ({ botData }) => {
8576
8732
  // If it's still false, the backend closed the connection abruptly.
8577
8733
  if (!closeState.completed) {
8578
8734
  closeState.completed = true;
8579
- // Mark as aborted so the completion effect skips the Submit button
8580
- setWasStreamingAborted(true);
8581
- wasStreamingAbortedRef.current = true;
8582
- // Mark the last in-progress step as completed
8583
- const currentSteps = stepRef.current;
8584
- if (currentSteps.length > 0) {
8585
- const lastStep = currentSteps[currentSteps.length - 1];
8586
- if (lastStep.step_status === "not-completed") {
8587
- lastStep.step_status = "completed";
8588
- }
8589
- stepRef.current = [...currentSteps];
8590
- setSteps([...currentSteps]);
8591
- }
8592
- // Show the abrupt close message. The final response array is structured as:
8593
- // [textResponseTobeParsed, ...appendedData] — so chatData.response renders FIRST,
8594
- // then widget items from appendedData render below it.
8595
- // To ensure the error message appears AFTER already-rendered widgets, we append it
8596
- // as a text widget item to appendedData instead of setting it on chatData.response.
8597
- const abruptCloseMessage = "The requested Task has ended unexpectedly, please retry again";
8598
- const hasExistingWidgets = messageToStoreRef.current.appendedData &&
8599
- (isArray(messageToStoreRef.current.appendedData)
8600
- ? messageToStoreRef.current.appendedData.length > 0
8601
- : Object.keys(messageToStoreRef.current.appendedData).length > 0);
8602
- if (hasExistingWidgets) {
8603
- // Widgets already exist — append error as the last widget so it renders at the bottom
8604
- const errorWidget = { type: "text", response: abruptCloseMessage };
8605
- if (isArray(messageToStoreRef.current.appendedData)) {
8606
- messageToStoreRef.current.appendedData.push(errorWidget);
8607
- }
8608
- else {
8609
- messageToStoreRef.current.appendedData = [messageToStoreRef.current.appendedData, errorWidget];
8610
- }
8611
- }
8612
- else {
8613
- // No widgets — set on chatData.response (will be the only rendered text)
8614
- messageToStoreRef.current.chatData.response = abruptCloseMessage;
8615
- }
8616
- setContent(abruptCloseMessage);
8617
8735
  // Stop thinking if in progress
8618
8736
  if (isThinking) {
8619
8737
  setIsThinking(false);
@@ -8624,18 +8742,40 @@ const StreamedContent = ({ botData }) => {
8624
8742
  setShowThoughtDropdown(true);
8625
8743
  messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
8626
8744
  }
8627
- // Dispatch minimized widget data
8628
- dispatch(smartBotActions.setMinimizedStreamData({
8629
- isStreaming: false,
8630
- stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8631
- stepSubHeader: "Connection closed",
8632
- stepStatus: "completed",
8633
- streamStartTime: streamStartTimeRef.current,
8634
- actionCount: questionsRef.current.length || 1,
8635
- }));
8636
- // Switch to agent_response tab and trigger completion flow
8637
- setStepsDone(true);
8638
- setIsStreamingDone(true);
8745
+ // Check if we can auto-retry
8746
+ if (retryCountRef.current < MAX_RETRY_COUNT) {
8747
+ // Still have retries left — start countdown instead of showing final error
8748
+ setStepsDone(true); // Switch to agent_response tab to show countdown
8749
+ setContent("The service is temporarily unavailable because of an unusual load. Please wait a moment and try again.");
8750
+ startRetryCountdown();
8751
+ }
8752
+ else {
8753
+ // Retries exhausted — show final elegant message
8754
+ setWasStreamingAborted(true);
8755
+ wasStreamingAbortedRef.current = true;
8756
+ const currentSteps = stepRef.current;
8757
+ if (currentSteps.length > 0) {
8758
+ const lastStep = currentSteps[currentSteps.length - 1];
8759
+ if (lastStep.step_status === "not-completed") {
8760
+ lastStep.step_status = "completed";
8761
+ }
8762
+ stepRef.current = [...currentSteps];
8763
+ setSteps([...currentSteps]);
8764
+ }
8765
+ const finalMessage = "Please reach out to IA for this, as this didn't work even after retry";
8766
+ messageToStoreRef.current.chatData.response = finalMessage;
8767
+ setContent(finalMessage);
8768
+ dispatch(smartBotActions.setMinimizedStreamData({
8769
+ isStreaming: false,
8770
+ stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8771
+ stepSubHeader: "Connection closed",
8772
+ stepStatus: "completed",
8773
+ streamStartTime: streamStartTimeRef.current,
8774
+ actionCount: questionsRef.current.length || 1,
8775
+ }));
8776
+ setStepsDone(true);
8777
+ setIsStreamingDone(true);
8778
+ }
8639
8779
  }
8640
8780
  // else: closeState.completed is already true — normal close after [DONE], nothing to do.
8641
8781
  });
@@ -8940,6 +9080,14 @@ const StreamedContent = ({ botData }) => {
8940
9080
  * Aborts the current streaming connection
8941
9081
  */
8942
9082
  const abortStreaming = () => {
9083
+ // If a retry countdown is running, clear it and prevent further retries
9084
+ if (retryTimerRef.current) {
9085
+ clearInterval(retryTimerRef.current);
9086
+ retryTimerRef.current = null;
9087
+ setIsRetrying(false);
9088
+ setRetryCountdown(0);
9089
+ }
9090
+ retryCountRef.current = MAX_RETRY_COUNT; // Exhaust retries so no further auto-retries happen
8943
9091
  if (sourceRef.current && isStreaming) {
8944
9092
  setWasStreamingAborted(true);
8945
9093
  wasStreamingAbortedRef.current = true;
@@ -8987,9 +9135,9 @@ const StreamedContent = ({ botData }) => {
8987
9135
  // If no content yet, show aborted message
8988
9136
  // if (isEmpty(messageToStoreRef.current.chatData?.response)) {
8989
9137
  messageToStoreRef.current.chatData.response =
8990
- messageToStoreRef.current.chatData.response + "\n\nStream was stopped by user.";
9138
+ messageToStoreRef.current.chatData.response + "\n\nYou stopped this response. You can ask a new question whenever you're ready";
8991
9139
  setContent((prev) => {
8992
- return prev + "\n\nStream was stopped by user.";
9140
+ return prev + "\n\nYou stopped this response. You can ask a new question whenever you're ready";
8993
9141
  });
8994
9142
  // }
8995
9143
  }
@@ -9011,10 +9159,16 @@ const StreamedContent = ({ botData }) => {
9011
9159
  * Cleanup on unmount: Do NOT close the stream here.
9012
9160
  * The stream connection is kept alive in streamStateMap so it survives
9013
9161
  * tab-switch remounts. It is only closed by explicit abortStreaming() or new chat.
9162
+ * Also clean up retry timer if running.
9014
9163
  */
9015
9164
  React.useEffect(() => {
9016
9165
  return () => {
9017
9166
  // Intentionally not closing the stream - it persists in streamStateMap
9167
+ // Clean up retry timer
9168
+ if (retryTimerRef.current) {
9169
+ clearInterval(retryTimerRef.current);
9170
+ retryTimerRef.current = null;
9171
+ }
9018
9172
  };
9019
9173
  }, []);
9020
9174
  /**
@@ -9066,7 +9220,7 @@ const StreamedContent = ({ botData }) => {
9066
9220
  * @returns {JSX.Element} Rendered content with optional blinking cursor
9067
9221
  */
9068
9222
  const renderContent = () => {
9069
- return (jsxRuntime.jsx("div", { className: classes.streamContainer, children: jsxRuntime.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 }) }));
9223
+ return (jsxRuntime.jsxs("div", { className: classes.streamContainer, children: [jsxRuntime.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 && (jsxRuntime.jsx("div", { className: classes.retryContainer, children: jsxRuntime.jsxs("span", { className: classes.retryMessage, children: ["Auto re-trying in ", retryCountdown, "s"] }) }))] }));
9070
9224
  };
9071
9225
  if (currentMode === "agent") {
9072
9226
  return renderContent();
@@ -9358,6 +9512,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9358
9512
  const [stepFormSubmitted, setStepFormSubmitted] = React.useState(false);
9359
9513
  const [hasNewStepFormFromRestream, setHasNewStepFormFromRestream] = React.useState(false);
9360
9514
  const [activeFormIntent, setActiveFormIntent] = React.useState(null);
9515
+ const [retryCountdown, setRetryCountdown] = React.useState(0);
9361
9516
  // Stable unique instance ID for this TabularContent mount
9362
9517
  const instanceIdRef = React.useRef(null);
9363
9518
  if (instanceIdRef.current === null) {
@@ -9423,10 +9578,15 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9423
9578
  const payload = stepFormStreamData;
9424
9579
  // Clear Redux immediately
9425
9580
  dispatch(smartBotActions.setStepFormStreamData(null));
9581
+ if (payload.status === "retrying") {
9582
+ setRetryCountdown(payload.countdown || 0);
9583
+ return;
9584
+ }
9426
9585
  if (payload.status === "streaming_start") {
9427
9586
  setIsRestreaming(true);
9428
9587
  setStepFormSubmitted(true);
9429
9588
  setHasNewStepFormFromRestream(false);
9589
+ setRetryCountdown(0);
9430
9590
  setTabValue("steps");
9431
9591
  return;
9432
9592
  }
@@ -9566,6 +9726,8 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9566
9726
  }
9567
9727
  return;
9568
9728
  }
9729
+ // Clear retry countdown when final processing starts
9730
+ setRetryCountdown(0);
9569
9731
  // Process all collected chunks at once (done or error)
9570
9732
  const chunks = payload.chunks || [];
9571
9733
  let newSteps = lodash.cloneDeep(stepsRef.current);
@@ -9714,7 +9876,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9714
9876
  },
9715
9877
  ], tabPanels: [
9716
9878
  jsxRuntime.jsx(Steps, { steps: stepsState, questions: questionsState, questionsStepsMap: questionsStepsMapState, stepFormDataMap: stepFormDataMapState, isFormDisabled: (isFormDisabled && !isRestreaming) || stepFormSubmitted, activeFormIntent: hasNewStepFormFromRestream ? activeFormIntent : null, isRestreaming: isRestreaming }),
9717
- jsxRuntime.jsxs(AgentResponse, { children: [children, renderedWidgets.length > 0 && (jsxRuntime.jsx("div", { className: "restream-widget-content", children: renderedWidgets }))] }),
9879
+ jsxRuntime.jsxs(AgentResponse, { children: [children, renderedWidgets.length > 0 && (jsxRuntime.jsx("div", { className: "restream-widget-content", children: renderedWidgets })), retryCountdown > 0 && (jsxRuntime.jsx("div", { style: { marginTop: "8px" }, children: jsxRuntime.jsxs("span", { style: { fontFamily: "Manrope", fontSize: "13px", fontWeight: 500, color: "#374151" }, children: ["Auto re-trying in ", retryCountdown, "s"] }) }))] }),
9718
9880
  ], value: tabValue }) }));
9719
9881
  };
9720
9882
  /** Reset the active instance tracker (call when a new conversation starts from the input field) */