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.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';
|
|
@@ -653,8 +653,11 @@ const handleMessageLike = async (question, liked, setLoadingState, displaySnackM
|
|
|
653
653
|
try {
|
|
654
654
|
let request;
|
|
655
655
|
if (currentMode === "agent") {
|
|
656
|
+
// Use the session stored on this specific message. The live sessionId is
|
|
657
|
+
// reset once a flow completes / awaits follow-up, so relying on it would
|
|
658
|
+
// send an empty session_id or the session of a newer chat.
|
|
656
659
|
const agentPayload = {
|
|
657
|
-
session_id: sessionId,
|
|
660
|
+
session_id: activeMessage?.chatSessionId || activeMessage?.sessionId || sessionId,
|
|
658
661
|
liked,
|
|
659
662
|
};
|
|
660
663
|
request = await likeDislikeCommentForAgent(agentPayload, baseUrl);
|
|
@@ -1098,6 +1101,66 @@ const getFormattedApplicationName = (applicationURL) => {
|
|
|
1098
1101
|
}
|
|
1099
1102
|
};
|
|
1100
1103
|
|
|
1104
|
+
/**
|
|
1105
|
+
* Resolves a link href to an in-app route (path + search + hash) when the link
|
|
1106
|
+
* points to a screen of this application, otherwise returns null.
|
|
1107
|
+
* Handles both relative links ("/inventory-smart/create-allocation?step=0") and
|
|
1108
|
+
* absolute links sent by the backend
|
|
1109
|
+
* ("https://tapestry.test.impactsmartsuite.com/inventory-smart/create-allocation?step=0").
|
|
1110
|
+
*/
|
|
1111
|
+
const resolveInternalPath = (href) => {
|
|
1112
|
+
if (!href || typeof href !== 'string')
|
|
1113
|
+
return null;
|
|
1114
|
+
// Non navigational protocols (mailto:, tel:, javascript:, #anchor) stay as-is
|
|
1115
|
+
if (/^(mailto:|tel:|javascript:)/i.test(href) || href.startsWith('#'))
|
|
1116
|
+
return null;
|
|
1117
|
+
try {
|
|
1118
|
+
const url = new URL(href, window.location.origin);
|
|
1119
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
1120
|
+
return null;
|
|
1121
|
+
const currentApp = window.location.pathname.split('/')[1];
|
|
1122
|
+
const targetApp = url.pathname.split('/')[1];
|
|
1123
|
+
// Same origin, or a different host of the same app (e.g. backend returns the
|
|
1124
|
+
// deployed host while running locally) - both resolve to a client side route
|
|
1125
|
+
const isInternal = url.origin === window.location.origin ||
|
|
1126
|
+
(!!currentApp && currentApp === targetApp);
|
|
1127
|
+
if (!isInternal)
|
|
1128
|
+
return null;
|
|
1129
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
1130
|
+
}
|
|
1131
|
+
catch (e) {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
/**
|
|
1136
|
+
* Minimizes the chat window so the user can see the screen they navigated to.
|
|
1137
|
+
* SmartBot (index.tsx) listens for this event and sets partialClose.
|
|
1138
|
+
*/
|
|
1139
|
+
const minimizeChatBot = () => {
|
|
1140
|
+
window.dispatchEvent(new CustomEvent("smartBotMinimize"));
|
|
1141
|
+
};
|
|
1142
|
+
/**
|
|
1143
|
+
* Anchor rendered inside chatbot markdown. Internal links are navigated through
|
|
1144
|
+
* react-router so the chatbot is not remounted by a full page load.
|
|
1145
|
+
*/
|
|
1146
|
+
const MarkdownLink = ({ href, children, ...props }) => {
|
|
1147
|
+
const navigate = useNavigate();
|
|
1148
|
+
const handleClick = (event) => {
|
|
1149
|
+
// Let the browser handle new tab / new window / download intents
|
|
1150
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0)
|
|
1151
|
+
return;
|
|
1152
|
+
const internalPath = resolveInternalPath(href);
|
|
1153
|
+
if (!internalPath)
|
|
1154
|
+
return;
|
|
1155
|
+
event.preventDefault();
|
|
1156
|
+
const currentPath = `${window.location.pathname}${window.location.search}`;
|
|
1157
|
+
if (currentPath !== internalPath) {
|
|
1158
|
+
navigate(internalPath);
|
|
1159
|
+
}
|
|
1160
|
+
minimizeChatBot();
|
|
1161
|
+
};
|
|
1162
|
+
return (jsx("a", { href: href, target: "_self", onClick: handleClick, ...props, children: children }));
|
|
1163
|
+
};
|
|
1101
1164
|
/**
|
|
1102
1165
|
* Checks whether the input string contains meaningful HTML markup
|
|
1103
1166
|
* (beyond simple inline tags like <b>, <i>, <em>, <strong>, <br>, <a>, <u>
|
|
@@ -1138,9 +1201,16 @@ const preprocessMarkdown = (content) => {
|
|
|
1138
1201
|
.replace(/(^|\n)([^\n-•]*[a-zA-Z0-9][^\n]*)\n([-•]\s+)/g, '$1$2\n\n$3')
|
|
1139
1202
|
// Handle list items with - or • appearing directly after text without newline
|
|
1140
1203
|
// Only treat as a new bullet when preceded by sentence-ending punctuation (to avoid splitting inline hyphens like "KSO- ORLANDO")
|
|
1141
|
-
|
|
1142
|
-
//
|
|
1143
|
-
.replace(/(\n
|
|
1204
|
+
// [^\S\n] (horizontal whitespace only) keeps this from crossing line breaks,
|
|
1205
|
+
// which would dedent already correctly indented nested bullets
|
|
1206
|
+
.replace(/([.!?:)"])[^\S\n]*([-\u2022])[^\S\n]+/g, '$1\n\n$2 ')
|
|
1207
|
+
// Handle nested list items with proper indentation.
|
|
1208
|
+
// Matching only horizontal whitespace after the newline keeps blank lines and
|
|
1209
|
+
// top level numbered items intact (indenting those turns them into plain text).
|
|
1210
|
+
// Normalize to 3 spaces: enough to nest under both "- " and "1. " parents, and
|
|
1211
|
+
// below the 4 space threshold that would turn the line into a code block.
|
|
1212
|
+
// Indents of 4 or more are left untouched (deliberate deeper nesting).
|
|
1213
|
+
.replace(/(\n[^\S\n]{2,3})([-\u2022]|\d+\.)[^\S\n]+/g, '\n $2 ')
|
|
1144
1214
|
// Ensure double line breaks after list sections
|
|
1145
1215
|
.replace(/(\n\d+\.\s+.*?)(\n\n###)/g, '$1\n$2')
|
|
1146
1216
|
.replace(/(\n[-•]\s+.*?)(\n\n###)/g, '$1\n$2')
|
|
@@ -1152,8 +1222,8 @@ const preprocessMarkdown = (content) => {
|
|
|
1152
1222
|
* Custom components for markdown rendering
|
|
1153
1223
|
*/
|
|
1154
1224
|
const markdownComponents = {
|
|
1155
|
-
// Custom link component that
|
|
1156
|
-
a:
|
|
1225
|
+
// Custom link component that navigates in-app without reloading the page
|
|
1226
|
+
a: MarkdownLink,
|
|
1157
1227
|
// Custom code component
|
|
1158
1228
|
code: ({ children, className, ...props }) => (jsx("code", { className: `markdown-code ${className || ''}`, ...props, children: children })),
|
|
1159
1229
|
// Custom pre component for code blocks
|
|
@@ -1173,13 +1243,32 @@ const markdownComponents = {
|
|
|
1173
1243
|
* Markdown renderer component with sanitization
|
|
1174
1244
|
*/
|
|
1175
1245
|
const TextRenderer = ({ text, thinking }) => {
|
|
1246
|
+
const navigate = useNavigate();
|
|
1247
|
+
// Click delegation for anchors inside raw HTML content (dangerouslySetInnerHTML),
|
|
1248
|
+
// so in-app links navigate through react-router instead of reloading the page.
|
|
1249
|
+
const handleHtmlClick = (event) => {
|
|
1250
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0)
|
|
1251
|
+
return;
|
|
1252
|
+
const anchor = event.target.closest?.('a');
|
|
1253
|
+
if (!anchor || anchor.target === '_blank')
|
|
1254
|
+
return;
|
|
1255
|
+
const internalPath = resolveInternalPath(anchor.getAttribute('href'));
|
|
1256
|
+
if (!internalPath)
|
|
1257
|
+
return;
|
|
1258
|
+
event.preventDefault();
|
|
1259
|
+
const currentPath = `${window.location.pathname}${window.location.search}`;
|
|
1260
|
+
if (currentPath !== internalPath) {
|
|
1261
|
+
navigate(internalPath);
|
|
1262
|
+
}
|
|
1263
|
+
minimizeChatBot();
|
|
1264
|
+
};
|
|
1176
1265
|
// If the input contains rich HTML, render it directly with DOMPurify sanitization
|
|
1177
1266
|
if (containsRichHtml(text)) {
|
|
1178
1267
|
const sanitizedHtml = DOMPurify.sanitize(text, {
|
|
1179
1268
|
ADD_TAGS: ['style'],
|
|
1180
1269
|
ADD_ATTR: ['style', 'class', 'target', 'rel'],
|
|
1181
1270
|
});
|
|
1182
|
-
return (jsx("div", { className: `markdown-content md-content ${thinking ? "thinking" : ""}`, dangerouslySetInnerHTML: { __html: sanitizedHtml } }));
|
|
1271
|
+
return (jsx("div", { className: `markdown-content md-content ${thinking ? "thinking" : ""}`, onClick: handleHtmlClick, dangerouslySetInnerHTML: { __html: sanitizedHtml } }));
|
|
1183
1272
|
}
|
|
1184
1273
|
// Otherwise, use the existing markdown pipeline
|
|
1185
1274
|
const processedContent = preprocessMarkdown(text);
|
|
@@ -5229,6 +5318,13 @@ const sseevent = (message, messageToStoreRef) => {
|
|
|
5229
5318
|
? parsedData.chat_id
|
|
5230
5319
|
: "";
|
|
5231
5320
|
messageToStoreRef.current.sessionId = parsedData?.session_id;
|
|
5321
|
+
// Sticky copy of the session id this message belongs to. sessionId gets
|
|
5322
|
+
// cleared on completed/follow-up so the next user message starts a fresh
|
|
5323
|
+
// session, but message level actions (like/dislike) still need to refer
|
|
5324
|
+
// to the session that actually produced this response.
|
|
5325
|
+
if (parsedData?.session_id) {
|
|
5326
|
+
messageToStoreRef.current.chatSessionId = parsedData.session_id;
|
|
5327
|
+
}
|
|
5232
5328
|
}
|
|
5233
5329
|
if (messageToStoreRef.current.currentMode === "navigation" &&
|
|
5234
5330
|
parsedData?.session_id) {
|
|
@@ -9575,7 +9671,12 @@ const StreamedContent = ({ botData, botProps }) => {
|
|
|
9575
9671
|
},
|
|
9576
9672
|
};
|
|
9577
9673
|
const hasStepFormWidgets = !isEmpty(stepFormDataMapRef.current);
|
|
9578
|
-
|
|
9674
|
+
// response.session_id is intentionally blank once the flow completes, so
|
|
9675
|
+
// keep the retained session separately and stamp it on the stored message.
|
|
9676
|
+
const messageChatSessionId = messageToStoreRef.current.chatSessionId ||
|
|
9677
|
+
messageToStoreRef.current.sessionId ||
|
|
9678
|
+
"";
|
|
9679
|
+
Promise.resolve(processResponse(response, botData.inputBody, currentMode, botData.utilityObject.customChatConfig, {
|
|
9579
9680
|
newChatData: chatDataInfoRef,
|
|
9580
9681
|
isTabEnabled: true,
|
|
9581
9682
|
steps: stepRef.current.map(s => ({ ...s })),
|
|
@@ -9583,7 +9684,15 @@ const StreamedContent = ({ botData, botProps }) => {
|
|
|
9583
9684
|
questions: [...questionsRef.current],
|
|
9584
9685
|
questionsStepsMap: { ...questionsStepsMapRef.current },
|
|
9585
9686
|
stepFormDataMap: { ...stepFormDataMapRef.current },
|
|
9586
|
-
}, activeConversationId)
|
|
9687
|
+
}, activeConversationId)).then(() => {
|
|
9688
|
+
if (!messageChatSessionId)
|
|
9689
|
+
return;
|
|
9690
|
+
const storedMessages = chatDataInfoRef.current?.[currentMode]?.conversations?.[activeConversationId]?.messages;
|
|
9691
|
+
if (storedMessages?.length) {
|
|
9692
|
+
storedMessages[storedMessages.length - 1].chatSessionId =
|
|
9693
|
+
messageChatSessionId;
|
|
9694
|
+
}
|
|
9695
|
+
});
|
|
9587
9696
|
// [
|
|
9588
9697
|
// {
|
|
9589
9698
|
// header: "Finding relevant information",
|
|
@@ -15031,13 +15140,19 @@ const SmartBot = (props) => {
|
|
|
15031
15140
|
if (newChatId !== undefined)
|
|
15032
15141
|
setUniqueChatId(newChatId);
|
|
15033
15142
|
};
|
|
15143
|
+
// Minimize the chat window when the user follows an in-app link from a bot response
|
|
15144
|
+
const handleMinimizeRequest = () => {
|
|
15145
|
+
setPartialClose(true);
|
|
15146
|
+
};
|
|
15034
15147
|
window.addEventListener("stepFormStreamStart", handleStepFormStreamStart);
|
|
15035
15148
|
window.addEventListener("stepFormStreamEnd", handleStepFormStreamEnd);
|
|
15036
15149
|
window.addEventListener("stepFormInitStateUpdate", handleStepFormInitStateUpdate);
|
|
15150
|
+
window.addEventListener("smartBotMinimize", handleMinimizeRequest);
|
|
15037
15151
|
return () => {
|
|
15038
15152
|
window.removeEventListener("stepFormStreamStart", handleStepFormStreamStart);
|
|
15039
15153
|
window.removeEventListener("stepFormStreamEnd", handleStepFormStreamEnd);
|
|
15040
15154
|
window.removeEventListener("stepFormInitStateUpdate", handleStepFormInitStateUpdate);
|
|
15155
|
+
window.removeEventListener("smartBotMinimize", handleMinimizeRequest);
|
|
15041
15156
|
};
|
|
15042
15157
|
}, []);
|
|
15043
15158
|
const fetchCustomBotConfigurations = async () => {
|