impact-chatbot 2.3.85 → 2.3.87

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
@@ -1,7 +1,7 @@
1
1
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
2
  import { cloneDeep, isEmpty as isEmpty$1, isNull, isArray as isArray$1, isObject, throttle } from 'lodash';
3
3
  import * as React from 'react';
4
- import { useCallback, useState, useEffect, useRef, createContext, useContext, useMemo, Fragment as Fragment$1 } from 'react';
4
+ import { useCallback, useState, useEffect, useRef, Fragment as Fragment$1, createContext, useContext, useMemo } from 'react';
5
5
  import { COMBINED_CROSS_DIMENSIONAL_API, BASE_API } from 'config/api';
6
6
  import axiosInstance from 'core/Utils/axios';
7
7
  import { tenantConfigApiCache } from 'core/actions/tenantConfigActions';
@@ -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';
@@ -1420,31 +1420,50 @@ const ThinkinHeaderInfo = (props) => {
1420
1420
  });
1421
1421
  const { streamStartTime, isStreamCompleted, finalElapsedSeconds, thinkingHeaderMessage } = thinkingContext;
1422
1422
  const [elapsed, setElapsed] = useState(0);
1423
+ const [frozenText, setFrozenText] = useState(null);
1423
1424
  const intervalRef = useRef(null);
1424
1425
  // Once this instance sees "completed", freeze it permanently so a future
1425
1426
  // chat's streamStartTime doesn't cause it to start ticking again.
1426
1427
  const frozenRef = useRef(false);
1427
1428
  const frozenTextRef = useRef(null);
1428
- 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
+ };
1429
1442
  useEffect(() => {
1430
1443
  // If already frozen (stream completed), NEVER unfreeze.
1431
1444
  // This prevents a new chat's streamStartTime from restarting this timer.
1432
1445
  if (frozenRef.current)
1433
1446
  return;
1434
- // Track streamStartTime changes for non-frozen instances
1435
- if (streamStartTimeRef.current !== streamStartTime) {
1436
- 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;
1437
1450
  }
1451
+ const ownStartTime = ownStartTimeRef.current;
1438
1452
  // Clear any existing interval
1439
1453
  if (intervalRef.current) {
1440
1454
  clearInterval(intervalRef.current);
1441
1455
  intervalRef.current = null;
1442
1456
  }
1443
1457
  if (isStreamCompleted && typeof finalElapsedSeconds === 'number') {
1444
- // Freeze this instance it will never tick again
1445
- frozenRef.current = true;
1446
- frozenTextRef.current = `Completed in ${formatElapsedTime(finalElapsedSeconds)}`;
1447
- 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);
1448
1467
  }
1449
1468
  else if (streamStartTime && !isStreamCompleted) {
1450
1469
  // Live timer — update every second
@@ -1465,8 +1484,8 @@ const ThinkinHeaderInfo = (props) => {
1465
1484
  }, [streamStartTime, isStreamCompleted, finalElapsedSeconds]);
1466
1485
  // Determine display text
1467
1486
  let displayText = thinkingHeaderMessage || '';
1468
- if (frozenRef.current && frozenTextRef.current) {
1469
- displayText = frozenTextRef.current;
1487
+ if (frozenText || (frozenRef.current && frozenTextRef.current)) {
1488
+ displayText = frozenText || frozenTextRef.current;
1470
1489
  }
1471
1490
  else if (streamStartTime && !isStreamCompleted) {
1472
1491
  displayText = `Working for ${formatElapsedTime(elapsed)}`;
@@ -2460,6 +2479,20 @@ const useChatFlow = (chatDataRef, setLoader, setFlowType, setScreenName, setUser
2460
2479
  }
2461
2480
  if (data.flow_type === "agent" && data.actionType === "direct") {
2462
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
+ }));
2463
2496
  prepareDataAndSendToAgent(data, false, {
2464
2497
  chatbotContext: chatbotContext,
2465
2498
  setChatbotContext: setChatbotContext,
@@ -5095,17 +5128,26 @@ const TextContent = ({ bodyText, botData }) => {
5095
5128
  return (jsx("div", { children: renderTextContent() }));
5096
5129
  };
5097
5130
 
5098
- const ChipsContent = ({ bodyText, props }) => {
5131
+ const ChipsContent = ({ bodyText, props, botData, isFormDisabled = false }) => {
5099
5132
  const classes = useStyles$a();
5100
5133
  const globalClasses = globalStyles();
5101
- return (jsx("div", { className: `${globalClasses.flexRow} ${globalClasses.flexWrap} ${globalClasses.gap} ${globalClasses.verticalAlignCenter}`, children: bodyText.map((data, index) => {
5102
- const callBack = data.interactable && props
5103
- ? Object.entries(props).filter((entry) => entry[0] === data.actionName)?.[0]?.[1]
5104
- : null;
5105
- return (jsx(Typography, { component: "span", variant: "body1", className: `${classes.gptChips} ${data.interactable ? globalClasses.cursorPointer : ""}`, onClick: () => {
5106
- callBack && callBack(data);
5107
- }, children: data.displayText }, index));
5108
- }) }));
5134
+ // Suggested-question chips are one-shot: once the user has clicked one of them
5135
+ // or asked a new question the block disappears. isFormDisabled is true for
5136
+ // every bot message except the latest, so it flips exactly when the user
5137
+ // moves past these chips. Only chips that opt in via isQuestionsDisabled
5138
+ // behave this way; all other chips stay visible.
5139
+ if (botData?.isQuestionsDisabled && isFormDisabled) {
5140
+ return null;
5141
+ }
5142
+ const showHeaderTitle = !botData?.noShowHeaderTitle && Boolean(botData?.headerTitle?.length);
5143
+ return (jsxs(Fragment$1, { children: [showHeaderTitle && (jsx("div", { className: `${classes.chatbotText} ${classes.boldText} ${classes.combinedBlockHeaderTitle}`, children: botData.headerTitle })), jsx("div", { className: `${globalClasses.flexRow} ${globalClasses.flexWrap} ${globalClasses.gap} ${globalClasses.verticalAlignCenter}`, children: bodyText.map((data, index) => {
5144
+ const callBack = data.interactable && props
5145
+ ? Object.entries(props).filter((entry) => entry[0] === data.actionName)?.[0]?.[1]
5146
+ : null;
5147
+ return (jsx(Typography, { component: "span", variant: "body1", className: `${classes.gptChips} ${data.interactable ? globalClasses.cursorPointer : ""}`, onClick: () => {
5148
+ callBack && callBack(data);
5149
+ }, children: data.displayText }, index));
5150
+ }) })] }));
5109
5151
  };
5110
5152
 
5111
5153
  /**
@@ -6102,7 +6144,8 @@ const TableContent = ({ bodyText }) => {
6102
6144
  };
6103
6145
 
6104
6146
  const GraphContent = ({ bodyText }) => {
6105
- const { chartOptions } = bodyText || {};
6147
+ const { chartOptions, title } = bodyText || {};
6148
+ const classes = useStyles$a();
6106
6149
  const chartRef = useRef(null);
6107
6150
  const handleChartRef = (ref) => {
6108
6151
  chartRef.current = ref;
@@ -6113,7 +6156,7 @@ const GraphContent = ({ bodyText }) => {
6113
6156
  const options = {
6114
6157
  ...chartOptions,
6115
6158
  };
6116
- return (jsx(Grid, { container: true, children: jsx(Grid, { item: true, xs: 12, children: jsx(CoreChart, { options: options, handleChartRef: handleChartRef }) }) }));
6159
+ return (jsxs(Grid, { container: true, children: [Boolean(title?.length) && (jsx(Grid, { item: true, xs: 12, children: jsx("div", { className: `${classes.chatbotText} ${classes.boldText} ${classes.combinedBlockHeaderTitle}`, children: title }) })), jsx(Grid, { item: true, xs: 12, children: jsx(CoreChart, { options: options, handleChartRef: handleChartRef }) })] }));
6117
6160
  };
6118
6161
 
6119
6162
  const SelectableChips = ({ bodyText, chipType, props, utilityData }) => {
@@ -7501,7 +7544,6 @@ const STEP_FORM_TIMEOUT_KEY = "__stepFormTimedOut";
7501
7544
  */
7502
7545
  const StepFormContent = ({ formData, messageIndex = 0, isFormDisabled = false, showSavedFilters = true, preSelectedFilters = null, botProps = null, currentMode = "", agentId = "", sessionId = "", }) => {
7503
7546
  const dispatch = useDispatch();
7504
- const classes = useStyles$a();
7505
7547
  const savedFilterSets = useSelector((state) => state.smartBotReducer.savedFilterSets);
7506
7548
  const persistedFormValues = useSelector((state) => state.smartBotReducer.persistedFormValues);
7507
7549
  const chatbotContext = useSelector((state) => state.smartBotReducer.chatbotContext);
@@ -7774,13 +7816,13 @@ const StepFormContent = ({ formData, messageIndex = 0, isFormDisabled = false, s
7774
7816
  case "text":
7775
7817
  return jsx(TextContent, { bodyText: parsedData.bodyText, botData: parsedData }, key);
7776
7818
  case "chips":
7777
- return parsedData.isMultiSelect ? (jsx(SelectableChips, { bodyText: parsedData.bodyText, chipType: "selectable", utilityData: parsedData.utilityData, props: botProps }, key)) : (jsx(ChipsContent, { bodyText: parsedData.bodyText, props: botProps }, key));
7819
+ return parsedData.isMultiSelect ? (jsx(SelectableChips, { bodyText: parsedData.bodyText, chipType: "selectable", utilityData: parsedData.utilityData, props: botProps }, key)) : (jsx(ChipsContent, { bodyText: parsedData.bodyText, props: botProps, botData: parsedData, isFormDisabled: isFormDisabled }, key));
7778
7820
  case "questions":
7779
7821
  return jsx(QuestionsContent, { bodyText: parsedData.bodyText, props: botProps }, key);
7780
7822
  case "html":
7781
7823
  return jsx(HtmlContent, { bodyText: parsedData.bodyText }, key);
7782
7824
  case "table":
7783
- return jsx(TableContent, { bodyText: parsedData.bodyText }, key);
7825
+ return jsx(TableContent, { bodyText: parsedData.bodyText }, parsedData.bodyText?.table_name || key);
7784
7826
  case "graph":
7785
7827
  return jsx(GraphContent, { bodyText: parsedData.bodyText }, key);
7786
7828
  case "slider":
@@ -7815,17 +7857,11 @@ const StepFormContent = ({ formData, messageIndex = 0, isFormDisabled = false, s
7815
7857
  const body = renderItem(parsedData, index);
7816
7858
  if (!body)
7817
7859
  return;
7818
- // Skip the block header when it is just a repeat of the label the widget
7819
- // renders itself (parseResponse derives headerTitle from label for inputs).
7820
- const showHeaderTitle = !parsedData?.noShowHeaderTitle &&
7821
- Boolean(parsedData?.headerTitle?.length) &&
7822
- parsedData.headerTitle !== parsedData?.bodyText?.label;
7823
- const rendered = showHeaderTitle ? (jsxs(Fragment$1, { children: [jsx("div", { className: `${classes.chatbotText} ${classes.boldText} ${classes.combinedBlockHeaderTitle}`, children: parsedData.headerTitle }), body] }, `step-form-block-${index}`)) : (body);
7824
7860
  if (parsedData.bodyType === "button") {
7825
- buttonItems.push(rendered);
7861
+ buttonItems.push(body);
7826
7862
  }
7827
7863
  else {
7828
- formFields.push(rendered);
7864
+ formFields.push(body);
7829
7865
  }
7830
7866
  }
7831
7867
  catch (error) {
@@ -8335,7 +8371,10 @@ const renderWidgetItem = (item, index, isFormDisabled) => {
8335
8371
  case "text":
8336
8372
  return jsx(TextContent, { bodyText: parsedData.bodyText, botData: parsedData }, key);
8337
8373
  case "table":
8338
- return jsx(TableContent, { bodyText: parsedData.bodyText }, key);
8374
+ // Key on table_name so a different table never reuses a mounted grid.
8375
+ // ag-grid captures its fetch callback once at init, so a reused grid
8376
+ // would keep querying the previous table's table_name.
8377
+ return jsx(TableContent, { bodyText: parsedData.bodyText }, parsedData.bodyText?.table_name || key);
8339
8378
  case "graph":
8340
8379
  return jsx(GraphContent, { bodyText: parsedData.bodyText }, key);
8341
8380
  case "radio":
@@ -8673,7 +8712,7 @@ const StreamedContent = ({ botData, botProps }) => {
8673
8712
  const retryTimerRef = useRef(null); // Interval ref for countdown
8674
8713
  const retryCountdownRef = useRef(0); // Non-state countdown for interval callback
8675
8714
  const thinkingContentRef = useRef("");
8676
- useSelector((state) => {
8715
+ const thinkingContext = useSelector((state) => {
8677
8716
  return state.smartBotReducer.thinkingContext;
8678
8717
  });
8679
8718
  const stepRef = useRef(_isAbortedRemount
@@ -8696,7 +8735,10 @@ const StreamedContent = ({ botData, botProps }) => {
8696
8735
  const thinkingStartTimeRef = useRef(null);
8697
8736
  const thinkingTimeFinalRef = useRef(0);
8698
8737
  const thinkingHeaderMessageRef = useRef("Working for 0m:00s");
8699
- const streamStartTimeRef = useRef(null);
8738
+ // Inherit the in-flight start time so a remount (e.g. tab switch, where
8739
+ // processStream is not re-run) keeps the original one instead of resetting.
8740
+ // Null for a fresh question - processStream sets it.
8741
+ const streamStartTimeRef = useRef(thinkingContext?.isStreamCompleted ? null : thinkingContext?.streamStartTime || null);
8700
8742
  const thinkingDoneRef = useRef(false);
8701
8743
  const thinkingStartedRef = useRef(false);
8702
8744
  const setThinkingContentRef = useRef(setThinkingContent); // Store current setThinkingContent function
@@ -10281,7 +10323,6 @@ let instanceCounter = 0;
10281
10323
  let activeTabularInstanceId = null;
10282
10324
  const TabularContent = ({ steps: initialSteps, currentTabValue, children, questions: initialQuestions = [], questionsStepsMap: initialQuestionsStepsMap = {}, stepFormDataMap: initialStepFormDataMap = {}, isFormDisabled = false, sessionId: propSessionId = "", botProps = null, currentMode = "", agentId = "" }) => {
10283
10325
  const dispatch = useDispatch();
10284
- const chatClasses = useStyles$a();
10285
10326
  const stepFormStreamData = useSelector((state) => state.smartBotReducer.stepFormStreamData);
10286
10327
  const thinkingContext = useSelector((state) => state.smartBotReducer.thinkingContext);
10287
10328
  const streamStartTimeRef = useRef(thinkingContext?.streamStartTime);
@@ -10370,13 +10411,13 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
10370
10411
  case "text":
10371
10412
  return jsx(TextContent, { bodyText: parsedData.bodyText, botData: parsedData }, key);
10372
10413
  case "chips":
10373
- return parsedData.isMultiSelect ? (jsx(SelectableChips, { bodyText: parsedData.bodyText, chipType: "selectable", utilityData: parsedData.utilityData, props: botProps }, key)) : (jsx(ChipsContent, { bodyText: parsedData.bodyText, props: botProps }, key));
10414
+ return parsedData.isMultiSelect ? (jsx(SelectableChips, { bodyText: parsedData.bodyText, chipType: "selectable", utilityData: parsedData.utilityData, props: botProps }, key)) : (jsx(ChipsContent, { bodyText: parsedData.bodyText, props: botProps, botData: parsedData, isFormDisabled: isFormDisabled }, key));
10374
10415
  case "questions":
10375
10416
  return jsx(QuestionsContent, { bodyText: parsedData.bodyText, props: botProps }, key);
10376
10417
  case "image":
10377
10418
  return jsx(ImageContent, { bodyText: parsedData.bodyText }, key);
10378
10419
  case "table":
10379
- return jsx(TableContent, { bodyText: parsedData.bodyText }, key);
10420
+ return jsx(TableContent, { bodyText: parsedData.bodyText }, parsedData.bodyText?.table_name || key);
10380
10421
  case "graph":
10381
10422
  return jsx(GraphContent, { bodyText: parsedData.bodyText }, key);
10382
10423
  case "radio":
@@ -10407,13 +10448,7 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
10407
10448
  if (!parsedData)
10408
10449
  return null;
10409
10450
  const key = `restream-widget-${index}`;
10410
- const body = renderWidgetBody(parsedData, key);
10411
- if (!body)
10412
- return null;
10413
- const showHeaderTitle = !parsedData?.noShowHeaderTitle && Boolean(parsedData?.headerTitle?.length);
10414
- if (!showHeaderTitle)
10415
- return body;
10416
- return (jsxs(Fragment$1, { children: [jsx("div", { className: `${chatClasses.chatbotText} ${chatClasses.boldText} ${chatClasses.combinedBlockHeaderTitle}`, children: parsedData.headerTitle }), body] }, `restream-widget-block-${index}`));
10451
+ return renderWidgetBody(parsedData, key);
10417
10452
  }
10418
10453
  catch (e) {
10419
10454
  console.error("[TabularContent] renderWidget error:", e);
@@ -10777,19 +10812,10 @@ const resetActiveTabularInstance = () => {
10777
10812
  };
10778
10813
 
10779
10814
  const CombinedContent = ({ botData, props }) => {
10780
- const classes = useStyles$a();
10781
10815
  const isFormDisabled = botData?.isFormDisabled || false;
10782
10816
  const isTabEnabled = botData?.utilityData?.isTabEnabled;
10783
10817
  // Get the array of content items from bodyText
10784
10818
  const contentItems = Array.isArray(botData.bodyText) ? botData.bodyText : [];
10785
- // Renders the optional block-level header title above a content item.
10786
- const renderHeaderTitle = (parsedData) => {
10787
- const showHeaderTitle = !parsedData?.noShowHeaderTitle &&
10788
- Boolean(parsedData?.headerTitle?.length);
10789
- if (!showHeaderTitle)
10790
- return null;
10791
- return (jsx("div", { className: `${classes.chatbotText} ${classes.boldText} ${classes.combinedBlockHeaderTitle}`, children: parsedData.headerTitle }));
10792
- };
10793
10819
  // Function to render individual content based on its type
10794
10820
  const renderIndividualContent = (parsedData, index) => {
10795
10821
  const key = `combined-content-${index}`;
@@ -10801,12 +10827,12 @@ const CombinedContent = ({ botData, props }) => {
10801
10827
  return (jsx(SelectableChips, { bodyText: parsedData.bodyText, chipType: "selectable", utilityData: parsedData.utilityData, props: props }, key));
10802
10828
  }
10803
10829
  else {
10804
- return jsx(ChipsContent, { bodyText: parsedData.bodyText, props: props }, key);
10830
+ return (jsx(ChipsContent, { bodyText: parsedData.bodyText, props: props, botData: parsedData, isFormDisabled: isFormDisabled }, key));
10805
10831
  }
10806
10832
  case "questions":
10807
10833
  return jsx(QuestionsContent, { bodyText: parsedData.bodyText, props: props }, key);
10808
10834
  case "table":
10809
- return jsx(TableContent, { bodyText: parsedData.bodyText }, key);
10835
+ return jsx(TableContent, { bodyText: parsedData.bodyText }, parsedData.bodyText?.table_name || key);
10810
10836
  case "graph":
10811
10837
  return jsx(GraphContent, { bodyText: parsedData.bodyText }, key);
10812
10838
  case "slider":
@@ -10842,8 +10868,8 @@ const CombinedContent = ({ botData, props }) => {
10842
10868
  if (!parsedData) {
10843
10869
  return null;
10844
10870
  }
10845
- // Render the parsed content, preceded by the block's header title when present
10846
- return (jsxs(Fragment$1, { children: [renderHeaderTitle(parsedData), renderIndividualContent(parsedData, index)] }, `combined-block-${index}`));
10871
+ // Render the parsed content
10872
+ return renderIndividualContent(parsedData, index);
10847
10873
  }
10848
10874
  catch (error) {
10849
10875
  console.error(`Error parsing combined content item at index ${index}:`, error);
@@ -15856,10 +15882,14 @@ const SmartBot = (props) => {
15856
15882
  answerMode: answerMode,
15857
15883
  };
15858
15884
  // if(!isEmpty(userInput)) {
15885
+ // Clear the previous response's completed state so the incoming
15886
+ // ThinkinHeaderInfo doesn't freeze on it. streamStartTime stays null
15887
+ // here: processStream sets it, so a question has exactly one start
15888
+ // time (two would make the new header look like a superseded one).
15859
15889
  dispatch(setThinkingContext({
15860
15890
  thinkingContent: "",
15861
15891
  thinkingHeaderMessage: "Working for 0m:00s",
15862
- streamStartTime: Date.now(),
15892
+ streamStartTime: null,
15863
15893
  isStreamCompleted: false,
15864
15894
  finalElapsedSeconds: null,
15865
15895
  }));