@constructor-io/constructorio-ui-asa 1.7.2 → 1.8.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 +21 -17
- package/dist/constructorio-ui-asa-bundled.js +17 -17
- package/lib/cjs/components/Chat/AiMessage.js +2 -3
- package/lib/cjs/components/Chat/Chat.js +38 -10
- package/lib/cjs/components/CioAsa/CioAsa.js +6 -2
- package/lib/cjs/components/CioAsaProvider/CioAsaProvider.js +44 -3
- package/lib/cjs/components/ResultsBlock/ResultsGroup.js +5 -5
- package/lib/cjs/hooks/agentStream.js +113 -0
- package/lib/cjs/hooks/asaStreamHandlers.js +2 -1
- package/lib/cjs/hooks/persistence/backgroundTurns.js +121 -0
- package/lib/cjs/hooks/persistence/useChatPersistence.js +485 -0
- package/lib/cjs/hooks/persistence/useForeignStream.js +34 -0
- package/lib/cjs/hooks/persistence/useThreadList.js +45 -0
- package/lib/cjs/hooks/persistence/useWriteQueue.js +17 -0
- package/lib/cjs/hooks/useAsaResults.js +93 -160
- package/lib/cjs/hooks/useAsaTracking.js +3 -3
- package/lib/cjs/hooks/useIsMounted.js +15 -0
- package/lib/cjs/hooks/useLatest.js +10 -0
- package/lib/cjs/hooks/useViewportTracking.js +4 -3
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/utils/chatRecordValidation.js +54 -0
- package/lib/cjs/utils/chatSession.js +98 -0
- package/lib/cjs/utils/chatThreads.js +248 -0
- package/lib/cjs/utils/clientOptions.js +11 -0
- package/lib/cjs/utils/localStoragePersistence.js +391 -0
- package/lib/cjs/utils/translate.js +1 -0
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/Chat/AiMessage.js +2 -3
- package/lib/mjs/components/Chat/Chat.js +36 -10
- package/lib/mjs/components/CioAsa/CioAsa.js +6 -2
- package/lib/mjs/components/CioAsaProvider/CioAsaProvider.js +44 -4
- package/lib/mjs/components/ResultsBlock/ResultsGroup.js +2 -1
- package/lib/mjs/hooks/agentStream.js +106 -0
- package/lib/mjs/hooks/asaStreamHandlers.js +1 -1
- package/lib/mjs/hooks/persistence/backgroundTurns.js +120 -0
- package/lib/mjs/hooks/persistence/useChatPersistence.js +474 -0
- package/lib/mjs/hooks/persistence/useForeignStream.js +31 -0
- package/lib/mjs/hooks/persistence/useThreadList.js +41 -0
- package/lib/mjs/hooks/persistence/useWriteQueue.js +14 -0
- package/lib/mjs/hooks/useAsaResults.js +87 -157
- package/lib/mjs/hooks/useAsaTracking.js +2 -1
- package/lib/mjs/hooks/useIsMounted.js +12 -0
- package/lib/mjs/hooks/useLatest.js +7 -0
- package/lib/mjs/hooks/useViewportTracking.js +3 -3
- package/lib/mjs/index.js +1 -0
- package/lib/mjs/utils/chatRecordValidation.js +51 -0
- package/lib/mjs/utils/chatSession.js +86 -0
- package/lib/mjs/utils/chatThreads.js +219 -0
- package/lib/mjs/utils/clientOptions.js +7 -0
- package/lib/mjs/utils/localStoragePersistence.js +373 -0
- package/lib/mjs/utils/translate.js +1 -0
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/Chat/Chat.d.ts +8 -2
- package/lib/types/components/CioAsa/CioAsa.d.ts +1 -1
- package/lib/types/hooks/agentStream.d.ts +48 -0
- package/lib/types/hooks/asaStreamHandlers.d.ts +1 -0
- package/lib/types/hooks/persistence/backgroundTurns.d.ts +43 -0
- package/lib/types/hooks/persistence/useChatPersistence.d.ts +34 -0
- package/lib/types/hooks/persistence/useForeignStream.d.ts +9 -0
- package/lib/types/hooks/persistence/useThreadList.d.ts +9 -0
- package/lib/types/hooks/persistence/useWriteQueue.d.ts +5 -0
- package/lib/types/hooks/useAsaTracking.d.ts +2 -0
- package/lib/types/hooks/useIsMounted.d.ts +3 -0
- package/lib/types/hooks/useLatest.d.ts +3 -0
- package/lib/types/index.d.ts +2 -1
- package/lib/types/types.d.ts +134 -2
- package/lib/types/utils/chatRecordValidation.d.ts +2 -0
- package/lib/types/utils/chatSession.d.ts +45 -0
- package/lib/types/utils/chatThreads.d.ts +63 -0
- package/lib/types/utils/clientOptions.d.ts +5 -0
- package/lib/types/utils/localStoragePersistence.d.ts +26 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -12,9 +12,8 @@ function AiMessage({ message, componentOverrides, translations }) {
|
|
|
12
12
|
const hasGroups = !!((_a = message.groups) === null || _a === void 0 ? void 0 : _a.length);
|
|
13
13
|
// On error, fall back to the (translatable) error message when the agent
|
|
14
14
|
// returned no partial text of its own.
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
: message.text || '';
|
|
15
|
+
const errorKey = message.interrupted ? 'CioAsa.error.interrupted' : 'CioAsa.error.message';
|
|
16
|
+
const text = isError ? message.text || (0, translate_1.default)(errorKey, translations) : message.text || '';
|
|
18
17
|
const hasText = text.length > 0;
|
|
19
18
|
const loaderRenderProps = { translations };
|
|
20
19
|
const textRenderProps = { text };
|
|
@@ -3,7 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const react_1 = tslib_1.__importStar(require("react"));
|
|
5
5
|
const useAsaResults_1 = tslib_1.__importDefault(require("../../hooks/useAsaResults"));
|
|
6
|
+
const useCioAsaContext_1 = require("../../hooks/useCioAsaContext");
|
|
6
7
|
const useFocusTrap_1 = tslib_1.__importDefault(require("../../hooks/useFocusTrap"));
|
|
8
|
+
const useLatest_1 = tslib_1.__importDefault(require("../../hooks/useLatest"));
|
|
9
|
+
const productNormalizer_1 = require("../../utils/productNormalizer");
|
|
7
10
|
const translate_1 = tslib_1.__importDefault(require("../../utils/translate"));
|
|
8
11
|
const ChatHeader_1 = tslib_1.__importDefault(require("./ChatHeader"));
|
|
9
12
|
const WelcomeScreen_1 = tslib_1.__importDefault(require("./WelcomeScreen"));
|
|
@@ -26,20 +29,43 @@ function getAnnouncement(messages, translations) {
|
|
|
26
29
|
}
|
|
27
30
|
return '';
|
|
28
31
|
}
|
|
29
|
-
const Chat = (0, react_1.forwardRef)(({ onClose, className, initialSuggestions, termsText, onProductClick, onViewMore, onAddToCart, aspectRatio, currency, normalizeItem, componentOverrides, translations, initialThreadId, showStopButton = false, }, ref) => {
|
|
30
|
-
|
|
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
|
+
var _a;
|
|
34
|
+
const { messages, sendMessage, isStreaming, canAbort, abort, clearHistory, isHydrating, threads, activeThreadId, newThread, switchThread, } = (0, useAsaResults_1.default)({
|
|
31
35
|
initialThreadId,
|
|
36
|
+
normalizeItem: (item) => (normalizeItem !== null && normalizeItem !== void 0 ? normalizeItem : productNormalizer_1.normalizeItemToProduct)(item, {
|
|
37
|
+
saleBadgeText: (0, translate_1.default)('CioAsa.results.saleBadge', translations),
|
|
38
|
+
}),
|
|
32
39
|
});
|
|
33
40
|
const chatViewRef = (0, react_1.useRef)(null);
|
|
34
41
|
const containerRef = (0, react_1.useRef)(null);
|
|
35
42
|
const isWelcome = messages.length === 0;
|
|
43
|
+
// While another thread loads, keep the chat frame (header, close button) instead of a blank body.
|
|
44
|
+
const lastViewRef = (0, react_1.useRef)(null);
|
|
45
|
+
if (!isHydrating)
|
|
46
|
+
lastViewRef.current = isWelcome ? 'welcome' : 'chat';
|
|
47
|
+
const showWelcome = !isHydrating && isWelcome;
|
|
48
|
+
const showChat = !isHydrating && !isWelcome;
|
|
49
|
+
const showChatFrame = showChat || (isHydrating && lastViewRef.current === 'chat');
|
|
36
50
|
const isModal = typeof onClose === 'function';
|
|
37
51
|
const announcement = getAnnouncement(messages, translations);
|
|
38
|
-
(0, react_1.useImperativeHandle)(ref, () => ({
|
|
52
|
+
(0, react_1.useImperativeHandle)(ref, () => ({ abort, clearHistory, newThread, switchThread }), [
|
|
39
53
|
abort,
|
|
40
54
|
clearHistory,
|
|
41
|
-
|
|
55
|
+
newThread,
|
|
56
|
+
switchThread,
|
|
57
|
+
]);
|
|
58
|
+
const hasPersistence = Boolean((_a = (0, useCioAsaContext_1.useCioAsaContext)()) === null || _a === void 0 ? void 0 : _a.persistence);
|
|
59
|
+
const onThreadsChangeRef = (0, useLatest_1.default)(onThreadsChange);
|
|
42
60
|
(0, react_1.useEffect)(() => {
|
|
61
|
+
var _a;
|
|
62
|
+
if (!hasPersistence || isHydrating)
|
|
63
|
+
return;
|
|
64
|
+
(_a = onThreadsChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onThreadsChangeRef, threads, activeThreadId);
|
|
65
|
+
}, [threads, activeThreadId, isHydrating, hasPersistence, onThreadsChangeRef]);
|
|
66
|
+
(0, react_1.useEffect)(() => {
|
|
67
|
+
if (isHydrating)
|
|
68
|
+
return;
|
|
43
69
|
const root = isWelcome ? containerRef.current : chatViewRef.current;
|
|
44
70
|
const input = root === null || root === void 0 ? void 0 : root.querySelector('input');
|
|
45
71
|
if (input) {
|
|
@@ -52,15 +78,17 @@ const Chat = (0, react_1.forwardRef)(({ onClose, className, initialSuggestions,
|
|
|
52
78
|
input.focus();
|
|
53
79
|
}
|
|
54
80
|
}
|
|
55
|
-
}, [isWelcome]);
|
|
81
|
+
}, [isWelcome, isHydrating]);
|
|
56
82
|
(0, useFocusTrap_1.default)(containerRef, { onEscape: onClose, trapFocus: isModal });
|
|
57
83
|
return (react_1.default.createElement("div", { className: ['cio-asa', 'cio-asa-chat', className].filter(Boolean).join(' '), ref: containerRef, role: 'dialog', "aria-modal": isModal ? 'true' : undefined, "aria-label": (0, translate_1.default)(isWelcome ? 'CioAsa.welcome.title' : 'CioAsa.header.title', translations) },
|
|
58
84
|
react_1.default.createElement("div", { className: 'cio-sr-only', role: 'status' }, announcement),
|
|
59
|
-
react_1.default.createElement("div", { className: 'cio-asa-chat-body' },
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
react_1.default.createElement(
|
|
63
|
-
|
|
85
|
+
react_1.default.createElement("div", { className: 'cio-asa-chat-body' },
|
|
86
|
+
showWelcome && (react_1.default.createElement("div", { className: 'cio-asa-chat-view cio-asa-chat-view--welcome' },
|
|
87
|
+
react_1.default.createElement(WelcomeScreen_1.default, { suggestions: initialSuggestions, onSend: sendMessage, onClose: onClose, termsText: termsText, translations: translations, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.welcomeScreen }))),
|
|
88
|
+
showChatFrame && (react_1.default.createElement("div", { className: 'cio-asa-chat-view cio-asa-chat-view--chat', ref: chatViewRef },
|
|
89
|
+
react_1.default.createElement(ChatHeader_1.default, { onClose: onClose, translations: translations, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.header }),
|
|
90
|
+
react_1.default.createElement(ChatMessageList_1.default, { messages: messages, onProductClick: onProductClick, onViewMore: onViewMore, onAddToCart: onAddToCart, aspectRatio: aspectRatio, currency: currency, normalizeItem: normalizeItem, addToCartText: (0, translate_1.default)('CioAsa.results.addToCart', translations), viewMoreText: (0, translate_1.default)('CioAsa.results.viewMore', translations), aiMessageOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.aiMessage, userMessageOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.userMessage, resultsBlockOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.resultsBlock, translations: translations, onSend: sendMessage, isStreaming: isStreaming }),
|
|
91
|
+
react_1.default.createElement(ChatInput_1.default, { onSubmit: sendMessage, isDisabled: isStreaming || isHydrating, isStreaming: isStreaming, onAbort: canAbort ? abort : undefined, showStopButton: showStopButton, translations: translations, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.input }))))));
|
|
64
92
|
});
|
|
65
93
|
Chat.displayName = 'Chat';
|
|
66
94
|
exports.default = Chat;
|
|
@@ -15,8 +15,12 @@ const Chat_1 = tslib_1.__importDefault(require("../Chat/Chat"));
|
|
|
15
15
|
const CioAsa = react_1.default.forwardRef((props, ref) => {
|
|
16
16
|
const {
|
|
17
17
|
// Provider configuration — everything else belongs to Chat.
|
|
18
|
-
apiKey, cioClient, testCells, staticRequestConfigs, formatters, urlHelpers, callbacks, section } = props, chatProps = tslib_1.__rest(props, ["apiKey", "cioClient", "testCells", "staticRequestConfigs", "formatters", "urlHelpers", "callbacks", "section"]);
|
|
19
|
-
|
|
18
|
+
apiKey, cioClient, testCells, staticRequestConfigs, formatters, urlHelpers, callbacks, section, persistConversation, userId = null } = props, chatProps = tslib_1.__rest(props, ["apiKey", "cioClient", "testCells", "staticRequestConfigs", "formatters", "urlHelpers", "callbacks", "section", "persistConversation", "userId"]);
|
|
19
|
+
// Split so TypeScript can match the pair against the provider's props.
|
|
20
|
+
const persistenceProps = persistConversation
|
|
21
|
+
? { persistConversation, userId }
|
|
22
|
+
: { userId };
|
|
23
|
+
return (react_1.default.createElement(CioAsaProvider_1.default, Object.assign({ apiKey: apiKey, cioClient: cioClient, testCells: testCells, staticRequestConfigs: staticRequestConfigs, formatters: formatters, urlHelpers: urlHelpers, callbacks: callbacks, section: section }, persistenceProps),
|
|
20
24
|
react_1.default.createElement(Chat_1.default, Object.assign({}, chatProps, { ref: ref }))));
|
|
21
25
|
});
|
|
22
26
|
CioAsa.displayName = 'CioAsa';
|
|
@@ -6,15 +6,42 @@ const useCioClient_1 = tslib_1.__importDefault(require("../../hooks/useCioClient
|
|
|
6
6
|
const useCioAsaContext_1 = require("../../hooks/useCioAsaContext");
|
|
7
7
|
const defaultFormatters = tslib_1.__importStar(require("../../utils/formatters"));
|
|
8
8
|
const defaultUrlHelpers = tslib_1.__importStar(require("../../utils/urlHelpers"));
|
|
9
|
+
const clientOptions_1 = require("../../utils/clientOptions");
|
|
10
|
+
const localStoragePersistence_1 = require("../../utils/localStoragePersistence");
|
|
9
11
|
function CioAsaProvider(props) {
|
|
10
|
-
|
|
12
|
+
var _a;
|
|
13
|
+
const { apiKey, formatters, urlHelpers, staticRequestConfigs = { domain: 'chatbot' }, cioClient: customCioClient, testCells, callbacks, section = 'Products', persistConversation: persistOptions, userId: userIdProp, children, } = props;
|
|
14
|
+
// Read field by field: an inline options object is a new reference on every render.
|
|
15
|
+
const persistenceEnabled = Boolean(persistOptions === null || persistOptions === void 0 ? void 0 : persistOptions.enabled);
|
|
11
16
|
const [cioClientOptions, setCioClientOptions] = (0, react_1.useState)({});
|
|
17
|
+
const initialUserId = (0, react_1.useRef)(userIdProp).current;
|
|
18
|
+
const clientInit = (0, react_1.useMemo)(() => initialUserId === undefined
|
|
19
|
+
? cioClientOptions
|
|
20
|
+
: Object.assign(Object.assign({}, cioClientOptions), { userId: (0, localStoragePersistence_1.shopperId)(initialUserId) }), [cioClientOptions, initialUserId]);
|
|
12
21
|
const cioClient = (0, useCioClient_1.default)({
|
|
13
22
|
apiKey,
|
|
14
23
|
cioClient: customCioClient,
|
|
15
|
-
cioClientOptions,
|
|
24
|
+
cioClientOptions: clientInit,
|
|
16
25
|
testCells,
|
|
17
26
|
});
|
|
27
|
+
const userId = (0, localStoragePersistence_1.shopperId)(userIdProp);
|
|
28
|
+
const syncUserId = persistenceEnabled || userIdProp !== undefined;
|
|
29
|
+
(0, react_1.useEffect)(() => {
|
|
30
|
+
if (customCioClient || !cioClient || !syncUserId)
|
|
31
|
+
return;
|
|
32
|
+
cioClient.setClientOptions({ userId });
|
|
33
|
+
}, [cioClient, customCioClient, syncUserId, userId]);
|
|
34
|
+
const resolvedApiKey = apiKey !== null && apiKey !== void 0 ? apiKey : (_a = (0, clientOptions_1.readClientOptions)(cioClient)) === null || _a === void 0 ? void 0 : _a.apiKey;
|
|
35
|
+
const { domain } = staticRequestConfigs;
|
|
36
|
+
const persistence = (0, react_1.useMemo)(() => {
|
|
37
|
+
if (!persistenceEnabled || resolvedApiKey === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
return (0, localStoragePersistence_1.sharedLocalStoragePersistence)((0, localStoragePersistence_1.persistenceNamespace)({ apiKey: resolvedApiKey, domain, userId }), (0, localStoragePersistence_1.storageAreaFor)(userId));
|
|
40
|
+
}, [persistenceEnabled, resolvedApiKey, domain, userId]);
|
|
41
|
+
const persistenceScope = persistence && (userId === undefined ? 'guest' : 'user');
|
|
42
|
+
const persistenceIndex = persistence && resolvedApiKey !== undefined
|
|
43
|
+
? (0, localStoragePersistence_1.persistenceNamespace)({ apiKey: resolvedApiKey, domain })
|
|
44
|
+
: undefined;
|
|
18
45
|
const contextValue = (0, react_1.useMemo)(() => ({
|
|
19
46
|
cioClient,
|
|
20
47
|
cioClientOptions,
|
|
@@ -24,7 +51,21 @@ function CioAsaProvider(props) {
|
|
|
24
51
|
urlHelpers: Object.assign(Object.assign({}, defaultUrlHelpers), urlHelpers),
|
|
25
52
|
callbacks,
|
|
26
53
|
section,
|
|
27
|
-
|
|
54
|
+
persistence,
|
|
55
|
+
persistenceScope,
|
|
56
|
+
persistenceIndex,
|
|
57
|
+
}), [
|
|
58
|
+
cioClient,
|
|
59
|
+
cioClientOptions,
|
|
60
|
+
formatters,
|
|
61
|
+
urlHelpers,
|
|
62
|
+
staticRequestConfigs,
|
|
63
|
+
callbacks,
|
|
64
|
+
section,
|
|
65
|
+
persistence,
|
|
66
|
+
persistenceScope,
|
|
67
|
+
persistenceIndex,
|
|
68
|
+
]);
|
|
28
69
|
return (react_1.default.createElement(useCioAsaContext_1.AsaContext.Provider, { value: contextValue }, typeof children === 'function' ? children(contextValue) : children));
|
|
29
70
|
}
|
|
30
71
|
exports.default = CioAsaProvider;
|
|
@@ -20,9 +20,9 @@ function NextButton() {
|
|
|
20
20
|
* `searchResultId` are known.
|
|
21
21
|
*/
|
|
22
22
|
function ResultsGroup({ groupData, label, slidesToShow, gap, showTitle, viewMoreText, addToCartText, saleBadgeText, currency, intent, intentResultId, normalizeItem, onProductClick, onAddToCart, onViewMore, componentOverrides, tracking, callbacks, }) {
|
|
23
|
-
var _a, _b, _c, _d;
|
|
23
|
+
var _a, _b, _c, _d, _e;
|
|
24
24
|
const { searchResultId } = groupData;
|
|
25
|
-
const products = groupData.searchResults.map((item) => normalizeItem(item, { saleBadgeText }));
|
|
25
|
+
const products = (_a = groupData.products) !== null && _a !== void 0 ? _a : groupData.searchResults.map((item) => normalizeItem(item, { saleBadgeText }));
|
|
26
26
|
const canTrack = !!intent && !!searchResultId;
|
|
27
27
|
const { ref: viewRef } = (0, useViewportTracking_1.default)({
|
|
28
28
|
enabled: canTrack,
|
|
@@ -83,7 +83,7 @@ function ResultsGroup({ groupData, label, slidesToShow, gap, showTitle, viewMore
|
|
|
83
83
|
addToCartText,
|
|
84
84
|
onProductClick: handleProductClick,
|
|
85
85
|
onAddToCart,
|
|
86
|
-
componentOverrides: (
|
|
86
|
+
componentOverrides: (_c = (_b = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel) === null || _b === void 0 ? void 0 : _b.item) === null || _c === void 0 ? void 0 : _c.productCard,
|
|
87
87
|
});
|
|
88
88
|
const titleRenderProps = { label };
|
|
89
89
|
const viewMoreRenderProps = {
|
|
@@ -95,12 +95,12 @@ function ResultsGroup({ groupData, label, slidesToShow, gap, showTitle, viewMore
|
|
|
95
95
|
reactNode: renderProductCard,
|
|
96
96
|
} }) });
|
|
97
97
|
return (react_1.default.createElement("div", { className: 'cio-asa-results-group', ref: viewRef },
|
|
98
|
-
showTitle && label && (react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { override: (
|
|
98
|
+
showTitle && label && (react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { override: (_d = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.groupTitle) === null || _d === void 0 ? void 0 : _d.reactNode, props: titleRenderProps },
|
|
99
99
|
react_1.default.createElement("h3", { className: 'cio-asa-results-group__label' }, label))),
|
|
100
100
|
react_1.default.createElement(constructorio_ui_components_1.Carousel, { items: products, loop: false, responsive: {
|
|
101
101
|
0: { gap, slidesToShow },
|
|
102
102
|
}, componentOverrides: carouselOverrides }),
|
|
103
|
-
onViewMore && (react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { override: (
|
|
103
|
+
onViewMore && (react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { override: (_e = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.viewMore) === null || _e === void 0 ? void 0 : _e.reactNode, props: viewMoreRenderProps },
|
|
104
104
|
react_1.default.createElement("button", { type: 'button', className: 'cio-asa-results-group__view-more', onClick: handleViewMore },
|
|
105
105
|
react_1.default.createElement("span", { className: 'cio-asa-results-group__view-more-text' }, viewMoreText),
|
|
106
106
|
react_1.default.createElement(icons_1.ArrowRightIcon, null))))));
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startTurn = exports.readAgentStream = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const asaStreamHandlers_1 = require("./asaStreamHandlers");
|
|
6
|
+
/** Drains an agent stream into the assistant message identified by `assistantId`. */
|
|
7
|
+
function readAgentStream(stream, assistantId, setMessages, callbacks) {
|
|
8
|
+
const reader = stream.getReader();
|
|
9
|
+
let cancelled = false;
|
|
10
|
+
let pendingGroup = null;
|
|
11
|
+
let intentResultId;
|
|
12
|
+
let loadStartFired = false;
|
|
13
|
+
let groupCount = 0;
|
|
14
|
+
const patchAssistant = (patch) => {
|
|
15
|
+
(0, asaStreamHandlers_1.updateMessageById)(setMessages, assistantId, (m) => (Object.assign(Object.assign({}, m), patch)));
|
|
16
|
+
};
|
|
17
|
+
const fireLoadStart = () => {
|
|
18
|
+
if (loadStartFired)
|
|
19
|
+
return;
|
|
20
|
+
loadStartFired = true;
|
|
21
|
+
callbacks.onLoadStart(intentResultId);
|
|
22
|
+
};
|
|
23
|
+
const done = (() => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
var _a, _b, _c, _d;
|
|
25
|
+
try {
|
|
26
|
+
while (!cancelled) {
|
|
27
|
+
// eslint-disable-next-line no-await-in-loop
|
|
28
|
+
const res = yield reader.read();
|
|
29
|
+
if (cancelled)
|
|
30
|
+
break;
|
|
31
|
+
if (res.done) {
|
|
32
|
+
(0, asaStreamHandlers_1.handleStreamEnd)(assistantId, setMessages);
|
|
33
|
+
callbacks.onFinish({ searchResultCount: groupCount, intentResultId });
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
const { type, data } = res.value;
|
|
37
|
+
if (!intentResultId && (data === null || data === void 0 ? void 0 : data.intent_result_id)) {
|
|
38
|
+
intentResultId = data.intent_result_id;
|
|
39
|
+
patchAssistant({ intentResultId });
|
|
40
|
+
}
|
|
41
|
+
if (type === 'start') {
|
|
42
|
+
if (data === null || data === void 0 ? void 0 : data.thread_id)
|
|
43
|
+
patchAssistant({ threadId: data.thread_id });
|
|
44
|
+
callbacks.onStart(data === null || data === void 0 ? void 0 : data.thread_id);
|
|
45
|
+
fireLoadStart();
|
|
46
|
+
}
|
|
47
|
+
else if (type === 'group') {
|
|
48
|
+
pendingGroup = {
|
|
49
|
+
display_name: (_b = (_a = data === null || data === void 0 ? void 0 : data.display_name) !== null && _a !== void 0 ? _a : data === null || data === void 0 ? void 0 : data.group) !== null && _b !== void 0 ? _b : '',
|
|
50
|
+
value: (_d = (_c = data === null || data === void 0 ? void 0 : data.value) !== null && _c !== void 0 ? _c : data === null || data === void 0 ? void 0 : data.group) !== null && _d !== void 0 ? _d : '',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
else if (type === 'search_result') {
|
|
54
|
+
fireLoadStart();
|
|
55
|
+
groupCount += 1;
|
|
56
|
+
pendingGroup = (0, asaStreamHandlers_1.handleSearchResult)(data, pendingGroup, assistantId, setMessages);
|
|
57
|
+
}
|
|
58
|
+
else if (type === 'message') {
|
|
59
|
+
fireLoadStart();
|
|
60
|
+
(0, asaStreamHandlers_1.handleMessage)(data, assistantId, setMessages);
|
|
61
|
+
}
|
|
62
|
+
else if (type === 'follow_up_refinement') {
|
|
63
|
+
fireLoadStart();
|
|
64
|
+
(0, asaStreamHandlers_1.handleFollowUpRefinement)(data, assistantId, setMessages);
|
|
65
|
+
}
|
|
66
|
+
else if (type === 'server_error') {
|
|
67
|
+
(0, asaStreamHandlers_1.handleServerError)(assistantId, setMessages);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (_e) {
|
|
73
|
+
if (!cancelled)
|
|
74
|
+
(0, asaStreamHandlers_1.handleStreamError)(assistantId, setMessages);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
// Cancelling an errored stream rejects with the stored error; nothing here needs it.
|
|
78
|
+
if (!cancelled)
|
|
79
|
+
reader.cancel().catch(() => { });
|
|
80
|
+
}
|
|
81
|
+
}))();
|
|
82
|
+
return {
|
|
83
|
+
assistantId,
|
|
84
|
+
done,
|
|
85
|
+
cancel: () => {
|
|
86
|
+
cancelled = true;
|
|
87
|
+
reader.cancel().catch(() => { });
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
exports.readAgentStream = readAgentStream;
|
|
92
|
+
/** Streams into `assistant`, reporting to the owner `createOwner` returns for the new turn. */
|
|
93
|
+
function startTurn(stream, assistant, createOwner, callbacks) {
|
|
94
|
+
const turn = { assistant };
|
|
95
|
+
turn.owner = createOwner(turn);
|
|
96
|
+
const handle = readAgentStream(stream, assistant.id, (action) => {
|
|
97
|
+
if (typeof action === 'function')
|
|
98
|
+
[turn.assistant] = action([turn.assistant]);
|
|
99
|
+
turn.owner.setMessages(action);
|
|
100
|
+
}, {
|
|
101
|
+
onLoadStart: callbacks.onLoadStart,
|
|
102
|
+
onFinish: (result) => callbacks.onFinish(Object.assign(Object.assign({}, result), { threadId: turn.serverThreadId })),
|
|
103
|
+
onStart: (threadId) => {
|
|
104
|
+
if (threadId)
|
|
105
|
+
turn.serverThreadId = threadId;
|
|
106
|
+
turn.owner.onStart(threadId);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
Object.assign(turn, handle);
|
|
110
|
+
handle.done.then(() => turn.owner.onDone());
|
|
111
|
+
return turn;
|
|
112
|
+
}
|
|
113
|
+
exports.startTurn = startTurn;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleStreamError = exports.handleStreamEnd = exports.handleServerError = exports.handleFollowUpRefinement = exports.handleMessage = exports.handleSearchResult = void 0;
|
|
3
|
+
exports.handleStreamError = exports.handleStreamEnd = exports.handleServerError = exports.handleFollowUpRefinement = exports.handleMessage = exports.handleSearchResult = exports.updateMessageById = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Internal A/B configuration echoed back on the request. Stripped before the request reaches
|
|
6
6
|
* consumers: it is unstable (the backend omits it on some pods within a single response) and
|
|
@@ -14,6 +14,7 @@ function omitInternalRequestFields(request) {
|
|
|
14
14
|
function updateMessageById(setMessages, id, patch) {
|
|
15
15
|
setMessages((prev) => prev.map((msg) => (msg.id === id ? patch(msg) : msg)));
|
|
16
16
|
}
|
|
17
|
+
exports.updateMessageById = updateMessageById;
|
|
17
18
|
function handleSearchResult(data, pendingGroup, assistantId, setMessages) {
|
|
18
19
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
19
20
|
const baseGroup = pendingGroup !== null && pendingGroup !== void 0 ? pendingGroup : {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.subscribeBackgroundTurns = exports.moveBackgroundTurns = exports.resumeBackgroundTurn = exports.continueInBackground = exports.BackgroundTurn = void 0;
|
|
4
|
+
const chatSession_1 = require("../../utils/chatSession");
|
|
5
|
+
const chatThreads_1 = require("../../utils/chatThreads");
|
|
6
|
+
const turns = new Set();
|
|
7
|
+
const listeners = new Set();
|
|
8
|
+
const notify = () => listeners.forEach((listener) => listener());
|
|
9
|
+
/** An answer that went on after its conversation left the screen, until it is stored or shown again. */
|
|
10
|
+
class BackgroundTurn {
|
|
11
|
+
constructor(turn, params) {
|
|
12
|
+
this.streaming = true;
|
|
13
|
+
/** Whether the latest snapshot is known to be stored. */
|
|
14
|
+
this.saved = false;
|
|
15
|
+
/** Every id the conversation was stored under, so a list read before a re-key still finds it. */
|
|
16
|
+
this.ids = new Set();
|
|
17
|
+
this.turn = turn;
|
|
18
|
+
this.store = params.store;
|
|
19
|
+
this.session = params.session;
|
|
20
|
+
this.messages = params.messages;
|
|
21
|
+
this.writes = params.after;
|
|
22
|
+
if (this.session.storageThreadId)
|
|
23
|
+
this.ids.add(this.session.storageThreadId);
|
|
24
|
+
this.turn.owner = {
|
|
25
|
+
setMessages: (action) => {
|
|
26
|
+
this.messages = typeof action === 'function' ? action(this.messages) : action;
|
|
27
|
+
},
|
|
28
|
+
onStart: (threadId) => {
|
|
29
|
+
if (threadId)
|
|
30
|
+
this.session.serverThreadId = threadId;
|
|
31
|
+
this.save().then(notify);
|
|
32
|
+
},
|
|
33
|
+
onDone: () => {
|
|
34
|
+
this.streaming = false;
|
|
35
|
+
this.messages = (0, chatThreads_1.normalizeHydratedMessages)(this.messages);
|
|
36
|
+
this.save().then(() => {
|
|
37
|
+
if (this.saved)
|
|
38
|
+
turns.delete(this);
|
|
39
|
+
notify();
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
save() {
|
|
45
|
+
const { snapshot, staleIds } = (0, chatSession_1.prepareSnapshot)(this.session, this.messages);
|
|
46
|
+
const { store } = this;
|
|
47
|
+
this.ids.add(snapshot.threadId);
|
|
48
|
+
this.saved = false;
|
|
49
|
+
this.writes = this.writes
|
|
50
|
+
.then(() => (0, chatThreads_1.saveThreadAndRetireStale)(store, snapshot, staleIds))
|
|
51
|
+
.then(({ saved, leftover }) => {
|
|
52
|
+
if (leftover.length > 0) {
|
|
53
|
+
this.session.orphanIds = Array.from(new Set([...this.session.orphanIds, ...leftover]));
|
|
54
|
+
}
|
|
55
|
+
if (this.session.lastSyncedAt === snapshot.updatedAt)
|
|
56
|
+
this.saved = saved;
|
|
57
|
+
})
|
|
58
|
+
.catch(() => { });
|
|
59
|
+
return this.writes;
|
|
60
|
+
}
|
|
61
|
+
/** Later writes go into `store`. */
|
|
62
|
+
moveTo(store) {
|
|
63
|
+
this.store = store;
|
|
64
|
+
}
|
|
65
|
+
/** Stores the conversation settled, without awaiting: the page is going away. */
|
|
66
|
+
flush() {
|
|
67
|
+
if (!this.streaming && this.saved)
|
|
68
|
+
return;
|
|
69
|
+
const { snapshot, staleIds } = (0, chatSession_1.prepareSnapshot)(this.session, (0, chatThreads_1.normalizeHydratedMessages)(this.messages));
|
|
70
|
+
if (this.store.saveThreadSync)
|
|
71
|
+
this.store.saveThreadSync(snapshot, staleIds);
|
|
72
|
+
else
|
|
73
|
+
this.store.saveThread(snapshot).catch(() => { });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.BackgroundTurn = BackgroundTurn;
|
|
77
|
+
let listeningForPagehide = false;
|
|
78
|
+
/**
|
|
79
|
+
* Keeps `turn` streaming with no chat showing it. It is saved into `store` at once, on `start`
|
|
80
|
+
* and when done, each write queued behind `after`, and is dropped from here once the last one landed.
|
|
81
|
+
*/
|
|
82
|
+
function continueInBackground(turn, params) {
|
|
83
|
+
const bg = new BackgroundTurn(turn, params);
|
|
84
|
+
turns.add(bg);
|
|
85
|
+
if (!listeningForPagehide && typeof window !== 'undefined') {
|
|
86
|
+
listeningForPagehide = true;
|
|
87
|
+
window.addEventListener('pagehide', () => turns.forEach((t) => t.flush()));
|
|
88
|
+
}
|
|
89
|
+
bg.save().then(notify);
|
|
90
|
+
}
|
|
91
|
+
exports.continueInBackground = continueInBackground;
|
|
92
|
+
/**
|
|
93
|
+
* Takes back the background turn stored in `store` under `threadId` for a chat to show, handing an
|
|
94
|
+
* unfinished one to `createOwner`; `null` if there is none.
|
|
95
|
+
*/
|
|
96
|
+
function resumeBackgroundTurn(store, threadId, createOwner) {
|
|
97
|
+
const bg = Array.from(turns).find((t) => t.store === store && t.ids.has(threadId));
|
|
98
|
+
if (!bg)
|
|
99
|
+
return null;
|
|
100
|
+
turns.delete(bg);
|
|
101
|
+
if (bg.streaming)
|
|
102
|
+
bg.turn.owner = createOwner(bg.turn);
|
|
103
|
+
return bg;
|
|
104
|
+
}
|
|
105
|
+
exports.resumeBackgroundTurn = resumeBackgroundTurn;
|
|
106
|
+
/** Points the background turns of `from` at `to`; resolves once their writes into `from` settled. */
|
|
107
|
+
function moveBackgroundTurns(from, to) {
|
|
108
|
+
const moving = Array.from(turns).filter((bg) => bg.store === from);
|
|
109
|
+
const settled = Promise.all(moving.map((bg) => bg.writes)).then(() => { });
|
|
110
|
+
moving.forEach((bg) => bg.moveTo(to));
|
|
111
|
+
return settled;
|
|
112
|
+
}
|
|
113
|
+
exports.moveBackgroundTurns = moveBackgroundTurns;
|
|
114
|
+
/** Called whenever a background turn is stored or finishes. */
|
|
115
|
+
function subscribeBackgroundTurns(listener) {
|
|
116
|
+
listeners.add(listener);
|
|
117
|
+
return () => {
|
|
118
|
+
listeners.delete(listener);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
exports.subscribeBackgroundTurns = subscribeBackgroundTurns;
|