impact-chatbot 2.3.82 → 2.3.84

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
@@ -17,9 +17,9 @@ import rehypeSanitize from 'rehype-sanitize';
17
17
  import remarkBreaks from 'remark-breaks';
18
18
  import remarkGfm from 'remark-gfm';
19
19
  import DOMPurify from 'dompurify';
20
+ import { useNavigate, useLocation } from 'react-router-dom-v5-compat';
20
21
  import { setSelectedFilters, setFilterConfiguration, getFilterUserConfiguration } from 'core/actions/filterAction';
21
22
  import { setChatbotContext, setStepFormStreamData, setMinimizedStreamData, setThinkingContext, setPersistedFormValues, clearPersistedFormValues, setCurrentAgentChatId, setHierarchyKeyValue, setChatbotFilterOptions, setSavedFilterSets } from 'core/actions/smartBotActions';
22
- import { useNavigate, useLocation } from 'react-router-dom-v5-compat';
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';
@@ -326,9 +326,9 @@ const parseResponse = (data, type, agentId = "", currentMode = "", disableTimeAn
326
326
  userType: "bot",
327
327
  userName: userName,
328
328
  bodyText: data?.response,
329
- headerTitle: data?.response_heading,
329
+ headerTitle: "",
330
330
  thinkingResponse: data?.thinkingResponse,
331
- noShowHeaderTitle: false,
331
+ noShowHeaderTitle: true,
332
332
  bodyType: "text",
333
333
  };
334
334
  case "stream":
@@ -592,7 +592,8 @@ const parseResponse = (data, type, agentId = "", currentMode = "", disableTimeAn
592
592
  timeStamp: timeString,
593
593
  userType: "bot",
594
594
  userName: userName,
595
- headerTitle: data?.response_heading || "",
595
+ headerTitle: "",
596
+ noShowHeaderTitle: true,
596
597
  bodyType: "image",
597
598
  bodyText: data?.image || data,
598
599
  };
@@ -1097,6 +1098,66 @@ const getFormattedApplicationName = (applicationURL) => {
1097
1098
  }
1098
1099
  };
1099
1100
 
1101
+ /**
1102
+ * Resolves a link href to an in-app route (path + search + hash) when the link
1103
+ * points to a screen of this application, otherwise returns null.
1104
+ * Handles both relative links ("/inventory-smart/create-allocation?step=0") and
1105
+ * absolute links sent by the backend
1106
+ * ("https://tapestry.test.impactsmartsuite.com/inventory-smart/create-allocation?step=0").
1107
+ */
1108
+ const resolveInternalPath = (href) => {
1109
+ if (!href || typeof href !== 'string')
1110
+ return null;
1111
+ // Non navigational protocols (mailto:, tel:, javascript:, #anchor) stay as-is
1112
+ if (/^(mailto:|tel:|javascript:)/i.test(href) || href.startsWith('#'))
1113
+ return null;
1114
+ try {
1115
+ const url = new URL(href, window.location.origin);
1116
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
1117
+ return null;
1118
+ const currentApp = window.location.pathname.split('/')[1];
1119
+ const targetApp = url.pathname.split('/')[1];
1120
+ // Same origin, or a different host of the same app (e.g. backend returns the
1121
+ // deployed host while running locally) - both resolve to a client side route
1122
+ const isInternal = url.origin === window.location.origin ||
1123
+ (!!currentApp && currentApp === targetApp);
1124
+ if (!isInternal)
1125
+ return null;
1126
+ return `${url.pathname}${url.search}${url.hash}`;
1127
+ }
1128
+ catch (e) {
1129
+ return null;
1130
+ }
1131
+ };
1132
+ /**
1133
+ * Minimizes the chat window so the user can see the screen they navigated to.
1134
+ * SmartBot (index.tsx) listens for this event and sets partialClose.
1135
+ */
1136
+ const minimizeChatBot = () => {
1137
+ window.dispatchEvent(new CustomEvent("smartBotMinimize"));
1138
+ };
1139
+ /**
1140
+ * Anchor rendered inside chatbot markdown. Internal links are navigated through
1141
+ * react-router so the chatbot is not remounted by a full page load.
1142
+ */
1143
+ const MarkdownLink = ({ href, children, ...props }) => {
1144
+ const navigate = useNavigate();
1145
+ const handleClick = (event) => {
1146
+ // Let the browser handle new tab / new window / download intents
1147
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0)
1148
+ return;
1149
+ const internalPath = resolveInternalPath(href);
1150
+ if (!internalPath)
1151
+ return;
1152
+ event.preventDefault();
1153
+ const currentPath = `${window.location.pathname}${window.location.search}`;
1154
+ if (currentPath !== internalPath) {
1155
+ navigate(internalPath);
1156
+ }
1157
+ minimizeChatBot();
1158
+ };
1159
+ return (jsx("a", { href: href, target: "_self", onClick: handleClick, ...props, children: children }));
1160
+ };
1100
1161
  /**
1101
1162
  * Checks whether the input string contains meaningful HTML markup
1102
1163
  * (beyond simple inline tags like <b>, <i>, <em>, <strong>, <br>, <a>, <u>
@@ -1137,9 +1198,16 @@ const preprocessMarkdown = (content) => {
1137
1198
  .replace(/(^|\n)([^\n-•]*[a-zA-Z0-9][^\n]*)\n([-•]\s+)/g, '$1$2\n\n$3')
1138
1199
  // Handle list items with - or • appearing directly after text without newline
1139
1200
  // Only treat as a new bullet when preceded by sentence-ending punctuation (to avoid splitting inline hyphens like "KSO- ORLANDO")
1140
- .replace(/([.!?:)\"])\s*([-•])\s+/g, '$1\n\n$2 ')
1141
- // Handle nested list items with proper indentation
1142
- .replace(/(\n\s{2,})([-•]|\d+\.)\s+/g, '\n $2 ')
1201
+ // [^\S\n] (horizontal whitespace only) keeps this from crossing line breaks,
1202
+ // which would dedent already correctly indented nested bullets
1203
+ .replace(/([.!?:)"])[^\S\n]*([-\u2022])[^\S\n]+/g, '$1\n\n$2 ')
1204
+ // Handle nested list items with proper indentation.
1205
+ // Matching only horizontal whitespace after the newline keeps blank lines and
1206
+ // top level numbered items intact (indenting those turns them into plain text).
1207
+ // Normalize to 3 spaces: enough to nest under both "- " and "1. " parents, and
1208
+ // below the 4 space threshold that would turn the line into a code block.
1209
+ // Indents of 4 or more are left untouched (deliberate deeper nesting).
1210
+ .replace(/(\n[^\S\n]{2,3})([-\u2022]|\d+\.)[^\S\n]+/g, '\n $2 ')
1143
1211
  // Ensure double line breaks after list sections
1144
1212
  .replace(/(\n\d+\.\s+.*?)(\n\n###)/g, '$1\n$2')
1145
1213
  .replace(/(\n[-•]\s+.*?)(\n\n###)/g, '$1\n$2')
@@ -1151,8 +1219,8 @@ const preprocessMarkdown = (content) => {
1151
1219
  * Custom components for markdown rendering
1152
1220
  */
1153
1221
  const markdownComponents = {
1154
- // Custom link component that opens in the same tab
1155
- a: ({ href, children, ...props }) => (jsx("a", { href: href, target: "_self", ...props, children: children })),
1222
+ // Custom link component that navigates in-app without reloading the page
1223
+ a: MarkdownLink,
1156
1224
  // Custom code component
1157
1225
  code: ({ children, className, ...props }) => (jsx("code", { className: `markdown-code ${className || ''}`, ...props, children: children })),
1158
1226
  // Custom pre component for code blocks
@@ -1172,13 +1240,32 @@ const markdownComponents = {
1172
1240
  * Markdown renderer component with sanitization
1173
1241
  */
1174
1242
  const TextRenderer = ({ text, thinking }) => {
1243
+ const navigate = useNavigate();
1244
+ // Click delegation for anchors inside raw HTML content (dangerouslySetInnerHTML),
1245
+ // so in-app links navigate through react-router instead of reloading the page.
1246
+ const handleHtmlClick = (event) => {
1247
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0)
1248
+ return;
1249
+ const anchor = event.target.closest?.('a');
1250
+ if (!anchor || anchor.target === '_blank')
1251
+ return;
1252
+ const internalPath = resolveInternalPath(anchor.getAttribute('href'));
1253
+ if (!internalPath)
1254
+ return;
1255
+ event.preventDefault();
1256
+ const currentPath = `${window.location.pathname}${window.location.search}`;
1257
+ if (currentPath !== internalPath) {
1258
+ navigate(internalPath);
1259
+ }
1260
+ minimizeChatBot();
1261
+ };
1175
1262
  // If the input contains rich HTML, render it directly with DOMPurify sanitization
1176
1263
  if (containsRichHtml(text)) {
1177
1264
  const sanitizedHtml = DOMPurify.sanitize(text, {
1178
1265
  ADD_TAGS: ['style'],
1179
1266
  ADD_ATTR: ['style', 'class', 'target', 'rel'],
1180
1267
  });
1181
- return (jsx("div", { className: `markdown-content md-content ${thinking ? "thinking" : ""}`, dangerouslySetInnerHTML: { __html: sanitizedHtml } }));
1268
+ return (jsx("div", { className: `markdown-content md-content ${thinking ? "thinking" : ""}`, onClick: handleHtmlClick, dangerouslySetInnerHTML: { __html: sanitizedHtml } }));
1182
1269
  }
1183
1270
  // Otherwise, use the existing markdown pipeline
1184
1271
  const processedContent = preprocessMarkdown(text);
@@ -5009,7 +5096,7 @@ const ChipsContent = ({ bodyText, props }) => {
5009
5096
  const classes = useStyles$a();
5010
5097
  const globalClasses = globalStyles();
5011
5098
  return (jsx("div", { className: `${globalClasses.flexRow} ${globalClasses.flexWrap} ${globalClasses.gap} ${globalClasses.verticalAlignCenter}`, children: bodyText.map((data, index) => {
5012
- const callBack = data.interactable
5099
+ const callBack = data.interactable && props
5013
5100
  ? Object.entries(props).filter((entry) => entry[0] === data.actionName)?.[0]?.[1]
5014
5101
  : null;
5015
5102
  return (jsx(Typography, { component: "span", variant: "body1", className: `${classes.gptChips} ${data.interactable ? globalClasses.cursorPointer : ""}`, onClick: () => {
@@ -5076,7 +5163,7 @@ const QuestionsContent = ({ bodyText, props }) => {
5076
5163
  };
5077
5164
  };
5078
5165
  return (jsx("div", { className: `${globalClasses.flexRow} ${globalClasses.flexColumn} ${classes.gptQuestionContainer}`, children: bodyText.map((data, index) => {
5079
- const callBack = data.interactable
5166
+ const callBack = data.interactable && props
5080
5167
  ? Object.entries(props).filter((entry) => entry[0] === data.actionName)?.[0]?.[1]
5081
5168
  : null;
5082
5169
  return (jsxs("div", { className: `${globalClasses.layoutAlignStart} ${classes.gptQuestionBlock}`, style: {
@@ -6827,6 +6914,36 @@ const fetchCrossFilterOptions = async (filterConfig, existingSelections = [], al
6827
6914
  }
6828
6915
  };
6829
6916
 
6917
+ const useStyles$6 = makeStyles(() => ({
6918
+ htmlContentContainer: {
6919
+ width: "100%",
6920
+ "& *": {
6921
+ boxSizing: "border-box",
6922
+ },
6923
+ },
6924
+ }));
6925
+ const HtmlContent = ({ bodyText }) => {
6926
+ const classes = useStyles$6();
6927
+ const containerRef = useRef(null);
6928
+ const content = bodyText?.content || "";
6929
+ useEffect(() => {
6930
+ if (containerRef.current && content) {
6931
+ const sanitizedHtml = DOMPurify.sanitize(content, {
6932
+ ADD_TAGS: ["style", "details", "summary"],
6933
+ ADD_ATTR: ["open", "target", "rel", "class", "style"],
6934
+ ALLOW_DATA_ATTR: true,
6935
+ FORCE_BODY: true,
6936
+ WHOLE_DOCUMENT: false,
6937
+ });
6938
+ containerRef.current.innerHTML = sanitizedHtml;
6939
+ }
6940
+ }, [content]);
6941
+ if (!content) {
6942
+ return null;
6943
+ }
6944
+ return (jsx("div", { ref: containerRef, className: classes.htmlContentContainer }));
6945
+ };
6946
+
6830
6947
  const SliderContent = ({ bodyText, isFormDisabled = false, messageIndex }) => {
6831
6948
  const formKey = `${messageIndex}_${bodyText?.paramName}`;
6832
6949
  const { header, headerOrentiation, inputPosition, label, max, min, required, disabled, } = bodyText;
@@ -7372,8 +7489,9 @@ const STEP_FORM_TIMEOUT_KEY = "__stepFormTimedOut";
7372
7489
  * @param {Array} props.formData - Array of raw widget_data items from step_form chunk
7373
7490
  * @param {number} props.messageIndex - Index for form state persistence keys
7374
7491
  */
7375
- const StepFormContent = ({ formData, messageIndex = 0, isFormDisabled = false, showSavedFilters = true, preSelectedFilters = null }) => {
7492
+ const StepFormContent = ({ formData, messageIndex = 0, isFormDisabled = false, showSavedFilters = true, preSelectedFilters = null, botProps = null, currentMode = "", agentId = "", sessionId = "", }) => {
7376
7493
  const dispatch = useDispatch();
7494
+ const classes = useStyles$a();
7377
7495
  const savedFilterSets = useSelector((state) => state.smartBotReducer.savedFilterSets);
7378
7496
  const persistedFormValues = useSelector((state) => state.smartBotReducer.persistedFormValues);
7379
7497
  const chatbotContext = useSelector((state) => state.smartBotReducer.chatbotContext);
@@ -7646,7 +7764,11 @@ const StepFormContent = ({ formData, messageIndex = 0, isFormDisabled = false, s
7646
7764
  case "text":
7647
7765
  return jsx(TextContent, { bodyText: parsedData.bodyText, botData: parsedData }, key);
7648
7766
  case "chips":
7649
- return jsx(ChipsContent, { bodyText: parsedData.bodyText, props: {} }, key);
7767
+ return parsedData.isMultiSelect ? (jsx(SelectableChips, { bodyText: parsedData.bodyText, chipType: "selectable", utilityData: parsedData.utilityData, props: botProps }, key)) : (jsx(ChipsContent, { bodyText: parsedData.bodyText, props: botProps }, key));
7768
+ case "questions":
7769
+ return jsx(QuestionsContent, { bodyText: parsedData.bodyText, props: botProps }, key);
7770
+ case "html":
7771
+ return jsx(HtmlContent, { bodyText: parsedData.bodyText }, key);
7650
7772
  case "table":
7651
7773
  return jsx(TableContent, { bodyText: parsedData.bodyText }, key);
7652
7774
  case "graph":
@@ -7677,12 +7799,18 @@ const StepFormContent = ({ formData, messageIndex = 0, isFormDisabled = false, s
7677
7799
  const buttonItems = [];
7678
7800
  formData.forEach((item, index) => {
7679
7801
  try {
7680
- const parsedData = parseResponse(item, item.type, "", "", true);
7802
+ const parsedData = parseResponse(item, item.type, agentId, currentMode, true, sessionId);
7681
7803
  if (!parsedData)
7682
7804
  return;
7683
- const rendered = renderItem(parsedData, index);
7684
- if (!rendered)
7805
+ const body = renderItem(parsedData, index);
7806
+ if (!body)
7685
7807
  return;
7808
+ // Skip the block header when it is just a repeat of the label the widget
7809
+ // renders itself (parseResponse derives headerTitle from label for inputs).
7810
+ const showHeaderTitle = !parsedData?.noShowHeaderTitle &&
7811
+ Boolean(parsedData?.headerTitle?.length) &&
7812
+ parsedData.headerTitle !== parsedData?.bodyText?.label;
7813
+ 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);
7686
7814
  if (parsedData.bodyType === "button") {
7687
7815
  buttonItems.push(rendered);
7688
7816
  }
@@ -7705,7 +7833,7 @@ const resetStepFormTimeoutFlag = () => {
7705
7833
  sessionStorage.removeItem(STEP_FORM_TIMEOUT_KEY);
7706
7834
  };
7707
7835
 
7708
- const useStyles$6 = makeStyles(() => ({
7836
+ const useStyles$5 = makeStyles(() => ({
7709
7837
  subStepMarkdown: {
7710
7838
  // Inherit font styles from parent (.progressSubItem)
7711
7839
  fontFamily: 'inherit',
@@ -7779,14 +7907,14 @@ const preprocessSubStepMarkdown = (content) => {
7779
7907
  .trim();
7780
7908
  };
7781
7909
  const SubStepRenderer = ({ text }) => {
7782
- const classes = useStyles$6();
7910
+ const classes = useStyles$5();
7783
7911
  if (!text)
7784
7912
  return null;
7785
7913
  const processed = preprocessSubStepMarkdown(text);
7786
7914
  return (jsx("div", { className: classes.subStepMarkdown, children: jsx(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], children: processed }) }));
7787
7915
  };
7788
7916
 
7789
- const useStyles$5 = makeStyles$1((theme) => ({
7917
+ const useStyles$4 = makeStyles$1((theme) => ({
7790
7918
  "@global": {
7791
7919
  "@keyframes progressDotPulse": {
7792
7920
  "0%": {
@@ -8060,7 +8188,7 @@ const getQuestionStatus$1 = (questionSteps) => {
8060
8188
  /**
8061
8189
  * Renders a single progress bar item (main point + sub-items)
8062
8190
  */
8063
- const ProgressBarItem$1 = ({ question, questionSteps, isLast, classes, formData, showSavedFilters = true, preSelectedFilters = null, isFormDisabled, onAllSubItemsAnimated = undefined }) => {
8191
+ const ProgressBarItem$1 = ({ question, questionSteps, isLast, classes, formData, showSavedFilters = true, preSelectedFilters = null, isFormDisabled, onAllSubItemsAnimated = undefined, botProps = null, currentMode = "", agentId = "", sessionId = "" }) => {
8064
8192
  const status = getQuestionStatus$1(questionSteps);
8065
8193
  const animatedCountRef = useRef(0);
8066
8194
  const [isExpanded, setIsExpanded] = useState(true);
@@ -8094,10 +8222,10 @@ const ProgressBarItem$1 = ({ question, questionSteps, isLast, classes, formData,
8094
8222
  if (animatedCountRef.current >= arr.length && onAllSubItemsAnimated) {
8095
8223
  onAllSubItemsAnimated();
8096
8224
  }
8097
- } }) }, idx))) })), formData && isExpanded && (jsx("div", { className: classes.stepFormContainer, children: jsx(StepFormContent, { formData: formData, isFormDisabled: isFormDisabled, showSavedFilters: showSavedFilters, preSelectedFilters: preSelectedFilters }) }))] })] }));
8225
+ } }) }, idx))) })), formData && isExpanded && (jsx("div", { className: classes.stepFormContainer, children: jsx(StepFormContent, { formData: formData, isFormDisabled: isFormDisabled, showSavedFilters: showSavedFilters, preSelectedFilters: preSelectedFilters, botProps: botProps, currentMode: currentMode, agentId: agentId, sessionId: sessionId }) }))] })] }));
8098
8226
  };
8099
- const Steps$1 = ({ steps, setSteps, done, setTabValue, setDone, finalStepDone, setFinalStepDone, stepChange, currentMode, questions = [], questionsStepsMap = {}, stepFormDataMap = {}, isFormDisabled = false, }) => {
8100
- const classes = useStyles$5();
8227
+ const Steps$1 = ({ steps, setSteps, done, setTabValue, setDone, finalStepDone, setFinalStepDone, stepChange, currentMode, questions = [], questionsStepsMap = {}, stepFormDataMap = {}, isFormDisabled = false, botProps = null, agentId = "", sessionId = "", }) => {
8228
+ const classes = useStyles$4();
8101
8229
  useState(false);
8102
8230
  const [showThinking, setShowThinking] = useState(false);
8103
8231
  // Determine if last question is fully completed (all sub-steps done)
@@ -8179,7 +8307,7 @@ const Steps$1 = ({ steps, setSteps, done, setTabValue, setDone, finalStepDone, s
8179
8307
  const showSavedFilters = formEntry && !Array.isArray(formEntry) ? formEntry.showSavedFilters : true;
8180
8308
  const preSelectedFilters = formEntry && !Array.isArray(formEntry) ? formEntry.preSelectedFilters : null;
8181
8309
  const isLast = index === questions.length - 1;
8182
- return (jsx(ProgressBarItem$1, { question: question, questionSteps: questionSteps, isLast: isLast && !showThinking, classes: classes, formData: formData, isFormDisabled: isFormDisabled, showSavedFilters: showSavedFilters, preSelectedFilters: preSelectedFilters, onAllSubItemsAnimated: isLast && lastQuestionCompleted && !done
8310
+ return (jsx(ProgressBarItem$1, { question: question, questionSteps: questionSteps, isLast: isLast && !showThinking, classes: classes, formData: formData, isFormDisabled: isFormDisabled, showSavedFilters: showSavedFilters, preSelectedFilters: preSelectedFilters, botProps: botProps, currentMode: currentMode, agentId: agentId, sessionId: sessionId, onAllSubItemsAnimated: isLast && lastQuestionCompleted && !done
8183
8311
  ? () => setShowThinking(true)
8184
8312
  : undefined }, index));
8185
8313
  }), showThinking && !done && (jsx("div", { style: {
@@ -8187,36 +8315,6 @@ const Steps$1 = ({ steps, setSteps, done, setTabValue, setDone, finalStepDone, s
8187
8315
  }, children: jsx(ProgressBarItem$1, { question: "Thinking", questionSteps: [{ header: "", sub_header: "", step_status: "not-completed" }], isLast: true, classes: classes, formData: null, isFormDisabled: isFormDisabled }) }))] }));
8188
8316
  };
8189
8317
 
8190
- const useStyles$4 = makeStyles(() => ({
8191
- htmlContentContainer: {
8192
- width: "100%",
8193
- "& *": {
8194
- boxSizing: "border-box",
8195
- },
8196
- },
8197
- }));
8198
- const HtmlContent = ({ bodyText }) => {
8199
- const classes = useStyles$4();
8200
- const containerRef = useRef(null);
8201
- const content = bodyText?.content || "";
8202
- useEffect(() => {
8203
- if (containerRef.current && content) {
8204
- const sanitizedHtml = DOMPurify.sanitize(content, {
8205
- ADD_TAGS: ["style", "details", "summary"],
8206
- ADD_ATTR: ["open", "target", "rel", "class", "style"],
8207
- ALLOW_DATA_ATTR: true,
8208
- FORCE_BODY: true,
8209
- WHOLE_DOCUMENT: false,
8210
- });
8211
- containerRef.current.innerHTML = sanitizedHtml;
8212
- }
8213
- }, [content]);
8214
- if (!content) {
8215
- return null;
8216
- }
8217
- return (jsx("div", { ref: containerRef, className: classes.htmlContentContainer }));
8218
- };
8219
-
8220
8318
  const renderWidgetItem = (item, index, isFormDisabled) => {
8221
8319
  try {
8222
8320
  const parsedData = parseResponse(item, item.type, "", "", true);
@@ -8268,7 +8366,7 @@ const AgentResponse$1 = (props) => {
8268
8366
  };
8269
8367
 
8270
8368
  const StepsResponseTab = (props) => {
8271
- const { steps, setSteps, stepsDone, setStepsDone, finalStepDone, setFinalStepDone, content, isStreaming, stepChange, currentMode, questions, questionsStepsMap, stepFormDataMap, isFormDisabled, streamingWidgetData, isRetrying, } = props;
8369
+ const { steps, setSteps, stepsDone, setStepsDone, finalStepDone, setFinalStepDone, content, isStreaming, stepChange, currentMode, questions, questionsStepsMap, stepFormDataMap, isFormDisabled, streamingWidgetData, isRetrying, botProps, agentId, sessionId, } = props;
8272
8370
  const dispatch = useDispatch();
8273
8371
  const thinkingContext = useSelector((state) => state.smartBotReducer.thinkingContext);
8274
8372
  const streamStartTimeRef = useRef(thinkingContext?.streamStartTime);
@@ -8332,7 +8430,7 @@ const StepsResponseTab = (props) => {
8332
8430
  icon: jsx(PsychologyOutlinedIcon, { fontSize: "large" }),
8333
8431
  },
8334
8432
  ], tabPanels: [
8335
- jsx(Steps$1, { steps: steps, setSteps: setSteps, done: stepsDone, setDone: setStepsDone, setTabValue: setTabValue, finalStepDone: finalStepDone, setFinalStepDone: setFinalStepDone, stepChange: stepChange, currentMode: currentMode, questions: questions, questionsStepsMap: questionsStepsMap, stepFormDataMap: stepFormDataMap, isFormDisabled: isFormDisabled || isTimedOut }),
8433
+ jsx(Steps$1, { steps: steps, setSteps: setSteps, done: stepsDone, setDone: setStepsDone, setTabValue: setTabValue, finalStepDone: finalStepDone, setFinalStepDone: setFinalStepDone, stepChange: stepChange, currentMode: currentMode, questions: questions, questionsStepsMap: questionsStepsMap, stepFormDataMap: stepFormDataMap, isFormDisabled: isFormDisabled || isTimedOut, botProps: botProps, agentId: agentId, sessionId: sessionId }),
8336
8434
  jsxs(Fragment, { children: [jsx(AgentResponse$1, { content: content, isStreaming: isStreaming, streamingWidgetData: streamingWidgetData, isFormDisabled: isFormDisabled || isTimedOut }), timeoutMessage && (jsx("div", { style: { marginTop: "8px", padding: "0 8px" }, children: jsx(TextRenderer, { text: timeoutMessage, thinking: "" }) }))] }),
8337
8435
  ], value: tabValue }) }));
8338
8436
  };
@@ -8514,7 +8612,7 @@ const streamStateMap = new Map();
8514
8612
  * @param {React.RefObject} botData.utilityObject.chatBodyRef - Ref to chat body element
8515
8613
  * @param {Function} botData.utilityObject.setChatDataState - Function to update chat data state
8516
8614
  */
8517
- const StreamedContent = ({ botData }) => {
8615
+ const StreamedContent = ({ botData, botProps }) => {
8518
8616
  const { activeConversationId, currentMode, chatDataRef, chatBodyRef, setChatDataState, chatDataInfoRef, setLoader = (params) => { }, processResponse = (params) => { }, setThinkingContent, thinkingContent, isThinking: isThinkingFromParent, setIsThinking: setIsThinkingFromParent, chatId, setChatId, isStop, setIsStop, functionsRef, functionsState, setFunctionsState, thinkingHeaderMessage, setThinkingHeaderMessage, baseUrl, setNavSessionId } = botData.utilityObject || {};
8519
8617
  const classes = useStyles$7();
8520
8618
  useStyles$a();
@@ -9693,8 +9791,13 @@ const StreamedContent = ({ botData }) => {
9693
9791
  }, 1000);
9694
9792
  }
9695
9793
  }, [isStreamingDone, thinkingTime, activeConversationId]);
9794
+ // Show/hide the stop (cancel execution) icon while the stream is active.
9795
+ // Gate on state only - sourceRef is a ref, so mutating it never re-runs this
9796
+ // effect. Relying on it meant that if the source wasn't assigned yet on the
9797
+ // first run, isStop was set to false and never re-evaluated for the rest of
9798
+ // the stream, leaving the cancel button hidden.
9696
9799
  useEffect(() => {
9697
- if (sourceRef.current && isStreaming) {
9800
+ if (isStreaming && !isStreamingDone) {
9698
9801
  setIsStop(true);
9699
9802
  setFunctionsState({
9700
9803
  ...functionsState,
@@ -9705,7 +9808,7 @@ const StreamedContent = ({ botData }) => {
9705
9808
  else {
9706
9809
  setIsStop(false);
9707
9810
  }
9708
- }, [isStreaming]);
9811
+ }, [isStreaming, isStreamingDone]);
9709
9812
  /**
9710
9813
  * Aborts the current streaming connection
9711
9814
  */
@@ -9718,10 +9821,12 @@ const StreamedContent = ({ botData }) => {
9718
9821
  setRetryCountdown(0);
9719
9822
  }
9720
9823
  retryCountRef.current = MAX_RETRY_COUNT; // Exhaust retries so no further auto-retries happen
9721
- if (sourceRef.current && isStreaming) {
9824
+ if (isStreaming) {
9722
9825
  setWasStreamingAborted(true);
9723
9826
  wasStreamingAbortedRef.current = true;
9724
- sourceRef.current.close();
9827
+ if (sourceRef.current) {
9828
+ sourceRef.current.close();
9829
+ }
9725
9830
  setIsStreaming(false);
9726
9831
  setIsStreamingDone(true);
9727
9832
  // Mark as completed+aborted so tab-switch remounts don't restart the stream.
@@ -9881,7 +9986,7 @@ const StreamedContent = ({ botData }) => {
9881
9986
  * @returns {JSX.Element} Rendered content with optional blinking cursor
9882
9987
  */
9883
9988
  const renderContent = () => {
9884
- 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: jsx(TextRenderer, { text: `Auto re-trying in ${retryCountdown}s`, thinking: "" }) }))] }));
9989
+ 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, botProps: botProps, agentId: botData?.inputBody?.agent_id || "", sessionId: messageToStoreRef.current?.sessionId || "" }), isRetrying && (jsx("div", { className: classes.retryContainer, children: jsx(TextRenderer, { text: `Auto re-trying in ${retryCountdown}s`, thinking: "" }) }))] }));
9885
9990
  };
9886
9991
  if (currentMode === "agent") {
9887
9992
  return renderContent();
@@ -10151,8 +10256,9 @@ const AgentResponse = ({ children }) => {
10151
10256
  // Only the active instance should process stepFormStreamData from Redux.
10152
10257
  let instanceCounter = 0;
10153
10258
  let activeTabularInstanceId = null;
10154
- const TabularContent = ({ steps: initialSteps, currentTabValue, children, questions: initialQuestions = [], questionsStepsMap: initialQuestionsStepsMap = {}, stepFormDataMap: initialStepFormDataMap = {}, isFormDisabled = false, sessionId: propSessionId = "" }) => {
10259
+ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questions: initialQuestions = [], questionsStepsMap: initialQuestionsStepsMap = {}, stepFormDataMap: initialStepFormDataMap = {}, isFormDisabled = false, sessionId: propSessionId = "", botProps = null, currentMode = "", agentId = "" }) => {
10155
10260
  const dispatch = useDispatch();
10261
+ const chatClasses = useStyles$a();
10156
10262
  const stepFormStreamData = useSelector((state) => state.smartBotReducer.stepFormStreamData);
10157
10263
  const thinkingContext = useSelector((state) => state.smartBotReducer.thinkingContext);
10158
10264
  const streamStartTimeRef = useRef(thinkingContext?.streamStartTime);
@@ -10236,40 +10342,55 @@ const TabularContent = ({ steps: initialSteps, currentTabValue, children, questi
10236
10342
  };
10237
10343
  }, []);
10238
10344
  // Render a widget item from SSE data
10345
+ const renderWidgetBody = (parsedData, key) => {
10346
+ switch (parsedData.bodyType) {
10347
+ case "text":
10348
+ return jsx(TextContent, { bodyText: parsedData.bodyText, botData: parsedData }, key);
10349
+ case "chips":
10350
+ return parsedData.isMultiSelect ? (jsx(SelectableChips, { bodyText: parsedData.bodyText, chipType: "selectable", utilityData: parsedData.utilityData, props: botProps }, key)) : (jsx(ChipsContent, { bodyText: parsedData.bodyText, props: botProps }, key));
10351
+ case "questions":
10352
+ return jsx(QuestionsContent, { bodyText: parsedData.bodyText, props: botProps }, key);
10353
+ case "image":
10354
+ return jsx(ImageContent, { bodyText: parsedData.bodyText }, key);
10355
+ case "table":
10356
+ return jsx(TableContent, { bodyText: parsedData.bodyText }, key);
10357
+ case "graph":
10358
+ return jsx(GraphContent, { bodyText: parsedData.bodyText }, key);
10359
+ case "radio":
10360
+ return jsx(RadioContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10361
+ case "checkbox":
10362
+ return jsx(CheckboxContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10363
+ case "select":
10364
+ return jsx(SelectContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10365
+ case "slider":
10366
+ return jsx(SliderContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10367
+ case "button":
10368
+ return jsx(ButtonContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10369
+ case "input":
10370
+ return jsx(InputContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10371
+ case "datePicker":
10372
+ return jsx(DatePickerContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10373
+ case "dateRangePicker":
10374
+ return jsx(DateRangePickerContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10375
+ case "html":
10376
+ return jsx(HtmlContent, { bodyText: parsedData.bodyText }, key);
10377
+ default:
10378
+ return null;
10379
+ }
10380
+ };
10239
10381
  const renderWidget = (item, index) => {
10240
10382
  try {
10241
- const parsedData = parseResponse(item, item.type, "", "", true);
10383
+ const parsedData = parseResponse(item, item.type, agentId, currentMode, true, propSessionId);
10242
10384
  if (!parsedData)
10243
10385
  return null;
10244
10386
  const key = `restream-widget-${index}`;
10245
- switch (parsedData.bodyType) {
10246
- case "text":
10247
- return jsx(TextContent, { bodyText: parsedData.bodyText, botData: parsedData }, key);
10248
- case "table":
10249
- return jsx(TableContent, { bodyText: parsedData.bodyText }, key);
10250
- case "graph":
10251
- return jsx(GraphContent, { bodyText: parsedData.bodyText }, key);
10252
- case "radio":
10253
- return jsx(RadioContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10254
- case "checkbox":
10255
- return jsx(CheckboxContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10256
- case "select":
10257
- return jsx(SelectContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10258
- case "slider":
10259
- return jsx(SliderContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10260
- case "button":
10261
- return jsx(ButtonContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10262
- case "input":
10263
- return jsx(InputContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10264
- case "datePicker":
10265
- return jsx(DatePickerContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10266
- case "dateRangePicker":
10267
- return jsx(DateRangePickerContent, { bodyText: parsedData.bodyText, isFormDisabled: isFormDisabled }, key);
10268
- case "html":
10269
- return jsx(HtmlContent, { bodyText: parsedData.bodyText }, key);
10270
- default:
10271
- return null;
10272
- }
10387
+ const body = renderWidgetBody(parsedData, key);
10388
+ if (!body)
10389
+ return null;
10390
+ const showHeaderTitle = !parsedData?.noShowHeaderTitle && Boolean(parsedData?.headerTitle?.length);
10391
+ if (!showHeaderTitle)
10392
+ return body;
10393
+ return (jsxs(Fragment$1, { children: [jsx("div", { className: `${chatClasses.chatbotText} ${chatClasses.boldText} ${chatClasses.combinedBlockHeaderTitle}`, children: parsedData.headerTitle }), body] }, `restream-widget-block-${index}`));
10273
10394
  }
10274
10395
  catch (e) {
10275
10396
  console.error("[TabularContent] renderWidget error:", e);
@@ -10639,11 +10760,8 @@ const CombinedContent = ({ botData, props }) => {
10639
10760
  // Get the array of content items from bodyText
10640
10761
  const contentItems = Array.isArray(botData.bodyText) ? botData.bodyText : [];
10641
10762
  // Renders the optional block-level header title above a content item.
10642
- // Text blocks are skipped because their heading is the echoed user query
10643
- // (response_heading), which we don't want to duplicate in the response.
10644
10763
  const renderHeaderTitle = (parsedData) => {
10645
- const showHeaderTitle = parsedData?.bodyType !== "text" &&
10646
- !parsedData?.noShowHeaderTitle &&
10764
+ const showHeaderTitle = !parsedData?.noShowHeaderTitle &&
10647
10765
  Boolean(parsedData?.headerTitle?.length);
10648
10766
  if (!showHeaderTitle)
10649
10767
  return null;
@@ -10713,7 +10831,7 @@ const CombinedContent = ({ botData, props }) => {
10713
10831
  const validContent = renderedContent.filter(content => content !== null);
10714
10832
  const renderCombinedContent = () => (jsx("div", { className: "combined-content-container", children: validContent.length > 0 ? (validContent.map((content, index) => (jsx("div", { className: "combined-content-item", children: content }, `wrapper-${index}`)))) : (jsx("div", { children: "No valid content to display" })) }));
10715
10833
  if (isTabEnabled) {
10716
- return (jsx(TabularContent, { steps: botData?.utilityData?.steps || [], currentTabValue: botData?.utilityData?.currentTabValue || "steps", questions: botData?.utilityData?.questions || [], questionsStepsMap: botData?.utilityData?.questionsStepsMap || {}, stepFormDataMap: botData?.utilityData?.stepFormDataMap || {}, isFormDisabled: isFormDisabled, sessionId: botData?.sessionId || "", children: renderCombinedContent() }));
10834
+ return (jsx(TabularContent, { steps: botData?.utilityData?.steps || [], currentTabValue: botData?.utilityData?.currentTabValue || "steps", questions: botData?.utilityData?.questions || [], questionsStepsMap: botData?.utilityData?.questionsStepsMap || {}, stepFormDataMap: botData?.utilityData?.stepFormDataMap || {}, isFormDisabled: isFormDisabled, sessionId: botData?.sessionId || "", botProps: props, currentMode: botData?.currentMode || "", agentId: botData?.agentId || "", children: renderCombinedContent() }));
10717
10835
  }
10718
10836
  return renderCombinedContent();
10719
10837
  };
@@ -10752,7 +10870,7 @@ const BotMessage = ({ botData, state, handleLikeDislike, props }) => {
10752
10870
  case "text":
10753
10871
  return jsx(TextContent, { bodyText: botData.bodyText, botData: botData });
10754
10872
  case "stream":
10755
- return jsx(StreamedContent, { botData: botData });
10873
+ return jsx(StreamedContent, { botData: botData, botProps: props });
10756
10874
  case "chips":
10757
10875
  if (botData.isMultiSelect) {
10758
10876
  return (jsx(SelectableChips, { bodyText: botData.bodyText, utilityData: botData.utilityData, props: props }));
@@ -14999,13 +15117,19 @@ const SmartBot = (props) => {
14999
15117
  if (newChatId !== undefined)
15000
15118
  setUniqueChatId(newChatId);
15001
15119
  };
15120
+ // Minimize the chat window when the user follows an in-app link from a bot response
15121
+ const handleMinimizeRequest = () => {
15122
+ setPartialClose(true);
15123
+ };
15002
15124
  window.addEventListener("stepFormStreamStart", handleStepFormStreamStart);
15003
15125
  window.addEventListener("stepFormStreamEnd", handleStepFormStreamEnd);
15004
15126
  window.addEventListener("stepFormInitStateUpdate", handleStepFormInitStateUpdate);
15127
+ window.addEventListener("smartBotMinimize", handleMinimizeRequest);
15005
15128
  return () => {
15006
15129
  window.removeEventListener("stepFormStreamStart", handleStepFormStreamStart);
15007
15130
  window.removeEventListener("stepFormStreamEnd", handleStepFormStreamEnd);
15008
15131
  window.removeEventListener("stepFormInitStateUpdate", handleStepFormInitStateUpdate);
15132
+ window.removeEventListener("smartBotMinimize", handleMinimizeRequest);
15009
15133
  };
15010
15134
  }, []);
15011
15135
  const fetchCustomBotConfigurations = async () => {
@@ -15141,6 +15265,13 @@ const SmartBot = (props) => {
15141
15265
  // );
15142
15266
  // return;
15143
15267
  // } else {
15268
+ // The library re-fires the `initialClick` tab's onClick from an
15269
+ // effect whose deps (tabList/menuItems/onChatBotResize) are
15270
+ // recreated on every render. Track the previous tab so we only
15271
+ // reset transient state on a real tab change - otherwise these
15272
+ // spurious re-invocations wipe isStop mid-stream and hide the
15273
+ // cancel execution button.
15274
+ const previousActiveTab = activeTab.current.activeTab;
15144
15275
  setShowSavedFilters(false);
15145
15276
  const agentConversations = chatDataInfoRef?.current[params?.name?.toLowerCase()]?.conversations;
15146
15277
  const firstConversationId = agentConversations ? Object.keys(agentConversations)[0] : undefined;
@@ -15159,7 +15290,9 @@ const SmartBot = (props) => {
15159
15290
  localStorage.setItem("currentModeData", params?.name?.toLowerCase());
15160
15291
  setNewChatScreen(false);
15161
15292
  activeTab.current.activeTab = "agent";
15162
- setIsStop(false);
15293
+ if (previousActiveTab !== "agent") {
15294
+ setIsStop(false);
15295
+ }
15163
15296
  // }
15164
15297
  // setConversation([]);
15165
15298
  // chatDataInfoRef.current[currentMode] = [];
@@ -15178,6 +15311,8 @@ const SmartBot = (props) => {
15178
15311
  // );
15179
15312
  // return;
15180
15313
  // } else {
15314
+ // Only reset transient state on a real tab change.
15315
+ const previousActiveTab = activeTab.current.activeTab;
15181
15316
  setShowSavedFilters(false);
15182
15317
  let currentModeValue = params?.name?.toLowerCase();
15183
15318
  const modeConversations = chatDataInfoRef?.current[currentModeValue]?.conversations;
@@ -15201,7 +15336,9 @@ const SmartBot = (props) => {
15201
15336
  chatDataInfoRef.current = cloneDeep(parsedData);
15202
15337
  }
15203
15338
  // setNewChatScreen(true);
15204
- setIsStop(false);
15339
+ if (previousActiveTab !== "navigation") {
15340
+ setIsStop(false);
15341
+ }
15205
15342
  setCurrentMode(params?.name?.toLowerCase());
15206
15343
  if (firstConversationId) {
15207
15344
  setActiveConversationId(firstConversationId);
@@ -15270,6 +15407,9 @@ const SmartBot = (props) => {
15270
15407
  // );
15271
15408
  // return;
15272
15409
  // } else {
15410
+ // Only reset transient state on a real tab change - the library
15411
+ // re-fires this initialClick handler on every render.
15412
+ const previousActiveTab = activeTab.current.activeTab;
15273
15413
  setShowSavedFilters(false);
15274
15414
  let currentModeValue = params?.name?.toLowerCase();
15275
15415
  const modeConversations = chatDataInfoRef?.current[currentModeValue]?.conversations;
@@ -15293,7 +15433,9 @@ const SmartBot = (props) => {
15293
15433
  chatDataInfoRef.current = cloneDeep(parsedData);
15294
15434
  }
15295
15435
  // setNewChatScreen(true);
15296
- setIsStop(false);
15436
+ if (previousActiveTab !== "navigation") {
15437
+ setIsStop(false);
15438
+ }
15297
15439
  setCurrentMode(params?.name?.toLowerCase());
15298
15440
  if (firstConversationId) {
15299
15441
  setActiveConversationId(firstConversationId);
@@ -15324,6 +15466,9 @@ const SmartBot = (props) => {
15324
15466
  // );
15325
15467
  // return;
15326
15468
  // } else {
15469
+ // Only reset transient state on a real tab change - the library
15470
+ // re-fires this initialClick handler on every render.
15471
+ const previousActiveTab = activeTab.current.activeTab;
15327
15472
  setShowSavedFilters(false);
15328
15473
  let currentModeValue = params?.name?.toLowerCase();
15329
15474
  const modeConversations = chatDataInfoRef?.current[currentModeValue]?.conversations;
@@ -15347,7 +15492,9 @@ const SmartBot = (props) => {
15347
15492
  chatDataInfoRef.current = cloneDeep(parsedData);
15348
15493
  }
15349
15494
  // setNewChatScreen(true);
15350
- setIsStop(false);
15495
+ if (previousActiveTab !== "navigation") {
15496
+ setIsStop(false);
15497
+ }
15351
15498
  setCurrentMode(params?.name?.toLowerCase());
15352
15499
  if (firstConversationId) {
15353
15500
  setActiveConversationId(firstConversationId);