impact-chatbot 2.3.84 → 2.3.86

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
@@ -19,7 +19,7 @@ import remarkGfm from 'remark-gfm';
19
19
  import DOMPurify from 'dompurify';
20
20
  import { useNavigate, useLocation } from 'react-router-dom-v5-compat';
21
21
  import { setSelectedFilters, setFilterConfiguration, getFilterUserConfiguration } from 'core/actions/filterAction';
22
- import { setChatbotContext, setStepFormStreamData, setMinimizedStreamData, setThinkingContext, setPersistedFormValues, clearPersistedFormValues, setCurrentAgentChatId, setHierarchyKeyValue, setChatbotFilterOptions, setSavedFilterSets } from 'core/actions/smartBotActions';
22
+ import { setThinkingContext, setChatbotContext, setStepFormStreamData, setMinimizedStreamData, setPersistedFormValues, clearPersistedFormValues, setCurrentAgentChatId, setHierarchyKeyValue, setChatbotFilterOptions, setSavedFilterSets } from 'core/actions/smartBotActions';
23
23
  import RefreshIcon from '@mui/icons-material/Refresh';
24
24
  import styled from 'styled-components';
25
25
  import { CircularProgress, Typography, Grid } from '@mui/material';
@@ -653,8 +653,11 @@ const handleMessageLike = async (question, liked, setLoadingState, displaySnackM
653
653
  try {
654
654
  let request;
655
655
  if (currentMode === "agent") {
656
+ // Use the session stored on this specific message. The live sessionId is
657
+ // reset once a flow completes / awaits follow-up, so relying on it would
658
+ // send an empty session_id or the session of a newer chat.
656
659
  const agentPayload = {
657
- session_id: sessionId,
660
+ session_id: activeMessage?.chatSessionId || activeMessage?.sessionId || sessionId,
658
661
  liked,
659
662
  };
660
663
  request = await likeDislikeCommentForAgent(agentPayload, baseUrl);
@@ -1417,31 +1420,50 @@ const ThinkinHeaderInfo = (props) => {
1417
1420
  });
1418
1421
  const { streamStartTime, isStreamCompleted, finalElapsedSeconds, thinkingHeaderMessage } = thinkingContext;
1419
1422
  const [elapsed, setElapsed] = useState(0);
1423
+ const [frozenText, setFrozenText] = useState(null);
1420
1424
  const intervalRef = useRef(null);
1421
1425
  // Once this instance sees "completed", freeze it permanently so a future
1422
1426
  // chat's streamStartTime doesn't cause it to start ticking again.
1423
1427
  const frozenRef = useRef(false);
1424
1428
  const frozenTextRef = useRef(null);
1425
- const streamStartTimeRef = useRef(null); // Track streamStartTime changes to unfreeze
1429
+ // The streamStartTime this instance latched onto. Used to detect when a
1430
+ // different stream has taken over (e.g. the user asked a new question while
1431
+ // this message's step form was still waiting for input).
1432
+ const ownStartTimeRef = useRef(null);
1433
+ /**
1434
+ * Freezes this instance permanently on a final label and elapsed value.
1435
+ */
1436
+ const freeze = (text, seconds) => {
1437
+ frozenRef.current = true;
1438
+ frozenTextRef.current = text;
1439
+ setFrozenText(text);
1440
+ setElapsed(seconds);
1441
+ };
1426
1442
  useEffect(() => {
1427
1443
  // If already frozen (stream completed), NEVER unfreeze.
1428
1444
  // This prevents a new chat's streamStartTime from restarting this timer.
1429
1445
  if (frozenRef.current)
1430
1446
  return;
1431
- // Track streamStartTime changes for non-frozen instances
1432
- if (streamStartTimeRef.current !== streamStartTime) {
1433
- streamStartTimeRef.current = streamStartTime;
1447
+ // Adopt the first non-null streamStartTime we see as this instance's own
1448
+ if (ownStartTimeRef.current === null && streamStartTime) {
1449
+ ownStartTimeRef.current = streamStartTime;
1434
1450
  }
1451
+ const ownStartTime = ownStartTimeRef.current;
1435
1452
  // Clear any existing interval
1436
1453
  if (intervalRef.current) {
1437
1454
  clearInterval(intervalRef.current);
1438
1455
  intervalRef.current = null;
1439
1456
  }
1440
1457
  if (isStreamCompleted && typeof finalElapsedSeconds === 'number') {
1441
- // Freeze this instance it will never tick again
1442
- frozenRef.current = true;
1443
- frozenTextRef.current = `Completed in ${formatElapsedTime(finalElapsedSeconds)}`;
1444
- setElapsed(finalElapsedSeconds);
1458
+ // Freeze this instance - it will never tick again
1459
+ freeze(`Completed in ${formatElapsedTime(finalElapsedSeconds)}`, finalElapsedSeconds);
1460
+ }
1461
+ else if (ownStartTime !== null && streamStartTime !== ownStartTime) {
1462
+ // A different stream has started (new question) while this one never
1463
+ // completed - typically an abandoned step form. Freeze on this
1464
+ // instance's OWN elapsed time instead of following the new stream.
1465
+ const ownElapsed = Math.floor((Date.now() - ownStartTime) / 1000);
1466
+ freeze(`Completed in ${formatElapsedTime(ownElapsed)}`, ownElapsed);
1445
1467
  }
1446
1468
  else if (streamStartTime && !isStreamCompleted) {
1447
1469
  // Live timer — update every second
@@ -1462,8 +1484,8 @@ const ThinkinHeaderInfo = (props) => {
1462
1484
  }, [streamStartTime, isStreamCompleted, finalElapsedSeconds]);
1463
1485
  // Determine display text
1464
1486
  let displayText = thinkingHeaderMessage || '';
1465
- if (frozenRef.current && frozenTextRef.current) {
1466
- displayText = frozenTextRef.current;
1487
+ if (frozenText || (frozenRef.current && frozenTextRef.current)) {
1488
+ displayText = frozenText || frozenTextRef.current;
1467
1489
  }
1468
1490
  else if (streamStartTime && !isStreamCompleted) {
1469
1491
  displayText = `Working for ${formatElapsedTime(elapsed)}`;
@@ -2457,6 +2479,20 @@ const useChatFlow = (chatDataRef, setLoader, setFlowType, setScreenName, setUser
2457
2479
  }
2458
2480
  if (data.flow_type === "agent" && data.actionType === "direct") {
2459
2481
  data.baseUrl = baseUrl;
2482
+ // Reset the timer before the request goes out (same as index.jsx
2483
+ // handleSendMessage). Without this the new ThinkinHeaderInfo mounts while
2484
+ // the previous response's "completed" state is still in Redux and freezes
2485
+ // itself on that stale elapsed time.
2486
+ // streamStartTime stays null here: processStream sets it, so a question
2487
+ // has exactly one start time (two would make the new header look like a
2488
+ // superseded one and freeze it at 0m:00s).
2489
+ dispatch(setThinkingContext({
2490
+ thinkingContent: "",
2491
+ thinkingHeaderMessage: "Working for 0m:00s",
2492
+ streamStartTime: null,
2493
+ isStreamCompleted: false,
2494
+ finalElapsedSeconds: null,
2495
+ }));
2460
2496
  prepareDataAndSendToAgent(data, false, {
2461
2497
  chatbotContext: chatbotContext,
2462
2498
  setChatbotContext: setChatbotContext,
@@ -5315,6 +5351,13 @@ const sseevent = (message, messageToStoreRef) => {
5315
5351
  ? parsedData.chat_id
5316
5352
  : "";
5317
5353
  messageToStoreRef.current.sessionId = parsedData?.session_id;
5354
+ // Sticky copy of the session id this message belongs to. sessionId gets
5355
+ // cleared on completed/follow-up so the next user message starts a fresh
5356
+ // session, but message level actions (like/dislike) still need to refer
5357
+ // to the session that actually produced this response.
5358
+ if (parsedData?.session_id) {
5359
+ messageToStoreRef.current.chatSessionId = parsedData.session_id;
5360
+ }
5318
5361
  }
5319
5362
  if (messageToStoreRef.current.currentMode === "navigation" &&
5320
5363
  parsedData?.session_id) {
@@ -8663,7 +8706,7 @@ const StreamedContent = ({ botData, botProps }) => {
8663
8706
  const retryTimerRef = useRef(null); // Interval ref for countdown
8664
8707
  const retryCountdownRef = useRef(0); // Non-state countdown for interval callback
8665
8708
  const thinkingContentRef = useRef("");
8666
- useSelector((state) => {
8709
+ const thinkingContext = useSelector((state) => {
8667
8710
  return state.smartBotReducer.thinkingContext;
8668
8711
  });
8669
8712
  const stepRef = useRef(_isAbortedRemount
@@ -8686,7 +8729,10 @@ const StreamedContent = ({ botData, botProps }) => {
8686
8729
  const thinkingStartTimeRef = useRef(null);
8687
8730
  const thinkingTimeFinalRef = useRef(0);
8688
8731
  const thinkingHeaderMessageRef = useRef("Working for 0m:00s");
8689
- const streamStartTimeRef = useRef(null);
8732
+ // Inherit the in-flight start time so a remount (e.g. tab switch, where
8733
+ // processStream is not re-run) keeps the original one instead of resetting.
8734
+ // Null for a fresh question - processStream sets it.
8735
+ const streamStartTimeRef = useRef(thinkingContext?.isStreamCompleted ? null : thinkingContext?.streamStartTime || null);
8690
8736
  const thinkingDoneRef = useRef(false);
8691
8737
  const thinkingStartedRef = useRef(false);
8692
8738
  const setThinkingContentRef = useRef(setThinkingContent); // Store current setThinkingContent function
@@ -9661,7 +9707,12 @@ const StreamedContent = ({ botData, botProps }) => {
9661
9707
  },
9662
9708
  };
9663
9709
  const hasStepFormWidgets = !isEmpty(stepFormDataMapRef.current);
9664
- processResponse(response, botData.inputBody, currentMode, botData.utilityObject.customChatConfig, {
9710
+ // response.session_id is intentionally blank once the flow completes, so
9711
+ // keep the retained session separately and stamp it on the stored message.
9712
+ const messageChatSessionId = messageToStoreRef.current.chatSessionId ||
9713
+ messageToStoreRef.current.sessionId ||
9714
+ "";
9715
+ Promise.resolve(processResponse(response, botData.inputBody, currentMode, botData.utilityObject.customChatConfig, {
9665
9716
  newChatData: chatDataInfoRef,
9666
9717
  isTabEnabled: true,
9667
9718
  steps: stepRef.current.map(s => ({ ...s })),
@@ -9669,7 +9720,15 @@ const StreamedContent = ({ botData, botProps }) => {
9669
9720
  questions: [...questionsRef.current],
9670
9721
  questionsStepsMap: { ...questionsStepsMapRef.current },
9671
9722
  stepFormDataMap: { ...stepFormDataMapRef.current },
9672
- }, activeConversationId);
9723
+ }, activeConversationId)).then(() => {
9724
+ if (!messageChatSessionId)
9725
+ return;
9726
+ const storedMessages = chatDataInfoRef.current?.[currentMode]?.conversations?.[activeConversationId]?.messages;
9727
+ if (storedMessages?.length) {
9728
+ storedMessages[storedMessages.length - 1].chatSessionId =
9729
+ messageChatSessionId;
9730
+ }
9731
+ });
9673
9732
  // [
9674
9733
  // {
9675
9734
  // header: "Finding relevant information",
@@ -15833,10 +15892,14 @@ const SmartBot = (props) => {
15833
15892
  answerMode: answerMode,
15834
15893
  };
15835
15894
  // if(!isEmpty(userInput)) {
15895
+ // Clear the previous response's completed state so the incoming
15896
+ // ThinkinHeaderInfo doesn't freeze on it. streamStartTime stays null
15897
+ // here: processStream sets it, so a question has exactly one start
15898
+ // time (two would make the new header look like a superseded one).
15836
15899
  dispatch(setThinkingContext({
15837
15900
  thinkingContent: "",
15838
15901
  thinkingHeaderMessage: "Working for 0m:00s",
15839
- streamStartTime: Date.now(),
15902
+ streamStartTime: null,
15840
15903
  isStreamCompleted: false,
15841
15904
  finalElapsedSeconds: null,
15842
15905
  }));