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 +99 -7
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +99 -7
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
1164
|
-
//
|
|
1165
|
-
.replace(/(\n
|
|
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
|
|
1178
|
-
a:
|
|
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 () => {
|