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 CHANGED
@@ -5225,9 +5225,28 @@ const sseevent = (message, messageToStoreRef) => {
5225
5225
  messageToStoreRef.current.navSessionId = parsedData.session_id;
5226
5226
  }
5227
5227
  if (parsedData?.is_error) {
5228
- messageToStoreRef.current.chatData.response =
5229
- messageToStoreRef.current.chatData.response +
5230
- "There is an error, please reach out to IA with this use case.";
5228
+ // Only append the error message once, even if multiple is_error chunks arrive.
5229
+ // Append as a widget item to appendedData so it renders AFTER any existing
5230
+ // widgets (tables, graphs, text widgets) rather than at the top of the response.
5231
+ if (!messageToStoreRef.current.hasErrorMessage) {
5232
+ messageToStoreRef.current.hasErrorMessage = true;
5233
+ const errorMsg = parsedData?.message && parsedData.message !== "[DONE]"
5234
+ ? parsedData.message
5235
+ : "There is an error, please reach out to IA with this use case.";
5236
+ const errorWidget = { type: "text", response: errorMsg };
5237
+ const prevAppended = messageToStoreRef.current.appendedData;
5238
+ if (Array.isArray(prevAppended) && prevAppended.length > 0) {
5239
+ messageToStoreRef.current.appendedData = [...prevAppended, errorWidget];
5240
+ }
5241
+ else if (prevAppended && typeof prevAppended === "object" && Object.keys(prevAppended).length > 0) {
5242
+ messageToStoreRef.current.appendedData = [prevAppended, errorWidget];
5243
+ }
5244
+ else {
5245
+ // No existing widgets — put on chatData.response instead
5246
+ messageToStoreRef.current.chatData.response =
5247
+ messageToStoreRef.current.chatData.response + errorMsg;
5248
+ }
5249
+ }
5231
5250
  // Still process completed/follow-up status even on error chunks
5232
5251
  // so that initValue is set correctly and dummyButton is suppressed
5233
5252
  if (parsedData?.status === "completed" ||
@@ -5409,6 +5428,7 @@ const AxiosSource = (url, opts, messageToStoreRef) => {
5409
5428
  })
5410
5429
  .catch((error) => {
5411
5430
  let reason = "Network request failed";
5431
+ let statusCode = 0;
5412
5432
  console.error("[AxiosSource] Request failed:", error);
5413
5433
  // Determine specific error reasons
5414
5434
  if (axios.isCancel(error)) {
@@ -5417,8 +5437,15 @@ const AxiosSource = (url, opts, messageToStoreRef) => {
5417
5437
  else if (error.code === "ECONNABORTED") {
5418
5438
  reason = "Network request timed out";
5419
5439
  }
5420
- // Emit error event with the specific reason
5421
- 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);
5422
5449
  });
5423
5450
  // Add method to manually close the connection
5424
5451
  eventTarget.close = () => {
@@ -5459,10 +5486,15 @@ const useStyles$6 = styles.makeStyles((theme) => ({
5459
5486
  marginTop: pxToRem(16),
5460
5487
  }
5461
5488
  }));
5489
+ const MAX_RETRY_COUNT_BUTTON = 1;
5490
+ const RETRY_COUNTDOWN_SECONDS_BUTTON = 15;
5462
5491
  const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = false, isFormValid = true }) => {
5463
5492
  const classes = useStyles$6();
5464
5493
  const dispatch = reactRedux.useDispatch();
5465
5494
  const sourceRef = React.useRef(null);
5495
+ const retryCountRef = React.useRef(0);
5496
+ const retryTimerRef = React.useRef(null);
5497
+ const retryPayloadRef = React.useRef(null);
5466
5498
  const messageStoreRef = React.useRef({
5467
5499
  currentMode: "agent",
5468
5500
  chatData: {
@@ -5506,6 +5538,8 @@ const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = fa
5506
5538
  sendButton.click();
5507
5539
  };
5508
5540
  const callInitApiStream = (userInput) => {
5541
+ // Store payload for potential retries
5542
+ retryPayloadRef.current = userInput;
5509
5543
  // Prefer module-level values (set synchronously by StreamedContent), fall back to sessionStorage
5510
5544
  // If a previous completed/follow-up response updated the state, use those values.
5511
5545
  // Read from module-level stepFormStreamControl (survives remounts) instead of local messageStoreRef.
@@ -5705,12 +5739,65 @@ const ButtonContent = ({ bodyText, isFormDisabled = false, isStepFormSubmit = fa
5705
5739
  console.error("[ButtonContent] SSE parse error:", e);
5706
5740
  }
5707
5741
  });
5708
- sourceRef.current.addEventListener("error", () => {
5709
- // On error, still dispatch whatever we collected
5742
+ sourceRef.current.addEventListener("error", (event) => {
5710
5743
  stepFormStreamControl.isStreaming = false;
5711
5744
  stepFormStreamControl.abort = null;
5712
5745
  window.dispatchEvent(new CustomEvent("stepFormStreamEnd"));
5713
- 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
+ }
5774
+ });
5775
+ sourceRef.current.addEventListener("close", () => {
5776
+ if (stepFormStreamControl.isStreaming) {
5777
+ stepFormStreamControl.isStreaming = false;
5778
+ stepFormStreamControl.abort = null;
5779
+ window.dispatchEvent(new CustomEvent("stepFormStreamEnd"));
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
+ }
5800
+ }
5714
5801
  });
5715
5802
  };
5716
5803
  const renderButtons = () => {
@@ -6016,6 +6103,15 @@ const useStyles$5 = styles.makeStyles((theme) => ({
6016
6103
  position: "relative",
6017
6104
  background: colours.white,
6018
6105
  },
6106
+ retryContainer: {
6107
+ marginTop: pxToRem(8),
6108
+ },
6109
+ retryMessage: {
6110
+ fontFamily: "Manrope",
6111
+ fontSize: pxToRem(13),
6112
+ fontWeight: 500,
6113
+ color: "#374151",
6114
+ },
6019
6115
  }));
6020
6116
 
6021
6117
  var _path$i, _path2$2, _defs$5;
@@ -7720,8 +7816,20 @@ const AgentResponse$1 = (props) => {
7720
7816
  };
7721
7817
 
7722
7818
  const StepsResponseTab = (props) => {
7723
- 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;
7724
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]);
7725
7833
  const handleChangeTabValue = (_event, newValue) => {
7726
7834
  setTabValue(newValue);
7727
7835
  };
@@ -7951,6 +8059,14 @@ const StreamedContent = ({ botData }) => {
7951
8059
  const [finalStepDone, setFinalStepDone] = React.useState(false);
7952
8060
  const [wasStreamingAborted, setWasStreamingAborted] = React.useState(false);
7953
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
7954
8070
  const thinkingContentRef = React.useRef("");
7955
8071
  reactRedux.useSelector((state) => {
7956
8072
  return state.smartBotReducer.thinkingContext;
@@ -8070,6 +8186,94 @@ const StreamedContent = ({ botData }) => {
8070
8186
  }));
8071
8187
  }
8072
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
+ };
8073
8277
  /**
8074
8278
  * Main effect to initialize and handle the streaming connection
8075
8279
  * Sets up the SSE connection and event listeners for message processing
@@ -8175,6 +8379,26 @@ const StreamedContent = ({ botData }) => {
8175
8379
  }));
8176
8380
  return;
8177
8381
  }
8382
+ // Skip is_error chunks — already handled in AxiosEventSource sseevent()
8383
+ if (data?.is_error) {
8384
+ // Trigger completion if status is "completed"
8385
+ if (data?.status === "completed") {
8386
+ setStepsDone(true);
8387
+ setIsStreamingDone(true);
8388
+ const doneState = streamStateMap.get(streamKey);
8389
+ if (doneState)
8390
+ doneState.completed = true;
8391
+ dispatch(smartBotActions.setMinimizedStreamData({
8392
+ isStreaming: false,
8393
+ stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Completed",
8394
+ stepSubHeader: "Done",
8395
+ stepStatus: "completed",
8396
+ streamStartTime: streamStartTimeRef.current,
8397
+ actionCount: questionsRef.current.length || 1,
8398
+ }));
8399
+ }
8400
+ return;
8401
+ }
8178
8402
  if (data?.message || data?.status === "step" || data?.status === "step_form" || data?.status === "thinking" || data?.status === "questions" || data?.status === "widget") {
8179
8403
  if (data.status === "questions") {
8180
8404
  const incomingQuestions = data.widget_data?.[0]?.questions || [];
@@ -8444,64 +8668,36 @@ const StreamedContent = ({ botData }) => {
8444
8668
  const errState = streamStateMap.get(streamKey);
8445
8669
  if (!errState || errState.listenerGeneration !== generation)
8446
8670
  return;
8447
- console.error("Stream error:", event.reason);
8671
+ console.error("Stream error:", event.reason, "statusCode:", event.statusCode);
8448
8672
  setIsStreaming(false);
8449
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;
8450
8677
  if (isThinking) {
8451
8678
  setIsThinking(false);
8452
8679
  const endTime = Date.now();
8453
8680
  const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
8454
- const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
8681
+ const finalThinkingTime = Math.max(duration, 1);
8455
8682
  setThinkingTime(finalThinkingTime);
8456
8683
  setShowThoughtDropdown(true);
8457
8684
  thinkingStartTimeRef.current = finalThinkingTime;
8458
- // Store thinking time in messageToStoreRef for persistence
8459
8685
  messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
8460
8686
  }
8461
- // Mark as aborted so the completion effect skips the Submit button
8462
- setWasStreamingAborted(true);
8463
- wasStreamingAbortedRef.current = true;
8464
- // Mark the last in-progress step as completed
8465
- const currentSteps = stepRef.current;
8466
- if (currentSteps.length > 0) {
8467
- const lastStep = currentSteps[currentSteps.length - 1];
8468
- if (lastStep.step_status === "not-completed") {
8469
- lastStep.step_status = "completed";
8470
- }
8471
- stepRef.current = [...currentSteps];
8472
- setSteps([...currentSteps]);
8473
- }
8474
- // Set the response message for the agent_response tab
8475
- const abruptCloseMessage = "The requested Task has ended unexpectedly, please retry again";
8476
- messageToStoreRef.current.chatData.response = abruptCloseMessage;
8477
- setContent(abruptCloseMessage);
8478
- // Dispatch minimized widget data
8479
- dispatch(smartBotActions.setMinimizedStreamData({
8480
- isStreaming: false,
8481
- stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
8482
- stepSubHeader: "Connection closed",
8483
- stepStatus: "completed",
8484
- streamStartTime: streamStartTimeRef.current,
8485
- actionCount: questionsRef.current.length || 1,
8486
- }));
8487
- // Switch to agent_response tab and trigger completion flow
8488
- setStepsDone(true);
8489
- setIsStreamingDone(true);
8490
- });
8491
- // Handle stream closure
8492
- source.addEventListener("close", () => {
8493
- const closeState = streamStateMap.get(streamKey);
8494
- if (!closeState || closeState.listenerGeneration !== generation)
8495
- return;
8496
- setIsStreaming(false);
8497
- // If closeState.completed is already true, this is a normal close after [DONE].
8498
- // If it's still false, the backend closed the connection abruptly.
8499
- if (!closeState.completed) {
8500
- closeState.completed = true;
8501
- // Mark as aborted so the completion effect skips the Submit button
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();
8696
+ }
8697
+ else {
8698
+ // Retries exhausted — show final message
8502
8699
  setWasStreamingAborted(true);
8503
8700
  wasStreamingAbortedRef.current = true;
8504
- // Mark the last in-progress step as completed
8505
8701
  const currentSteps = stepRef.current;
8506
8702
  if (currentSteps.length > 0) {
8507
8703
  const lastStep = currentSteps[currentSteps.length - 1];
@@ -8511,21 +8707,9 @@ const StreamedContent = ({ botData }) => {
8511
8707
  stepRef.current = [...currentSteps];
8512
8708
  setSteps([...currentSteps]);
8513
8709
  }
8514
- // Set the response message for the agent_response tab
8515
- const abruptCloseMessage = "The requested Task has ended unexpectedly, please retry again";
8516
- messageToStoreRef.current.chatData.response = abruptCloseMessage;
8517
- setContent(abruptCloseMessage);
8518
- // Stop thinking if in progress
8519
- if (isThinking) {
8520
- setIsThinking(false);
8521
- const endTime = Date.now();
8522
- const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
8523
- const finalThinkingTime = Math.max(duration, 1);
8524
- setThinkingTime(finalThinkingTime);
8525
- setShowThoughtDropdown(true);
8526
- messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
8527
- }
8528
- // Dispatch minimized widget data
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);
8529
8713
  dispatch(smartBotActions.setMinimizedStreamData({
8530
8714
  isStreaming: false,
8531
8715
  stepHeader: questionsRef.current[questionsRef.current.length - 1] || "Ended unexpectedly",
@@ -8534,10 +8718,65 @@ const StreamedContent = ({ botData }) => {
8534
8718
  streamStartTime: streamStartTimeRef.current,
8535
8719
  actionCount: questionsRef.current.length || 1,
8536
8720
  }));
8537
- // Switch to agent_response tab and trigger completion flow
8538
8721
  setStepsDone(true);
8539
8722
  setIsStreamingDone(true);
8540
8723
  }
8724
+ });
8725
+ // Handle stream closure
8726
+ source.addEventListener("close", () => {
8727
+ const closeState = streamStateMap.get(streamKey);
8728
+ if (!closeState || closeState.listenerGeneration !== generation)
8729
+ return;
8730
+ setIsStreaming(false);
8731
+ // If closeState.completed is already true, this is a normal close after [DONE].
8732
+ // If it's still false, the backend closed the connection abruptly.
8733
+ if (!closeState.completed) {
8734
+ closeState.completed = true;
8735
+ // Stop thinking if in progress
8736
+ if (isThinking) {
8737
+ setIsThinking(false);
8738
+ const endTime = Date.now();
8739
+ const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
8740
+ const finalThinkingTime = Math.max(duration, 1);
8741
+ setThinkingTime(finalThinkingTime);
8742
+ setShowThoughtDropdown(true);
8743
+ messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
8744
+ }
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
+ }
8779
+ }
8541
8780
  // else: closeState.completed is already true — normal close after [DONE], nothing to do.
8542
8781
  });
8543
8782
  };
@@ -8841,6 +9080,14 @@ const StreamedContent = ({ botData }) => {
8841
9080
  * Aborts the current streaming connection
8842
9081
  */
8843
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
8844
9091
  if (sourceRef.current && isStreaming) {
8845
9092
  setWasStreamingAborted(true);
8846
9093
  wasStreamingAbortedRef.current = true;
@@ -8888,9 +9135,9 @@ const StreamedContent = ({ botData }) => {
8888
9135
  // If no content yet, show aborted message
8889
9136
  // if (isEmpty(messageToStoreRef.current.chatData?.response)) {
8890
9137
  messageToStoreRef.current.chatData.response =
8891
- 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";
8892
9139
  setContent((prev) => {
8893
- 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";
8894
9141
  });
8895
9142
  // }
8896
9143
  }
@@ -8912,10 +9159,16 @@ const StreamedContent = ({ botData }) => {
8912
9159
  * Cleanup on unmount: Do NOT close the stream here.
8913
9160
  * The stream connection is kept alive in streamStateMap so it survives
8914
9161
  * tab-switch remounts. It is only closed by explicit abortStreaming() or new chat.
9162
+ * Also clean up retry timer if running.
8915
9163
  */
8916
9164
  React.useEffect(() => {
8917
9165
  return () => {
8918
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
+ }
8919
9172
  };
8920
9173
  }, []);
8921
9174
  /**
@@ -8967,7 +9220,7 @@ const StreamedContent = ({ botData }) => {
8967
9220
  * @returns {JSX.Element} Rendered content with optional blinking cursor
8968
9221
  */
8969
9222
  const renderContent = () => {
8970
- 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"] }) }))] }));
8971
9224
  };
8972
9225
  if (currentMode === "agent") {
8973
9226
  return renderContent();
@@ -9259,6 +9512,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9259
9512
  const [stepFormSubmitted, setStepFormSubmitted] = React.useState(false);
9260
9513
  const [hasNewStepFormFromRestream, setHasNewStepFormFromRestream] = React.useState(false);
9261
9514
  const [activeFormIntent, setActiveFormIntent] = React.useState(null);
9515
+ const [retryCountdown, setRetryCountdown] = React.useState(0);
9262
9516
  // Stable unique instance ID for this TabularContent mount
9263
9517
  const instanceIdRef = React.useRef(null);
9264
9518
  if (instanceIdRef.current === null) {
@@ -9324,10 +9578,15 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9324
9578
  const payload = stepFormStreamData;
9325
9579
  // Clear Redux immediately
9326
9580
  dispatch(smartBotActions.setStepFormStreamData(null));
9581
+ if (payload.status === "retrying") {
9582
+ setRetryCountdown(payload.countdown || 0);
9583
+ return;
9584
+ }
9327
9585
  if (payload.status === "streaming_start") {
9328
9586
  setIsRestreaming(true);
9329
9587
  setStepFormSubmitted(true);
9330
9588
  setHasNewStepFormFromRestream(false);
9589
+ setRetryCountdown(0);
9331
9590
  setTabValue("steps");
9332
9591
  return;
9333
9592
  }
@@ -9467,6 +9726,8 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9467
9726
  }
9468
9727
  return;
9469
9728
  }
9729
+ // Clear retry countdown when final processing starts
9730
+ setRetryCountdown(0);
9470
9731
  // Process all collected chunks at once (done or error)
9471
9732
  const chunks = payload.chunks || [];
9472
9733
  let newSteps = lodash.cloneDeep(stepsRef.current);
@@ -9615,7 +9876,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
9615
9876
  },
9616
9877
  ], tabPanels: [
9617
9878
  jsxRuntime.jsx(Steps, { steps: stepsState, questions: questionsState, questionsStepsMap: questionsStepsMapState, stepFormDataMap: stepFormDataMapState, isFormDisabled: (isFormDisabled && !isRestreaming) || stepFormSubmitted, activeFormIntent: hasNewStepFormFromRestream ? activeFormIntent : null, isRestreaming: isRestreaming }),
9618
- 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"] }) }))] }),
9619
9880
  ], value: tabValue }) }));
9620
9881
  };
9621
9882
  /** Reset the active instance tracker (call when a new conversation starts from the input field) */