impact-chatbot 2.3.7 → 2.3.9

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
@@ -4504,7 +4504,7 @@ const ChatPlaceholder = (props) => {
4504
4504
  getBaseUrl();
4505
4505
  }, []);
4506
4506
  const handleRectangleClick = (agentId, title) => {
4507
- if (legacyAgentScreen) {
4507
+ if (legacyAgentScreen || displayQuestions) {
4508
4508
  setCurrentAgentId(agentId);
4509
4509
  const initiateAgentPayload = {
4510
4510
  agent_id: agentId,
@@ -4553,7 +4553,7 @@ const ChatPlaceholder = (props) => {
4553
4553
  agentId: card.agentId || card.id
4554
4554
  }));
4555
4555
  const dataToMap = legacyAgentScreen || displayQuestions ? transformedCardList : rectangleData;
4556
- return (jsxs("div", { className: classes.placeholderContainer, children: [jsx("div", { className: classes.centerIconContainer, children: jsx(SvgCenter3D, { className: classes.centerIcon }) }), jsx(Typography, { variant: "h1", className: classes.heading, children: "Alan's Capabilities" }), jsx(Typography, { variant: "body1", className: classes.headingHelperText, children: "Discover potential issues & opportunities Alan can help you with!" }), jsx("div", { className: classes.rectanglesContainer, children: dataToMap.map((item, index) => (jsx(Rectangle, { type: item.type, icon: item.icon, title: item.title, description: item.description, onClick: () => handleRectangleClick(item?.agentId, item?.title), hoverable: legacyAgentScreen }, index))) })] }));
4556
+ return (jsxs("div", { className: classes.placeholderContainer, children: [jsx("div", { className: classes.centerIconContainer, children: jsx(SvgCenter3D, { className: classes.centerIcon }) }), jsx(Typography, { variant: "h1", className: classes.heading, children: "Alan's Capabilities" }), jsx(Typography, { variant: "body1", className: classes.headingHelperText, children: "Discover potential issues & opportunities Alan can help you with!" }), jsx("div", { className: classes.rectanglesContainer, children: dataToMap.map((item, index) => (jsx(Rectangle, { type: item.type, icon: item.icon, title: item.title, description: item.description, onClick: () => handleRectangleClick(item?.agentId, item?.title), hoverable: legacyAgentScreen || displayQuestions }, index))) })] }));
4557
4557
  };
4558
4558
 
4559
4559
  const dateFormat = "DD-MM-YYYY HH:mm:ss";
@@ -5815,6 +5815,12 @@ const StepsResponseTab = (props) => {
5815
5815
  ], value: tabValue }) }));
5816
5816
  };
5817
5817
 
5818
+ /**
5819
+ * Module-level Map to persist streaming state across component remounts (tab switches).
5820
+ * Keyed by a stable identifier derived from input payload + mode + conversation ID.
5821
+ * This survives the cloneDeep replacement of botData in SmartBot's useEffect.
5822
+ */
5823
+ const streamStateMap = new Map();
5818
5824
  /**
5819
5825
  * Formats thinking time to display minutes when appropriate
5820
5826
  * @param {number} seconds - Time in seconds
@@ -5897,9 +5903,13 @@ const StreamedContent = ({ botData }) => {
5897
5903
  const thinkingDoneRef = useRef(false);
5898
5904
  const setThinkingContentRef = useRef(setThinkingContent); // Store current setThinkingContent function
5899
5905
  const streamTimeoutRef = useRef(null); // Ref for stream timeout
5900
- const messageToStoreRef = useRef({
5901
- // Accumulates the complete response
5906
+ // Stable key for the module-level streamStateMap (survives cloneDeep of botData)
5907
+ const streamKey = useRef(`${currentMode}_${activeConversationId}_${JSON.stringify(botData.inputBody)}`).current;
5908
+ // Look up existing stream state from the Map (persists across tab-switch remounts)
5909
+ const existingStreamState = streamStateMap.get(streamKey);
5910
+ const messageToStoreRef = useRef(existingStreamState?.messageStore || {
5902
5911
  status: "",
5912
+ currentMode: currentMode,
5903
5913
  chatData: {
5904
5914
  response: "",
5905
5915
  response_heading: "",
@@ -5910,12 +5920,11 @@ const StreamedContent = ({ botData }) => {
5910
5920
  thinkingHeading: botData?.utilityObject?.thinkingResponse?.thinkingHeading
5911
5921
  },
5912
5922
  },
5913
- currentMode: currentMode,
5914
5923
  appendedData: {},
5915
5924
  appendedDataFromLastChunk: {},
5916
5925
  initValue: false,
5917
5926
  sessionId: "",
5918
- uniqueChatId: ""
5927
+ uniqueChatId: "",
5919
5928
  });
5920
5929
  useRef(new Set()); // Tracks processed message chunks to prevent duplicates
5921
5930
  useRef(""); // Tracks current content before adding new chunk
@@ -5966,239 +5975,171 @@ const StreamedContent = ({ botData }) => {
5966
5975
  : undefined,
5967
5976
  timeout: 30000, // 30 second timeout
5968
5977
  }, messageToStoreRef);
5978
+ // Persist source in module-level Map so the connection survives tab-switch unmounts
5979
+ const state = streamStateMap.get(streamKey);
5980
+ if (state) {
5981
+ state.source = sourceRef.current;
5982
+ state.initiated = true;
5983
+ }
5969
5984
  // localStorage.setItem("isStreaming", "true");
5970
5985
  // Handle incoming messages
5971
- sourceRef.current.addEventListener("message", (event) => {
5972
- try {
5973
- if (currentMode === "agent" && !isThinkingFromParent) {
5974
- setIsThinkingFromParent(true);
5975
- botData.utilityObject.isThinking = true;
5976
- botData.utilityObject.thinkingResponse.isThinking = true;
5977
- thinkingStartTimeRef.current = Date.now();
5986
+ attachListeners(sourceRef.current);
5987
+ }
5988
+ catch (error) {
5989
+ console.error("Error processing stream:", error);
5990
+ setContent("Error streaming response. Please try again.");
5991
+ setIsStreaming(false);
5992
+ if (isThinking) {
5993
+ setIsThinking(false);
5994
+ const endTime = Date.now();
5995
+ const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
5996
+ const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
5997
+ setThinkingTime(finalThinkingTime);
5998
+ setShowThoughtDropdown(true);
5999
+ // Store thinking time in messageToStoreRef for persistence
6000
+ messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6001
+ }
6002
+ }
6003
+ };
6004
+ /**
6005
+ * Attaches event listeners to an SSE source with generation-based stale detection.
6006
+ * Only the latest mount's listeners will actually update state.
6007
+ */
6008
+ const attachListeners = (source) => {
6009
+ const state = streamStateMap.get(streamKey);
6010
+ const generation = state ? ++state.listenerGeneration : 0;
6011
+ source.addEventListener("message", (event) => {
6012
+ // Skip if this listener is from a stale (previous) mount
6013
+ const currState = streamStateMap.get(streamKey);
6014
+ if (!currState || currState.listenerGeneration !== generation)
6015
+ return;
6016
+ try {
6017
+ if (currentMode === "agent" && !isThinkingFromParent) {
6018
+ setIsThinkingFromParent(true);
6019
+ botData.utilityObject.isThinking = true;
6020
+ botData.utilityObject.thinkingResponse.isThinking = true;
6021
+ thinkingStartTimeRef.current = Date.now();
6022
+ }
6023
+ const data = JSON.parse(event.data);
6024
+ if (data?.message || data?.status === "step" || data?.status === "thinking" || data?.status === "questions") {
6025
+ if (data.status === "questions") {
6026
+ const incomingQuestions = data.widget_data?.[0]?.questions || [];
6027
+ questionsRef.current = incomingQuestions;
6028
+ setQuestions(incomingQuestions);
6029
+ const initialMap = {};
6030
+ incomingQuestions.forEach((q) => {
6031
+ initialMap[q] = [
6032
+ {
6033
+ header: "Processing Request",
6034
+ sub_header: "Analyzing the current request",
6035
+ step_status: "not-completed",
6036
+ },
6037
+ ];
6038
+ });
6039
+ questionsStepsMapRef.current = initialMap;
6040
+ setQuestionsStepsMap(cloneDeep(initialMap));
5978
6041
  }
5979
- const data = JSON.parse(event.data);
5980
- if (data?.message || data?.status === "step" || data?.status === "thinking" || data?.status === "questions") {
5981
- if (data.status === "questions") {
5982
- const incomingQuestions = data.widget_data?.[0]?.questions || [];
5983
- questionsRef.current = incomingQuestions;
5984
- setQuestions(incomingQuestions);
5985
- const initialMap = {};
5986
- incomingQuestions.forEach((q) => {
5987
- initialMap[q] = [
5988
- {
5989
- header: "Processing Request",
5990
- sub_header: "Analyzing the current request",
5991
- step_status: "not-completed",
5992
- },
5993
- ];
6042
+ else if (data.status === "thinking") {
6043
+ messageToStoreRef.current.chatData.thinkingResponse.thinkingHeading = "Planning next moves";
6044
+ // Start thinking if not already started
6045
+ if (!isThinking) {
6046
+ setIsThinking(true);
6047
+ }
6048
+ // Update thinking content in a more reliable way
6049
+ const newValue = (thinkingContentRef.current || "") + data.message;
6050
+ setThinkingContentRef.current(newValue);
6051
+ thinkingContentRef.current = thinkingContentRef.current + data.message;
6052
+ messageToStoreRef.current.chatData.thinkingResponse.thinkingStream = newValue;
6053
+ dispatch(setThinkingContext({
6054
+ thinkingHeaderMessage: thinkingHeaderMessageRef.current,
6055
+ thinkingContent: thinkingContentRef.current,
6056
+ }));
6057
+ setThinkingStarted(true);
6058
+ setIsThinkingFromParent(false);
6059
+ }
6060
+ else if (data.status === "step") {
6061
+ let newStep = data.widget_data[0];
6062
+ const currentIntent = data.widget_data[0]?.current_intent;
6063
+ setSteps([...steps, newStep]);
6064
+ let newSteps = cloneDeep(stepRef.current);
6065
+ if (newSteps.length === 1) {
6066
+ newSteps.forEach((step) => {
6067
+ step.step_status = "completed";
5994
6068
  });
5995
- questionsStepsMapRef.current = initialMap;
5996
- setQuestionsStepsMap(cloneDeep(initialMap));
5997
6069
  }
5998
- else if (data.status === "thinking") {
5999
- messageToStoreRef.current.chatData.thinkingResponse.thinkingHeading = "Planning next moves";
6000
- // Start thinking if not already started
6001
- if (!isThinking) {
6002
- setIsThinking(true);
6003
- }
6004
- // Update thinking content in a more reliable way
6005
- const newValue = (thinkingContentRef.current || "") + data.message;
6006
- setThinkingContentRef.current(newValue);
6007
- thinkingContentRef.current = thinkingContentRef.current + data.message;
6008
- messageToStoreRef.current.chatData.thinkingResponse.thinkingStream = newValue;
6009
- // Update the ThinkingIndicator components with the new accumulated content
6010
- // messageToStoreRef.current.chatData.thinkingResponse.thinkingContent = (
6011
- // <ThinkingIndicator
6012
- // thinkingContent={newValue}
6013
- // isStreaming={isStreaming}
6014
- // thinkDone={thinkDone}
6015
- // renderThinkingLoader={renderThinkingLoader}
6016
- // thinkingStarted={thinkingStarted}
6017
- // />
6018
- // );
6019
- dispatch(setThinkingContext({
6020
- thinkingHeaderMessage: thinkingHeaderMessageRef.current,
6021
- thinkingContent: thinkingContentRef.current,
6022
- }));
6023
- setThinkingStarted(true);
6024
- setIsThinkingFromParent(false);
6025
- // botData.utilityObject.thinkingResponse.thinkingContent = (
6026
- // <ThinkingIndicator
6027
- // thinkingContent={newValue}
6028
- // isStreaming={isStreaming}
6029
- // thinkDone={thinkDone}
6030
- // renderThinkingLoader={renderThinkingLoader}
6031
- // thinkingStarted={thinkingStarted}
6032
- // />
6033
- // );
6070
+ const existingStepIndex = newSteps.findIndex((step) => step.header === newStep.header);
6071
+ if (existingStepIndex !== -1) {
6072
+ newSteps[existingStepIndex] = newStep;
6034
6073
  }
6035
- else if (data.status === "step") {
6036
- let newStep = data.widget_data[0];
6037
- const currentIntent = data.widget_data[0]?.current_intent;
6038
- setSteps([...steps, newStep]);
6039
- let newSteps = cloneDeep(stepRef.current);
6040
- if (newSteps.length === 1) {
6041
- newSteps.forEach((step) => {
6042
- step.step_status = "completed";
6043
- });
6074
+ else {
6075
+ newSteps.push(newStep);
6076
+ }
6077
+ stepRef.current = newSteps;
6078
+ setSteps(newSteps);
6079
+ if (currentIntent && questionsStepsMapRef.current[currentIntent]) {
6080
+ let intentSteps = cloneDeep(questionsStepsMapRef.current[currentIntent]);
6081
+ if (intentSteps.length === 1 && intentSteps[0].header === "Processing Request") {
6082
+ intentSteps[0].step_status = "completed";
6044
6083
  }
6045
- const existingStepIndex = newSteps.findIndex((step) => step.header === newStep.header);
6046
- if (existingStepIndex !== -1) {
6047
- newSteps[existingStepIndex] = newStep;
6084
+ const existingIdx = intentSteps.findIndex((s) => s.header === newStep.header);
6085
+ if (existingIdx !== -1) {
6086
+ intentSteps[existingIdx] = newStep;
6048
6087
  }
6049
6088
  else {
6050
- newSteps.push(newStep);
6051
- }
6052
- stepRef.current = newSteps;
6053
- setSteps(newSteps);
6054
- if (currentIntent && questionsStepsMapRef.current[currentIntent]) {
6055
- let intentSteps = cloneDeep(questionsStepsMapRef.current[currentIntent]);
6056
- if (intentSteps.length === 1 && intentSteps[0].header === "Processing Request") {
6057
- intentSteps[0].step_status = "completed";
6058
- }
6059
- const existingIdx = intentSteps.findIndex((s) => s.header === newStep.header);
6060
- if (existingIdx !== -1) {
6061
- intentSteps[existingIdx] = newStep;
6062
- }
6063
- else {
6064
- intentSteps.push(newStep);
6065
- }
6066
- questionsStepsMapRef.current[currentIntent] = intentSteps;
6067
- setQuestionsStepsMap(cloneDeep(questionsStepsMapRef.current));
6068
- }
6069
- setStepChange((prev) => !prev);
6070
- }
6071
- else if (data.message !== "[DONE]") {
6072
- setStepsDone(true);
6073
- // setTimeout(() => {
6074
- // setStepsDone(true);
6075
- // }, 5000);
6076
- if (!thinkingDoneRef?.current && currentMode === "agent") {
6077
- thinkingDoneRef.current = true;
6078
- setThinkDone(true);
6079
- setIsThinkingFromParent(false);
6080
- const endTime = Date.now();
6081
- const startTime = thinkingStartTimeRef.current || endTime - 1000; // Fallback to 1 second ago
6082
- const duration = Math.round((endTime - startTime) / 1000);
6083
- const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6084
- setThinkingTime(finalThinkingTime);
6085
- setShowThoughtDropdown(true);
6086
- setIsThinking(false);
6087
- // Store thinking time in messageToStoreRef for persistence
6088
- messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6089
- thinkingTimeFinalRef.current = finalThinkingTime;
6090
- setThinkingHeaderMessage(`Thought for ${formatThinkingTime(finalThinkingTime)}`);
6091
- thinkingHeaderMessageRef.current = `Thought for ${formatThinkingTime(finalThinkingTime)}`;
6092
- dispatch(setThinkingContext({
6093
- thinkingHeaderMessage: `Thinking Completed`,
6094
- thinkingContent: thinkingContentRef.current,
6095
- }));
6096
- // dispatch(setThinkingContext({
6097
- // thinkingHeaderMessage: `Thought for ${formatThinkingTime(finalThinkingTime)}`,
6098
- // thinkingContent: thinkingContentRef.current,
6099
- // }));
6100
- // botData.utilityObject.thinkingResponse.thinkingHeading = (
6101
- // <ThinkinHeaderInfo
6102
- // thinkingHeaderMessage={`Thought for ${formatThinkingTime(finalThinkingTime)}`}
6103
- // />
6104
- // );
6089
+ intentSteps.push(newStep);
6105
6090
  }
6106
- // if(thinkingStarted.current && !thinkDone){
6107
- // setThinkDone(true);
6108
- // thinkingStarted.current = false;
6109
- // }
6110
- setContent((prev) => {
6111
- return prev + data.message;
6112
- });
6113
- // if (currentMode === "agent") {
6114
- // setIsThinkingFromParent(false);
6115
- // botData.utilityObject.isThinking = false;
6116
- // botData.utilityObject.thinkingResponse.isThinking = false;
6117
- // const finalThinkingTime =
6118
- // thinkingTime || thinkingTimeFinalRef.current || 1;
6119
- // let thinkingTimeFinal = cloneDeep(finalThinkingTime);
6120
- // botData.utilityObject.thinkingResponse.thinkingHeading = (
6121
- // <ThinkinHeaderInfo
6122
- // thinkingHeaderMessage={`Thought for ${formatThinkingTime(thinkingTimeFinal)}`}
6123
- // />
6124
- // );
6125
- // }
6126
- // // End thinking when regular content starts
6127
- // if (isThinking && currentMode === "agent") {
6128
- // setIsThinking(false);
6129
- // if (!thinkDone) {
6130
- // const endTime = Date.now();
6131
- // const startTime =
6132
- // thinkingStartTimeRef.current || endTime - 1000; // Fallback to 1 second ago
6133
- // const duration = Math.round((endTime - startTime) / 1000);
6134
- // const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6135
- // setThinkingTime(finalThinkingTime);
6136
- // setShowThoughtDropdown(true);
6137
- // // Store thinking time in messageToStoreRef for persistence
6138
- // messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6139
- // thinkingTimeFinalRef.current = finalThinkingTime;
6140
- // }
6141
- // }
6142
- // if(!thinkDone && currentMode === "agent") {
6143
- // setThinkDone(true);
6144
- // setIsThinking(false);
6145
- // setTimeout(() => {
6146
- // // setThinkDone(true);
6147
- // const endTime = Date.now();
6148
- // const startTime =
6149
- // thinkingStartTimeRef.current || endTime - 1000; // Fallback to 1 second ago
6150
- // const duration = Math.round((endTime - startTime) / 1000);
6151
- // const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6152
- // setThinkingTime(finalThinkingTime);
6153
- // setShowThoughtDropdown(true);
6154
- // // Store thinking time in messageToStoreRef for persistence
6155
- // messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6156
- // thinkingTimeFinalRef.current = finalThinkingTime;
6157
- // setContent((prev) => {
6158
- // return prev + data.message;
6159
- // });
6160
- // }, 1000);
6161
- // }
6162
- // else {
6163
- // setContent((prev) => {
6164
- // return prev + data.message;
6165
- // });
6166
- // }
6091
+ questionsStepsMapRef.current[currentIntent] = intentSteps;
6092
+ setQuestionsStepsMap(cloneDeep(questionsStepsMapRef.current));
6167
6093
  }
6168
- else {
6169
- setIsStreamingDone(true);
6094
+ setStepChange((prev) => !prev);
6095
+ }
6096
+ else if (data.message !== "[DONE]") {
6097
+ setStepsDone(true);
6098
+ if (!thinkingDoneRef?.current && currentMode === "agent") {
6099
+ thinkingDoneRef.current = true;
6100
+ setThinkDone(true);
6101
+ setIsThinkingFromParent(false);
6102
+ const endTime = Date.now();
6103
+ const startTime = thinkingStartTimeRef.current || endTime - 1000; // Fallback to 1 second ago
6104
+ const duration = Math.round((endTime - startTime) / 1000);
6105
+ const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6106
+ setThinkingTime(finalThinkingTime);
6107
+ setShowThoughtDropdown(true);
6108
+ setIsThinking(false);
6109
+ // Store thinking time in messageToStoreRef for persistence
6110
+ messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6111
+ thinkingTimeFinalRef.current = finalThinkingTime;
6112
+ setThinkingHeaderMessage(`Thought for ${formatThinkingTime(finalThinkingTime)}`);
6113
+ thinkingHeaderMessageRef.current = `Thought for ${formatThinkingTime(finalThinkingTime)}`;
6114
+ dispatch(setThinkingContext({
6115
+ thinkingHeaderMessage: `Thinking Completed`,
6116
+ thinkingContent: thinkingContentRef.current,
6117
+ }));
6170
6118
  }
6119
+ setContent((prev) => {
6120
+ return prev + data.message;
6121
+ });
6122
+ }
6123
+ else {
6124
+ setIsStreamingDone(true);
6125
+ const doneState = streamStateMap.get(streamKey);
6126
+ if (doneState)
6127
+ doneState.completed = true;
6171
6128
  }
6172
6129
  }
6173
- catch (e) {
6174
- console.error("Error processing message:", e);
6175
- }
6176
- });
6177
- // Handle stream errors
6178
- sourceRef.current.addEventListener("error", (event) => {
6179
- console.error("Stream error:", event.reason);
6180
- setIsStreaming(false);
6181
- if (isThinking) {
6182
- setIsThinking(false);
6183
- const endTime = Date.now();
6184
- const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
6185
- const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6186
- setThinkingTime(finalThinkingTime);
6187
- setShowThoughtDropdown(true);
6188
- thinkingStartTimeRef.current = finalThinkingTime;
6189
- // Store thinking time in messageToStoreRef for persistence
6190
- messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6191
- }
6192
- });
6193
- // Handle stream closure
6194
- sourceRef.current.addEventListener("close", () => {
6195
- setIsStreaming(false);
6196
- });
6197
- }
6198
- catch (error) {
6199
- console.error("Error processing stream:", error);
6200
- setContent("Error streaming response. Please try again.");
6130
+ }
6131
+ catch (e) {
6132
+ console.error("Error processing message:", e);
6133
+ }
6134
+ });
6135
+ // Handle stream errors
6136
+ source.addEventListener("error", (event) => {
6137
+ const errState = streamStateMap.get(streamKey);
6138
+ if (!errState || errState.listenerGeneration !== generation)
6139
+ return;
6140
+ console.error("Stream error:", event.reason);
6201
6141
  setIsStreaming(false);
6142
+ errState.completed = true;
6202
6143
  if (isThinking) {
6203
6144
  setIsThinking(false);
6204
6145
  const endTime = Date.now();
@@ -6206,13 +6147,62 @@ const StreamedContent = ({ botData }) => {
6206
6147
  const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6207
6148
  setThinkingTime(finalThinkingTime);
6208
6149
  setShowThoughtDropdown(true);
6150
+ thinkingStartTimeRef.current = finalThinkingTime;
6209
6151
  // Store thinking time in messageToStoreRef for persistence
6210
6152
  messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6211
6153
  }
6212
- }
6154
+ });
6155
+ // Handle stream closure
6156
+ source.addEventListener("close", () => {
6157
+ const closeState = streamStateMap.get(streamKey);
6158
+ if (!closeState || closeState.listenerGeneration !== generation)
6159
+ return;
6160
+ setIsStreaming(false);
6161
+ closeState.completed = true;
6162
+ });
6213
6163
  };
6164
+ /**
6165
+ * Effect to initialize streaming or restore state on tab-switch remount.
6166
+ * Uses module-level streamStateMap to detect remounts and avoid re-triggering the API.
6167
+ */
6214
6168
  useEffect(() => {
6215
- processStream();
6169
+ const mapState = streamStateMap.get(streamKey);
6170
+ if (mapState?.initiated) {
6171
+ // Remount after tab switch - restore state from persisted data
6172
+ const store = messageToStoreRef.current;
6173
+ // Restore accumulated content
6174
+ setContent(store.chatData?.response || "");
6175
+ // Restore thinking state
6176
+ if (store.chatData?.thinkingResponse?.thinkingStream) {
6177
+ thinkingContentRef.current = store.chatData.thinkingResponse.thinkingStream;
6178
+ }
6179
+ if (store.chatData?.thinkingResponse?.thinkingTime) {
6180
+ thinkingTimeFinalRef.current = store.chatData.thinkingResponse.thinkingTime;
6181
+ setThinkingTime(store.chatData.thinkingResponse.thinkingTime);
6182
+ }
6183
+ if (mapState.completed) {
6184
+ // Stream already finished while we were unmounted - trigger completion
6185
+ setIsStreaming(false);
6186
+ setIsStreamingDone(true);
6187
+ }
6188
+ else if (mapState.source) {
6189
+ // Stream still in progress - re-attach listeners to existing source
6190
+ sourceRef.current = mapState.source;
6191
+ setIsStreaming(true);
6192
+ attachListeners(mapState.source);
6193
+ }
6194
+ }
6195
+ else {
6196
+ // First mount - initialize Map entry and start streaming
6197
+ streamStateMap.set(streamKey, {
6198
+ messageStore: messageToStoreRef.current,
6199
+ source: null,
6200
+ initiated: false,
6201
+ completed: false,
6202
+ listenerGeneration: 0,
6203
+ });
6204
+ processStream();
6205
+ }
6216
6206
  }, []);
6217
6207
  /**
6218
6208
  * Effect to update chat data when streaming is complete
@@ -6381,6 +6371,8 @@ const StreamedContent = ({ botData }) => {
6381
6371
  questionsStepsMap: cloneDeep(questionsStepsMapRef.current),
6382
6372
  });
6383
6373
  }
6374
+ // Clean up module-level Map entry - stream is fully processed
6375
+ streamStateMap.delete(streamKey);
6384
6376
  // Trigger re-render by updating chatDataState
6385
6377
  setTimeout(() => {
6386
6378
  setChatDataState({ ...chatDataRef.current });
@@ -6412,6 +6404,12 @@ const StreamedContent = ({ botData }) => {
6412
6404
  sourceRef.current.close();
6413
6405
  setIsStreaming(false);
6414
6406
  setIsStreamingDone(true);
6407
+ // Clean up stream state in module-level Map
6408
+ const abortState = streamStateMap.get(streamKey);
6409
+ if (abortState) {
6410
+ abortState.completed = true;
6411
+ abortState.source = null;
6412
+ }
6415
6413
  // Stop the agent flow on the backend
6416
6414
  if (messageToStoreRef.current.sessionId) {
6417
6415
  stopAgentFlow({ session_id: messageToStoreRef.current.sessionId }, baseUrl);
@@ -6455,13 +6453,13 @@ const StreamedContent = ({ botData }) => {
6455
6453
  };
6456
6454
  }, [isStreaming]);
6457
6455
  /**
6458
- * Cleanup function to abort streaming on unmount
6456
+ * Cleanup on unmount: Do NOT close the stream here.
6457
+ * The stream connection is kept alive in streamStateMap so it survives
6458
+ * tab-switch remounts. It is only closed by explicit abortStreaming() or new chat.
6459
6459
  */
6460
6460
  useEffect(() => {
6461
6461
  return () => {
6462
- if (sourceRef.current && isStreaming) {
6463
- sourceRef.current.close();
6464
- }
6462
+ // Intentionally not closing the stream - it persists in streamStateMap
6465
6463
  };
6466
6464
  }, []);
6467
6465
  /**