impact-chatbot 2.3.83 → 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.cjs.js CHANGED
@@ -20,9 +20,9 @@ var rehypeSanitize = require('rehype-sanitize');
20
20
  var remarkBreaks = require('remark-breaks');
21
21
  var remarkGfm = require('remark-gfm');
22
22
  var DOMPurify = require('dompurify');
23
+ var reactRouterDomV5Compat = require('react-router-dom-v5-compat');
23
24
  var filterAction = require('core/actions/filterAction');
24
25
  var smartBotActions = require('core/actions/smartBotActions');
25
- var reactRouterDomV5Compat = require('react-router-dom-v5-compat');
26
26
  var RefreshIcon = require('@mui/icons-material/Refresh');
27
27
  var styled = require('styled-components');
28
28
  var material = require('@mui/material');
@@ -1120,6 +1120,66 @@ const getFormattedApplicationName = (applicationURL) => {
1120
1120
  }
1121
1121
  };
1122
1122
 
1123
+ /**
1124
+ * Resolves a link href to an in-app route (path + search + hash) when the link
1125
+ * points to a screen of this application, otherwise returns null.
1126
+ * Handles both relative links ("/inventory-smart/create-allocation?step=0") and
1127
+ * absolute links sent by the backend
1128
+ * ("https://tapestry.test.impactsmartsuite.com/inventory-smart/create-allocation?step=0").
1129
+ */
1130
+ const resolveInternalPath = (href) => {
1131
+ if (!href || typeof href !== 'string')
1132
+ return null;
1133
+ // Non navigational protocols (mailto:, tel:, javascript:, #anchor) stay as-is
1134
+ if (/^(mailto:|tel:|javascript:)/i.test(href) || href.startsWith('#'))
1135
+ return null;
1136
+ try {
1137
+ const url = new URL(href, window.location.origin);
1138
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
1139
+ return null;
1140
+ const currentApp = window.location.pathname.split('/')[1];
1141
+ const targetApp = url.pathname.split('/')[1];
1142
+ // Same origin, or a different host of the same app (e.g. backend returns the
1143
+ // deployed host while running locally) - both resolve to a client side route
1144
+ const isInternal = url.origin === window.location.origin ||
1145
+ (!!currentApp && currentApp === targetApp);
1146
+ if (!isInternal)
1147
+ return null;
1148
+ return `${url.pathname}${url.search}${url.hash}`;
1149
+ }
1150
+ catch (e) {
1151
+ return null;
1152
+ }
1153
+ };
1154
+ /**
1155
+ * Minimizes the chat window so the user can see the screen they navigated to.
1156
+ * SmartBot (index.tsx) listens for this event and sets partialClose.
1157
+ */
1158
+ const minimizeChatBot = () => {
1159
+ window.dispatchEvent(new CustomEvent("smartBotMinimize"));
1160
+ };
1161
+ /**
1162
+ * Anchor rendered inside chatbot markdown. Internal links are navigated through
1163
+ * react-router so the chatbot is not remounted by a full page load.
1164
+ */
1165
+ const MarkdownLink = ({ href, children, ...props }) => {
1166
+ const navigate = reactRouterDomV5Compat.useNavigate();
1167
+ const handleClick = (event) => {
1168
+ // Let the browser handle new tab / new window / download intents
1169
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0)
1170
+ return;
1171
+ const internalPath = resolveInternalPath(href);
1172
+ if (!internalPath)
1173
+ return;
1174
+ event.preventDefault();
1175
+ const currentPath = `${window.location.pathname}${window.location.search}`;
1176
+ if (currentPath !== internalPath) {
1177
+ navigate(internalPath);
1178
+ }
1179
+ minimizeChatBot();
1180
+ };
1181
+ return (jsxRuntime.jsx("a", { href: href, target: "_self", onClick: handleClick, ...props, children: children }));
1182
+ };
1123
1183
  /**
1124
1184
  * Checks whether the input string contains meaningful HTML markup
1125
1185
  * (beyond simple inline tags like <b>, <i>, <em>, <strong>, <br>, <a>, <u>
@@ -1160,9 +1220,16 @@ const preprocessMarkdown = (content) => {
1160
1220
  .replace(/(^|\n)([^\n-•]*[a-zA-Z0-9][^\n]*)\n([-•]\s+)/g, '$1$2\n\n$3')
1161
1221
  // Handle list items with - or • appearing directly after text without newline
1162
1222
  // Only treat as a new bullet when preceded by sentence-ending punctuation (to avoid splitting inline hyphens like "KSO- ORLANDO")
1163
- .replace(/([.!?:)\"])\s*([-•])\s+/g, '$1\n\n$2 ')
1164
- // Handle nested list items with proper indentation
1165
- .replace(/(\n\s{2,})([-•]|\d+\.)\s+/g, '\n $2 ')
1223
+ // [^\S\n] (horizontal whitespace only) keeps this from crossing line breaks,
1224
+ // which would dedent already correctly indented nested bullets
1225
+ .replace(/([.!?:)"])[^\S\n]*([-\u2022])[^\S\n]+/g, '$1\n\n$2 ')
1226
+ // Handle nested list items with proper indentation.
1227
+ // Matching only horizontal whitespace after the newline keeps blank lines and
1228
+ // top level numbered items intact (indenting those turns them into plain text).
1229
+ // Normalize to 3 spaces: enough to nest under both "- " and "1. " parents, and
1230
+ // below the 4 space threshold that would turn the line into a code block.
1231
+ // Indents of 4 or more are left untouched (deliberate deeper nesting).
1232
+ .replace(/(\n[^\S\n]{2,3})([-\u2022]|\d+\.)[^\S\n]+/g, '\n $2 ')
1166
1233
  // Ensure double line breaks after list sections
1167
1234
  .replace(/(\n\d+\.\s+.*?)(\n\n###)/g, '$1\n$2')
1168
1235
  .replace(/(\n[-•]\s+.*?)(\n\n###)/g, '$1\n$2')
@@ -1174,8 +1241,8 @@ const preprocessMarkdown = (content) => {
1174
1241
  * Custom components for markdown rendering
1175
1242
  */
1176
1243
  const markdownComponents = {
1177
- // Custom link component that opens in the same tab
1178
- a: ({ href, children, ...props }) => (jsxRuntime.jsx("a", { href: href, target: "_self", ...props, children: children })),
1244
+ // Custom link component that navigates in-app without reloading the page
1245
+ a: MarkdownLink,
1179
1246
  // Custom code component
1180
1247
  code: ({ children, className, ...props }) => (jsxRuntime.jsx("code", { className: `markdown-code ${className || ''}`, ...props, children: children })),
1181
1248
  // Custom pre component for code blocks
@@ -1195,13 +1262,32 @@ const markdownComponents = {
1195
1262
  * Markdown renderer component with sanitization
1196
1263
  */
1197
1264
  const TextRenderer = ({ text, thinking }) => {
1265
+ const navigate = reactRouterDomV5Compat.useNavigate();
1266
+ // Click delegation for anchors inside raw HTML content (dangerouslySetInnerHTML),
1267
+ // so in-app links navigate through react-router instead of reloading the page.
1268
+ const handleHtmlClick = (event) => {
1269
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0)
1270
+ return;
1271
+ const anchor = event.target.closest?.('a');
1272
+ if (!anchor || anchor.target === '_blank')
1273
+ return;
1274
+ const internalPath = resolveInternalPath(anchor.getAttribute('href'));
1275
+ if (!internalPath)
1276
+ return;
1277
+ event.preventDefault();
1278
+ const currentPath = `${window.location.pathname}${window.location.search}`;
1279
+ if (currentPath !== internalPath) {
1280
+ navigate(internalPath);
1281
+ }
1282
+ minimizeChatBot();
1283
+ };
1198
1284
  // If the input contains rich HTML, render it directly with DOMPurify sanitization
1199
1285
  if (containsRichHtml(text)) {
1200
1286
  const sanitizedHtml = DOMPurify.sanitize(text, {
1201
1287
  ADD_TAGS: ['style'],
1202
1288
  ADD_ATTR: ['style', 'class', 'target', 'rel'],
1203
1289
  });
1204
- return (jsxRuntime.jsx("div", { className: `markdown-content md-content ${thinking ? "thinking" : ""}`, dangerouslySetInnerHTML: { __html: sanitizedHtml } }));
1290
+ return (jsxRuntime.jsx("div", { className: `markdown-content md-content ${thinking ? "thinking" : ""}`, onClick: handleHtmlClick, dangerouslySetInnerHTML: { __html: sanitizedHtml } }));
1205
1291
  }
1206
1292
  // Otherwise, use the existing markdown pipeline
1207
1293
  const processedContent = preprocessMarkdown(text);
@@ -15053,13 +15139,19 @@ const SmartBot = (props) => {
15053
15139
  if (newChatId !== undefined)
15054
15140
  setUniqueChatId(newChatId);
15055
15141
  };
15142
+ // Minimize the chat window when the user follows an in-app link from a bot response
15143
+ const handleMinimizeRequest = () => {
15144
+ setPartialClose(true);
15145
+ };
15056
15146
  window.addEventListener("stepFormStreamStart", handleStepFormStreamStart);
15057
15147
  window.addEventListener("stepFormStreamEnd", handleStepFormStreamEnd);
15058
15148
  window.addEventListener("stepFormInitStateUpdate", handleStepFormInitStateUpdate);
15149
+ window.addEventListener("smartBotMinimize", handleMinimizeRequest);
15059
15150
  return () => {
15060
15151
  window.removeEventListener("stepFormStreamStart", handleStepFormStreamStart);
15061
15152
  window.removeEventListener("stepFormStreamEnd", handleStepFormStreamEnd);
15062
15153
  window.removeEventListener("stepFormInitStateUpdate", handleStepFormInitStateUpdate);
15154
+ window.removeEventListener("smartBotMinimize", handleMinimizeRequest);
15063
15155
  };
15064
15156
  }, []);
15065
15157
  const fetchCustomBotConfigurations = async () => {