@constructor-io/constructorio-ui-pia 2.1.2 → 2.2.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/README.md +8 -0
- package/dist/constructorio-ui-pia-bundled.js +15 -15
- package/lib/cjs/components/CioPia/CioPia.js +5 -3
- package/lib/cjs/components/ConversationHistory/ConversationHistory.js +18 -14
- package/lib/cjs/components/ConversationHistory/ConversationHistoryStyles.css +7 -0
- package/lib/cjs/components/PiaConversation/PiaConversation.js +2 -2
- package/lib/cjs/hooks/useAnswerResults.js +5 -5
- package/lib/cjs/hooks/useCioPia.js +2 -1
- package/lib/cjs/hooks/useConversation.js +6 -4
- package/lib/cjs/utils/transformers.js +12 -4
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/CioPia/CioPia.js +5 -3
- package/lib/mjs/components/ConversationHistory/ConversationHistory.js +18 -14
- package/lib/mjs/components/ConversationHistory/ConversationHistoryStyles.css +7 -0
- package/lib/mjs/components/PiaConversation/PiaConversation.js +2 -2
- package/lib/mjs/hooks/useAnswerResults.js +5 -5
- package/lib/mjs/hooks/useCioPia.js +2 -1
- package/lib/mjs/hooks/useConversation.js +5 -3
- package/lib/mjs/utils/transformers.js +12 -2
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/CioPia/CioPia.d.ts +15 -1
- package/lib/types/components/ConversationHistory/ConversationHistory.d.ts +12 -1
- package/lib/types/components/PiaConversation/PiaConversation.d.ts +1 -1
- package/lib/types/hooks/useAnswerResults.d.ts +3 -2
- package/lib/types/hooks/useCioPia.d.ts +3 -1
- package/lib/types/hooks/useConversation.d.ts +3 -2
- package/lib/types/types.d.ts +15 -0
- package/lib/types/utils/transformers.d.ts +2 -2
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -17,8 +17,8 @@ const PiaCustomCarousel_1 = tslib_1.__importDefault(require("./PiaCustomCarousel
|
|
|
17
17
|
const PiaModal_1 = tslib_1.__importDefault(require("../PiaConversation/PiaModal"));
|
|
18
18
|
const PiaConversation_1 = tslib_1.__importDefault(require("../PiaConversation/PiaConversation"));
|
|
19
19
|
function CioPia(props) {
|
|
20
|
-
const { apiKey, itemId, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, children, translations, suggestedQuestionsParameters, } = props;
|
|
21
|
-
const { learnMoreUrl, showFeedback, mode = 'default', type = 'inline' } = displayConfigs || {};
|
|
20
|
+
const { apiKey, itemId, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, children, translations, suggestedQuestionsParameters, } = props;
|
|
21
|
+
const { learnMoreUrl, showFeedback, mode = 'default', type = 'inline', showPreviousItems, } = displayConfigs || {};
|
|
22
22
|
const isConversation = mode === 'conversation' || type === 'modal';
|
|
23
23
|
const pia = (0, useCioPia_1.default)({
|
|
24
24
|
apiKey,
|
|
@@ -27,8 +27,9 @@ function CioPia(props) {
|
|
|
27
27
|
variationId,
|
|
28
28
|
cioClient,
|
|
29
29
|
suggestedQuestionsParameters,
|
|
30
|
+
formatImageUrl: formatters === null || formatters === void 0 ? void 0 : formatters.formatImageUrl,
|
|
30
31
|
});
|
|
31
|
-
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, handleSubmitQuestion, resetState, } = (0, useConversation_1.default)({ pia, itemId, isConversation });
|
|
32
|
+
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, handleSubmitQuestion, resetState, } = (0, useConversation_1.default)({ pia, itemId, isConversation, callbacks });
|
|
32
33
|
const renderProps = {
|
|
33
34
|
items: currentItems,
|
|
34
35
|
isLoading,
|
|
@@ -45,6 +46,7 @@ function CioPia(props) {
|
|
|
45
46
|
error,
|
|
46
47
|
currentItems,
|
|
47
48
|
showFeedback,
|
|
49
|
+
showPreviousItems,
|
|
48
50
|
learnMoreUrl,
|
|
49
51
|
translations,
|
|
50
52
|
callbacks,
|
|
@@ -8,7 +8,7 @@ const Disclaimer_1 = tslib_1.__importDefault(require("../CioPia/Disclaimer"));
|
|
|
8
8
|
const ErrorBlock_1 = tslib_1.__importDefault(require("../Error/ErrorBlock"));
|
|
9
9
|
const LoadingSkeleton_1 = tslib_1.__importDefault(require("../LoadingSkeleton/LoadingSkeleton"));
|
|
10
10
|
const PiaCustomCarousel_1 = tslib_1.__importDefault(require("../CioPia/PiaCustomCarousel"));
|
|
11
|
-
function ConversationHistory({ conversationHistory, isLoading, error, currentItems, showFeedback, learnMoreUrl, translations, callbacks, componentOverrides, }) {
|
|
11
|
+
function ConversationHistory({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems = true, learnMoreUrl, translations, callbacks, componentOverrides, }) {
|
|
12
12
|
const scrollContainerRef = (0, react_1.useRef)(null);
|
|
13
13
|
(0, react_1.useEffect)(() => {
|
|
14
14
|
const container = scrollContainerRef.current;
|
|
@@ -19,18 +19,22 @@ function ConversationHistory({ conversationHistory, isLoading, error, currentIte
|
|
|
19
19
|
});
|
|
20
20
|
return () => cancelAnimationFrame(frameId);
|
|
21
21
|
}, [conversationHistory, isLoading]);
|
|
22
|
-
return (react_1.default.createElement("div", {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
react_1.default.createElement(
|
|
31
|
-
|
|
32
|
-
isLast &&
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
return (react_1.default.createElement("div", { className: 'cio-pia-conversation-history' },
|
|
23
|
+
react_1.default.createElement("div", { ref: scrollContainerRef, className: 'cio-pia-conversation-entries', role: 'log', "aria-label": 'Conversation history' }, conversationHistory.map((entry, index) => {
|
|
24
|
+
const isLast = index === conversationHistory.length - 1;
|
|
25
|
+
const previousEntryItems = showPreviousItems ? entry.items : null;
|
|
26
|
+
const latestEntryItems = currentItems !== undefined ? currentItems : entry.items;
|
|
27
|
+
const carouselItems = isLast ? latestEntryItems : previousEntryItems;
|
|
28
|
+
return (react_1.default.createElement("div", { key: entry.id, className: 'cio-pia-conversation-entry' },
|
|
29
|
+
react_1.default.createElement("div", { className: 'cio-pia-chat-question' }, entry.question),
|
|
30
|
+
isLast && isLoading && (react_1.default.createElement("div", { className: 'cio-pia-conversation-loading', "aria-live": 'polite' },
|
|
31
|
+
react_1.default.createElement(LoadingSkeleton_1.default, null))),
|
|
32
|
+
isLast && !isLoading && error && (react_1.default.createElement(ErrorBlock_1.default, { message: error.message || 'Unexpected error' })),
|
|
33
|
+
entry.answer && (react_1.default.createElement("div", { className: 'cio-pia-answer-container' },
|
|
34
|
+
react_1.default.createElement(Answer_1.default, { text: entry.answer, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.answer }),
|
|
35
|
+
carouselItems && (react_1.default.createElement(PiaCustomCarousel_1.default, { items: carouselItems, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel, callbacks: callbacks })),
|
|
36
|
+
isLast && showFeedback && (react_1.default.createElement(Feedback_1.default, { translations: translations, onFeedback: callbacks === null || callbacks === void 0 ? void 0 : callbacks.onFeedback, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.feedback }))))));
|
|
37
|
+
})),
|
|
38
|
+
react_1.default.createElement(Disclaimer_1.default, { learnMoreUrl: learnMoreUrl, translations: translations, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.disclaimer })));
|
|
35
39
|
}
|
|
36
40
|
exports.default = ConversationHistory;
|
|
@@ -7,11 +7,11 @@ const SuggestedQuestionsContainer_1 = tslib_1.__importDefault(require("../Sugges
|
|
|
7
7
|
const SuggestedQuestionsSkeleton_1 = tslib_1.__importDefault(require("../SuggestedQuestionsContainer/SuggestedQuestionsSkeleton"));
|
|
8
8
|
const translate_1 = require("../../utils/translate");
|
|
9
9
|
const ConversationHistory_1 = tslib_1.__importDefault(require("../ConversationHistory/ConversationHistory"));
|
|
10
|
-
function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, learnMoreUrl, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, }) {
|
|
10
|
+
function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, }) {
|
|
11
11
|
const hasHistory = conversationHistory.length > 0;
|
|
12
12
|
return (react_1.default.createElement("div", { className: 'cio-pia-container cio-pia-conversation', "data-testid": 'cio-pia-container' },
|
|
13
13
|
!hasHistory && (react_1.default.createElement("p", { className: 'cio-pia-title', "data-testid": 'cio-pia-title' }, (0, translate_1.translate)('Any questions about this product?', translations))),
|
|
14
|
-
react_1.default.createElement(ConversationHistory_1.default, { conversationHistory: conversationHistory, isLoading: isLoading, error: error, currentItems: currentItems, showFeedback: showFeedback, learnMoreUrl: learnMoreUrl, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides }),
|
|
14
|
+
react_1.default.createElement(ConversationHistory_1.default, { conversationHistory: conversationHistory, isLoading: isLoading, error: error, currentItems: currentItems, showFeedback: showFeedback, showPreviousItems: showPreviousItems, learnMoreUrl: learnMoreUrl, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides }),
|
|
15
15
|
react_1.default.createElement("div", { className: 'cio-pia-conversation-footer' },
|
|
16
16
|
isLoading && !error && react_1.default.createElement(SuggestedQuestionsSkeleton_1.default, null),
|
|
17
17
|
!isLoading && !error && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: displayedQuestions, onQuestionClick: handleSubmitQuestion, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.suggestedQuestions })),
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const transformers_1 = require("../utils/transformers");
|
|
6
|
-
const extractAndTransformItems = (data) => {
|
|
6
|
+
const extractAndTransformItems = (data, formatImageUrl) => {
|
|
7
7
|
var _a, _b;
|
|
8
8
|
if (!((_b = (_a = data === null || data === void 0 ? void 0 : data.item_results) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.results)) {
|
|
9
9
|
return null;
|
|
@@ -13,7 +13,7 @@ const extractAndTransformItems = (data) => {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
const transformedItems = results
|
|
16
|
-
.map(transformers_1.transformResultItem)
|
|
16
|
+
.map((item) => (0, transformers_1.transformResultItem)(item, formatImageUrl))
|
|
17
17
|
.filter((item) => item !== null);
|
|
18
18
|
return transformedItems.length > 0 ? transformedItems : null;
|
|
19
19
|
};
|
|
@@ -26,7 +26,7 @@ const fetchAnswerResults = ({ client, itemId, question, variationId, threadId, }
|
|
|
26
26
|
});
|
|
27
27
|
return response;
|
|
28
28
|
});
|
|
29
|
-
function useAnswerResults({ itemId, variationId, threadId, cioClient, }) {
|
|
29
|
+
function useAnswerResults({ itemId, variationId, threadId, cioClient, formatImageUrl, }) {
|
|
30
30
|
const [answerResults, setAnswerResults] = (0, react_1.useState)(null);
|
|
31
31
|
const [items, setItems] = (0, react_1.useState)(null);
|
|
32
32
|
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
@@ -39,7 +39,7 @@ function useAnswerResults({ itemId, variationId, threadId, cioClient, }) {
|
|
|
39
39
|
fetchAnswerResults({ client: cioClient, itemId, question, variationId, threadId })
|
|
40
40
|
.then((fetchedAnswerResults) => {
|
|
41
41
|
setAnswerResults(fetchedAnswerResults);
|
|
42
|
-
setItems(extractAndTransformItems(fetchedAnswerResults));
|
|
42
|
+
setItems(extractAndTransformItems(fetchedAnswerResults, formatImageUrl));
|
|
43
43
|
setError(null);
|
|
44
44
|
})
|
|
45
45
|
.catch((err) => {
|
|
@@ -50,7 +50,7 @@ function useAnswerResults({ itemId, variationId, threadId, cioClient, }) {
|
|
|
50
50
|
.finally(() => {
|
|
51
51
|
setIsLoading(false);
|
|
52
52
|
});
|
|
53
|
-
}, [cioClient, itemId, variationId, threadId]);
|
|
53
|
+
}, [cioClient, itemId, variationId, threadId, formatImageUrl]);
|
|
54
54
|
return {
|
|
55
55
|
data: answerResults,
|
|
56
56
|
items,
|
|
@@ -5,7 +5,7 @@ const useAnswerResults_1 = tslib_1.__importDefault(require("./useAnswerResults")
|
|
|
5
5
|
const useCioClient_1 = tslib_1.__importDefault(require("./useCioClient"));
|
|
6
6
|
const useSuggestedQuestions_1 = tslib_1.__importDefault(require("./useSuggestedQuestions"));
|
|
7
7
|
function useCioPia(props) {
|
|
8
|
-
const { apiKey, itemId, variationId, threadId, cioClient: providedClient, suggestedQuestionsParameters, } = props;
|
|
8
|
+
const { apiKey, itemId, variationId, threadId, cioClient: providedClient, suggestedQuestionsParameters, formatImageUrl, } = props;
|
|
9
9
|
const defaultClient = (0, useCioClient_1.default)({ apiKey });
|
|
10
10
|
const client = providedClient || defaultClient;
|
|
11
11
|
const suggestedQuestions = (0, useSuggestedQuestions_1.default)({
|
|
@@ -20,6 +20,7 @@ function useCioPia(props) {
|
|
|
20
20
|
variationId,
|
|
21
21
|
threadId,
|
|
22
22
|
cioClient: client,
|
|
23
|
+
formatImageUrl,
|
|
23
24
|
});
|
|
24
25
|
return {
|
|
25
26
|
suggestedQuestions,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const react_1 = require("react");
|
|
4
|
-
function useConversation({ pia, itemId, isConversation, }) {
|
|
4
|
+
function useConversation({ pia, itemId, isConversation, callbacks, }) {
|
|
5
5
|
var _a, _b, _c, _d;
|
|
6
6
|
const { suggestedQuestions, answers } = pia;
|
|
7
7
|
const { getAnswer } = answers;
|
|
@@ -11,6 +11,8 @@ function useConversation({ pia, itemId, isConversation, }) {
|
|
|
11
11
|
const entryIdRef = (0, react_1.useRef)(0);
|
|
12
12
|
const prevAnswerValueRef = (0, react_1.useRef)((_a = answers.data) === null || _a === void 0 ? void 0 : _a.value);
|
|
13
13
|
const handleSubmitQuestion = (0, react_1.useCallback)((question) => {
|
|
14
|
+
var _a;
|
|
15
|
+
(_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onQuestionSubmit) === null || _a === void 0 ? void 0 : _a.call(callbacks, question);
|
|
14
16
|
setCurrentQuestion(question);
|
|
15
17
|
getAnswer(question);
|
|
16
18
|
if (isConversation) {
|
|
@@ -18,7 +20,7 @@ function useConversation({ pia, itemId, isConversation, }) {
|
|
|
18
20
|
const id = entryIdRef.current;
|
|
19
21
|
setConversationHistory((prev) => [...prev, { id, question, answer: '' }]);
|
|
20
22
|
}
|
|
21
|
-
}, [getAnswer, isConversation]);
|
|
23
|
+
}, [getAnswer, isConversation, callbacks]);
|
|
22
24
|
const resetState = (0, react_1.useCallback)(() => {
|
|
23
25
|
setCurrentQuestion('');
|
|
24
26
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
@@ -51,10 +53,10 @@ function useConversation({ pia, itemId, isConversation, }) {
|
|
|
51
53
|
if (prev.length === 0)
|
|
52
54
|
return prev;
|
|
53
55
|
const updated = [...prev];
|
|
54
|
-
updated[updated.length - 1] = Object.assign(Object.assign({}, updated[updated.length - 1]), { answer: answerValue });
|
|
56
|
+
updated[updated.length - 1] = Object.assign(Object.assign({}, updated[updated.length - 1]), { answer: answerValue, items: answers.items });
|
|
55
57
|
return updated;
|
|
56
58
|
});
|
|
57
|
-
}, [isConversation, answers.data]);
|
|
59
|
+
}, [isConversation, answers.data, answers.items]);
|
|
58
60
|
const currentAnswer = (_c = (_b = answers.data) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : '';
|
|
59
61
|
const currentItems = (_d = answers.items) !== null && _d !== void 0 ? _d : null;
|
|
60
62
|
const error = answers.error || suggestedQuestions.error;
|
|
@@ -6,18 +6,26 @@ const tslib_1 = require("tslib");
|
|
|
6
6
|
* Converts a raw ApiItem from the Get Answers API response into an Item object
|
|
7
7
|
* that can be used in the Carousel component.
|
|
8
8
|
*/
|
|
9
|
-
function transformResultItem(resultItem) {
|
|
9
|
+
function transformResultItem(resultItem, formatImageUrl) {
|
|
10
10
|
const { value, matched_terms: matchedTerms, data } = resultItem, otherFields = tslib_1.__rest(resultItem, ["value", "matched_terms", "data"]);
|
|
11
11
|
if (!data || typeof data !== 'object' || !data.id || !value) {
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
14
14
|
const { id, variation_id: variationId, description, url, image_url: imageUrl, price, sale_price: salePrice, rating, reviews_count: reviewsCount, tags, sl_campaign_id: slCampaignId, sl_campaign_owner: slCampaignOwner, badge } = data, otherMetadataFields = tslib_1.__rest(data, ["id", "variation_id", "description", "url", "image_url", "price", "sale_price", "rating", "reviews_count", "tags", "sl_campaign_id", "sl_campaign_owner", "badge"]);
|
|
15
|
+
// Skip formatting for absent or empty URLs; consumers should handle placeholders separately.
|
|
16
|
+
let formattedImageUrl = imageUrl;
|
|
17
|
+
if (formatImageUrl && imageUrl) {
|
|
18
|
+
try {
|
|
19
|
+
formattedImageUrl = formatImageUrl(imageUrl);
|
|
20
|
+
}
|
|
21
|
+
catch (_a) {
|
|
22
|
+
formattedImageUrl = imageUrl;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
return Object.assign({ id, name: value, matchedTerms,
|
|
16
26
|
variationId,
|
|
17
27
|
description,
|
|
18
|
-
url,
|
|
19
|
-
imageUrl,
|
|
20
|
-
price,
|
|
28
|
+
url, imageUrl: formattedImageUrl, price,
|
|
21
29
|
salePrice,
|
|
22
30
|
rating,
|
|
23
31
|
reviewsCount,
|
package/lib/cjs/version.js
CHANGED
|
@@ -14,8 +14,8 @@ import PiaCustomCarousel from './PiaCustomCarousel';
|
|
|
14
14
|
import PiaModal from '../PiaConversation/PiaModal';
|
|
15
15
|
import PiaConversation from '../PiaConversation/PiaConversation';
|
|
16
16
|
export default function CioPia(props) {
|
|
17
|
-
const { apiKey, itemId, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, children, translations, suggestedQuestionsParameters, } = props;
|
|
18
|
-
const { learnMoreUrl, showFeedback, mode = 'default', type = 'inline' } = displayConfigs || {};
|
|
17
|
+
const { apiKey, itemId, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, children, translations, suggestedQuestionsParameters, } = props;
|
|
18
|
+
const { learnMoreUrl, showFeedback, mode = 'default', type = 'inline', showPreviousItems, } = displayConfigs || {};
|
|
19
19
|
const isConversation = mode === 'conversation' || type === 'modal';
|
|
20
20
|
const pia = useCioPia({
|
|
21
21
|
apiKey,
|
|
@@ -24,8 +24,9 @@ export default function CioPia(props) {
|
|
|
24
24
|
variationId,
|
|
25
25
|
cioClient,
|
|
26
26
|
suggestedQuestionsParameters,
|
|
27
|
+
formatImageUrl: formatters?.formatImageUrl,
|
|
27
28
|
});
|
|
28
|
-
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, handleSubmitQuestion, resetState, } = useConversation({ pia, itemId, isConversation });
|
|
29
|
+
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, handleSubmitQuestion, resetState, } = useConversation({ pia, itemId, isConversation, callbacks });
|
|
29
30
|
const renderProps = {
|
|
30
31
|
items: currentItems,
|
|
31
32
|
isLoading,
|
|
@@ -42,6 +43,7 @@ export default function CioPia(props) {
|
|
|
42
43
|
error,
|
|
43
44
|
currentItems,
|
|
44
45
|
showFeedback,
|
|
46
|
+
showPreviousItems,
|
|
45
47
|
learnMoreUrl,
|
|
46
48
|
translations,
|
|
47
49
|
callbacks,
|
|
@@ -5,7 +5,7 @@ import Disclaimer from '../CioPia/Disclaimer';
|
|
|
5
5
|
import ErrorBlock from '../Error/ErrorBlock';
|
|
6
6
|
import LoadingSkeleton from '../LoadingSkeleton/LoadingSkeleton';
|
|
7
7
|
import PiaCustomCarousel from '../CioPia/PiaCustomCarousel';
|
|
8
|
-
export default function ConversationHistory({ conversationHistory, isLoading, error, currentItems, showFeedback, learnMoreUrl, translations, callbacks, componentOverrides, }) {
|
|
8
|
+
export default function ConversationHistory({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems = true, learnMoreUrl, translations, callbacks, componentOverrides, }) {
|
|
9
9
|
const scrollContainerRef = useRef(null);
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
const container = scrollContainerRef.current;
|
|
@@ -16,17 +16,21 @@ export default function ConversationHistory({ conversationHistory, isLoading, er
|
|
|
16
16
|
});
|
|
17
17
|
return () => cancelAnimationFrame(frameId);
|
|
18
18
|
}, [conversationHistory, isLoading]);
|
|
19
|
-
return (React.createElement("div", {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
React.createElement(
|
|
28
|
-
|
|
29
|
-
isLast &&
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
return (React.createElement("div", { className: 'cio-pia-conversation-history' },
|
|
20
|
+
React.createElement("div", { ref: scrollContainerRef, className: 'cio-pia-conversation-entries', role: 'log', "aria-label": 'Conversation history' }, conversationHistory.map((entry, index) => {
|
|
21
|
+
const isLast = index === conversationHistory.length - 1;
|
|
22
|
+
const previousEntryItems = showPreviousItems ? entry.items : null;
|
|
23
|
+
const latestEntryItems = currentItems !== undefined ? currentItems : entry.items;
|
|
24
|
+
const carouselItems = isLast ? latestEntryItems : previousEntryItems;
|
|
25
|
+
return (React.createElement("div", { key: entry.id, className: 'cio-pia-conversation-entry' },
|
|
26
|
+
React.createElement("div", { className: 'cio-pia-chat-question' }, entry.question),
|
|
27
|
+
isLast && isLoading && (React.createElement("div", { className: 'cio-pia-conversation-loading', "aria-live": 'polite' },
|
|
28
|
+
React.createElement(LoadingSkeleton, null))),
|
|
29
|
+
isLast && !isLoading && error && (React.createElement(ErrorBlock, { message: error.message || 'Unexpected error' })),
|
|
30
|
+
entry.answer && (React.createElement("div", { className: 'cio-pia-answer-container' },
|
|
31
|
+
React.createElement(Answer, { text: entry.answer, componentOverride: componentOverrides?.answer }),
|
|
32
|
+
carouselItems && (React.createElement(PiaCustomCarousel, { items: carouselItems, componentOverrides: componentOverrides?.carousel, callbacks: callbacks })),
|
|
33
|
+
isLast && showFeedback && (React.createElement(Feedback, { translations: translations, onFeedback: callbacks?.onFeedback, componentOverride: componentOverrides?.feedback }))))));
|
|
34
|
+
})),
|
|
35
|
+
React.createElement(Disclaimer, { learnMoreUrl: learnMoreUrl, translations: translations, componentOverride: componentOverrides?.disclaimer })));
|
|
32
36
|
}
|
|
@@ -4,11 +4,11 @@ import SuggestedQuestionsContainer from '../SuggestedQuestionsContainer/Suggeste
|
|
|
4
4
|
import SuggestedQuestionsSkeleton from '../SuggestedQuestionsContainer/SuggestedQuestionsSkeleton';
|
|
5
5
|
import { translate } from '../../utils/translate';
|
|
6
6
|
import ConversationHistory from '../ConversationHistory/ConversationHistory';
|
|
7
|
-
export default function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, learnMoreUrl, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, }) {
|
|
7
|
+
export default function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, }) {
|
|
8
8
|
const hasHistory = conversationHistory.length > 0;
|
|
9
9
|
return (React.createElement("div", { className: 'cio-pia-container cio-pia-conversation', "data-testid": 'cio-pia-container' },
|
|
10
10
|
!hasHistory && (React.createElement("p", { className: 'cio-pia-title', "data-testid": 'cio-pia-title' }, translate('Any questions about this product?', translations))),
|
|
11
|
-
React.createElement(ConversationHistory, { conversationHistory: conversationHistory, isLoading: isLoading, error: error, currentItems: currentItems, showFeedback: showFeedback, learnMoreUrl: learnMoreUrl, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides }),
|
|
11
|
+
React.createElement(ConversationHistory, { conversationHistory: conversationHistory, isLoading: isLoading, error: error, currentItems: currentItems, showFeedback: showFeedback, showPreviousItems: showPreviousItems, learnMoreUrl: learnMoreUrl, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides }),
|
|
12
12
|
React.createElement("div", { className: 'cio-pia-conversation-footer' },
|
|
13
13
|
isLoading && !error && React.createElement(SuggestedQuestionsSkeleton, null),
|
|
14
14
|
!isLoading && !error && (React.createElement(SuggestedQuestionsContainer, { questions: displayedQuestions, onQuestionClick: handleSubmitQuestion, componentOverride: componentOverrides?.suggestedQuestions })),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
2
|
import { transformResultItem } from '../utils/transformers';
|
|
3
|
-
const extractAndTransformItems = (data) => {
|
|
3
|
+
const extractAndTransformItems = (data, formatImageUrl) => {
|
|
4
4
|
if (!data?.item_results?.response?.results) {
|
|
5
5
|
return null;
|
|
6
6
|
}
|
|
@@ -9,7 +9,7 @@ const extractAndTransformItems = (data) => {
|
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
11
|
const transformedItems = results
|
|
12
|
-
.map(transformResultItem)
|
|
12
|
+
.map((item) => transformResultItem(item, formatImageUrl))
|
|
13
13
|
.filter((item) => item !== null);
|
|
14
14
|
return transformedItems.length > 0 ? transformedItems : null;
|
|
15
15
|
};
|
|
@@ -22,7 +22,7 @@ const fetchAnswerResults = async ({ client, itemId, question, variationId, threa
|
|
|
22
22
|
});
|
|
23
23
|
return response;
|
|
24
24
|
};
|
|
25
|
-
export default function useAnswerResults({ itemId, variationId, threadId, cioClient, }) {
|
|
25
|
+
export default function useAnswerResults({ itemId, variationId, threadId, cioClient, formatImageUrl, }) {
|
|
26
26
|
const [answerResults, setAnswerResults] = useState(null);
|
|
27
27
|
const [items, setItems] = useState(null);
|
|
28
28
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -35,7 +35,7 @@ export default function useAnswerResults({ itemId, variationId, threadId, cioCli
|
|
|
35
35
|
fetchAnswerResults({ client: cioClient, itemId, question, variationId, threadId })
|
|
36
36
|
.then((fetchedAnswerResults) => {
|
|
37
37
|
setAnswerResults(fetchedAnswerResults);
|
|
38
|
-
setItems(extractAndTransformItems(fetchedAnswerResults));
|
|
38
|
+
setItems(extractAndTransformItems(fetchedAnswerResults, formatImageUrl));
|
|
39
39
|
setError(null);
|
|
40
40
|
})
|
|
41
41
|
.catch((err) => {
|
|
@@ -46,7 +46,7 @@ export default function useAnswerResults({ itemId, variationId, threadId, cioCli
|
|
|
46
46
|
.finally(() => {
|
|
47
47
|
setIsLoading(false);
|
|
48
48
|
});
|
|
49
|
-
}, [cioClient, itemId, variationId, threadId]);
|
|
49
|
+
}, [cioClient, itemId, variationId, threadId, formatImageUrl]);
|
|
50
50
|
return {
|
|
51
51
|
data: answerResults,
|
|
52
52
|
items,
|
|
@@ -2,7 +2,7 @@ import useAnswerResults from './useAnswerResults';
|
|
|
2
2
|
import useCioClient from './useCioClient';
|
|
3
3
|
import useSuggestedQuestions from './useSuggestedQuestions';
|
|
4
4
|
export default function useCioPia(props) {
|
|
5
|
-
const { apiKey, itemId, variationId, threadId, cioClient: providedClient, suggestedQuestionsParameters, } = props;
|
|
5
|
+
const { apiKey, itemId, variationId, threadId, cioClient: providedClient, suggestedQuestionsParameters, formatImageUrl, } = props;
|
|
6
6
|
const defaultClient = useCioClient({ apiKey });
|
|
7
7
|
const client = providedClient || defaultClient;
|
|
8
8
|
const suggestedQuestions = useSuggestedQuestions({
|
|
@@ -17,6 +17,7 @@ export default function useCioPia(props) {
|
|
|
17
17
|
variationId,
|
|
18
18
|
threadId,
|
|
19
19
|
cioClient: client,
|
|
20
|
+
formatImageUrl,
|
|
20
21
|
});
|
|
21
22
|
return {
|
|
22
23
|
suggestedQuestions,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
export default function useConversation({ pia, itemId, isConversation, }) {
|
|
2
|
+
export default function useConversation({ pia, itemId, isConversation, callbacks, }) {
|
|
3
3
|
const { suggestedQuestions, answers } = pia;
|
|
4
4
|
const { getAnswer } = answers;
|
|
5
5
|
const [currentQuestion, setCurrentQuestion] = useState('');
|
|
@@ -8,6 +8,7 @@ export default function useConversation({ pia, itemId, isConversation, }) {
|
|
|
8
8
|
const entryIdRef = useRef(0);
|
|
9
9
|
const prevAnswerValueRef = useRef(answers.data?.value);
|
|
10
10
|
const handleSubmitQuestion = useCallback((question) => {
|
|
11
|
+
callbacks?.onQuestionSubmit?.(question);
|
|
11
12
|
setCurrentQuestion(question);
|
|
12
13
|
getAnswer(question);
|
|
13
14
|
if (isConversation) {
|
|
@@ -15,7 +16,7 @@ export default function useConversation({ pia, itemId, isConversation, }) {
|
|
|
15
16
|
const id = entryIdRef.current;
|
|
16
17
|
setConversationHistory((prev) => [...prev, { id, question, answer: '' }]);
|
|
17
18
|
}
|
|
18
|
-
}, [getAnswer, isConversation]);
|
|
19
|
+
}, [getAnswer, isConversation, callbacks]);
|
|
19
20
|
const resetState = useCallback(() => {
|
|
20
21
|
setCurrentQuestion('');
|
|
21
22
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
@@ -49,10 +50,11 @@ export default function useConversation({ pia, itemId, isConversation, }) {
|
|
|
49
50
|
updated[updated.length - 1] = {
|
|
50
51
|
...updated[updated.length - 1],
|
|
51
52
|
answer: answerValue,
|
|
53
|
+
items: answers.items,
|
|
52
54
|
};
|
|
53
55
|
return updated;
|
|
54
56
|
});
|
|
55
|
-
}, [isConversation, answers.data]);
|
|
57
|
+
}, [isConversation, answers.data, answers.items]);
|
|
56
58
|
const currentAnswer = answers.data?.value ?? '';
|
|
57
59
|
const currentItems = answers.items ?? null;
|
|
58
60
|
const error = answers.error || suggestedQuestions.error;
|
|
@@ -2,12 +2,22 @@
|
|
|
2
2
|
* Converts a raw ApiItem from the Get Answers API response into an Item object
|
|
3
3
|
* that can be used in the Carousel component.
|
|
4
4
|
*/
|
|
5
|
-
export function transformResultItem(resultItem) {
|
|
5
|
+
export function transformResultItem(resultItem, formatImageUrl) {
|
|
6
6
|
const { value, matched_terms: matchedTerms, data, ...otherFields } = resultItem;
|
|
7
7
|
if (!data || typeof data !== 'object' || !data.id || !value) {
|
|
8
8
|
return null;
|
|
9
9
|
}
|
|
10
10
|
const { id, variation_id: variationId, description, url, image_url: imageUrl, price, sale_price: salePrice, rating, reviews_count: reviewsCount, tags, sl_campaign_id: slCampaignId, sl_campaign_owner: slCampaignOwner, badge, ...otherMetadataFields } = data;
|
|
11
|
+
// Skip formatting for absent or empty URLs; consumers should handle placeholders separately.
|
|
12
|
+
let formattedImageUrl = imageUrl;
|
|
13
|
+
if (formatImageUrl && imageUrl) {
|
|
14
|
+
try {
|
|
15
|
+
formattedImageUrl = formatImageUrl(imageUrl);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
formattedImageUrl = imageUrl;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
return {
|
|
12
22
|
id,
|
|
13
23
|
name: value,
|
|
@@ -15,7 +25,7 @@ export function transformResultItem(resultItem) {
|
|
|
15
25
|
variationId,
|
|
16
26
|
description,
|
|
17
27
|
url,
|
|
18
|
-
imageUrl,
|
|
28
|
+
imageUrl: formattedImageUrl,
|
|
19
29
|
price,
|
|
20
30
|
salePrice,
|
|
21
31
|
rating,
|
package/lib/mjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '2.1
|
|
1
|
+
export default '2.2.1';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { IncludeComponentOverrides, IncludeRenderProps } from '@constructor-io/constructorio-ui-components';
|
|
3
3
|
import MockConstructorIOClient from '../../hooks/mocks/MockConstructorIOClient';
|
|
4
|
-
import { CioPiaRenderProps, CioPiaComponentOverrides, Callbacks, CioPiaDisplayConfigs, Translations, SuggestedQuestionsParameters } from '../../types';
|
|
4
|
+
import { CioPiaRenderProps, CioPiaComponentOverrides, Callbacks, CioPiaDisplayConfigs, Translations, SuggestedQuestionsParameters, Formatters } from '../../types';
|
|
5
5
|
export interface CioPiaProps extends IncludeRenderProps<CioPiaRenderProps>, IncludeComponentOverrides<CioPiaComponentOverrides> {
|
|
6
6
|
apiKey: string;
|
|
7
7
|
itemId: string;
|
|
@@ -10,7 +10,21 @@ export interface CioPiaProps extends IncludeRenderProps<CioPiaRenderProps>, Incl
|
|
|
10
10
|
variationId?: string;
|
|
11
11
|
cioClient?: MockConstructorIOClient;
|
|
12
12
|
displayConfigs?: CioPiaDisplayConfigs;
|
|
13
|
+
/**
|
|
14
|
+
* Callback handlers for user interactions:
|
|
15
|
+
*
|
|
16
|
+
* `onQuestionSubmit: (question: string) => void`
|
|
17
|
+
* Called when a question is submitted (via Enter key, Send button, or suggested question click).
|
|
18
|
+
*
|
|
19
|
+
* `onProductCardClick: (item: Item) => void`
|
|
20
|
+
* Called when a product card in the carousel is clicked.
|
|
21
|
+
*
|
|
22
|
+
* `onFeedback: (type: 'up' | 'down') => void`
|
|
23
|
+
* Called when the user submits positive or negative feedback on an answer.
|
|
24
|
+
*/
|
|
13
25
|
callbacks?: Callbacks;
|
|
26
|
+
/** Define formatter functions outside the component or memoize to avoid unnecessary re-renders. */
|
|
27
|
+
formatters?: Formatters;
|
|
14
28
|
translations?: Translations;
|
|
15
29
|
suggestedQuestionsParameters?: SuggestedQuestionsParameters;
|
|
16
30
|
}
|
|
@@ -4,11 +4,22 @@ export interface ConversationHistoryProps {
|
|
|
4
4
|
conversationHistory: ConversationEntry[];
|
|
5
5
|
isLoading: boolean;
|
|
6
6
|
error: Error | null;
|
|
7
|
+
/**
|
|
8
|
+
* Items for the latest conversation entry's carousel.
|
|
9
|
+
* - `undefined` (not provided): falls back to entry.items
|
|
10
|
+
* - `null`: explicitly no items, hides the carousel
|
|
11
|
+
* - `Item[]`: shows these items, overriding entry.items
|
|
12
|
+
*/
|
|
7
13
|
currentItems?: Item[] | null;
|
|
8
14
|
showFeedback?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Show product carousels on non-last conversation entries. Defaults to true.
|
|
17
|
+
* The last entry always falls back to its own items when currentItems is not provided.
|
|
18
|
+
*/
|
|
19
|
+
showPreviousItems?: boolean;
|
|
9
20
|
learnMoreUrl?: string;
|
|
10
21
|
translations?: Translations;
|
|
11
22
|
callbacks?: Callbacks;
|
|
12
23
|
componentOverrides?: CioPiaComponentOverrides;
|
|
13
24
|
}
|
|
14
|
-
export default function ConversationHistory({ conversationHistory, isLoading, error, currentItems, showFeedback, learnMoreUrl, translations, callbacks, componentOverrides, }: ConversationHistoryProps): React.JSX.Element;
|
|
25
|
+
export default function ConversationHistory({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, translations, callbacks, componentOverrides, }: ConversationHistoryProps): React.JSX.Element;
|
|
@@ -5,4 +5,4 @@ export interface PiaConversationProps extends ConversationHistoryProps {
|
|
|
5
5
|
displayedQuestions: Question[];
|
|
6
6
|
handleSubmitQuestion: (question: string) => void;
|
|
7
7
|
}
|
|
8
|
-
export default function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, learnMoreUrl, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, }: PiaConversationProps): React.JSX.Element;
|
|
8
|
+
export default function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, }: PiaConversationProps): React.JSX.Element;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Nullable } from '@constructor-io/constructorio-client-javascript';
|
|
2
2
|
import MockConstructorIOClient from './mocks/MockConstructorIOClient';
|
|
3
|
-
import { Item, GetAnswerResultsResponse } from '../types';
|
|
3
|
+
import { Formatters, Item, GetAnswerResultsResponse } from '../types';
|
|
4
4
|
export interface UseAnswerResultsProps {
|
|
5
5
|
itemId: string;
|
|
6
6
|
variationId?: string;
|
|
7
7
|
threadId?: string;
|
|
8
8
|
cioClient: MockConstructorIOClient;
|
|
9
9
|
parameters?: Record<string, any>;
|
|
10
|
+
formatImageUrl?: Formatters['formatImageUrl'];
|
|
10
11
|
}
|
|
11
12
|
export interface UseAnswerResultsReturn {
|
|
12
13
|
data: Nullable<GetAnswerResultsResponse>;
|
|
@@ -15,4 +16,4 @@ export interface UseAnswerResultsReturn {
|
|
|
15
16
|
error: Error | null;
|
|
16
17
|
getAnswer: (question: string) => void;
|
|
17
18
|
}
|
|
18
|
-
export default function useAnswerResults({ itemId, variationId, threadId, cioClient, }: UseAnswerResultsProps): UseAnswerResultsReturn;
|
|
19
|
+
export default function useAnswerResults({ itemId, variationId, threadId, cioClient, formatImageUrl, }: UseAnswerResultsProps): UseAnswerResultsReturn;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SuggestedQuestionsParameters } from '../types';
|
|
1
|
+
import { Formatters, SuggestedQuestionsParameters } from '../types';
|
|
2
2
|
import MockConstructorIOClient from './mocks/MockConstructorIOClient';
|
|
3
3
|
import { UseAnswerResultsReturn } from './useAnswerResults';
|
|
4
4
|
import { UseSuggestedQuestionsReturn } from './useSuggestedQuestions';
|
|
@@ -9,6 +9,8 @@ export interface UseCioPiaProps {
|
|
|
9
9
|
threadId?: string;
|
|
10
10
|
cioClient?: MockConstructorIOClient;
|
|
11
11
|
suggestedQuestionsParameters?: SuggestedQuestionsParameters;
|
|
12
|
+
/** Define outside the component or wrap with useCallback to avoid unnecessary re-renders. */
|
|
13
|
+
formatImageUrl?: Formatters['formatImageUrl'];
|
|
12
14
|
}
|
|
13
15
|
export interface UseCioPiaReturn {
|
|
14
16
|
suggestedQuestions: UseSuggestedQuestionsReturn;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { ConversationEntry, Question, Item } from '../types';
|
|
1
|
+
import { Callbacks, ConversationEntry, Question, Item } from '../types';
|
|
2
2
|
import { UseCioPiaReturn } from './useCioPia';
|
|
3
3
|
export interface UseConversationProps {
|
|
4
4
|
pia: UseCioPiaReturn;
|
|
5
5
|
itemId: string;
|
|
6
6
|
isConversation: boolean;
|
|
7
|
+
callbacks?: Callbacks;
|
|
7
8
|
}
|
|
8
9
|
export interface UseConversationReturn {
|
|
9
10
|
currentQuestion: string;
|
|
@@ -16,4 +17,4 @@ export interface UseConversationReturn {
|
|
|
16
17
|
handleSubmitQuestion: (question: string) => void;
|
|
17
18
|
resetState: () => void;
|
|
18
19
|
}
|
|
19
|
-
export default function useConversation({ pia, itemId, isConversation, }: UseConversationProps): UseConversationReturn;
|
|
20
|
+
export default function useConversation({ pia, itemId, isConversation, callbacks, }: UseConversationProps): UseConversationReturn;
|