@constructor-io/constructorio-ui-asa 1.0.0 → 1.1.1
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/lib/cjs/components/Chat/Chat.js +10 -1
- package/lib/cjs/components/Chat/ChatMessageList.js +1 -1
- package/lib/cjs/components/Chat/WelcomeScreen.js +3 -2
- package/lib/cjs/components/CioAsaProvider/CioAsaProvider.js +4 -2
- package/lib/cjs/components/ResultsBlock/ResultsBlock.js +13 -38
- package/lib/cjs/components/ResultsBlock/ResultsGroup.js +109 -0
- package/lib/cjs/hooks/asaStreamHandlers.js +3 -1
- package/lib/cjs/hooks/useAsaResults.js +78 -14
- package/lib/cjs/hooks/useAsaTracking.js +66 -0
- package/lib/cjs/hooks/useViewportTracking.js +39 -0
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/utils/productNormalizer.js +2 -11
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/Chat/Chat.js +10 -1
- package/lib/mjs/components/Chat/ChatMessageList.js +1 -1
- package/lib/mjs/components/Chat/WelcomeScreen.js +3 -2
- package/lib/mjs/components/CioAsaProvider/CioAsaProvider.js +4 -2
- package/lib/mjs/components/ResultsBlock/ResultsBlock.js +12 -45
- package/lib/mjs/components/ResultsBlock/ResultsGroup.js +117 -0
- package/lib/mjs/hooks/asaStreamHandlers.js +8 -1
- package/lib/mjs/hooks/useAsaResults.js +72 -10
- package/lib/mjs/hooks/useAsaTracking.js +91 -0
- package/lib/mjs/hooks/useViewportTracking.js +36 -0
- package/lib/mjs/index.js +1 -0
- package/lib/mjs/utils/productNormalizer.js +1 -0
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/Chat/WelcomeScreen.d.ts +2 -2
- package/lib/types/components/ResultsBlock/ResultsBlock.d.ts +4 -1
- package/lib/types/components/ResultsBlock/ResultsGroup.d.ts +36 -0
- package/lib/types/hooks/useAsaTracking.d.ts +54 -0
- package/lib/types/hooks/useViewportTracking.d.ts +15 -0
- package/lib/types/index.d.ts +3 -1
- package/lib/types/types.d.ts +63 -1
- package/lib/types/utils/productNormalizer.d.ts +1 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +5 -5
|
@@ -20,7 +20,16 @@ const Chat = (0, react_1.forwardRef)(({ onClose, className, initialSuggestions,
|
|
|
20
20
|
(0, react_1.useEffect)(() => {
|
|
21
21
|
const root = isWelcome ? containerRef.current : chatViewRef.current;
|
|
22
22
|
const input = root === null || root === void 0 ? void 0 : root.querySelector('input');
|
|
23
|
-
|
|
23
|
+
if (input) {
|
|
24
|
+
// preventScroll: focusing must not scroll the page — the host may render Chat
|
|
25
|
+
// below the fold (or, as in Storybook docs, several instances on one page).
|
|
26
|
+
try {
|
|
27
|
+
input.focus({ preventScroll: true });
|
|
28
|
+
}
|
|
29
|
+
catch (_a) {
|
|
30
|
+
input.focus();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
24
33
|
}, [isWelcome]);
|
|
25
34
|
(0, useFocusTrap_1.default)(containerRef, { onEscape: onClose });
|
|
26
35
|
return (react_1.default.createElement("div", { className: ['cio-asa', 'cio-asa-chat', className].filter(Boolean).join(' '), ref: containerRef, role: 'dialog', "aria-labelledby": 'cio-asa-chat-title' },
|
|
@@ -39,7 +39,7 @@ function ChatMessageList({ messages, onProductClick, onViewMore, onAddToCart, as
|
|
|
39
39
|
const hasGroups = !!((_a = message.groups) === null || _a === void 0 ? void 0 : _a.length);
|
|
40
40
|
return (react_1.default.createElement("div", { key: message.id, className: 'cio-asa-ai-message-group' },
|
|
41
41
|
react_1.default.createElement(AiMessage_1.default, { message: message, componentOverrides: aiMessageOverrides, translations: translations }),
|
|
42
|
-
hasGroups && (react_1.default.createElement(ResultsBlock_1.default, { groups: message.groups, onProductClick: onProductClick, onViewMore: onViewMore, onAddToCart: onAddToCart, aspectRatio: aspectRatio, currency: currency, normalizeItem: normalizeItem, addToCartText: addToCartText, viewMoreText: viewMoreText, saleBadgeText: (0, translate_1.default)('CioAsa.results.saleBadge', translations), componentOverrides: resultsBlockOverrides }))));
|
|
42
|
+
hasGroups && (react_1.default.createElement(ResultsBlock_1.default, { groups: message.groups, intent: message.intent, intentResultId: message.intentResultId, threadId: message.threadId, onProductClick: onProductClick, onViewMore: onViewMore, onAddToCart: onAddToCart, aspectRatio: aspectRatio, currency: currency, normalizeItem: normalizeItem, addToCartText: addToCartText, viewMoreText: viewMoreText, saleBadgeText: (0, translate_1.default)('CioAsa.results.saleBadge', translations), componentOverrides: resultsBlockOverrides }))));
|
|
43
43
|
})));
|
|
44
44
|
}
|
|
45
45
|
exports.default = ChatMessageList;
|
|
@@ -20,9 +20,10 @@ function WelcomeScreen({ suggestions = DEFAULT_SUGGESTIONS, onSend, onClose, ter
|
|
|
20
20
|
placeholder: (0, translate_1.default)('CioAsa.welcome.placeholder', translations),
|
|
21
21
|
isDisabled: disabled,
|
|
22
22
|
};
|
|
23
|
+
const handleSuggestionClick = (suggestion) => onSend(suggestion, 'suggestion');
|
|
23
24
|
const suggestionsRenderProps = {
|
|
24
25
|
suggestions,
|
|
25
|
-
onSuggestionClick:
|
|
26
|
+
onSuggestionClick: handleSuggestionClick,
|
|
26
27
|
};
|
|
27
28
|
return (react_1.default.createElement("div", { className: 'cio-asa-welcome-screen' },
|
|
28
29
|
onClose && (react_1.default.createElement("button", { type: 'button', className: 'cio-asa-welcome-screen__close', onClick: onClose, "aria-label": (0, translate_1.default)('CioAsa.header.close', translations) }, "\u2715")),
|
|
@@ -36,7 +37,7 @@ function WelcomeScreen({ suggestions = DEFAULT_SUGGESTIONS, onSend, onClose, ter
|
|
|
36
37
|
(0, translate_1.default)('CioAsa.welcome.sendButton', translations),
|
|
37
38
|
react_1.default.createElement(icons_1.SendPlaneIcon, null)))),
|
|
38
39
|
suggestions.length > 0 && (react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { override: (_c = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.suggestedQuestions) === null || _c === void 0 ? void 0 : _c.reactNode, props: suggestionsRenderProps },
|
|
39
|
-
react_1.default.createElement("nav", { className: 'cio-asa-welcome-screen__suggestions', "aria-label": (0, translate_1.default)('CioAsa.welcome.suggestionsAriaLabel', translations) }, suggestions.map((suggestion) => (react_1.default.createElement("button", { key: suggestion, type: 'button', className: 'cio-asa-welcome-screen__suggestion-chip', disabled: disabled, onClick: () =>
|
|
40
|
+
react_1.default.createElement("nav", { className: 'cio-asa-welcome-screen__suggestions', "aria-label": (0, translate_1.default)('CioAsa.welcome.suggestionsAriaLabel', translations) }, suggestions.map((suggestion) => (react_1.default.createElement("button", { key: suggestion, type: 'button', className: 'cio-asa-welcome-screen__suggestion-chip', disabled: disabled, onClick: () => handleSuggestionClick(suggestion) }, suggestion))))))),
|
|
40
41
|
termsText && react_1.default.createElement("div", { className: 'cio-asa-welcome-screen__terms' }, termsText)));
|
|
41
42
|
}
|
|
42
43
|
exports.default = WelcomeScreen;
|
|
@@ -7,7 +7,7 @@ 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
9
|
function CioAsaProvider(props) {
|
|
10
|
-
const { apiKey, formatters, urlHelpers, staticRequestConfigs = { domain: 'chatbot' }, cioClient: customCioClient, children, } = props;
|
|
10
|
+
const { apiKey, formatters, urlHelpers, staticRequestConfigs = { domain: 'chatbot' }, cioClient: customCioClient, callbacks, section = 'Products', children, } = props;
|
|
11
11
|
const [cioClientOptions, setCioClientOptions] = (0, react_1.useState)({});
|
|
12
12
|
const cioClient = (0, useCioClient_1.default)({ apiKey, cioClient: customCioClient, cioClientOptions });
|
|
13
13
|
const contextValue = (0, react_1.useMemo)(() => ({
|
|
@@ -17,7 +17,9 @@ function CioAsaProvider(props) {
|
|
|
17
17
|
staticRequestConfigs,
|
|
18
18
|
formatters: Object.assign(Object.assign({}, defaultFormatters), formatters),
|
|
19
19
|
urlHelpers: Object.assign(Object.assign({}, defaultUrlHelpers), urlHelpers),
|
|
20
|
-
|
|
20
|
+
callbacks,
|
|
21
|
+
section,
|
|
22
|
+
}), [cioClient, cioClientOptions, formatters, urlHelpers, staticRequestConfigs, callbacks, section]);
|
|
21
23
|
return (react_1.default.createElement(useCioAsaContext_1.AsaContext.Provider, { value: contextValue }, typeof children === 'function' ? children(contextValue) : children));
|
|
22
24
|
}
|
|
23
25
|
exports.default = CioAsaProvider;
|
|
@@ -2,22 +2,23 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const react_1 = tslib_1.__importStar(require("react"));
|
|
5
|
-
const constructorio_ui_components_1 = require("@constructor-io/constructorio-ui-components");
|
|
6
5
|
const productNormalizer_1 = require("../../utils/productNormalizer");
|
|
7
|
-
const
|
|
6
|
+
const useCioAsaContext_1 = require("../../hooks/useCioAsaContext");
|
|
7
|
+
const useAsaTracking_1 = tslib_1.__importDefault(require("../../hooks/useAsaTracking"));
|
|
8
8
|
const constants_1 = require("./constants");
|
|
9
|
-
const
|
|
9
|
+
const ResultsGroup_1 = tslib_1.__importDefault(require("./ResultsGroup"));
|
|
10
10
|
require("./ResultsBlock.css");
|
|
11
|
-
function
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
function NextButton() {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
function ResultsBlock({ groups, aspectRatio = '3:4', minCardWidth = 150, gap = 12, showTitle = true, viewMoreText = 'View more products', addToCartText = 'Add to cart', saleBadgeText = 'Sale', currency, normalizeItem = productNormalizer_1.normalizeItemToProduct, onProductClick, onAddToCart, onViewMore, componentOverrides, }) {
|
|
11
|
+
function ResultsBlock({ groups, aspectRatio = '3:4', minCardWidth = 150, gap = 12, showTitle = true, viewMoreText = 'View more products', addToCartText = 'Add to cart', saleBadgeText = 'Sale', currency, intent, intentResultId, threadId, normalizeItem = productNormalizer_1.normalizeItemToProduct, onProductClick, onAddToCart, onViewMore, componentOverrides, }) {
|
|
18
12
|
var _a, _b;
|
|
19
13
|
const containerRef = (0, react_1.useRef)(null);
|
|
20
14
|
const [slidesToShow, setSlidesToShow] = (0, react_1.useState)(3 + constants_1.PEEK_FRACTION);
|
|
15
|
+
const context = (0, useCioAsaContext_1.useCioAsaContext)();
|
|
16
|
+
const tracking = (0, useAsaTracking_1.default)({
|
|
17
|
+
tracker: (_b = (_a = context === null || context === void 0 ? void 0 : context.cioClient) === null || _a === void 0 ? void 0 : _a.tracker) !== null && _b !== void 0 ? _b : undefined,
|
|
18
|
+
section: context === null || context === void 0 ? void 0 : context.section,
|
|
19
|
+
threadId,
|
|
20
|
+
});
|
|
21
|
+
const callbacks = context === null || context === void 0 ? void 0 : context.callbacks;
|
|
21
22
|
(0, react_1.useEffect)(() => {
|
|
22
23
|
const el = containerRef.current;
|
|
23
24
|
if (!el)
|
|
@@ -36,37 +37,11 @@ function ResultsBlock({ groups, aspectRatio = '3:4', minCardWidth = 150, gap = 1
|
|
|
36
37
|
}, [minCardWidth, gap]);
|
|
37
38
|
if (!groups || groups.length === 0)
|
|
38
39
|
return null;
|
|
39
|
-
const renderProductCard = (0, renderProductCard_1.default)({
|
|
40
|
-
currency,
|
|
41
|
-
addToCartText,
|
|
42
|
-
onProductClick,
|
|
43
|
-
onAddToCart,
|
|
44
|
-
componentOverrides: (_b = (_a = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel) === null || _a === void 0 ? void 0 : _a.item) === null || _b === void 0 ? void 0 : _b.productCard,
|
|
45
|
-
});
|
|
46
40
|
return (react_1.default.createElement("div", { ref: containerRef, className: 'cio-asa-results-block', style: { '--cio-asa-image-ratio': constants_1.aspectRatioMap[aspectRatio] } }, groups.map((groupData, index) => {
|
|
47
|
-
var _a, _b, _c
|
|
41
|
+
var _a, _b, _c;
|
|
48
42
|
const label = ((_a = groupData.group) === null || _a === void 0 ? void 0 : _a.display_name) || ((_b = groupData.group) === null || _b === void 0 ? void 0 : _b.value) || '';
|
|
49
43
|
const groupKey = `${((_c = groupData.group) === null || _c === void 0 ? void 0 : _c.value) || label}-${index}`;
|
|
50
|
-
|
|
51
|
-
const titleRenderProps = { label };
|
|
52
|
-
const viewMoreRenderProps = {
|
|
53
|
-
group: groupData.group,
|
|
54
|
-
onClick: () => onViewMore === null || onViewMore === void 0 ? void 0 : onViewMore(groupData.group),
|
|
55
|
-
};
|
|
56
|
-
const userCarousel = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel;
|
|
57
|
-
const carouselOverrides = Object.assign(Object.assign({ previous: { reactNode: PreviousButton }, next: { reactNode: NextButton } }, userCarousel), { item: Object.assign(Object.assign({}, userCarousel === null || userCarousel === void 0 ? void 0 : userCarousel.item), { productCard: {
|
|
58
|
-
reactNode: renderProductCard,
|
|
59
|
-
} }) });
|
|
60
|
-
return (react_1.default.createElement("div", { className: 'cio-asa-results-group', key: groupKey },
|
|
61
|
-
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 },
|
|
62
|
-
react_1.default.createElement("h3", { className: 'cio-asa-results-group__label' }, label))),
|
|
63
|
-
react_1.default.createElement(constructorio_ui_components_1.Carousel, { items: products, loop: false, responsive: {
|
|
64
|
-
0: { gap, slidesToShow },
|
|
65
|
-
}, componentOverrides: carouselOverrides }),
|
|
66
|
-
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 },
|
|
67
|
-
react_1.default.createElement("button", { type: 'button', className: 'cio-asa-results-group__view-more', onClick: () => onViewMore(groupData.group) },
|
|
68
|
-
react_1.default.createElement("span", { className: 'cio-asa-results-group__view-more-text' }, viewMoreText),
|
|
69
|
-
react_1.default.createElement(icons_1.ArrowRightIcon, null))))));
|
|
44
|
+
return (react_1.default.createElement(ResultsGroup_1.default, { key: groupKey, groupData: groupData, label: label, slidesToShow: slidesToShow, gap: gap, showTitle: showTitle, viewMoreText: viewMoreText, addToCartText: addToCartText, saleBadgeText: saleBadgeText, currency: currency, intent: intent, intentResultId: intentResultId, normalizeItem: normalizeItem, onProductClick: onProductClick, onAddToCart: onAddToCart, onViewMore: onViewMore, componentOverrides: componentOverrides, tracking: tracking, callbacks: callbacks }));
|
|
70
45
|
})));
|
|
71
46
|
}
|
|
72
47
|
ResultsBlock.displayName = 'ResultsBlock';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const react_1 = tslib_1.__importDefault(require("react"));
|
|
5
|
+
const constructorio_ui_components_1 = require("@constructor-io/constructorio-ui-components");
|
|
6
|
+
const useViewportTracking_1 = tslib_1.__importDefault(require("../../hooks/useViewportTracking"));
|
|
7
|
+
const icons_1 = require("../icons");
|
|
8
|
+
const renderProductCard_1 = tslib_1.__importDefault(require("./renderProductCard"));
|
|
9
|
+
function PreviousButton() {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function NextButton() {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Renders a single search_result pod (one carousel) and fires its pod-level behavioral
|
|
17
|
+
* events: `assistant_search_result_view` (on scroll into view), `assistant_search_result_click`
|
|
18
|
+
* (product click), and `assistant_search_submit` ("view more"). Each SDK beacon is paired
|
|
19
|
+
* with its optional consumer callback. Events only fire when both `intent` and the pod's
|
|
20
|
+
* `searchResultId` are known.
|
|
21
|
+
*/
|
|
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;
|
|
24
|
+
const { searchResultId } = groupData;
|
|
25
|
+
const products = groupData.searchResults.map((item) => normalizeItem(item, { saleBadgeText }));
|
|
26
|
+
const canTrack = !!intent && !!searchResultId;
|
|
27
|
+
const { ref: viewRef } = (0, useViewportTracking_1.default)({
|
|
28
|
+
enabled: canTrack,
|
|
29
|
+
onView: () => {
|
|
30
|
+
var _a;
|
|
31
|
+
if (!intent || !searchResultId)
|
|
32
|
+
return;
|
|
33
|
+
const items = products.map((p) => (Object.assign({ itemId: p.id, itemName: p.name }, (p.variationId && { variationId: p.variationId }))));
|
|
34
|
+
const payload = {
|
|
35
|
+
intent,
|
|
36
|
+
searchResultId,
|
|
37
|
+
intentResultId,
|
|
38
|
+
numResultsViewed: products.length,
|
|
39
|
+
items,
|
|
40
|
+
};
|
|
41
|
+
tracking.trackResultView(payload);
|
|
42
|
+
(_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onResultView) === null || _a === void 0 ? void 0 : _a.call(callbacks, payload);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
const handleProductClick = (product) => {
|
|
46
|
+
var _a;
|
|
47
|
+
if (intent && searchResultId) {
|
|
48
|
+
tracking.trackResultClick({
|
|
49
|
+
intent,
|
|
50
|
+
searchResultId,
|
|
51
|
+
intentResultId,
|
|
52
|
+
itemId: product.id,
|
|
53
|
+
itemName: product.name,
|
|
54
|
+
variationId: product.variationId,
|
|
55
|
+
});
|
|
56
|
+
(_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onResultClick) === null || _a === void 0 ? void 0 : _a.call(callbacks, {
|
|
57
|
+
intent,
|
|
58
|
+
searchResultId,
|
|
59
|
+
intentResultId,
|
|
60
|
+
item: Object.assign({ itemId: product.id, itemName: product.name }, (product.variationId && { variationId: product.variationId })),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
onProductClick === null || onProductClick === void 0 ? void 0 : onProductClick(product);
|
|
64
|
+
};
|
|
65
|
+
const handleViewMore = () => {
|
|
66
|
+
var _a, _b, _c;
|
|
67
|
+
if (intent && searchResultId) {
|
|
68
|
+
const searchTerm = ((_a = groupData.group) === null || _a === void 0 ? void 0 : _a.value) || ((_b = groupData.group) === null || _b === void 0 ? void 0 : _b.display_name) || '';
|
|
69
|
+
const payload = {
|
|
70
|
+
intent,
|
|
71
|
+
searchTerm,
|
|
72
|
+
userInput: searchTerm,
|
|
73
|
+
searchResultId,
|
|
74
|
+
intentResultId,
|
|
75
|
+
};
|
|
76
|
+
tracking.trackSearchSubmit(payload);
|
|
77
|
+
(_c = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onSearchSubmit) === null || _c === void 0 ? void 0 : _c.call(callbacks, payload);
|
|
78
|
+
}
|
|
79
|
+
onViewMore === null || onViewMore === void 0 ? void 0 : onViewMore(groupData.group);
|
|
80
|
+
};
|
|
81
|
+
const renderProductCard = (0, renderProductCard_1.default)({
|
|
82
|
+
currency,
|
|
83
|
+
addToCartText,
|
|
84
|
+
onProductClick: handleProductClick,
|
|
85
|
+
onAddToCart,
|
|
86
|
+
componentOverrides: (_b = (_a = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel) === null || _a === void 0 ? void 0 : _a.item) === null || _b === void 0 ? void 0 : _b.productCard,
|
|
87
|
+
});
|
|
88
|
+
const titleRenderProps = { label };
|
|
89
|
+
const viewMoreRenderProps = {
|
|
90
|
+
group: groupData.group,
|
|
91
|
+
onClick: handleViewMore,
|
|
92
|
+
};
|
|
93
|
+
const userCarousel = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel;
|
|
94
|
+
const carouselOverrides = Object.assign(Object.assign({ previous: { reactNode: PreviousButton }, next: { reactNode: NextButton } }, userCarousel), { item: Object.assign(Object.assign({}, userCarousel === null || userCarousel === void 0 ? void 0 : userCarousel.item), { productCard: {
|
|
95
|
+
reactNode: renderProductCard,
|
|
96
|
+
} }) });
|
|
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: (_c = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.groupTitle) === null || _c === void 0 ? void 0 : _c.reactNode, props: titleRenderProps },
|
|
99
|
+
react_1.default.createElement("h3", { className: 'cio-asa-results-group__label' }, label))),
|
|
100
|
+
react_1.default.createElement(constructorio_ui_components_1.Carousel, { items: products, loop: false, responsive: {
|
|
101
|
+
0: { gap, slidesToShow },
|
|
102
|
+
}, componentOverrides: carouselOverrides }),
|
|
103
|
+
onViewMore && (react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { override: (_d = componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.viewMore) === null || _d === void 0 ? void 0 : _d.reactNode, props: viewMoreRenderProps },
|
|
104
|
+
react_1.default.createElement("button", { type: 'button', className: 'cio-asa-results-group__view-more', onClick: handleViewMore },
|
|
105
|
+
react_1.default.createElement("span", { className: 'cio-asa-results-group__view-more-text' }, viewMoreText),
|
|
106
|
+
react_1.default.createElement(icons_1.ArrowRightIcon, null))))));
|
|
107
|
+
}
|
|
108
|
+
exports.default = ResultsGroup;
|
|
109
|
+
ResultsGroup.displayName = 'ResultsGroup';
|
|
@@ -11,7 +11,9 @@ function handleSearchResult(data, pendingGroup, assistantId, setMessages) {
|
|
|
11
11
|
value: (_h = (_g = (_f = (_e = data === null || data === void 0 ? void 0 : data.response) === null || _e === void 0 ? void 0 : _e.search_request) === null || _f === void 0 ? void 0 : _f.search_term) !== null && _g !== void 0 ? _g : data === null || data === void 0 ? void 0 : data.title) !== null && _h !== void 0 ? _h : '',
|
|
12
12
|
};
|
|
13
13
|
const results = (_k = (_j = data === null || data === void 0 ? void 0 : data.response) === null || _j === void 0 ? void 0 : _j.results) !== null && _k !== void 0 ? _k : ((data === null || data === void 0 ? void 0 : data.results) ? data.results : [data]);
|
|
14
|
-
|
|
14
|
+
// `result_id` is unique per search_result event; `intent_result_id` is shared across
|
|
15
|
+
// the stream. Both sit at the top level of the SSE event payload.
|
|
16
|
+
const newGroup = Object.assign(Object.assign({ group: resolvedGroup, searchResults: results }, ((data === null || data === void 0 ? void 0 : data.result_id) && { searchResultId: data.result_id })), ((data === null || data === void 0 ? void 0 : data.intent_result_id) && { intentResultId: data.intent_result_id }));
|
|
15
17
|
updateMessageById(setMessages, assistantId, (msg) => (Object.assign(Object.assign({}, msg), { status: 'streaming', groups: [...(msg.groups || []), newGroup] })));
|
|
16
18
|
return null;
|
|
17
19
|
}
|
|
@@ -4,7 +4,9 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const useCioAsaContext_1 = require("./useCioAsaContext");
|
|
6
6
|
const asaStreamHandlers_1 = require("./asaStreamHandlers");
|
|
7
|
+
const useAsaTracking_1 = tslib_1.__importDefault(require("./useAsaTracking"));
|
|
7
8
|
function useAsaResults() {
|
|
9
|
+
var _a;
|
|
8
10
|
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
9
11
|
const [isStreaming, setIsStreaming] = (0, react_1.useState)(false);
|
|
10
12
|
const isStreamingRef = (0, react_1.useRef)(false);
|
|
@@ -16,22 +18,37 @@ function useAsaResults() {
|
|
|
16
18
|
if (!contextValue) {
|
|
17
19
|
throw new Error('useAsaResults must be used within a CioAsaProvider.');
|
|
18
20
|
}
|
|
19
|
-
const { cioClient, staticRequestConfigs } = contextValue;
|
|
21
|
+
const { cioClient, staticRequestConfigs, callbacks, section } = contextValue;
|
|
20
22
|
const { domain } = staticRequestConfigs || {};
|
|
21
23
|
if (!cioClient || !domain) {
|
|
22
24
|
throw new Error('useAsaResults requires a configured cioClient and domain. Check your CioAsaProvider props.');
|
|
23
25
|
}
|
|
26
|
+
const tracking = (0, useAsaTracking_1.default)({
|
|
27
|
+
tracker: cioClient.tracker,
|
|
28
|
+
section,
|
|
29
|
+
threadId: (_a = threadIdRef.current) !== null && _a !== void 0 ? _a : undefined,
|
|
30
|
+
});
|
|
31
|
+
const trackingRef = (0, react_1.useRef)(tracking);
|
|
32
|
+
trackingRef.current = tracking;
|
|
33
|
+
const callbacksRef = (0, react_1.useRef)(callbacks);
|
|
34
|
+
callbacksRef.current = callbacks;
|
|
35
|
+
const staticRequestConfigsRef = (0, react_1.useRef)(staticRequestConfigs);
|
|
36
|
+
staticRequestConfigsRef.current = staticRequestConfigs;
|
|
24
37
|
const nextMessageId = (0, react_1.useCallback)(() => {
|
|
25
38
|
idCounterRef.current += 1;
|
|
26
39
|
return `msg-${idCounterRef.current}-${Date.now()}`;
|
|
27
40
|
}, []);
|
|
28
|
-
const sendMessage = (0, react_1.useCallback)((text) => {
|
|
29
|
-
|
|
41
|
+
const sendMessage = (0, react_1.useCallback)((text, source = 'input') => {
|
|
42
|
+
var _a, _b;
|
|
43
|
+
const intent = text.trim();
|
|
44
|
+
if (!intent || isStreamingRef.current)
|
|
30
45
|
return;
|
|
46
|
+
trackingRef.current.trackSubmit(intent);
|
|
47
|
+
(_b = (_a = callbacksRef.current) === null || _a === void 0 ? void 0 : _a.onAssistantSubmit) === null || _b === void 0 ? void 0 : _b.call(_a, { intent, source });
|
|
31
48
|
const userMessage = {
|
|
32
49
|
id: nextMessageId(),
|
|
33
50
|
role: 'user',
|
|
34
|
-
text:
|
|
51
|
+
text: intent,
|
|
35
52
|
status: 'done',
|
|
36
53
|
};
|
|
37
54
|
const assistantMessage = {
|
|
@@ -40,17 +57,44 @@ function useAsaResults() {
|
|
|
40
57
|
text: '',
|
|
41
58
|
groups: [],
|
|
42
59
|
status: 'loading',
|
|
60
|
+
intent,
|
|
43
61
|
};
|
|
44
62
|
setMessages((prev) => [...prev, userMessage, assistantMessage]);
|
|
45
63
|
setIsStreaming(true);
|
|
46
64
|
isStreamingRef.current = true;
|
|
47
65
|
killSwitchRef.current = false;
|
|
48
|
-
|
|
66
|
+
// `intent` is passed as the first argument and `threadId` is managed per-stream
|
|
67
|
+
// below, so both are stripped here. Everything else configured on the provider
|
|
68
|
+
// (filters, guard, numResultsPerEvent, numResultEvents, qsParam,
|
|
69
|
+
// preFilterExpression, fmtOptions, ...) is forwarded to the agent as-is.
|
|
70
|
+
const agentParams = Object.assign({}, staticRequestConfigsRef.current);
|
|
71
|
+
delete agentParams.intent;
|
|
72
|
+
delete agentParams.threadId;
|
|
73
|
+
const stream = cioClient.agent.getAgentResultsStream(intent, Object.assign(Object.assign(Object.assign({}, agentParams), { domain }), (threadIdRef.current && { threadId: threadIdRef.current })));
|
|
49
74
|
const reader = stream.getReader();
|
|
50
75
|
readerRef.current = reader;
|
|
76
|
+
// Mutable per-stream state kept on an object so the closures below don't capture
|
|
77
|
+
// reassigned loop-locals (which eslint's no-loop-func forbids).
|
|
78
|
+
const streamState = {
|
|
79
|
+
pendingGroup: null,
|
|
80
|
+
intentResultId: undefined,
|
|
81
|
+
loadStartFired: false,
|
|
82
|
+
groupCount: 0,
|
|
83
|
+
};
|
|
84
|
+
const patchAssistant = (patch) => {
|
|
85
|
+
setMessages((prev) => prev.map((m) => (m.id === assistantMessage.id ? Object.assign(Object.assign({}, m), patch) : m)));
|
|
86
|
+
};
|
|
87
|
+
const fireLoadStart = () => {
|
|
88
|
+
var _a, _b;
|
|
89
|
+
if (streamState.loadStartFired)
|
|
90
|
+
return;
|
|
91
|
+
streamState.loadStartFired = true;
|
|
92
|
+
const args = { intent, intentResultId: streamState.intentResultId };
|
|
93
|
+
trackingRef.current.trackResultLoadStarted(args);
|
|
94
|
+
(_b = (_a = callbacksRef.current) === null || _a === void 0 ? void 0 : _a.onResultLoadStart) === null || _b === void 0 ? void 0 : _b.call(_a, args);
|
|
95
|
+
};
|
|
51
96
|
(() => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
52
|
-
var
|
|
53
|
-
let pendingGroup = null;
|
|
97
|
+
var _c, _d, _e, _f, _g, _h;
|
|
54
98
|
try {
|
|
55
99
|
// eslint-disable-next-line no-await-in-loop
|
|
56
100
|
while (!killSwitchRef.current) {
|
|
@@ -60,22 +104,42 @@ function useAsaResults() {
|
|
|
60
104
|
break;
|
|
61
105
|
if (res.done) {
|
|
62
106
|
(0, asaStreamHandlers_1.handleStreamEnd)(assistantMessage.id, setMessages);
|
|
107
|
+
const finishArgs = {
|
|
108
|
+
intent,
|
|
109
|
+
searchResultCount: streamState.groupCount,
|
|
110
|
+
intentResultId: streamState.intentResultId,
|
|
111
|
+
};
|
|
112
|
+
trackingRef.current.trackResultLoadFinished(finishArgs);
|
|
113
|
+
(_d = (_c = callbacksRef.current) === null || _c === void 0 ? void 0 : _c.onResultLoadFinish) === null || _d === void 0 ? void 0 : _d.call(_c, finishArgs);
|
|
63
114
|
break;
|
|
64
115
|
}
|
|
65
116
|
const { type, data } = res.value;
|
|
66
|
-
|
|
67
|
-
|
|
117
|
+
// `intent_result_id` is shared across the stream; capture it from the first
|
|
118
|
+
// event that carries it so load-start/finish can attribute correctly.
|
|
119
|
+
if (!streamState.intentResultId && (data === null || data === void 0 ? void 0 : data.intent_result_id)) {
|
|
120
|
+
streamState.intentResultId = data.intent_result_id;
|
|
121
|
+
patchAssistant({ intentResultId: streamState.intentResultId });
|
|
122
|
+
}
|
|
123
|
+
if (type === 'start') {
|
|
124
|
+
if (data === null || data === void 0 ? void 0 : data.thread_id) {
|
|
125
|
+
threadIdRef.current = data.thread_id;
|
|
126
|
+
patchAssistant({ threadId: data.thread_id });
|
|
127
|
+
}
|
|
128
|
+
fireLoadStart();
|
|
68
129
|
}
|
|
69
130
|
else if (type === 'group') {
|
|
70
|
-
pendingGroup = {
|
|
71
|
-
display_name: (
|
|
72
|
-
value: (
|
|
131
|
+
streamState.pendingGroup = {
|
|
132
|
+
display_name: (_f = (_e = data === null || data === void 0 ? void 0 : data.display_name) !== null && _e !== void 0 ? _e : data === null || data === void 0 ? void 0 : data.group) !== null && _f !== void 0 ? _f : '',
|
|
133
|
+
value: (_h = (_g = data === null || data === void 0 ? void 0 : data.value) !== null && _g !== void 0 ? _g : data === null || data === void 0 ? void 0 : data.group) !== null && _h !== void 0 ? _h : '',
|
|
73
134
|
};
|
|
74
135
|
}
|
|
75
136
|
else if (type === 'search_result') {
|
|
76
|
-
|
|
137
|
+
fireLoadStart();
|
|
138
|
+
streamState.groupCount += 1;
|
|
139
|
+
streamState.pendingGroup = (0, asaStreamHandlers_1.handleSearchResult)(data, streamState.pendingGroup, assistantMessage.id, setMessages);
|
|
77
140
|
}
|
|
78
141
|
else if (type === 'message') {
|
|
142
|
+
fireLoadStart();
|
|
79
143
|
(0, asaStreamHandlers_1.handleMessage)(data, assistantMessage.id, setMessages);
|
|
80
144
|
}
|
|
81
145
|
else if (type === 'server_error') {
|
|
@@ -84,7 +148,7 @@ function useAsaResults() {
|
|
|
84
148
|
}
|
|
85
149
|
}
|
|
86
150
|
}
|
|
87
|
-
catch (
|
|
151
|
+
catch (_j) {
|
|
88
152
|
(0, asaStreamHandlers_1.handleStreamError)(assistantMessage.id, setMessages);
|
|
89
153
|
}
|
|
90
154
|
finally {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
const NOOP_TRACKING = {
|
|
5
|
+
trackSubmit: () => { },
|
|
6
|
+
trackResultLoadStarted: () => { },
|
|
7
|
+
trackResultLoadFinished: () => { },
|
|
8
|
+
trackResultClick: () => { },
|
|
9
|
+
trackResultView: () => { },
|
|
10
|
+
trackSearchSubmit: () => { },
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Fires ASA behavioral tracking events through the Constructor client. Each returned
|
|
14
|
+
* function maps to one `trackAssistant*` beacon and merges in `section` + `threadId`.
|
|
15
|
+
* Mirrors the tracking-hook pattern used by constructorio-ui-pia.
|
|
16
|
+
*/
|
|
17
|
+
function useAsaTracking({ tracker, section, threadId, }) {
|
|
18
|
+
const assistant = tracker;
|
|
19
|
+
const base = (0, react_1.useMemo)(() => (Object.assign(Object.assign({}, (section && { section })), (threadId && { threadId }))), [section, threadId]);
|
|
20
|
+
const trackSubmit = (0, react_1.useCallback)((intent) => {
|
|
21
|
+
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantSubmit(Object.assign({ intent }, base));
|
|
22
|
+
}, [assistant, base]);
|
|
23
|
+
const trackResultLoadStarted = (0, react_1.useCallback)(({ intent, intentResultId }) => {
|
|
24
|
+
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantResultLoadStarted(Object.assign(Object.assign({ intent }, (intentResultId && { intentResultId })), base));
|
|
25
|
+
}, [assistant, base]);
|
|
26
|
+
const trackResultLoadFinished = (0, react_1.useCallback)(({ intent, searchResultCount, intentResultId }) => {
|
|
27
|
+
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantResultLoadFinished(Object.assign(Object.assign({ intent,
|
|
28
|
+
searchResultCount }, (intentResultId && { intentResultId })), base));
|
|
29
|
+
}, [assistant, base]);
|
|
30
|
+
const trackResultClick = (0, react_1.useCallback)(({ intent, searchResultId, intentResultId, itemId, itemName, variationId, }) => {
|
|
31
|
+
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantResultClick(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ intent,
|
|
32
|
+
searchResultId }, (intentResultId && { intentResultId })), (itemId && { itemId })), (itemName && { itemName })), (variationId && { variationId })), base));
|
|
33
|
+
}, [assistant, base]);
|
|
34
|
+
const trackResultView = (0, react_1.useCallback)(({ intent, searchResultId, numResultsViewed, intentResultId, items }) => {
|
|
35
|
+
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantResultView(Object.assign(Object.assign(Object.assign({ intent,
|
|
36
|
+
searchResultId,
|
|
37
|
+
numResultsViewed }, (intentResultId && { intentResultId })), (items && items.length > 0 && { items })), base));
|
|
38
|
+
}, [assistant, base]);
|
|
39
|
+
const trackSearchSubmit = (0, react_1.useCallback)(({ intent, searchTerm, userInput, searchResultId, intentResultId, groupId, }) => {
|
|
40
|
+
assistant === null || assistant === void 0 ? void 0 : assistant.trackAssistantSearchSubmit(Object.assign(Object.assign(Object.assign({ intent,
|
|
41
|
+
searchTerm,
|
|
42
|
+
userInput,
|
|
43
|
+
searchResultId }, (intentResultId && { intentResultId })), (groupId && { groupId })), base));
|
|
44
|
+
}, [assistant, base]);
|
|
45
|
+
return (0, react_1.useMemo)(() => {
|
|
46
|
+
if (!assistant)
|
|
47
|
+
return NOOP_TRACKING;
|
|
48
|
+
return {
|
|
49
|
+
trackSubmit,
|
|
50
|
+
trackResultLoadStarted,
|
|
51
|
+
trackResultLoadFinished,
|
|
52
|
+
trackResultClick,
|
|
53
|
+
trackResultView,
|
|
54
|
+
trackSearchSubmit,
|
|
55
|
+
};
|
|
56
|
+
}, [
|
|
57
|
+
assistant,
|
|
58
|
+
trackSubmit,
|
|
59
|
+
trackResultLoadStarted,
|
|
60
|
+
trackResultLoadFinished,
|
|
61
|
+
trackResultClick,
|
|
62
|
+
trackResultView,
|
|
63
|
+
trackSearchSubmit,
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
exports.default = useAsaTracking;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
/**
|
|
5
|
+
* Fires `onView` once when the observed element scrolls into the viewport, modeling a
|
|
6
|
+
* per-pod `assistant_search_result_view` impression. Unlike the PIA widget-level view
|
|
7
|
+
* tracker, this fires a single impression per pod (not repeated enter/exit cycles).
|
|
8
|
+
*/
|
|
9
|
+
function useViewportTracking({ onView, enabled = true, }) {
|
|
10
|
+
const onViewRef = (0, react_1.useRef)(onView);
|
|
11
|
+
onViewRef.current = onView;
|
|
12
|
+
const firedRef = (0, react_1.useRef)(false);
|
|
13
|
+
const cleanupRef = (0, react_1.useRef)(null);
|
|
14
|
+
const ref = (0, react_1.useCallback)((node) => {
|
|
15
|
+
if (cleanupRef.current) {
|
|
16
|
+
cleanupRef.current();
|
|
17
|
+
cleanupRef.current = null;
|
|
18
|
+
}
|
|
19
|
+
if (!node || !enabled || firedRef.current)
|
|
20
|
+
return;
|
|
21
|
+
if (typeof IntersectionObserver === 'undefined')
|
|
22
|
+
return;
|
|
23
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
24
|
+
if (entry.isIntersecting && !firedRef.current) {
|
|
25
|
+
firedRef.current = true;
|
|
26
|
+
onViewRef.current();
|
|
27
|
+
observer.disconnect();
|
|
28
|
+
}
|
|
29
|
+
}, { threshold: 0.5 });
|
|
30
|
+
observer.observe(node);
|
|
31
|
+
cleanupRef.current = () => observer.disconnect();
|
|
32
|
+
}, [enabled]);
|
|
33
|
+
(0, react_1.useEffect)(() => () => {
|
|
34
|
+
if (cleanupRef.current)
|
|
35
|
+
cleanupRef.current();
|
|
36
|
+
}, []);
|
|
37
|
+
return { ref };
|
|
38
|
+
}
|
|
39
|
+
exports.default = useViewportTracking;
|
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.normalizeItemToProduct = exports.useAsaResults = exports.Button = exports.ResultsBlock = exports.Chat = exports.CioAsaProvider = void 0;
|
|
6
|
+
exports.ConstructorIOClient = exports.normalizeItemToProduct = exports.useAsaTracking = exports.useAsaResults = exports.Button = exports.ResultsBlock = exports.Chat = exports.CioAsaProvider = void 0;
|
|
7
7
|
// Components
|
|
8
8
|
var CioAsaProvider_1 = require("./components/CioAsaProvider/CioAsaProvider");
|
|
9
9
|
Object.defineProperty(exports, "CioAsaProvider", { enumerable: true, get: function () { return __importDefault(CioAsaProvider_1).default; } });
|
|
@@ -16,6 +16,8 @@ Object.defineProperty(exports, "Button", { enumerable: true, get: function () {
|
|
|
16
16
|
// Hooks
|
|
17
17
|
var useAsaResults_1 = require("./hooks/useAsaResults");
|
|
18
18
|
Object.defineProperty(exports, "useAsaResults", { enumerable: true, get: function () { return __importDefault(useAsaResults_1).default; } });
|
|
19
|
+
var useAsaTracking_1 = require("./hooks/useAsaTracking");
|
|
20
|
+
Object.defineProperty(exports, "useAsaTracking", { enumerable: true, get: function () { return __importDefault(useAsaTracking_1).default; } });
|
|
19
21
|
// Utils
|
|
20
22
|
var productNormalizer_1 = require("./utils/productNormalizer");
|
|
21
23
|
Object.defineProperty(exports, "normalizeItemToProduct", { enumerable: true, get: function () { return productNormalizer_1.normalizeItemToProduct; } });
|
|
@@ -4,17 +4,8 @@ exports.normalizeItemToProduct = void 0;
|
|
|
4
4
|
function normalizeItemToProduct(item, options) {
|
|
5
5
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
6
6
|
const data = item.data || {};
|
|
7
|
-
return {
|
|
8
|
-
id: String((_c = (_b = (_a = data.id) !== null && _a !== void 0 ? _a : data.result_id) !== null && _b !== void 0 ? _b : item.value) !== null && _c !== void 0 ? _c : ''),
|
|
9
|
-
name: String((_e = (_d = item.value) !== null && _d !== void 0 ? _d : data.item_name) !== null && _e !== void 0 ? _e : ''),
|
|
10
|
-
url: data.url,
|
|
11
|
-
imageUrl: (_f = data.image_url) !== null && _f !== void 0 ? _f : data.imageUrl,
|
|
12
|
-
price: data.price,
|
|
13
|
-
salePrice: (_g = data.sale_price) !== null && _g !== void 0 ? _g : data.salePrice,
|
|
14
|
-
description: data.description,
|
|
15
|
-
badge: data.sale_price != null || data.salePrice != null
|
|
7
|
+
return Object.assign(Object.assign({ id: String((_c = (_b = (_a = data.id) !== null && _a !== void 0 ? _a : data.result_id) !== null && _b !== void 0 ? _b : item.value) !== null && _c !== void 0 ? _c : ''), name: String((_e = (_d = item.value) !== null && _d !== void 0 ? _d : data.item_name) !== null && _e !== void 0 ? _e : '') }, (data.variation_id != null && { variationId: String(data.variation_id) })), { url: data.url, imageUrl: (_f = data.image_url) !== null && _f !== void 0 ? _f : data.imageUrl, price: data.price, salePrice: (_g = data.sale_price) !== null && _g !== void 0 ? _g : data.salePrice, description: data.description, badge: data.sale_price != null || data.salePrice != null
|
|
16
8
|
? (_h = options === null || options === void 0 ? void 0 : options.saleBadgeText) !== null && _h !== void 0 ? _h : 'Sale'
|
|
17
|
-
: undefined
|
|
18
|
-
};
|
|
9
|
+
: undefined });
|
|
19
10
|
}
|
|
20
11
|
exports.normalizeItemToProduct = normalizeItemToProduct;
|
package/lib/cjs/version.js
CHANGED
|
@@ -17,7 +17,16 @@ const Chat = forwardRef(({ onClose, className, initialSuggestions, termsText, on
|
|
|
17
17
|
useEffect(() => {
|
|
18
18
|
const root = isWelcome ? containerRef.current : chatViewRef.current;
|
|
19
19
|
const input = root?.querySelector('input');
|
|
20
|
-
input
|
|
20
|
+
if (input) {
|
|
21
|
+
// preventScroll: focusing must not scroll the page — the host may render Chat
|
|
22
|
+
// below the fold (or, as in Storybook docs, several instances on one page).
|
|
23
|
+
try {
|
|
24
|
+
input.focus({ preventScroll: true });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
input.focus();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
21
30
|
}, [isWelcome]);
|
|
22
31
|
useFocusTrap(containerRef, { onEscape: onClose });
|
|
23
32
|
return (React.createElement("div", { className: ['cio-asa', 'cio-asa-chat', className].filter(Boolean).join(' '), ref: containerRef, role: 'dialog', "aria-labelledby": 'cio-asa-chat-title' },
|
|
@@ -35,7 +35,7 @@ export default function ChatMessageList({ messages, onProductClick, onViewMore,
|
|
|
35
35
|
const hasGroups = !!message.groups?.length;
|
|
36
36
|
return (React.createElement("div", { key: message.id, className: 'cio-asa-ai-message-group' },
|
|
37
37
|
React.createElement(AiMessage, { message: message, componentOverrides: aiMessageOverrides, translations: translations }),
|
|
38
|
-
hasGroups && (React.createElement(ResultsBlock, { groups: message.groups, onProductClick: onProductClick, onViewMore: onViewMore, onAddToCart: onAddToCart, aspectRatio: aspectRatio, currency: currency, normalizeItem: normalizeItem, addToCartText: addToCartText, viewMoreText: viewMoreText, saleBadgeText: translate('CioAsa.results.saleBadge', translations), componentOverrides: resultsBlockOverrides }))));
|
|
38
|
+
hasGroups && (React.createElement(ResultsBlock, { groups: message.groups, intent: message.intent, intentResultId: message.intentResultId, threadId: message.threadId, onProductClick: onProductClick, onViewMore: onViewMore, onAddToCart: onAddToCart, aspectRatio: aspectRatio, currency: currency, normalizeItem: normalizeItem, addToCartText: addToCartText, viewMoreText: viewMoreText, saleBadgeText: translate('CioAsa.results.saleBadge', translations), componentOverrides: resultsBlockOverrides }))));
|
|
39
39
|
})));
|
|
40
40
|
}
|
|
41
41
|
ChatMessageList.displayName = 'ChatMessageList';
|