impact-chatbot 2.3.8 → 2.3.10

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
@@ -5837,6 +5837,12 @@ const StepsResponseTab = (props) => {
5837
5837
  ], value: tabValue }) }));
5838
5838
  };
5839
5839
 
5840
+ /**
5841
+ * Module-level Map to persist streaming state across component remounts (tab switches).
5842
+ * Keyed by a stable identifier derived from input payload + mode + conversation ID.
5843
+ * This survives the cloneDeep replacement of botData in SmartBot's useEffect.
5844
+ */
5845
+ const streamStateMap = new Map();
5840
5846
  /**
5841
5847
  * Formats thinking time to display minutes when appropriate
5842
5848
  * @param {number} seconds - Time in seconds
@@ -5919,9 +5925,13 @@ const StreamedContent = ({ botData }) => {
5919
5925
  const thinkingDoneRef = React.useRef(false);
5920
5926
  const setThinkingContentRef = React.useRef(setThinkingContent); // Store current setThinkingContent function
5921
5927
  const streamTimeoutRef = React.useRef(null); // Ref for stream timeout
5922
- const messageToStoreRef = React.useRef({
5923
- // Accumulates the complete response
5928
+ // Stable key for the module-level streamStateMap (survives cloneDeep of botData)
5929
+ const streamKey = React.useRef(`${currentMode}_${activeConversationId}_${JSON.stringify(botData.inputBody)}`).current;
5930
+ // Look up existing stream state from the Map (persists across tab-switch remounts)
5931
+ const existingStreamState = streamStateMap.get(streamKey);
5932
+ const messageToStoreRef = React.useRef(existingStreamState?.messageStore || {
5924
5933
  status: "",
5934
+ currentMode: currentMode,
5925
5935
  chatData: {
5926
5936
  response: "",
5927
5937
  response_heading: "",
@@ -5932,12 +5942,11 @@ const StreamedContent = ({ botData }) => {
5932
5942
  thinkingHeading: botData?.utilityObject?.thinkingResponse?.thinkingHeading
5933
5943
  },
5934
5944
  },
5935
- currentMode: currentMode,
5936
5945
  appendedData: {},
5937
5946
  appendedDataFromLastChunk: {},
5938
5947
  initValue: false,
5939
5948
  sessionId: "",
5940
- uniqueChatId: ""
5949
+ uniqueChatId: "",
5941
5950
  });
5942
5951
  React.useRef(new Set()); // Tracks processed message chunks to prevent duplicates
5943
5952
  React.useRef(""); // Tracks current content before adding new chunk
@@ -5988,239 +5997,171 @@ const StreamedContent = ({ botData }) => {
5988
5997
  : undefined,
5989
5998
  timeout: 30000, // 30 second timeout
5990
5999
  }, messageToStoreRef);
6000
+ // Persist source in module-level Map so the connection survives tab-switch unmounts
6001
+ const state = streamStateMap.get(streamKey);
6002
+ if (state) {
6003
+ state.source = sourceRef.current;
6004
+ state.initiated = true;
6005
+ }
5991
6006
  // localStorage.setItem("isStreaming", "true");
5992
6007
  // Handle incoming messages
5993
- sourceRef.current.addEventListener("message", (event) => {
5994
- try {
5995
- if (currentMode === "agent" && !isThinkingFromParent) {
5996
- setIsThinkingFromParent(true);
5997
- botData.utilityObject.isThinking = true;
5998
- botData.utilityObject.thinkingResponse.isThinking = true;
5999
- thinkingStartTimeRef.current = Date.now();
6008
+ attachListeners(sourceRef.current);
6009
+ }
6010
+ catch (error) {
6011
+ console.error("Error processing stream:", error);
6012
+ setContent("Error streaming response. Please try again.");
6013
+ setIsStreaming(false);
6014
+ if (isThinking) {
6015
+ setIsThinking(false);
6016
+ const endTime = Date.now();
6017
+ const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
6018
+ const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6019
+ setThinkingTime(finalThinkingTime);
6020
+ setShowThoughtDropdown(true);
6021
+ // Store thinking time in messageToStoreRef for persistence
6022
+ messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6023
+ }
6024
+ }
6025
+ };
6026
+ /**
6027
+ * Attaches event listeners to an SSE source with generation-based stale detection.
6028
+ * Only the latest mount's listeners will actually update state.
6029
+ */
6030
+ const attachListeners = (source) => {
6031
+ const state = streamStateMap.get(streamKey);
6032
+ const generation = state ? ++state.listenerGeneration : 0;
6033
+ source.addEventListener("message", (event) => {
6034
+ // Skip if this listener is from a stale (previous) mount
6035
+ const currState = streamStateMap.get(streamKey);
6036
+ if (!currState || currState.listenerGeneration !== generation)
6037
+ return;
6038
+ try {
6039
+ if (currentMode === "agent" && !isThinkingFromParent) {
6040
+ setIsThinkingFromParent(true);
6041
+ botData.utilityObject.isThinking = true;
6042
+ botData.utilityObject.thinkingResponse.isThinking = true;
6043
+ thinkingStartTimeRef.current = Date.now();
6044
+ }
6045
+ const data = JSON.parse(event.data);
6046
+ if (data?.message || data?.status === "step" || data?.status === "thinking" || data?.status === "questions") {
6047
+ if (data.status === "questions") {
6048
+ const incomingQuestions = data.widget_data?.[0]?.questions || [];
6049
+ questionsRef.current = incomingQuestions;
6050
+ setQuestions(incomingQuestions);
6051
+ const initialMap = {};
6052
+ incomingQuestions.forEach((q) => {
6053
+ initialMap[q] = [
6054
+ {
6055
+ header: "Processing Request",
6056
+ sub_header: "Analyzing the current request",
6057
+ step_status: "not-completed",
6058
+ },
6059
+ ];
6060
+ });
6061
+ questionsStepsMapRef.current = initialMap;
6062
+ setQuestionsStepsMap(lodash.cloneDeep(initialMap));
6000
6063
  }
6001
- const data = JSON.parse(event.data);
6002
- if (data?.message || data?.status === "step" || data?.status === "thinking" || data?.status === "questions") {
6003
- if (data.status === "questions") {
6004
- const incomingQuestions = data.widget_data?.[0]?.questions || [];
6005
- questionsRef.current = incomingQuestions;
6006
- setQuestions(incomingQuestions);
6007
- const initialMap = {};
6008
- incomingQuestions.forEach((q) => {
6009
- initialMap[q] = [
6010
- {
6011
- header: "Processing Request",
6012
- sub_header: "Analyzing the current request",
6013
- step_status: "not-completed",
6014
- },
6015
- ];
6064
+ else if (data.status === "thinking") {
6065
+ messageToStoreRef.current.chatData.thinkingResponse.thinkingHeading = "Planning next moves";
6066
+ // Start thinking if not already started
6067
+ if (!isThinking) {
6068
+ setIsThinking(true);
6069
+ }
6070
+ // Update thinking content in a more reliable way
6071
+ const newValue = (thinkingContentRef.current || "") + data.message;
6072
+ setThinkingContentRef.current(newValue);
6073
+ thinkingContentRef.current = thinkingContentRef.current + data.message;
6074
+ messageToStoreRef.current.chatData.thinkingResponse.thinkingStream = newValue;
6075
+ dispatch(smartBotActions.setThinkingContext({
6076
+ thinkingHeaderMessage: thinkingHeaderMessageRef.current,
6077
+ thinkingContent: thinkingContentRef.current,
6078
+ }));
6079
+ setThinkingStarted(true);
6080
+ setIsThinkingFromParent(false);
6081
+ }
6082
+ else if (data.status === "step") {
6083
+ let newStep = data.widget_data[0];
6084
+ const currentIntent = data.widget_data[0]?.current_intent;
6085
+ setSteps([...steps, newStep]);
6086
+ let newSteps = lodash.cloneDeep(stepRef.current);
6087
+ if (newSteps.length === 1) {
6088
+ newSteps.forEach((step) => {
6089
+ step.step_status = "completed";
6016
6090
  });
6017
- questionsStepsMapRef.current = initialMap;
6018
- setQuestionsStepsMap(lodash.cloneDeep(initialMap));
6019
6091
  }
6020
- else if (data.status === "thinking") {
6021
- messageToStoreRef.current.chatData.thinkingResponse.thinkingHeading = "Planning next moves";
6022
- // Start thinking if not already started
6023
- if (!isThinking) {
6024
- setIsThinking(true);
6025
- }
6026
- // Update thinking content in a more reliable way
6027
- const newValue = (thinkingContentRef.current || "") + data.message;
6028
- setThinkingContentRef.current(newValue);
6029
- thinkingContentRef.current = thinkingContentRef.current + data.message;
6030
- messageToStoreRef.current.chatData.thinkingResponse.thinkingStream = newValue;
6031
- // Update the ThinkingIndicator components with the new accumulated content
6032
- // messageToStoreRef.current.chatData.thinkingResponse.thinkingContent = (
6033
- // <ThinkingIndicator
6034
- // thinkingContent={newValue}
6035
- // isStreaming={isStreaming}
6036
- // thinkDone={thinkDone}
6037
- // renderThinkingLoader={renderThinkingLoader}
6038
- // thinkingStarted={thinkingStarted}
6039
- // />
6040
- // );
6041
- dispatch(smartBotActions.setThinkingContext({
6042
- thinkingHeaderMessage: thinkingHeaderMessageRef.current,
6043
- thinkingContent: thinkingContentRef.current,
6044
- }));
6045
- setThinkingStarted(true);
6046
- setIsThinkingFromParent(false);
6047
- // botData.utilityObject.thinkingResponse.thinkingContent = (
6048
- // <ThinkingIndicator
6049
- // thinkingContent={newValue}
6050
- // isStreaming={isStreaming}
6051
- // thinkDone={thinkDone}
6052
- // renderThinkingLoader={renderThinkingLoader}
6053
- // thinkingStarted={thinkingStarted}
6054
- // />
6055
- // );
6092
+ const existingStepIndex = newSteps.findIndex((step) => step.header === newStep.header);
6093
+ if (existingStepIndex !== -1) {
6094
+ newSteps[existingStepIndex] = newStep;
6056
6095
  }
6057
- else if (data.status === "step") {
6058
- let newStep = data.widget_data[0];
6059
- const currentIntent = data.widget_data[0]?.current_intent;
6060
- setSteps([...steps, newStep]);
6061
- let newSteps = lodash.cloneDeep(stepRef.current);
6062
- if (newSteps.length === 1) {
6063
- newSteps.forEach((step) => {
6064
- step.step_status = "completed";
6065
- });
6096
+ else {
6097
+ newSteps.push(newStep);
6098
+ }
6099
+ stepRef.current = newSteps;
6100
+ setSteps(newSteps);
6101
+ if (currentIntent && questionsStepsMapRef.current[currentIntent]) {
6102
+ let intentSteps = lodash.cloneDeep(questionsStepsMapRef.current[currentIntent]);
6103
+ if (intentSteps.length === 1 && intentSteps[0].header === "Processing Request") {
6104
+ intentSteps[0].step_status = "completed";
6066
6105
  }
6067
- const existingStepIndex = newSteps.findIndex((step) => step.header === newStep.header);
6068
- if (existingStepIndex !== -1) {
6069
- newSteps[existingStepIndex] = newStep;
6106
+ const existingIdx = intentSteps.findIndex((s) => s.header === newStep.header);
6107
+ if (existingIdx !== -1) {
6108
+ intentSteps[existingIdx] = newStep;
6070
6109
  }
6071
6110
  else {
6072
- newSteps.push(newStep);
6073
- }
6074
- stepRef.current = newSteps;
6075
- setSteps(newSteps);
6076
- if (currentIntent && questionsStepsMapRef.current[currentIntent]) {
6077
- let intentSteps = lodash.cloneDeep(questionsStepsMapRef.current[currentIntent]);
6078
- if (intentSteps.length === 1 && intentSteps[0].header === "Processing Request") {
6079
- intentSteps[0].step_status = "completed";
6080
- }
6081
- const existingIdx = intentSteps.findIndex((s) => s.header === newStep.header);
6082
- if (existingIdx !== -1) {
6083
- intentSteps[existingIdx] = newStep;
6084
- }
6085
- else {
6086
- intentSteps.push(newStep);
6087
- }
6088
- questionsStepsMapRef.current[currentIntent] = intentSteps;
6089
- setQuestionsStepsMap(lodash.cloneDeep(questionsStepsMapRef.current));
6111
+ intentSteps.push(newStep);
6090
6112
  }
6091
- setStepChange((prev) => !prev);
6113
+ questionsStepsMapRef.current[currentIntent] = intentSteps;
6114
+ setQuestionsStepsMap(lodash.cloneDeep(questionsStepsMapRef.current));
6092
6115
  }
6093
- else if (data.message !== "[DONE]") {
6094
- setStepsDone(true);
6095
- // setTimeout(() => {
6096
- // setStepsDone(true);
6097
- // }, 5000);
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(smartBotActions.setThinkingContext({
6115
- thinkingHeaderMessage: `Thinking Completed`,
6116
- thinkingContent: thinkingContentRef.current,
6117
- }));
6118
- // dispatch(setThinkingContext({
6119
- // thinkingHeaderMessage: `Thought for ${formatThinkingTime(finalThinkingTime)}`,
6120
- // thinkingContent: thinkingContentRef.current,
6121
- // }));
6122
- // botData.utilityObject.thinkingResponse.thinkingHeading = (
6123
- // <ThinkinHeaderInfo
6124
- // thinkingHeaderMessage={`Thought for ${formatThinkingTime(finalThinkingTime)}`}
6125
- // />
6126
- // );
6127
- }
6128
- // if(thinkingStarted.current && !thinkDone){
6129
- // setThinkDone(true);
6130
- // thinkingStarted.current = false;
6131
- // }
6132
- setContent((prev) => {
6133
- return prev + data.message;
6134
- });
6135
- // if (currentMode === "agent") {
6136
- // setIsThinkingFromParent(false);
6137
- // botData.utilityObject.isThinking = false;
6138
- // botData.utilityObject.thinkingResponse.isThinking = false;
6139
- // const finalThinkingTime =
6140
- // thinkingTime || thinkingTimeFinalRef.current || 1;
6141
- // let thinkingTimeFinal = cloneDeep(finalThinkingTime);
6142
- // botData.utilityObject.thinkingResponse.thinkingHeading = (
6143
- // <ThinkinHeaderInfo
6144
- // thinkingHeaderMessage={`Thought for ${formatThinkingTime(thinkingTimeFinal)}`}
6145
- // />
6146
- // );
6147
- // }
6148
- // // End thinking when regular content starts
6149
- // if (isThinking && currentMode === "agent") {
6150
- // setIsThinking(false);
6151
- // if (!thinkDone) {
6152
- // const endTime = Date.now();
6153
- // const startTime =
6154
- // thinkingStartTimeRef.current || endTime - 1000; // Fallback to 1 second ago
6155
- // const duration = Math.round((endTime - startTime) / 1000);
6156
- // const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6157
- // setThinkingTime(finalThinkingTime);
6158
- // setShowThoughtDropdown(true);
6159
- // // Store thinking time in messageToStoreRef for persistence
6160
- // messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6161
- // thinkingTimeFinalRef.current = finalThinkingTime;
6162
- // }
6163
- // }
6164
- // if(!thinkDone && currentMode === "agent") {
6165
- // setThinkDone(true);
6166
- // setIsThinking(false);
6167
- // setTimeout(() => {
6168
- // // setThinkDone(true);
6169
- // const endTime = Date.now();
6170
- // const startTime =
6171
- // thinkingStartTimeRef.current || endTime - 1000; // Fallback to 1 second ago
6172
- // const duration = Math.round((endTime - startTime) / 1000);
6173
- // const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6174
- // setThinkingTime(finalThinkingTime);
6175
- // setShowThoughtDropdown(true);
6176
- // // Store thinking time in messageToStoreRef for persistence
6177
- // messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6178
- // thinkingTimeFinalRef.current = finalThinkingTime;
6179
- // setContent((prev) => {
6180
- // return prev + data.message;
6181
- // });
6182
- // }, 1000);
6183
- // }
6184
- // else {
6185
- // setContent((prev) => {
6186
- // return prev + data.message;
6187
- // });
6188
- // }
6189
- }
6190
- else {
6191
- setIsStreamingDone(true);
6116
+ setStepChange((prev) => !prev);
6117
+ }
6118
+ else if (data.message !== "[DONE]") {
6119
+ setStepsDone(true);
6120
+ if (!thinkingDoneRef?.current && currentMode === "agent") {
6121
+ thinkingDoneRef.current = true;
6122
+ setThinkDone(true);
6123
+ setIsThinkingFromParent(false);
6124
+ const endTime = Date.now();
6125
+ const startTime = thinkingStartTimeRef.current || endTime - 1000; // Fallback to 1 second ago
6126
+ const duration = Math.round((endTime - startTime) / 1000);
6127
+ const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6128
+ setThinkingTime(finalThinkingTime);
6129
+ setShowThoughtDropdown(true);
6130
+ setIsThinking(false);
6131
+ // Store thinking time in messageToStoreRef for persistence
6132
+ messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6133
+ thinkingTimeFinalRef.current = finalThinkingTime;
6134
+ setThinkingHeaderMessage(`Thought for ${formatThinkingTime(finalThinkingTime)}`);
6135
+ thinkingHeaderMessageRef.current = `Thought for ${formatThinkingTime(finalThinkingTime)}`;
6136
+ dispatch(smartBotActions.setThinkingContext({
6137
+ thinkingHeaderMessage: `Thinking Completed`,
6138
+ thinkingContent: thinkingContentRef.current,
6139
+ }));
6192
6140
  }
6141
+ setContent((prev) => {
6142
+ return prev + data.message;
6143
+ });
6144
+ }
6145
+ else {
6146
+ setIsStreamingDone(true);
6147
+ const doneState = streamStateMap.get(streamKey);
6148
+ if (doneState)
6149
+ doneState.completed = true;
6193
6150
  }
6194
6151
  }
6195
- catch (e) {
6196
- console.error("Error processing message:", e);
6197
- }
6198
- });
6199
- // Handle stream errors
6200
- sourceRef.current.addEventListener("error", (event) => {
6201
- console.error("Stream error:", event.reason);
6202
- setIsStreaming(false);
6203
- if (isThinking) {
6204
- setIsThinking(false);
6205
- const endTime = Date.now();
6206
- const duration = Math.round((endTime - thinkingStartTimeRef.current) / 1000);
6207
- const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6208
- setThinkingTime(finalThinkingTime);
6209
- setShowThoughtDropdown(true);
6210
- thinkingStartTimeRef.current = finalThinkingTime;
6211
- // Store thinking time in messageToStoreRef for persistence
6212
- messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6213
- }
6214
- });
6215
- // Handle stream closure
6216
- sourceRef.current.addEventListener("close", () => {
6217
- setIsStreaming(false);
6218
- });
6219
- }
6220
- catch (error) {
6221
- console.error("Error processing stream:", error);
6222
- setContent("Error streaming response. Please try again.");
6152
+ }
6153
+ catch (e) {
6154
+ console.error("Error processing message:", e);
6155
+ }
6156
+ });
6157
+ // Handle stream errors
6158
+ source.addEventListener("error", (event) => {
6159
+ const errState = streamStateMap.get(streamKey);
6160
+ if (!errState || errState.listenerGeneration !== generation)
6161
+ return;
6162
+ console.error("Stream error:", event.reason);
6223
6163
  setIsStreaming(false);
6164
+ errState.completed = true;
6224
6165
  if (isThinking) {
6225
6166
  setIsThinking(false);
6226
6167
  const endTime = Date.now();
@@ -6228,13 +6169,62 @@ const StreamedContent = ({ botData }) => {
6228
6169
  const finalThinkingTime = Math.max(duration, 1); // Ensure at least 1 second
6229
6170
  setThinkingTime(finalThinkingTime);
6230
6171
  setShowThoughtDropdown(true);
6172
+ thinkingStartTimeRef.current = finalThinkingTime;
6231
6173
  // Store thinking time in messageToStoreRef for persistence
6232
6174
  messageToStoreRef.current.chatData.thinkingResponse.thinkingTime = finalThinkingTime;
6233
6175
  }
6234
- }
6176
+ });
6177
+ // Handle stream closure
6178
+ source.addEventListener("close", () => {
6179
+ const closeState = streamStateMap.get(streamKey);
6180
+ if (!closeState || closeState.listenerGeneration !== generation)
6181
+ return;
6182
+ setIsStreaming(false);
6183
+ closeState.completed = true;
6184
+ });
6235
6185
  };
6186
+ /**
6187
+ * Effect to initialize streaming or restore state on tab-switch remount.
6188
+ * Uses module-level streamStateMap to detect remounts and avoid re-triggering the API.
6189
+ */
6236
6190
  React.useEffect(() => {
6237
- processStream();
6191
+ const mapState = streamStateMap.get(streamKey);
6192
+ if (mapState?.initiated) {
6193
+ // Remount after tab switch - restore state from persisted data
6194
+ const store = messageToStoreRef.current;
6195
+ // Restore accumulated content
6196
+ setContent(store.chatData?.response || "");
6197
+ // Restore thinking state
6198
+ if (store.chatData?.thinkingResponse?.thinkingStream) {
6199
+ thinkingContentRef.current = store.chatData.thinkingResponse.thinkingStream;
6200
+ }
6201
+ if (store.chatData?.thinkingResponse?.thinkingTime) {
6202
+ thinkingTimeFinalRef.current = store.chatData.thinkingResponse.thinkingTime;
6203
+ setThinkingTime(store.chatData.thinkingResponse.thinkingTime);
6204
+ }
6205
+ if (mapState.completed) {
6206
+ // Stream already finished while we were unmounted - trigger completion
6207
+ setIsStreaming(false);
6208
+ setIsStreamingDone(true);
6209
+ }
6210
+ else if (mapState.source) {
6211
+ // Stream still in progress - re-attach listeners to existing source
6212
+ sourceRef.current = mapState.source;
6213
+ setIsStreaming(true);
6214
+ attachListeners(mapState.source);
6215
+ }
6216
+ }
6217
+ else {
6218
+ // First mount - initialize Map entry and start streaming
6219
+ streamStateMap.set(streamKey, {
6220
+ messageStore: messageToStoreRef.current,
6221
+ source: null,
6222
+ initiated: false,
6223
+ completed: false,
6224
+ listenerGeneration: 0,
6225
+ });
6226
+ processStream();
6227
+ }
6238
6228
  }, []);
6239
6229
  /**
6240
6230
  * Effect to update chat data when streaming is complete
@@ -6403,6 +6393,8 @@ const StreamedContent = ({ botData }) => {
6403
6393
  questionsStepsMap: lodash.cloneDeep(questionsStepsMapRef.current),
6404
6394
  });
6405
6395
  }
6396
+ // Clean up module-level Map entry - stream is fully processed
6397
+ streamStateMap.delete(streamKey);
6406
6398
  // Trigger re-render by updating chatDataState
6407
6399
  setTimeout(() => {
6408
6400
  setChatDataState({ ...chatDataRef.current });
@@ -6434,6 +6426,11 @@ const StreamedContent = ({ botData }) => {
6434
6426
  sourceRef.current.close();
6435
6427
  setIsStreaming(false);
6436
6428
  setIsStreamingDone(true);
6429
+ // Delete module-level Map entry immediately on abort.
6430
+ // This ensures if the component unmounts before the isStreamingDone effect runs
6431
+ // (e.g. handleNewChatClick clears conversation), the next mount with the same
6432
+ // streamKey starts a fresh stream instead of restoring the aborted one.
6433
+ streamStateMap.delete(streamKey);
6437
6434
  // Stop the agent flow on the backend
6438
6435
  if (messageToStoreRef.current.sessionId) {
6439
6436
  chatbotServices.stopAgentFlow({ session_id: messageToStoreRef.current.sessionId }, baseUrl);
@@ -6477,13 +6474,13 @@ const StreamedContent = ({ botData }) => {
6477
6474
  };
6478
6475
  }, [isStreaming]);
6479
6476
  /**
6480
- * Cleanup function to abort streaming on unmount
6477
+ * Cleanup on unmount: Do NOT close the stream here.
6478
+ * The stream connection is kept alive in streamStateMap so it survives
6479
+ * tab-switch remounts. It is only closed by explicit abortStreaming() or new chat.
6481
6480
  */
6482
6481
  React.useEffect(() => {
6483
6482
  return () => {
6484
- if (sourceRef.current && isStreaming) {
6485
- sourceRef.current.close();
6486
- }
6483
+ // Intentionally not closing the stream - it persists in streamStateMap
6487
6484
  };
6488
6485
  }, []);
6489
6486
  /**