@constructor-io/constructorio-ui-asa 1.8.0 → 1.10.0
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/README.md +8 -210
- package/dist/constructorio-ui-asa-bundled.js +79 -21
- package/lib/cjs/components/Button/Button.js +22 -3
- package/lib/cjs/components/Chat/AiMessage.css +52 -0
- package/lib/cjs/components/Chat/AiMessage.js +6 -2
- package/lib/cjs/components/Chat/Chat.js +18 -4
- package/lib/cjs/components/Chat/ChatInput.js +3 -3
- package/lib/cjs/hooks/asaStreamHandlers.js +1 -1
- package/lib/cjs/hooks/persistence/backgroundTurns.js +35 -0
- package/lib/cjs/hooks/persistence/useChatPersistence.js +35 -10
- package/lib/cjs/hooks/useAsaResults.js +54 -26
- package/lib/cjs/hooks/useAsaTracking.js +31 -16
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/utils/chatThreads.js +15 -1
- package/lib/cjs/utils/localStoragePersistence.js +110 -19
- package/lib/cjs/utils/markdown.js +60 -0
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/Button/Button.js +26 -3
- package/lib/mjs/components/Chat/AiMessage.css +52 -0
- package/lib/mjs/components/Chat/AiMessage.js +6 -2
- package/lib/mjs/components/Chat/Chat.js +18 -4
- package/lib/mjs/components/Chat/ChatInput.js +3 -3
- package/lib/mjs/hooks/asaStreamHandlers.js +1 -1
- package/lib/mjs/hooks/persistence/backgroundTurns.js +35 -1
- package/lib/mjs/hooks/persistence/useChatPersistence.js +35 -11
- package/lib/mjs/hooks/useAsaResults.js +53 -24
- package/lib/mjs/hooks/useAsaTracking.js +40 -16
- package/lib/mjs/index.js +1 -0
- package/lib/mjs/utils/chatThreads.js +12 -0
- package/lib/mjs/utils/localStoragePersistence.js +106 -14
- package/lib/mjs/utils/markdown.js +53 -0
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/Button/Button.d.ts +3 -2
- package/lib/types/components/Chat/Chat.d.ts +13 -1
- package/lib/types/hooks/persistence/backgroundTurns.d.ts +6 -0
- package/lib/types/hooks/persistence/useChatPersistence.d.ts +4 -0
- package/lib/types/hooks/useAsaTracking.d.ts +4 -2
- package/lib/types/index.d.ts +3 -2
- package/lib/types/types.d.ts +57 -8
- package/lib/types/utils/chatThreads.d.ts +4 -0
- package/lib/types/utils/markdown.d.ts +8 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +6 -2
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
|
-
const react_1 = tslib_1.
|
|
4
|
+
const react_1 = tslib_1.__importStar(require("react"));
|
|
5
5
|
const constructorio_ui_components_1 = require("@constructor-io/constructorio-ui-components");
|
|
6
6
|
const icons_1 = require("../icons");
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const useCioAsaContext_1 = require("../../hooks/useCioAsaContext");
|
|
8
|
+
const useAsaTracking_1 = tslib_1.__importDefault(require("../../hooks/useAsaTracking"));
|
|
9
|
+
const AGENT_BUTTON_CLICK_MODE = 'chat';
|
|
10
|
+
function Button({ theme = 'dark', size = 'sm', onClick, label = 'Shopping assistant', positionOnPage, pageType, instanceId, }) {
|
|
11
|
+
var _a, _b, _c;
|
|
12
|
+
const context = (0, useCioAsaContext_1.useCioAsaContext)();
|
|
13
|
+
const agentDomain = (_a = context === null || context === void 0 ? void 0 : context.staticRequestConfigs) === null || _a === void 0 ? void 0 : _a.domain;
|
|
14
|
+
const tracking = (0, useAsaTracking_1.default)({
|
|
15
|
+
tracker: (_c = (_b = context === null || context === void 0 ? void 0 : context.cioClient) === null || _b === void 0 ? void 0 : _b.tracker) !== null && _c !== void 0 ? _c : undefined,
|
|
16
|
+
section: context === null || context === void 0 ? void 0 : context.section,
|
|
17
|
+
});
|
|
18
|
+
const handleClick = (0, react_1.useCallback)(() => {
|
|
19
|
+
var _a, _b;
|
|
20
|
+
if (agentDomain) {
|
|
21
|
+
const payload = Object.assign(Object.assign(Object.assign({ mode: AGENT_BUTTON_CLICK_MODE, agentDomain }, (positionOnPage && { positionOnPage })), (pageType && { pageType })), (instanceId && { instanceId }));
|
|
22
|
+
tracking.trackAgentButtonClick(payload);
|
|
23
|
+
(_b = (_a = context === null || context === void 0 ? void 0 : context.callbacks) === null || _a === void 0 ? void 0 : _a.onAgentButtonClick) === null || _b === void 0 ? void 0 : _b.call(_a, payload);
|
|
24
|
+
}
|
|
25
|
+
onClick === null || onClick === void 0 ? void 0 : onClick();
|
|
26
|
+
}, [agentDomain, positionOnPage, pageType, instanceId, tracking, context, onClick]);
|
|
27
|
+
return (react_1.default.createElement(constructorio_ui_components_1.Button, { className: `cio-asa-button cio-asa-button--${theme} cio-asa-button--${size}`, size: size === 'lg' ? 'default' : 'sm', onClick: handleClick },
|
|
9
28
|
theme === 'dark' ? react_1.default.createElement(icons_1.ChatBubbleDarkIcon, null) : react_1.default.createElement(icons_1.ChatBubbleLightIcon, null),
|
|
10
29
|
react_1.default.createElement("span", { className: 'cio-asa-button__label' }, label)));
|
|
11
30
|
}
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
.cio-asa-ai-message__text {
|
|
26
|
+
width: 100%;
|
|
27
|
+
min-width: 0;
|
|
28
|
+
overflow-wrap: break-word;
|
|
26
29
|
font-weight: 400;
|
|
27
30
|
font-size: 14px;
|
|
28
31
|
line-height: 20px;
|
|
@@ -33,3 +36,52 @@
|
|
|
33
36
|
.cio-asa-ai-message__bubble--error .cio-asa-ai-message__text {
|
|
34
37
|
color: #B42318;
|
|
35
38
|
}
|
|
39
|
+
|
|
40
|
+
/* Agent markdown (see utils/markdown.ts) */
|
|
41
|
+
.cio-asa-ai-message__text > :first-child {
|
|
42
|
+
margin-top: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.cio-asa-ai-message__text > :last-child {
|
|
46
|
+
margin-bottom: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.cio-asa-ai-message__text p {
|
|
50
|
+
margin: 0 0 8px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.cio-asa-ai-message__text ul,
|
|
54
|
+
.cio-asa-ai-message__text ol {
|
|
55
|
+
margin: 0 0 8px;
|
|
56
|
+
padding-left: 20px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.cio-asa-ai-message__text a {
|
|
60
|
+
color: inherit;
|
|
61
|
+
text-decoration: underline;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Wide tables scroll inside the bubble instead of widening it. */
|
|
65
|
+
.cio-asa-ai-message__text table {
|
|
66
|
+
display: block;
|
|
67
|
+
max-width: 100%;
|
|
68
|
+
overflow-x: auto;
|
|
69
|
+
margin: 0 0 8px;
|
|
70
|
+
border-collapse: collapse;
|
|
71
|
+
font-size: 12px;
|
|
72
|
+
line-height: 16px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.cio-asa-ai-message__text th,
|
|
76
|
+
.cio-asa-ai-message__text td {
|
|
77
|
+
padding: 6px 8px;
|
|
78
|
+
border: 1px solid #D5D9E2;
|
|
79
|
+
background: #FFFFFF;
|
|
80
|
+
text-align: left;
|
|
81
|
+
vertical-align: top;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.cio-asa-ai-message__text th {
|
|
85
|
+
background: #F6F7F9;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
}
|
|
@@ -4,6 +4,7 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
const react_1 = tslib_1.__importDefault(require("react"));
|
|
5
5
|
const constructorio_ui_components_1 = require("@constructor-io/constructorio-ui-components");
|
|
6
6
|
const translate_1 = tslib_1.__importDefault(require("../../utils/translate"));
|
|
7
|
+
const markdown_1 = require("../../utils/markdown");
|
|
7
8
|
const TypingIndicator_1 = tslib_1.__importDefault(require("./TypingIndicator"));
|
|
8
9
|
function AiMessage({ message, componentOverrides, translations }) {
|
|
9
10
|
var _a, _b, _c;
|
|
@@ -15,6 +16,7 @@ function AiMessage({ message, componentOverrides, translations }) {
|
|
|
15
16
|
const errorKey = message.interrupted ? 'CioAsa.error.interrupted' : 'CioAsa.error.message';
|
|
16
17
|
const text = isError ? message.text || (0, translate_1.default)(errorKey, translations) : message.text || '';
|
|
17
18
|
const hasText = text.length > 0;
|
|
19
|
+
const html = (0, markdown_1.renderMarkdown)(text);
|
|
18
20
|
const loaderRenderProps = { translations };
|
|
19
21
|
const textRenderProps = { text };
|
|
20
22
|
return (react_1.default.createElement("div", { className: 'cio-asa-ai-message' },
|
|
@@ -26,8 +28,10 @@ function AiMessage({ message, componentOverrides, translations }) {
|
|
|
26
28
|
isError && 'cio-asa-ai-message__bubble--error',
|
|
27
29
|
]
|
|
28
30
|
.filter(Boolean)
|
|
29
|
-
.join(' '), role: isError ? 'alert' : undefined },
|
|
30
|
-
|
|
31
|
+
.join(' '), role: isError ? 'alert' : undefined }, html ? (react_1.default.createElement("div", { className: 'cio-asa-ai-message__text',
|
|
32
|
+
// Sanitized by renderMarkdown
|
|
33
|
+
// eslint-disable-next-line react/no-danger
|
|
34
|
+
dangerouslySetInnerHTML: { __html: html } })) : (react_1.default.createElement("div", { className: 'cio-asa-ai-message__text' }, text)))))));
|
|
31
35
|
}
|
|
32
36
|
exports.default = AiMessage;
|
|
33
37
|
AiMessage.displayName = 'AiMessage';
|
|
@@ -8,6 +8,7 @@ const useFocusTrap_1 = tslib_1.__importDefault(require("../../hooks/useFocusTrap
|
|
|
8
8
|
const useLatest_1 = tslib_1.__importDefault(require("../../hooks/useLatest"));
|
|
9
9
|
const productNormalizer_1 = require("../../utils/productNormalizer");
|
|
10
10
|
const translate_1 = tslib_1.__importDefault(require("../../utils/translate"));
|
|
11
|
+
const markdown_1 = require("../../utils/markdown");
|
|
11
12
|
const ChatHeader_1 = tslib_1.__importDefault(require("./ChatHeader"));
|
|
12
13
|
const WelcomeScreen_1 = tslib_1.__importDefault(require("./WelcomeScreen"));
|
|
13
14
|
const ChatMessageList_1 = tslib_1.__importDefault(require("./ChatMessageList"));
|
|
@@ -25,11 +26,11 @@ function getAnnouncement(messages, translations) {
|
|
|
25
26
|
return `${youSaid}: ${userText}. ${typing}`;
|
|
26
27
|
}
|
|
27
28
|
if (lastMessage.status === 'done' && lastMessage.text) {
|
|
28
|
-
return `${(0, translate_1.default)('CioAsa.aiMessage.ariaLabel', translations)}: ${lastMessage.text}`;
|
|
29
|
+
return `${(0, translate_1.default)('CioAsa.aiMessage.ariaLabel', translations)}: ${(0, markdown_1.markdownToText)(lastMessage.text)}`;
|
|
29
30
|
}
|
|
30
31
|
return '';
|
|
31
32
|
}
|
|
32
|
-
const Chat = (0, react_1.forwardRef)(({ onClose, className, initialSuggestions, termsText, onProductClick, onViewMore, onAddToCart, aspectRatio, currency, normalizeItem, componentOverrides, translations, initialThreadId, onThreadsChange, showStopButton = false, }, ref) => {
|
|
33
|
+
const Chat = (0, react_1.forwardRef)(({ onClose, className, initialSuggestions, termsText, onProductClick, onViewMore, onAddToCart, aspectRatio, currency, normalizeItem, componentOverrides, translations, initialThreadId, initialPrompt, onThreadsChange, showStopButton = false, }, ref) => {
|
|
33
34
|
var _a;
|
|
34
35
|
const { messages, sendMessage, isStreaming, canAbort, abort, clearHistory, isHydrating, threads, activeThreadId, newThread, switchThread, } = (0, useAsaResults_1.default)({
|
|
35
36
|
initialThreadId,
|
|
@@ -49,12 +50,25 @@ const Chat = (0, react_1.forwardRef)(({ onClose, className, initialSuggestions,
|
|
|
49
50
|
const showChatFrame = showChat || (isHydrating && lastViewRef.current === 'chat');
|
|
50
51
|
const isModal = typeof onClose === 'function';
|
|
51
52
|
const announcement = getAnnouncement(messages, translations);
|
|
52
|
-
(0, react_1.useImperativeHandle)(ref, () => ({
|
|
53
|
+
(0, react_1.useImperativeHandle)(ref, () => ({
|
|
53
54
|
abort,
|
|
54
55
|
clearHistory,
|
|
55
56
|
newThread,
|
|
56
57
|
switchThread,
|
|
57
|
-
|
|
58
|
+
sendMessage: (text, options) => sendMessage(text, 'external', options),
|
|
59
|
+
}), [abort, clearHistory, newThread, switchThread, sendMessage]);
|
|
60
|
+
// Sent from a timer so StrictMode's mount, unmount, mount cannot cancel the stream it starts.
|
|
61
|
+
const initialPromptRef = (0, react_1.useRef)(initialPrompt);
|
|
62
|
+
(0, react_1.useEffect)(() => {
|
|
63
|
+
const prompt = initialPromptRef.current;
|
|
64
|
+
if (isHydrating || !prompt)
|
|
65
|
+
return undefined;
|
|
66
|
+
const timer = setTimeout(() => {
|
|
67
|
+
initialPromptRef.current = undefined;
|
|
68
|
+
sendMessage(prompt, 'external');
|
|
69
|
+
});
|
|
70
|
+
return () => clearTimeout(timer);
|
|
71
|
+
}, [isHydrating, sendMessage]);
|
|
58
72
|
const hasPersistence = Boolean((_a = (0, useCioAsaContext_1.useCioAsaContext)()) === null || _a === void 0 ? void 0 : _a.persistence);
|
|
59
73
|
const onThreadsChangeRef = (0, useLatest_1.default)(onThreadsChange);
|
|
60
74
|
(0, react_1.useEffect)(() => {
|
|
@@ -12,8 +12,7 @@ function ChatInput({ onSubmit, isDisabled = false, isStreaming = false, onAbort,
|
|
|
12
12
|
isDisabled,
|
|
13
13
|
submitOnEnterOnly: true,
|
|
14
14
|
});
|
|
15
|
-
|
|
16
|
-
const canAbort = isStreaming && showStopButton && typeof onAbort === 'function';
|
|
15
|
+
const canAbort = isStreaming && typeof onAbort === 'function';
|
|
17
16
|
const renderProps = {
|
|
18
17
|
value,
|
|
19
18
|
onChange: setValue,
|
|
@@ -21,13 +20,14 @@ function ChatInput({ onSubmit, isDisabled = false, isStreaming = false, onAbort,
|
|
|
21
20
|
placeholder: (0, translate_1.default)('CioAsa.input.placeholder', translations),
|
|
22
21
|
isDisabled,
|
|
23
22
|
isStreaming,
|
|
23
|
+
canAbort,
|
|
24
24
|
onAbort: onAbort !== null && onAbort !== void 0 ? onAbort : (() => { }),
|
|
25
25
|
};
|
|
26
26
|
return (react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { override: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.reactNode, props: renderProps },
|
|
27
27
|
react_1.default.createElement("div", { className: 'cio-asa-chat-input-wrapper' },
|
|
28
28
|
react_1.default.createElement("div", { className: 'cio-asa-chat-input' },
|
|
29
29
|
react_1.default.createElement("input", { type: 'text', className: 'cio-asa-chat-input__field', value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleKeyDown, placeholder: renderProps.placeholder, disabled: isDisabled, "aria-label": (0, translate_1.default)('CioAsa.input.ariaLabel', translations) }),
|
|
30
|
-
canAbort ? (react_1.default.createElement("button", { type: 'button', className: 'cio-asa-chat-input__send cio-asa-chat-input__stop', onClick: onAbort, "aria-label": (0, translate_1.default)('CioAsa.input.stopAriaLabel', translations) },
|
|
30
|
+
canAbort && showStopButton ? (react_1.default.createElement("button", { type: 'button', className: 'cio-asa-chat-input__send cio-asa-chat-input__stop', onClick: onAbort, "aria-label": (0, translate_1.default)('CioAsa.input.stopAriaLabel', translations) },
|
|
31
31
|
react_1.default.createElement(icons_1.StopIcon, null))) : (react_1.default.createElement("button", { type: 'button', className: 'cio-asa-chat-input__send', onClick: handleSubmit, disabled: isDisabled || !value.trim(), "aria-label": (0, translate_1.default)('CioAsa.input.sendAriaLabel', translations) },
|
|
32
32
|
react_1.default.createElement(icons_1.SendArrowIcon, null)))))));
|
|
33
33
|
}
|
|
@@ -36,7 +36,7 @@ function handleSearchResult(data, pendingGroup, assistantId, setMessages) {
|
|
|
36
36
|
}
|
|
37
37
|
exports.handleSearchResult = handleSearchResult;
|
|
38
38
|
function handleMessage(data, assistantId, setMessages) {
|
|
39
|
-
updateMessageById(setMessages, assistantId, (msg) => (Object.assign(Object.assign({}, msg), { status: 'streaming', text:
|
|
39
|
+
updateMessageById(setMessages, assistantId, (msg) => (Object.assign(Object.assign({}, msg), { status: 'streaming', text: [msg.text, data === null || data === void 0 ? void 0 : data.text].filter(Boolean).join('\n') })));
|
|
40
40
|
}
|
|
41
41
|
exports.handleMessage = handleMessage;
|
|
42
42
|
function handleFollowUpRefinement(data, assistantId, setMessages) {
|
|
@@ -31,6 +31,9 @@ class BackgroundTurn {
|
|
|
31
31
|
this.save().then(notify);
|
|
32
32
|
},
|
|
33
33
|
onDone: () => {
|
|
34
|
+
if (!this.streaming)
|
|
35
|
+
return;
|
|
36
|
+
this.release();
|
|
34
37
|
this.streaming = false;
|
|
35
38
|
this.messages = (0, chatThreads_1.normalizeHydratedMessages)(this.messages);
|
|
36
39
|
this.save().then(() => {
|
|
@@ -61,6 +64,36 @@ class BackgroundTurn {
|
|
|
61
64
|
/** Later writes go into `store`. */
|
|
62
65
|
moveTo(store) {
|
|
63
66
|
this.store = store;
|
|
67
|
+
if (this.streaming)
|
|
68
|
+
this.listen();
|
|
69
|
+
}
|
|
70
|
+
/** Stops the answer when another tab asks to, through the store it is saved into. */
|
|
71
|
+
listen() {
|
|
72
|
+
var _a, _b;
|
|
73
|
+
this.release();
|
|
74
|
+
this.stopListening = (_b = (_a = this.store).subscribeAbort) === null || _b === void 0 ? void 0 : _b.call(_a, ({ messageId }) => {
|
|
75
|
+
if (messageId === this.turn.assistantId)
|
|
76
|
+
this.abort();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
release() {
|
|
80
|
+
var _a;
|
|
81
|
+
(_a = this.stopListening) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
82
|
+
this.stopListening = undefined;
|
|
83
|
+
}
|
|
84
|
+
/** Stops the answer and stores it settled, as the stop button does on screen. */
|
|
85
|
+
abort() {
|
|
86
|
+
if (!this.streaming)
|
|
87
|
+
return;
|
|
88
|
+
this.streaming = false;
|
|
89
|
+
this.release();
|
|
90
|
+
this.turn.cancel();
|
|
91
|
+
this.messages = (0, chatThreads_1.settleCancelledReply)(this.messages, this.turn.assistantId);
|
|
92
|
+
this.save().then(() => {
|
|
93
|
+
if (this.saved)
|
|
94
|
+
turns.delete(this);
|
|
95
|
+
notify();
|
|
96
|
+
});
|
|
64
97
|
}
|
|
65
98
|
/** Stores the conversation settled, without awaiting: the page is going away. */
|
|
66
99
|
flush() {
|
|
@@ -82,6 +115,7 @@ let listeningForPagehide = false;
|
|
|
82
115
|
function continueInBackground(turn, params) {
|
|
83
116
|
const bg = new BackgroundTurn(turn, params);
|
|
84
117
|
turns.add(bg);
|
|
118
|
+
bg.listen();
|
|
85
119
|
if (!listeningForPagehide && typeof window !== 'undefined') {
|
|
86
120
|
listeningForPagehide = true;
|
|
87
121
|
window.addEventListener('pagehide', () => turns.forEach((t) => t.flush()));
|
|
@@ -98,6 +132,7 @@ function resumeBackgroundTurn(store, threadId, createOwner) {
|
|
|
98
132
|
if (!bg)
|
|
99
133
|
return null;
|
|
100
134
|
turns.delete(bg);
|
|
135
|
+
bg.release();
|
|
101
136
|
if (bg.streaming)
|
|
102
137
|
bg.turn.owner = createOwner(bg.turn);
|
|
103
138
|
return bg;
|
|
@@ -10,10 +10,12 @@ const useWriteQueue_1 = tslib_1.__importDefault(require("./useWriteQueue"));
|
|
|
10
10
|
const useThreadList_1 = tslib_1.__importDefault(require("./useThreadList"));
|
|
11
11
|
const useForeignStream_1 = tslib_1.__importDefault(require("./useForeignStream"));
|
|
12
12
|
const backgroundTurns_1 = require("./backgroundTurns");
|
|
13
|
+
/** Whether `store` can both ask another tab to stop an answer and hear such a request. */
|
|
14
|
+
const canAbortAcrossTabs = (store) => Boolean((store === null || store === void 0 ? void 0 : store.requestAbort) && store.subscribeAbort);
|
|
13
15
|
const hasUnsavedWork = (session) => session.isStreaming || session.dirty || session.orphanIds.length > 0;
|
|
14
16
|
/** Restores, saves and syncs the conversation with `store`; inert when `store` is undefined. */
|
|
15
17
|
function useChatPersistence(params) {
|
|
16
|
-
const { store, scope, index, session, initialThreadId, messages, setMessages, isStreaming, setIsStreaming, turnRef, cancelStream, } = params;
|
|
18
|
+
const { store, scope, index, session, initialThreadId, messages, setMessages, isStreaming, setIsStreaming, turnRef, cancelStream, abortTurn, } = params;
|
|
17
19
|
if (store)
|
|
18
20
|
(0, chatThreads_1.getTabId)();
|
|
19
21
|
const [isHydrating, setIsHydrating] = (0, react_1.useState)(Boolean(store));
|
|
@@ -61,6 +63,15 @@ function useChatPersistence(params) {
|
|
|
61
63
|
setIsHydrating(Boolean(store));
|
|
62
64
|
}
|
|
63
65
|
}
|
|
66
|
+
/** Settles the answer another tab left in flight, and writes it back. */
|
|
67
|
+
const settleForeign = (0, react_1.useCallback)((threadId) => {
|
|
68
|
+
if (!mountedRef.current || session.storageThreadId !== threadId)
|
|
69
|
+
return;
|
|
70
|
+
if (session.isStreaming)
|
|
71
|
+
return;
|
|
72
|
+
session.dirty = true;
|
|
73
|
+
setMessages((prev) => (0, chatThreads_1.normalizeHydratedMessages)(prev));
|
|
74
|
+
}, [session, setMessages, mountedRef]);
|
|
64
75
|
/** Puts a stored conversation on screen and makes it the one this tab continues. */
|
|
65
76
|
const showStoredChat = (0, react_1.useCallback)((chat) => {
|
|
66
77
|
(0, chatSession_1.adoptStoredChat)(session, chat);
|
|
@@ -75,15 +86,18 @@ function useChatPersistence(params) {
|
|
|
75
86
|
return;
|
|
76
87
|
}
|
|
77
88
|
setMessages(chat.messages);
|
|
78
|
-
watchForeign(remainingMs, () =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
watchForeign(remainingMs, () => settleForeign(chat.threadId));
|
|
90
|
+
}, [session, stopForeign, watchForeign, settleForeign, setMessages]);
|
|
91
|
+
/** Asks the tab streaming the answer on screen to stop it; settles it here if no tab does. */
|
|
92
|
+
const abortForeign = (0, react_1.useCallback)(() => {
|
|
93
|
+
const { current } = storeRef;
|
|
94
|
+
const threadId = session.storageThreadId;
|
|
95
|
+
const last = messagesRef.current[messagesRef.current.length - 1];
|
|
96
|
+
if (!canAbortAcrossTabs(current) || !session.foreignInFlight || !threadId || !last)
|
|
97
|
+
return;
|
|
98
|
+
current.requestAbort(threadId, last.id);
|
|
99
|
+
watchForeign(chatThreads_1.FOREIGN_ABORT_TIMEOUT_MS, () => settleForeign(threadId));
|
|
100
|
+
}, [session, storeRef, messagesRef, watchForeign, settleForeign]);
|
|
87
101
|
/** Writes the conversation as it is now; `settle` finishes an in-flight answer, `sync` skips awaiting. */
|
|
88
102
|
const persistNow = (0, react_1.useCallback)((mode) => {
|
|
89
103
|
const { current } = storeRef;
|
|
@@ -327,6 +341,15 @@ function useChatPersistence(params) {
|
|
|
327
341
|
settleAndPersist();
|
|
328
342
|
}, [detachTurn, settleAndPersist]);
|
|
329
343
|
(0, react_1.useEffect)(() => (0, backgroundTurns_1.subscribeBackgroundTurns)(refreshThreads), [refreshThreads]);
|
|
344
|
+
(0, react_1.useEffect)(() => {
|
|
345
|
+
if (!(store === null || store === void 0 ? void 0 : store.subscribeAbort))
|
|
346
|
+
return undefined;
|
|
347
|
+
return store.subscribeAbort(({ messageId }) => {
|
|
348
|
+
var _a;
|
|
349
|
+
if (((_a = turnRef.current) === null || _a === void 0 ? void 0 : _a.assistantId) === messageId)
|
|
350
|
+
abortTurn();
|
|
351
|
+
});
|
|
352
|
+
}, [store, turnRef, abortTurn]);
|
|
330
353
|
// Follow changes another tab made to the list or to the thread on screen.
|
|
331
354
|
const syncFromStore = (0, react_1.useCallback)(() => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
332
355
|
var _a;
|
|
@@ -475,6 +498,8 @@ function useChatPersistence(params) {
|
|
|
475
498
|
threads,
|
|
476
499
|
activeThreadId,
|
|
477
500
|
foreignInFlight,
|
|
501
|
+
canAbortForeign: foreignInFlight && canAbortAcrossTabs(store),
|
|
502
|
+
abortForeign,
|
|
478
503
|
beginTurn,
|
|
479
504
|
screenOwner,
|
|
480
505
|
clearHistory,
|
|
@@ -8,6 +8,7 @@ const useChatPersistence_1 = tslib_1.__importDefault(require("./persistence/useC
|
|
|
8
8
|
const agentStream_1 = require("./agentStream");
|
|
9
9
|
const chatSession_1 = require("../utils/chatSession");
|
|
10
10
|
const productNormalizer_1 = require("../utils/productNormalizer");
|
|
11
|
+
const chatThreads_1 = require("../utils/chatThreads");
|
|
11
12
|
const useLatest_1 = tslib_1.__importDefault(require("./useLatest"));
|
|
12
13
|
function useAsaResults(options) {
|
|
13
14
|
var _a;
|
|
@@ -43,6 +44,19 @@ function useAsaResults(options) {
|
|
|
43
44
|
(_a = turnRef.current) === null || _a === void 0 ? void 0 : _a.cancel();
|
|
44
45
|
turnRef.current = null;
|
|
45
46
|
}, []);
|
|
47
|
+
const abortOwnTurn = (0, react_1.useCallback)(() => {
|
|
48
|
+
var _a;
|
|
49
|
+
if (!session.isStreaming)
|
|
50
|
+
return;
|
|
51
|
+
const abortedId = (_a = turnRef.current) === null || _a === void 0 ? void 0 : _a.assistantId;
|
|
52
|
+
cancelStream();
|
|
53
|
+
session.isStreaming = false;
|
|
54
|
+
setIsStreaming(false);
|
|
55
|
+
if (abortedId)
|
|
56
|
+
setMessages((prev) => (0, chatThreads_1.settleCancelledReply)(prev, abortedId));
|
|
57
|
+
// With persistence on, the cancelled turn is stored as it stands.
|
|
58
|
+
session.dirty = true;
|
|
59
|
+
}, [session, cancelStream]);
|
|
46
60
|
const store = (0, useChatPersistence_1.default)({
|
|
47
61
|
store: persistence,
|
|
48
62
|
scope: persistenceScope,
|
|
@@ -55,16 +69,17 @@ function useAsaResults(options) {
|
|
|
55
69
|
setIsStreaming,
|
|
56
70
|
turnRef,
|
|
57
71
|
cancelStream,
|
|
72
|
+
abortTurn: abortOwnTurn,
|
|
58
73
|
});
|
|
59
|
-
const { beginTurn, screenOwner } = store;
|
|
74
|
+
const { beginTurn, screenOwner, abortForeign } = store;
|
|
60
75
|
(0, react_1.useEffect)(() => cancelStream, [cancelStream]);
|
|
61
|
-
const
|
|
76
|
+
const sendToCurrentThread = (0, react_1.useCallback)((text, source) => {
|
|
62
77
|
var _a, _b;
|
|
63
78
|
const intent = text.trim();
|
|
64
79
|
if (!intent || session.isStreaming || session.foreignInFlight)
|
|
65
80
|
return;
|
|
66
81
|
beginTurn();
|
|
67
|
-
trackingRef.current.trackSubmit(intent);
|
|
82
|
+
trackingRef.current.trackSubmit(intent, source);
|
|
68
83
|
(_b = (_a = callbacksRef.current) === null || _a === void 0 ? void 0 : _a.onAssistantSubmit) === null || _b === void 0 ? void 0 : _b.call(_a, { intent, source });
|
|
69
84
|
const userMessage = {
|
|
70
85
|
id: (0, chatSession_1.nextMessageId)(session),
|
|
@@ -113,6 +128,32 @@ function useAsaResults(options) {
|
|
|
113
128
|
callbacksRef,
|
|
114
129
|
staticRequestConfigsRef,
|
|
115
130
|
]);
|
|
131
|
+
const { newThread, switchThread } = store;
|
|
132
|
+
/**
|
|
133
|
+
* Sends `text`. With `threadId`, first moves to that conversation, loading it from storage
|
|
134
|
+
* when persistence has it and otherwise continuing it on the server with an empty screen.
|
|
135
|
+
*/
|
|
136
|
+
const sendMessage = (0, react_1.useCallback)((text, source = 'input', { threadId } = {}) => {
|
|
137
|
+
if (!threadId ||
|
|
138
|
+
threadId === session.serverThreadId ||
|
|
139
|
+
threadId === session.storageThreadId) {
|
|
140
|
+
sendToCurrentThread(text, source);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
(() => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
if (persistence)
|
|
145
|
+
yield switchThread(threadId);
|
|
146
|
+
else
|
|
147
|
+
newThread();
|
|
148
|
+
// A turn that started meanwhile (or one still streaming in the background) owns the screen.
|
|
149
|
+
if (session.isStreaming || session.foreignInFlight)
|
|
150
|
+
return;
|
|
151
|
+
if (!session.serverThreadId && !(0, chatThreads_1.isLocalThreadId)(threadId)) {
|
|
152
|
+
session.serverThreadId = threadId;
|
|
153
|
+
}
|
|
154
|
+
sendToCurrentThread(text, source);
|
|
155
|
+
}))();
|
|
156
|
+
}, [session, persistence, newThread, switchThread, sendToCurrentThread]);
|
|
116
157
|
/**
|
|
117
158
|
* Cancels the in-flight request while keeping the conversation: the partial reply is
|
|
118
159
|
* settled as `done` (so the typing indicator stops and streamed text survives) and the
|
|
@@ -127,40 +168,27 @@ function useAsaResults(options) {
|
|
|
127
168
|
* No beacon is sent: there is no "aborted" ASA event, and reporting the load as
|
|
128
169
|
* finished would be false. An aborted turn therefore leaves an
|
|
129
170
|
* `assistant_result_load_started` with no matching finished event.
|
|
171
|
+
*
|
|
172
|
+
* An answer streaming in another tab is stopped by that tab, which stores the result.
|
|
130
173
|
*/
|
|
131
174
|
const abort = (0, react_1.useCallback)(() => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
session.isStreaming = false;
|
|
138
|
-
setIsStreaming(false);
|
|
139
|
-
if (abortedId) {
|
|
140
|
-
setMessages((prev) => prev.flatMap((msg) => {
|
|
141
|
-
var _a;
|
|
142
|
-
if (msg.id !== abortedId)
|
|
143
|
-
return [msg];
|
|
144
|
-
if (!msg.text && !((_a = msg.groups) === null || _a === void 0 ? void 0 : _a.length) && !msg.refinement)
|
|
145
|
-
return [];
|
|
146
|
-
return [Object.assign(Object.assign({}, msg), { status: 'done' })];
|
|
147
|
-
}));
|
|
148
|
-
}
|
|
149
|
-
// With persistence on, the cancelled turn is stored as it stands.
|
|
150
|
-
session.dirty = true;
|
|
151
|
-
}, [session, cancelStream]);
|
|
175
|
+
if (session.isStreaming)
|
|
176
|
+
abortOwnTurn();
|
|
177
|
+
else
|
|
178
|
+
abortForeign();
|
|
179
|
+
}, [session, abortOwnTurn, abortForeign]);
|
|
152
180
|
return {
|
|
153
181
|
messages,
|
|
154
182
|
sendMessage,
|
|
155
183
|
isStreaming: isStreaming || store.foreignInFlight,
|
|
156
|
-
canAbort: isStreaming,
|
|
184
|
+
canAbort: isStreaming || store.canAbortForeign,
|
|
157
185
|
abort,
|
|
158
186
|
clearHistory: store.clearHistory,
|
|
159
187
|
isHydrating: store.isHydrating,
|
|
160
188
|
threads: store.threads,
|
|
161
189
|
activeThreadId: store.activeThreadId,
|
|
162
|
-
newThread
|
|
163
|
-
switchThread
|
|
190
|
+
newThread,
|
|
191
|
+
switchThread,
|
|
164
192
|
};
|
|
165
193
|
}
|
|
166
194
|
exports.default = useAsaResults;
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const react_1 = require("react");
|
|
4
|
+
/** Beacon `source` values, aligned with the ones the behavioral-actions API documents. */
|
|
5
|
+
const BEACON_SUBMIT_SOURCES = {
|
|
6
|
+
input: 'input',
|
|
7
|
+
suggestion: 'suggestion',
|
|
8
|
+
refinement: 'follow_up',
|
|
9
|
+
// A prompt the host page sent, typically a suggested question clicked outside the chat.
|
|
10
|
+
external: 'suggestion',
|
|
11
|
+
};
|
|
4
12
|
const NOOP_TRACKING = {
|
|
13
|
+
trackAgentButtonClick: () => { },
|
|
5
14
|
trackSubmit: () => { },
|
|
6
15
|
trackResultLoadStarted: () => { },
|
|
7
16
|
trackResultLoadFinished: () => { },
|
|
@@ -15,37 +24,42 @@ const NOOP_TRACKING = {
|
|
|
15
24
|
* Mirrors the tracking-hook pattern used by constructorio-ui-pia.
|
|
16
25
|
*/
|
|
17
26
|
function useAsaTracking({ tracker, section, threadId, }) {
|
|
18
|
-
const assistant = tracker;
|
|
19
27
|
const base = (0, react_1.useMemo)(() => (Object.assign(Object.assign({}, (section && { section })), (threadId && { threadId }))), [section, threadId]);
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
const trackAgentButtonClick = (0, react_1.useCallback)(({ mode, agentDomain, positionOnPage, pageType, instanceId }) => {
|
|
29
|
+
var _a;
|
|
30
|
+
(_a = tracker === null || tracker === void 0 ? void 0 : tracker.trackAgentButtonClick) === null || _a === void 0 ? void 0 : _a.call(tracker, Object.assign(Object.assign(Object.assign(Object.assign({ mode,
|
|
31
|
+
agentDomain }, (positionOnPage && { positionOnPage })), (pageType && { pageType })), (instanceId && { instanceId })), (section && { section })));
|
|
32
|
+
}, [tracker, section]);
|
|
33
|
+
const trackSubmit = (0, react_1.useCallback)((intent, source) => {
|
|
34
|
+
tracker === null || tracker === void 0 ? void 0 : tracker.trackAssistantSubmit(Object.assign(Object.assign({ intent }, (source && { source: BEACON_SUBMIT_SOURCES[source] })), base));
|
|
35
|
+
}, [tracker, base]);
|
|
23
36
|
const trackResultLoadStarted = (0, react_1.useCallback)(({ intent, intentResultId }) => {
|
|
24
|
-
|
|
25
|
-
}, [
|
|
37
|
+
tracker === null || tracker === void 0 ? void 0 : tracker.trackAssistantResultLoadStarted(Object.assign(Object.assign({ intent }, (intentResultId && { intentResultId })), base));
|
|
38
|
+
}, [tracker, base]);
|
|
26
39
|
const trackResultLoadFinished = (0, react_1.useCallback)(({ intent, searchResultCount, intentResultId, threadId: turnThreadId, }) => {
|
|
27
|
-
|
|
40
|
+
tracker === null || tracker === void 0 ? void 0 : tracker.trackAssistantResultLoadFinished(Object.assign(Object.assign(Object.assign({ intent,
|
|
28
41
|
searchResultCount }, (intentResultId && { intentResultId })), base), (turnThreadId && { threadId: turnThreadId })));
|
|
29
|
-
}, [
|
|
42
|
+
}, [tracker, base]);
|
|
30
43
|
const trackResultClick = (0, react_1.useCallback)(({ intent, searchResultId, intentResultId, itemId, itemName, variationId, }) => {
|
|
31
|
-
|
|
44
|
+
tracker === null || tracker === void 0 ? void 0 : tracker.trackAssistantResultClick(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ intent,
|
|
32
45
|
searchResultId }, (intentResultId && { intentResultId })), (itemId && { itemId })), (itemName && { itemName })), (variationId && { variationId })), base));
|
|
33
|
-
}, [
|
|
46
|
+
}, [tracker, base]);
|
|
34
47
|
const trackResultView = (0, react_1.useCallback)(({ intent, searchResultId, numResultsViewed, intentResultId, items }) => {
|
|
35
|
-
|
|
48
|
+
tracker === null || tracker === void 0 ? void 0 : tracker.trackAssistantResultView(Object.assign(Object.assign(Object.assign({ intent,
|
|
36
49
|
searchResultId,
|
|
37
50
|
numResultsViewed }, (intentResultId && { intentResultId })), (items && items.length > 0 && { items })), base));
|
|
38
|
-
}, [
|
|
51
|
+
}, [tracker, base]);
|
|
39
52
|
const trackSearchSubmit = (0, react_1.useCallback)(({ intent, searchTerm, userInput, searchResultId, intentResultId, groupId, }) => {
|
|
40
|
-
|
|
53
|
+
tracker === null || tracker === void 0 ? void 0 : tracker.trackAssistantSearchSubmit(Object.assign(Object.assign(Object.assign({ intent,
|
|
41
54
|
searchTerm,
|
|
42
55
|
userInput,
|
|
43
56
|
searchResultId }, (intentResultId && { intentResultId })), (groupId && { groupId })), base));
|
|
44
|
-
}, [
|
|
57
|
+
}, [tracker, base]);
|
|
45
58
|
return (0, react_1.useMemo)(() => {
|
|
46
|
-
if (!
|
|
59
|
+
if (!tracker)
|
|
47
60
|
return NOOP_TRACKING;
|
|
48
61
|
return {
|
|
62
|
+
trackAgentButtonClick,
|
|
49
63
|
trackSubmit,
|
|
50
64
|
trackResultLoadStarted,
|
|
51
65
|
trackResultLoadFinished,
|
|
@@ -54,7 +68,8 @@ function useAsaTracking({ tracker, section, threadId, }) {
|
|
|
54
68
|
trackSearchSubmit,
|
|
55
69
|
};
|
|
56
70
|
}, [
|
|
57
|
-
|
|
71
|
+
tracker,
|
|
72
|
+
trackAgentButtonClick,
|
|
58
73
|
trackSubmit,
|
|
59
74
|
trackResultLoadStarted,
|
|
60
75
|
trackResultLoadFinished,
|
package/lib/cjs/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ConstructorIOClient = exports.clearPersistedConversations = exports.normalizeItemToProduct = exports.useCioAsaContext = exports.useAsaTracking = exports.useAsaResults = exports.Button = exports.ResultsBlock = exports.Chat = exports.CioAsaProvider = exports.CioAsa = void 0;
|
|
6
|
+
exports.ConstructorIOClient = exports.renderMarkdown = exports.clearPersistedConversations = exports.normalizeItemToProduct = exports.useCioAsaContext = exports.useAsaTracking = exports.useAsaResults = exports.Button = exports.ResultsBlock = exports.Chat = exports.CioAsaProvider = exports.CioAsa = void 0;
|
|
7
7
|
// Components
|
|
8
8
|
var CioAsa_1 = require("./components/CioAsa/CioAsa");
|
|
9
9
|
Object.defineProperty(exports, "CioAsa", { enumerable: true, get: function () { return __importDefault(CioAsa_1).default; } });
|
|
@@ -27,6 +27,8 @@ var productNormalizer_1 = require("./utils/productNormalizer");
|
|
|
27
27
|
Object.defineProperty(exports, "normalizeItemToProduct", { enumerable: true, get: function () { return productNormalizer_1.normalizeItemToProduct; } });
|
|
28
28
|
var localStoragePersistence_1 = require("./utils/localStoragePersistence");
|
|
29
29
|
Object.defineProperty(exports, "clearPersistedConversations", { enumerable: true, get: function () { return localStoragePersistence_1.clearPersistedConversations; } });
|
|
30
|
+
var markdown_1 = require("./utils/markdown");
|
|
31
|
+
Object.defineProperty(exports, "renderMarkdown", { enumerable: true, get: function () { return markdown_1.renderMarkdown; } });
|
|
30
32
|
// Re-exported client value from the JS client (used to construct/type a cioClient)
|
|
31
33
|
var types_1 = require("./types");
|
|
32
34
|
Object.defineProperty(exports, "ConstructorIOClient", { enumerable: true, get: function () { return types_1.ConstructorIOClient; } });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.moveThreads = exports.foreignStreamRemainingMs = exports.isThreadStreaming = exports.findRekeyedThread = exports.saveThreadAndRetireStale = exports.mergeMessages = exports.compactMessages = exports.normalizeHydratedMessages = exports.isInFlight = exports.nextMessageCounter = exports.getThreadTitle = exports.isLocalThreadId = exports.getTabId = exports.createLocalThreadId = exports.randomId = exports.IN_FLIGHT_GRACE_MS = exports.DEFAULT_PERSISTENCE_TTL_MS = exports.DEFAULT_PERSISTENCE_KEY = exports.PERSISTED_CHAT_VERSION = void 0;
|
|
3
|
+
exports.moveThreads = exports.foreignStreamRemainingMs = exports.isThreadStreaming = exports.findRekeyedThread = exports.saveThreadAndRetireStale = exports.mergeMessages = exports.compactMessages = exports.settleCancelledReply = exports.normalizeHydratedMessages = exports.isInFlight = exports.nextMessageCounter = exports.getThreadTitle = exports.isLocalThreadId = exports.getTabId = exports.createLocalThreadId = exports.randomId = exports.FOREIGN_ABORT_TIMEOUT_MS = exports.IN_FLIGHT_GRACE_MS = exports.DEFAULT_PERSISTENCE_TTL_MS = exports.DEFAULT_PERSISTENCE_KEY = exports.PERSISTED_CHAT_VERSION = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
/** Stored record format; records with another version are ignored on read. */
|
|
6
6
|
exports.PERSISTED_CHAT_VERSION = 1;
|
|
@@ -10,6 +10,8 @@ exports.DEFAULT_PERSISTENCE_KEY = 'cio-asa:chat';
|
|
|
10
10
|
exports.DEFAULT_PERSISTENCE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
11
11
|
/** How long a stored answer written by another tab is still shown as "typing" before it is settled. */
|
|
12
12
|
exports.IN_FLIGHT_GRACE_MS = 60 * 1000;
|
|
13
|
+
/** How long to wait for the tab streaming an answer to store it stopped, before settling it here. */
|
|
14
|
+
exports.FOREIGN_ABORT_TIMEOUT_MS = 3 * 1000;
|
|
13
15
|
/** Marks client-side thread ids, used before the server has assigned one. Never sent to the agent. */
|
|
14
16
|
const LOCAL_THREAD_PREFIX = 'local-';
|
|
15
17
|
/** Thread titles are the first question, cut to this many characters. */
|
|
@@ -125,6 +127,18 @@ function normalizeHydratedMessages(messages) {
|
|
|
125
127
|
});
|
|
126
128
|
}
|
|
127
129
|
exports.normalizeHydratedMessages = normalizeHydratedMessages;
|
|
130
|
+
/** `messages` with the cancelled reply settled: `done` when it has content, dropped otherwise. */
|
|
131
|
+
function settleCancelledReply(messages, assistantId) {
|
|
132
|
+
return messages.flatMap((m) => {
|
|
133
|
+
var _a;
|
|
134
|
+
if (m.id !== assistantId)
|
|
135
|
+
return [m];
|
|
136
|
+
if (!m.text && !((_a = m.groups) === null || _a === void 0 ? void 0 : _a.length) && !m.refinement)
|
|
137
|
+
return [];
|
|
138
|
+
return [Object.assign(Object.assign({}, m), { status: 'done' })];
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
exports.settleCancelledReply = settleCancelledReply;
|
|
128
142
|
/** Messages as stored: raw items replaced by the cards `normalize` makes; a restored group keeps its cards. */
|
|
129
143
|
function compactMessages(messages, normalize) {
|
|
130
144
|
return messages.map((m) => {
|