impact-chatbot 2.3.83 → 2.3.85
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 +125 -10
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +125 -10
- 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');
|
|
@@ -675,8 +675,11 @@ const handleMessageLike = async (question, liked, setLoadingState, displaySnackM
|
|
|
675
675
|
try {
|
|
676
676
|
let request;
|
|
677
677
|
if (currentMode === "agent") {
|
|
678
|
+
// Use the session stored on this specific message. The live sessionId is
|
|
679
|
+
// reset once a flow completes / awaits follow-up, so relying on it would
|
|
680
|
+
// send an empty session_id or the session of a newer chat.
|
|
678
681
|
const agentPayload = {
|
|
679
|
-
session_id: sessionId,
|
|
682
|
+
session_id: activeMessage?.chatSessionId || activeMessage?.sessionId || sessionId,
|
|
680
683
|
liked,
|
|
681
684
|
};
|
|
682
685
|
request = await likeDislikeCommentForAgent(agentPayload, baseUrl);
|
|
@@ -1120,6 +1123,66 @@ const getFormattedApplicationName = (applicationURL) => {
|
|
|
1120
1123
|
}
|
|
1121
1124
|
};
|
|
1122
1125
|
|
|
1126
|
+
/**
|
|
1127
|
+
* Resolves a link href to an in-app route (path + search + hash) when the link
|
|
1128
|
+
* points to a screen of this application, otherwise returns null.
|
|
1129
|
+
* Handles both relative links ("/inventory-smart/create-allocation?step=0") and
|
|
1130
|
+
* absolute links sent by the backend
|
|
1131
|
+
* ("https://tapestry.test.impactsmartsuite.com/inventory-smart/create-allocation?step=0").
|
|
1132
|
+
*/
|
|
1133
|
+
const resolveInternalPath = (href) => {
|
|
1134
|
+
if (!href || typeof href !== 'string')
|
|
1135
|
+
return null;
|
|
1136
|
+
// Non navigational protocols (mailto:, tel:, javascript:, #anchor) stay as-is
|
|
1137
|
+
if (/^(mailto:|tel:|javascript:)/i.test(href) || href.startsWith('#'))
|
|
1138
|
+
return null;
|
|
1139
|
+
try {
|
|
1140
|
+
const url = new URL(href, window.location.origin);
|
|
1141
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
1142
|
+
return null;
|
|
1143
|
+
const currentApp = window.location.pathname.split('/')[1];
|
|
1144
|
+
const targetApp = url.pathname.split('/')[1];
|
|
1145
|
+
// Same origin, or a different host of the same app (e.g. backend returns the
|
|
1146
|
+
// deployed host while running locally) - both resolve to a client side route
|
|
1147
|
+
const isInternal = url.origin === window.location.origin ||
|
|
1148
|
+
(!!currentApp && currentApp === targetApp);
|
|
1149
|
+
if (!isInternal)
|
|
1150
|
+
return null;
|
|
1151
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
1152
|
+
}
|
|
1153
|
+
catch (e) {
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
/**
|
|
1158
|
+
* Minimizes the chat window so the user can see the screen they navigated to.
|
|
1159
|
+
* SmartBot (index.tsx) listens for this event and sets partialClose.
|
|
1160
|
+
*/
|
|
1161
|
+
const minimizeChatBot = () => {
|
|
1162
|
+
window.dispatchEvent(new CustomEvent("smartBotMinimize"));
|
|
1163
|
+
};
|
|
1164
|
+
/**
|
|
1165
|
+
* Anchor rendered inside chatbot markdown. Internal links are navigated through
|
|
1166
|
+
* react-router so the chatbot is not remounted by a full page load.
|
|
1167
|
+
*/
|
|
1168
|
+
const MarkdownLink = ({ href, children, ...props }) => {
|
|
1169
|
+
const navigate = reactRouterDomV5Compat.useNavigate();
|
|
1170
|
+
const handleClick = (event) => {
|
|
1171
|
+
// Let the browser handle new tab / new window / download intents
|
|
1172
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0)
|
|
1173
|
+
return;
|
|
1174
|
+
const internalPath = resolveInternalPath(href);
|
|
1175
|
+
if (!internalPath)
|
|
1176
|
+
return;
|
|
1177
|
+
event.preventDefault();
|
|
1178
|
+
const currentPath = `${window.location.pathname}${window.location.search}`;
|
|
1179
|
+
if (currentPath !== internalPath) {
|
|
1180
|
+
navigate(internalPath);
|
|
1181
|
+
}
|
|
1182
|
+
minimizeChatBot();
|
|
1183
|
+
};
|
|
1184
|
+
return (jsxRuntime.jsx("a", { href: href, target: "_self", onClick: handleClick, ...props, children: children }));
|
|
1185
|
+
};
|
|
1123
1186
|
/**
|
|
1124
1187
|
* Checks whether the input string contains meaningful HTML markup
|
|
1125
1188
|
* (beyond simple inline tags like <b>, <i>, <em>, <strong>, <br>, <a>, <u>
|
|
@@ -1160,9 +1223,16 @@ const preprocessMarkdown = (content) => {
|
|
|
1160
1223
|
.replace(/(^|\n)([^\n-•]*[a-zA-Z0-9][^\n]*)\n([-•]\s+)/g, '$1$2\n\n$3')
|
|
1161
1224
|
// Handle list items with - or • appearing directly after text without newline
|
|
1162
1225
|
// 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
|
|
1226
|
+
// [^\S\n] (horizontal whitespace only) keeps this from crossing line breaks,
|
|
1227
|
+
// which would dedent already correctly indented nested bullets
|
|
1228
|
+
.replace(/([.!?:)"])[^\S\n]*([-\u2022])[^\S\n]+/g, '$1\n\n$2 ')
|
|
1229
|
+
// Handle nested list items with proper indentation.
|
|
1230
|
+
// Matching only horizontal whitespace after the newline keeps blank lines and
|
|
1231
|
+
// top level numbered items intact (indenting those turns them into plain text).
|
|
1232
|
+
// Normalize to 3 spaces: enough to nest under both "- " and "1. " parents, and
|
|
1233
|
+
// below the 4 space threshold that would turn the line into a code block.
|
|
1234
|
+
// Indents of 4 or more are left untouched (deliberate deeper nesting).
|
|
1235
|
+
.replace(/(\n[^\S\n]{2,3})([-\u2022]|\d+\.)[^\S\n]+/g, '\n $2 ')
|
|
1166
1236
|
// Ensure double line breaks after list sections
|
|
1167
1237
|
.replace(/(\n\d+\.\s+.*?)(\n\n###)/g, '$1\n$2')
|
|
1168
1238
|
.replace(/(\n[-•]\s+.*?)(\n\n###)/g, '$1\n$2')
|
|
@@ -1174,8 +1244,8 @@ const preprocessMarkdown = (content) => {
|
|
|
1174
1244
|
* Custom components for markdown rendering
|
|
1175
1245
|
*/
|
|
1176
1246
|
const markdownComponents = {
|
|
1177
|
-
// Custom link component that
|
|
1178
|
-
a:
|
|
1247
|
+
// Custom link component that navigates in-app without reloading the page
|
|
1248
|
+
a: MarkdownLink,
|
|
1179
1249
|
// Custom code component
|
|
1180
1250
|
code: ({ children, className, ...props }) => (jsxRuntime.jsx("code", { className: `markdown-code ${className || ''}`, ...props, children: children })),
|
|
1181
1251
|
// Custom pre component for code blocks
|
|
@@ -1195,13 +1265,32 @@ const markdownComponents = {
|
|
|
1195
1265
|
* Markdown renderer component with sanitization
|
|
1196
1266
|
*/
|
|
1197
1267
|
const TextRenderer = ({ text, thinking }) => {
|
|
1268
|
+
const navigate = reactRouterDomV5Compat.useNavigate();
|
|
1269
|
+
// Click delegation for anchors inside raw HTML content (dangerouslySetInnerHTML),
|
|
1270
|
+
// so in-app links navigate through react-router instead of reloading the page.
|
|
1271
|
+
const handleHtmlClick = (event) => {
|
|
1272
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0)
|
|
1273
|
+
return;
|
|
1274
|
+
const anchor = event.target.closest?.('a');
|
|
1275
|
+
if (!anchor || anchor.target === '_blank')
|
|
1276
|
+
return;
|
|
1277
|
+
const internalPath = resolveInternalPath(anchor.getAttribute('href'));
|
|
1278
|
+
if (!internalPath)
|
|
1279
|
+
return;
|
|
1280
|
+
event.preventDefault();
|
|
1281
|
+
const currentPath = `${window.location.pathname}${window.location.search}`;
|
|
1282
|
+
if (currentPath !== internalPath) {
|
|
1283
|
+
navigate(internalPath);
|
|
1284
|
+
}
|
|
1285
|
+
minimizeChatBot();
|
|
1286
|
+
};
|
|
1198
1287
|
// If the input contains rich HTML, render it directly with DOMPurify sanitization
|
|
1199
1288
|
if (containsRichHtml(text)) {
|
|
1200
1289
|
const sanitizedHtml = DOMPurify.sanitize(text, {
|
|
1201
1290
|
ADD_TAGS: ['style'],
|
|
1202
1291
|
ADD_ATTR: ['style', 'class', 'target', 'rel'],
|
|
1203
1292
|
});
|
|
1204
|
-
return (jsxRuntime.jsx("div", { className: `markdown-content md-content ${thinking ? "thinking" : ""}`, dangerouslySetInnerHTML: { __html: sanitizedHtml } }));
|
|
1293
|
+
return (jsxRuntime.jsx("div", { className: `markdown-content md-content ${thinking ? "thinking" : ""}`, onClick: handleHtmlClick, dangerouslySetInnerHTML: { __html: sanitizedHtml } }));
|
|
1205
1294
|
}
|
|
1206
1295
|
// Otherwise, use the existing markdown pipeline
|
|
1207
1296
|
const processedContent = preprocessMarkdown(text);
|
|
@@ -5251,6 +5340,13 @@ const sseevent = (message, messageToStoreRef) => {
|
|
|
5251
5340
|
? parsedData.chat_id
|
|
5252
5341
|
: "";
|
|
5253
5342
|
messageToStoreRef.current.sessionId = parsedData?.session_id;
|
|
5343
|
+
// Sticky copy of the session id this message belongs to. sessionId gets
|
|
5344
|
+
// cleared on completed/follow-up so the next user message starts a fresh
|
|
5345
|
+
// session, but message level actions (like/dislike) still need to refer
|
|
5346
|
+
// to the session that actually produced this response.
|
|
5347
|
+
if (parsedData?.session_id) {
|
|
5348
|
+
messageToStoreRef.current.chatSessionId = parsedData.session_id;
|
|
5349
|
+
}
|
|
5254
5350
|
}
|
|
5255
5351
|
if (messageToStoreRef.current.currentMode === "navigation" &&
|
|
5256
5352
|
parsedData?.session_id) {
|
|
@@ -9597,7 +9693,12 @@ const StreamedContent = ({ botData, botProps }) => {
|
|
|
9597
9693
|
},
|
|
9598
9694
|
};
|
|
9599
9695
|
const hasStepFormWidgets = !isEmpty(stepFormDataMapRef.current);
|
|
9600
|
-
|
|
9696
|
+
// response.session_id is intentionally blank once the flow completes, so
|
|
9697
|
+
// keep the retained session separately and stamp it on the stored message.
|
|
9698
|
+
const messageChatSessionId = messageToStoreRef.current.chatSessionId ||
|
|
9699
|
+
messageToStoreRef.current.sessionId ||
|
|
9700
|
+
"";
|
|
9701
|
+
Promise.resolve(processResponse(response, botData.inputBody, currentMode, botData.utilityObject.customChatConfig, {
|
|
9601
9702
|
newChatData: chatDataInfoRef,
|
|
9602
9703
|
isTabEnabled: true,
|
|
9603
9704
|
steps: stepRef.current.map(s => ({ ...s })),
|
|
@@ -9605,7 +9706,15 @@ const StreamedContent = ({ botData, botProps }) => {
|
|
|
9605
9706
|
questions: [...questionsRef.current],
|
|
9606
9707
|
questionsStepsMap: { ...questionsStepsMapRef.current },
|
|
9607
9708
|
stepFormDataMap: { ...stepFormDataMapRef.current },
|
|
9608
|
-
}, activeConversationId)
|
|
9709
|
+
}, activeConversationId)).then(() => {
|
|
9710
|
+
if (!messageChatSessionId)
|
|
9711
|
+
return;
|
|
9712
|
+
const storedMessages = chatDataInfoRef.current?.[currentMode]?.conversations?.[activeConversationId]?.messages;
|
|
9713
|
+
if (storedMessages?.length) {
|
|
9714
|
+
storedMessages[storedMessages.length - 1].chatSessionId =
|
|
9715
|
+
messageChatSessionId;
|
|
9716
|
+
}
|
|
9717
|
+
});
|
|
9609
9718
|
// [
|
|
9610
9719
|
// {
|
|
9611
9720
|
// header: "Finding relevant information",
|
|
@@ -15053,13 +15162,19 @@ const SmartBot = (props) => {
|
|
|
15053
15162
|
if (newChatId !== undefined)
|
|
15054
15163
|
setUniqueChatId(newChatId);
|
|
15055
15164
|
};
|
|
15165
|
+
// Minimize the chat window when the user follows an in-app link from a bot response
|
|
15166
|
+
const handleMinimizeRequest = () => {
|
|
15167
|
+
setPartialClose(true);
|
|
15168
|
+
};
|
|
15056
15169
|
window.addEventListener("stepFormStreamStart", handleStepFormStreamStart);
|
|
15057
15170
|
window.addEventListener("stepFormStreamEnd", handleStepFormStreamEnd);
|
|
15058
15171
|
window.addEventListener("stepFormInitStateUpdate", handleStepFormInitStateUpdate);
|
|
15172
|
+
window.addEventListener("smartBotMinimize", handleMinimizeRequest);
|
|
15059
15173
|
return () => {
|
|
15060
15174
|
window.removeEventListener("stepFormStreamStart", handleStepFormStreamStart);
|
|
15061
15175
|
window.removeEventListener("stepFormStreamEnd", handleStepFormStreamEnd);
|
|
15062
15176
|
window.removeEventListener("stepFormInitStateUpdate", handleStepFormInitStateUpdate);
|
|
15177
|
+
window.removeEventListener("smartBotMinimize", handleMinimizeRequest);
|
|
15063
15178
|
};
|
|
15064
15179
|
}, []);
|
|
15065
15180
|
const fetchCustomBotConfigurations = async () => {
|