impact-chatbot 2.3.62 → 2.3.64

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,57 @@ 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
+ 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 = "The service is temporarily unavailable because of an unusual load. Please wait a moment and try again.";
8692
+ setContent(retryMessage);
8693
+ startRetryCountdown();
8551
8694
  }
8552
8695
  else {
8553
- messageToStoreRef.current.chatData.response = abruptCloseMessage;
8696
+ // Retries exhausted — show final message
8697
+ setWasStreamingAborted(true);
8698
+ wasStreamingAbortedRef.current = true;
8699
+ const currentSteps = stepRef.current;
8700
+ if (currentSteps.length > 0) {
8701
+ const lastStep = currentSteps[currentSteps.length - 1];
8702
+ if (lastStep.step_status === "not-completed") {
8703
+ lastStep.step_status = "completed";
8704
+ }
8705
+ stepRef.current = [...currentSteps];
8706
+ setSteps([...currentSteps]);
8707
+ }
8708
+ const finalMessage = "Please reach out to IA for this, as this didn't work even after retry";
8709
+ messageToStoreRef.current.chatData.response = finalMessage;
8710
+ setContent(finalMessage);
8711
+ dispatch(smartBotActions.setMinimizedStreamData({
8712
+ isStreaming: false,
8713
+ stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8714
+ stepSubHeader: "Connection closed",
8715
+ stepStatus: "completed",
8716
+ streamStartTime: streamStartTimeRef.current,
8717
+ actionCount: questionsRef.current.length || 1,
8718
+ }));
8719
+ setStepsDone(true);
8720
+ setIsStreamingDone(true);
8554
8721
  }
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
8722
  });
8569
8723
  // Handle stream closure
8570
8724
  source.addEventListener("close", () => {
@@ -8576,44 +8730,6 @@ const StreamedContent = ({ botData }) => {
8576
8730
  // If it's still false, the backend closed the connection abruptly.
8577
8731
  if (!closeState.completed) {
8578
8732
  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
8733
  // Stop thinking if in progress
8618
8734
  if (isThinking) {
8619
8735
  setIsThinking(false);
@@ -8624,18 +8740,40 @@ const StreamedContent = ({ botData }) => {
8624
8740
  setShowThoughtDropdown(true);
8625
8741
  messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
8626
8742
  }
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);
8743
+ // Check if we can auto-retry
8744
+ if (retryCountRef.current < MAX_RETRY_COUNT) {
8745
+ // Still have retries left — start countdown instead of showing final error
8746
+ setStepsDone(true); // Switch to agent_response tab to show countdown
8747
+ setContent("The service is temporarily unavailable because of an unusual load. Please wait a moment and try again.");
8748
+ startRetryCountdown();
8749
+ }
8750
+ else {
8751
+ // Retries exhausted — show final elegant message
8752
+ setWasStreamingAborted(true);
8753
+ wasStreamingAbortedRef.current = true;
8754
+ const currentSteps = stepRef.current;
8755
+ if (currentSteps.length > 0) {
8756
+ const lastStep = currentSteps[currentSteps.length - 1];
8757
+ if (lastStep.step_status === "not-completed") {
8758
+ lastStep.step_status = "completed";
8759
+ }
8760
+ stepRef.current = [...currentSteps];
8761
+ setSteps([...currentSteps]);
8762
+ }
8763
+ const finalMessage = "Please reach out to IA for this, as this didn't work even after retry";
8764
+ messageToStoreRef.current.chatData.response = finalMessage;
8765
+ setContent(finalMessage);
8766
+ dispatch(smartBotActions.setMinimizedStreamData({
8767
+ isStreaming: false,
8768
+ stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8769
+ stepSubHeader: "Connection closed",
8770
+ stepStatus: "completed",
8771
+ streamStartTime: streamStartTimeRef.current,
8772
+ actionCount: questionsRef.current.length || 1,
8773
+ }));
8774
+ setStepsDone(true);
8775
+ setIsStreamingDone(true);
8776
+ }
8639
8777
  }
8640
8778
  // else: closeState.completed is already true — normal close after [DONE], nothing to do.
8641
8779
  });
@@ -8940,6 +9078,14 @@ const StreamedContent = ({ botData }) => {
8940
9078
  * Aborts the current streaming connection
8941
9079
  */
8942
9080
  const abortStreaming = () => {
9081
+ // If a retry countdown is running, clear it and prevent further retries
9082
+ if (retryTimerRef.current) {
9083
+ clearInterval(retryTimerRef.current);
9084
+ retryTimerRef.current = null;
9085
+ setIsRetrying(false);
9086
+ setRetryCountdown(0);
9087
+ }
9088
+ retryCountRef.current = MAX_RETRY_COUNT; // Exhaust retries so no further auto-retries happen
8943
9089
  if (sourceRef.current && isStreaming) {
8944
9090
  setWasStreamingAborted(true);
8945
9091
  wasStreamingAbortedRef.current = true;
@@ -8987,9 +9133,9 @@ const StreamedContent = ({ botData }) => {
8987
9133
  // If no content yet, show aborted message
8988
9134
  // if (isEmpty(messageToStoreRef.current.chatData?.response)) {
8989
9135
  messageToStoreRef.current.chatData.response =
8990
- messageToStoreRef.current.chatData.response + "\n\nStream was stopped by user.";
9136
+ messageToStoreRef.current.chatData.response + "\n\nYou stopped this response. You can ask a new question whenever you're ready";
8991
9137
  setContent((prev) => {
8992
- return prev + "\n\nStream was stopped by user.";
9138
+ return prev + "\n\nYou stopped this response. You can ask a new question whenever you're ready";
8993
9139
  });
8994
9140
  // }
8995
9141
  }
@@ -9011,10 +9157,16 @@ const StreamedContent = ({ botData }) => {
9011
9157
  * Cleanup on unmount: Do NOT close the stream here.
9012
9158
  * The stream connection is kept alive in streamStateMap so it survives
9013
9159
  * tab-switch remounts. It is only closed by explicit abortStreaming() or new chat.
9160
+ * Also clean up retry timer if running.
9014
9161
  */
9015
9162
  React.useEffect(() => {
9016
9163
  return () => {
9017
9164
  // Intentionally not closing the stream - it persists in streamStateMap
9165
+ // Clean up retry timer
9166
+ if (retryTimerRef.current) {
9167
+ clearInterval(retryTimerRef.current);
9168
+ retryTimerRef.current = null;
9169
+ }
9018
9170
  };
9019
9171
  }, []);
9020
9172
  /**
@@ -9066,7 +9218,7 @@ const StreamedContent = ({ botData }) => {
9066
9218
  * @returns {JSX.Element} Rendered content with optional blinking cursor
9067
9219
  */
9068
9220
  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 }) }));
9221
+ 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
9222
  };
9071
9223
  if (currentMode === "agent") {
9072
9224
  return renderContent();
@@ -9358,6 +9510,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9358
9510
  const [stepFormSubmitted, setStepFormSubmitted] = React.useState(false);
9359
9511
  const [hasNewStepFormFromRestream, setHasNewStepFormFromRestream] = React.useState(false);
9360
9512
  const [activeFormIntent, setActiveFormIntent] = React.useState(null);
9513
+ const [retryCountdown, setRetryCountdown] = React.useState(0);
9361
9514
  // Stable unique instance ID for this TabularContent mount
9362
9515
  const instanceIdRef = React.useRef(null);
9363
9516
  if (instanceIdRef.current === null) {
@@ -9423,10 +9576,15 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9423
9576
  const payload = stepFormStreamData;
9424
9577
  // Clear Redux immediately
9425
9578
  dispatch(smartBotActions.setStepFormStreamData(null));
9579
+ if (payload.status === "retrying") {
9580
+ setRetryCountdown(payload.countdown || 0);
9581
+ return;
9582
+ }
9426
9583
  if (payload.status === "streaming_start") {
9427
9584
  setIsRestreaming(true);
9428
9585
  setStepFormSubmitted(true);
9429
9586
  setHasNewStepFormFromRestream(false);
9587
+ setRetryCountdown(0);
9430
9588
  setTabValue("steps");
9431
9589
  return;
9432
9590
  }
@@ -9566,6 +9724,8 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9566
9724
  }
9567
9725
  return;
9568
9726
  }
9727
+ // Clear retry countdown when final processing starts
9728
+ setRetryCountdown(0);
9569
9729
  // Process all collected chunks at once (done or error)
9570
9730
  const chunks = payload.chunks || [];
9571
9731
  let newSteps = lodash.cloneDeep(stepsRef.current);
@@ -9714,7 +9874,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9714
9874
  },
9715
9875
  ], tabPanels: [
9716
9876
  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 }))] }),
9877
+ 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
9878
  ], value: tabValue }) }));
9719
9879
  };
9720
9880
  /** Reset the active instance tracker (call when a new conversation starts from the input field) */