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