@constructor-io/constructorio-ui-pia 2.15.0 → 2.16.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 +50 -12
- package/dist/constructorio-ui-pia-bundled.js +45 -45
- package/lib/cjs/components/CioPia/CioPiaQna.js +11 -3
- package/lib/cjs/components/ConversationHistory/ConversationHistory.js +7 -2
- package/lib/cjs/hooks/useConversation.js +41 -6
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/CioPia/CioPiaQna.js +11 -3
- package/lib/mjs/components/ConversationHistory/ConversationHistory.js +7 -3
- package/lib/mjs/hooks/useConversation.js +39 -4
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/CioPia/types.d.ts +10 -1
- package/lib/types/hooks/useConversation.d.ts +2 -1
- package/lib/types/types.d.ts +5 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +3 -1
|
@@ -23,7 +23,7 @@ const CheckoutTriggerBar_1 = tslib_1.__importDefault(require("./CheckoutTriggerB
|
|
|
23
23
|
// eslint-disable-next-line complexity
|
|
24
24
|
function CioPiaQna(props) {
|
|
25
25
|
var _a;
|
|
26
|
-
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, checkoutTriggers, abTest, } = props;
|
|
26
|
+
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, checkoutTriggers, abTest, initialConversationHistory, } = props;
|
|
27
27
|
const { learnMoreUrl, showFeedback, mode = 'default', type = 'inline', showPreviousItems, disclaimerPosition = 'bottom', } = displayConfigs || {};
|
|
28
28
|
const isConversation = mode === 'conversation' || type === 'modal';
|
|
29
29
|
const { priceCurrency } = productCardProps || {};
|
|
@@ -46,7 +46,14 @@ function CioPiaQna(props) {
|
|
|
46
46
|
variationId,
|
|
47
47
|
threadId: pia.threadId,
|
|
48
48
|
});
|
|
49
|
-
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, context, handleSubmitQuestion, handleQuestionClick, handleInputFocus, handleFeedback, resetState, } = (0, useConversation_1.default)({
|
|
49
|
+
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, context, handleSubmitQuestion, handleQuestionClick, handleInputFocus, handleFeedback, resetState, } = (0, useConversation_1.default)({
|
|
50
|
+
pia,
|
|
51
|
+
itemId,
|
|
52
|
+
isConversation,
|
|
53
|
+
callbacks,
|
|
54
|
+
tracking,
|
|
55
|
+
initialConversationHistory,
|
|
56
|
+
});
|
|
50
57
|
const { containerRef: viewportContainerRef } = (0, useViewportTracking_1.default)({
|
|
51
58
|
tracking,
|
|
52
59
|
questions: displayedQuestions,
|
|
@@ -81,7 +88,8 @@ function CioPiaQna(props) {
|
|
|
81
88
|
isAnswerLoading,
|
|
82
89
|
error,
|
|
83
90
|
checkoutElement,
|
|
84
|
-
|
|
91
|
+
// Until a live answer arrives, a seeded last entry shows its own items. `null` would hide them.
|
|
92
|
+
currentItems: pia.answers.data ? currentItems : undefined,
|
|
85
93
|
showFeedback,
|
|
86
94
|
showPreviousItems,
|
|
87
95
|
learnMoreUrl,
|
|
@@ -22,11 +22,16 @@ function ConversationHistory({ conversationHistory, isLoading, isAnswerLoading =
|
|
|
22
22
|
return () => cancelAnimationFrame(frameId);
|
|
23
23
|
}, [conversationHistory, isLoading]);
|
|
24
24
|
const lastEntry = conversationHistory[conversationHistory.length - 1];
|
|
25
|
-
|
|
25
|
+
// An entry answered before mount (a restored conversation) was not just answered: announcing
|
|
26
|
+
// "Answer ready" on page load would tell the shopper something happened when nothing did.
|
|
27
|
+
const [answeredOnMountId] = (0, react_1.useState)(() => ((lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.answer) ? lastEntry.id : undefined));
|
|
28
|
+
const hasNewAnswer = !!(lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.answer) && lastEntry.id !== answeredOnMountId;
|
|
29
|
+
const answerStatus = (0, StatusRegion_1.answerStatusMessage)(isAnswerLoading, hasNewAnswer, translations);
|
|
26
30
|
const disclaimer = (react_1.default.createElement(Disclaimer_1.default, { learnMoreUrl: learnMoreUrl, translations: translations, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.disclaimer }));
|
|
27
31
|
return (react_1.default.createElement("div", { className: 'cio-pia-conversation-history' },
|
|
28
32
|
disclaimerPosition === 'top' && disclaimer,
|
|
29
33
|
react_1.default.createElement("div", { ref: scrollContainerRef, className: 'cio-pia-conversation-entries', role: 'log', tabIndex: 0, "aria-label": (0, translate_1.translate)('Conversation history', translations) }, conversationHistory.map((entry, index) => {
|
|
34
|
+
var _a;
|
|
30
35
|
const isLast = index === conversationHistory.length - 1;
|
|
31
36
|
const previousEntryItems = showPreviousItems ? entry.items : null;
|
|
32
37
|
const latestEntryItems = currentItems !== undefined ? currentItems : entry.items;
|
|
@@ -38,7 +43,7 @@ function ConversationHistory({ conversationHistory, isLoading, isAnswerLoading =
|
|
|
38
43
|
isLast && !isLoading && error && (react_1.default.createElement(ErrorBlock_1.default, { key: error.message || (0, translate_1.translate)('Unexpected error', translations), message: error.message, translations: translations, announce: false })),
|
|
39
44
|
entry.answer && (react_1.default.createElement("div", { className: 'cio-pia-answer-container' },
|
|
40
45
|
react_1.default.createElement(Answer_1.default, { text: entry.answer, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.answer }),
|
|
41
|
-
carouselItems && (react_1.default.createElement(PiaCustomCarousel_1.default, { items: carouselItems, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: qnaResultId, translations: translations, priceCurrency: priceCurrency })),
|
|
46
|
+
carouselItems && (react_1.default.createElement(PiaCustomCarousel_1.default, { items: carouselItems, componentOverrides: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: (_a = entry.qnaResultId) !== null && _a !== void 0 ? _a : (isLast ? qnaResultId : undefined), translations: translations, priceCurrency: priceCurrency })),
|
|
42
47
|
isLast && showFeedback && (react_1.default.createElement(Feedback_1.default, { translations: translations, onFeedback: handleFeedback, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.feedback }))))));
|
|
43
48
|
})),
|
|
44
49
|
disclaimerPosition === 'bottom' && disclaimer,
|
|
@@ -1,15 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const react_1 = require("react");
|
|
4
|
-
function
|
|
4
|
+
function isRestorableEntry(entry) {
|
|
5
|
+
if (typeof entry !== 'object' || entry === null)
|
|
6
|
+
return false;
|
|
7
|
+
const { question, answer, items } = entry;
|
|
8
|
+
return (typeof question === 'string' &&
|
|
9
|
+
typeof answer === 'string' &&
|
|
10
|
+
(items === undefined || items === null || Array.isArray(items)));
|
|
11
|
+
}
|
|
12
|
+
// The seed comes from the host's own storage, typed or not: a malformed one must not take the widget down.
|
|
13
|
+
function restoreHistory(seed) {
|
|
14
|
+
if (!Array.isArray(seed)) {
|
|
15
|
+
console.warn('[CioPia] initialConversationHistory must be an array. It was ignored.');
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const entries = seed.filter(isRestorableEntry);
|
|
19
|
+
if (entries.length < seed.length) {
|
|
20
|
+
console.warn(`[CioPia] ${seed.length - entries.length} initialConversationHistory entries were ignored. Each needs a string question and answer, and items must be an array.`);
|
|
21
|
+
}
|
|
22
|
+
// Seeded ids are replaced: `id` is the React key, so a caller's values cannot be trusted to be unique.
|
|
23
|
+
return entries.map((entry, index) => (Object.assign(Object.assign({}, entry), { id: index + 1 })));
|
|
24
|
+
}
|
|
25
|
+
function useConversation({ pia, itemId, isConversation, callbacks, tracking, initialConversationHistory, }) {
|
|
5
26
|
var _a, _b, _c;
|
|
6
27
|
const { suggestedQuestions, answers, threadId } = pia;
|
|
7
28
|
const { getAnswer } = answers;
|
|
8
29
|
const context = (0, react_1.useMemo)(() => ({ itemId, threadId }), [itemId, threadId]);
|
|
9
30
|
const [currentQuestion, setCurrentQuestion] = (0, react_1.useState)('');
|
|
10
31
|
const [displayedQuestions, setDisplayedQuestions] = (0, react_1.useState)([]);
|
|
11
|
-
const [conversationHistory, setConversationHistory] = (0, react_1.useState)(
|
|
12
|
-
|
|
32
|
+
const [conversationHistory, setConversationHistory] = (0, react_1.useState)(() => isConversation && initialConversationHistory !== undefined
|
|
33
|
+
? restoreHistory(initialConversationHistory)
|
|
34
|
+
: []);
|
|
35
|
+
const entryIdRef = (0, react_1.useRef)(conversationHistory.length);
|
|
36
|
+
const conversationHistoryRef = (0, react_1.useRef)(conversationHistory);
|
|
37
|
+
const prevItemIdRef = (0, react_1.useRef)(itemId);
|
|
13
38
|
const prevAnswerDataRef = (0, react_1.useRef)(answers.data);
|
|
14
39
|
const hasTrackedCurrentAnswerRef = (0, react_1.useRef)(false);
|
|
15
40
|
const answersRef = (0, react_1.useRef)(answers);
|
|
@@ -33,6 +58,9 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, })
|
|
|
33
58
|
(0, react_1.useEffect)(() => {
|
|
34
59
|
answersRef.current = answers;
|
|
35
60
|
}, [answers]);
|
|
61
|
+
(0, react_1.useEffect)(() => {
|
|
62
|
+
conversationHistoryRef.current = conversationHistory;
|
|
63
|
+
}, [conversationHistory]);
|
|
36
64
|
const submitQuestion = (0, react_1.useCallback)((question, source) => {
|
|
37
65
|
lastSourceRef.current = source;
|
|
38
66
|
lastQuestionRef.current = question;
|
|
@@ -63,9 +91,12 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, })
|
|
|
63
91
|
(_c = (_b = callbacksRef.current) === null || _b === void 0 ? void 0 : _b.onFocus) === null || _c === void 0 ? void 0 : _c.call(_b, contextRef.current);
|
|
64
92
|
}, []);
|
|
65
93
|
const handleFeedback = (0, react_1.useCallback)((type) => {
|
|
66
|
-
var _a, _b, _c, _d;
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
var _a, _b, _c, _d, _e, _f;
|
|
95
|
+
// A seeded last entry has no live response behind it; its own id is the answer rated.
|
|
96
|
+
const history = conversationHistoryRef.current;
|
|
97
|
+
const qnaResultId = (_b = (_a = answersRef.current.data) === null || _a === void 0 ? void 0 : _a.qna_result_id) !== null && _b !== void 0 ? _b : (_c = history[history.length - 1]) === null || _c === void 0 ? void 0 : _c.qnaResultId;
|
|
98
|
+
(_d = trackingRef.current) === null || _d === void 0 ? void 0 : _d.trackAnswerFeedback(type, qnaResultId);
|
|
99
|
+
(_f = (_e = callbacksRef.current) === null || _e === void 0 ? void 0 : _e.onFeedback) === null || _f === void 0 ? void 0 : _f.call(_e, type);
|
|
69
100
|
}, []);
|
|
70
101
|
const resetState = (0, react_1.useCallback)(() => {
|
|
71
102
|
setCurrentQuestion('');
|
|
@@ -75,6 +106,10 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, })
|
|
|
75
106
|
showsFollowUpsRef.current = false;
|
|
76
107
|
}, [suggestedQuestions.data]);
|
|
77
108
|
(0, react_1.useEffect)(() => {
|
|
109
|
+
// Skip the mount run: it would wipe the seeded history.
|
|
110
|
+
if (prevItemIdRef.current === itemId)
|
|
111
|
+
return;
|
|
112
|
+
prevItemIdRef.current = itemId;
|
|
78
113
|
setCurrentQuestion('');
|
|
79
114
|
setDisplayedQuestions([]);
|
|
80
115
|
setConversationHistory([]);
|
package/lib/cjs/version.js
CHANGED
|
@@ -19,7 +19,7 @@ import CheckoutTriggerBar from './CheckoutTriggerBar';
|
|
|
19
19
|
/** The question-and-answer experience: `mode: 'default'`, `mode: 'conversation'`, and the modal. */
|
|
20
20
|
// eslint-disable-next-line complexity
|
|
21
21
|
export default function CioPiaQna(props) {
|
|
22
|
-
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, checkoutTriggers, abTest, } = props;
|
|
22
|
+
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, checkoutTriggers, abTest, initialConversationHistory, } = props;
|
|
23
23
|
const { learnMoreUrl, showFeedback, mode = 'default', type = 'inline', showPreviousItems, disclaimerPosition = 'bottom', } = displayConfigs || {};
|
|
24
24
|
const isConversation = mode === 'conversation' || type === 'modal';
|
|
25
25
|
const { priceCurrency } = productCardProps || {};
|
|
@@ -42,7 +42,14 @@ export default function CioPiaQna(props) {
|
|
|
42
42
|
variationId,
|
|
43
43
|
threadId: pia.threadId,
|
|
44
44
|
});
|
|
45
|
-
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, context, handleSubmitQuestion, handleQuestionClick, handleInputFocus, handleFeedback, resetState, } = useConversation({
|
|
45
|
+
const { currentQuestion, displayedQuestions, conversationHistory, currentAnswer, currentItems, isLoading, error, context, handleSubmitQuestion, handleQuestionClick, handleInputFocus, handleFeedback, resetState, } = useConversation({
|
|
46
|
+
pia,
|
|
47
|
+
itemId,
|
|
48
|
+
isConversation,
|
|
49
|
+
callbacks,
|
|
50
|
+
tracking,
|
|
51
|
+
initialConversationHistory,
|
|
52
|
+
});
|
|
46
53
|
const { containerRef: viewportContainerRef } = useViewportTracking({
|
|
47
54
|
tracking,
|
|
48
55
|
questions: displayedQuestions,
|
|
@@ -77,7 +84,8 @@ export default function CioPiaQna(props) {
|
|
|
77
84
|
isAnswerLoading,
|
|
78
85
|
error,
|
|
79
86
|
checkoutElement,
|
|
80
|
-
|
|
87
|
+
// Until a live answer arrives, a seeded last entry shows its own items. `null` would hide them.
|
|
88
|
+
currentItems: pia.answers.data ? currentItems : undefined,
|
|
81
89
|
showFeedback,
|
|
82
90
|
showPreviousItems,
|
|
83
91
|
learnMoreUrl,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import Answer from '../Answer/Answer';
|
|
3
3
|
import Feedback from '../Feedback/Feedback';
|
|
4
4
|
import Disclaimer from '../CioPia/Disclaimer';
|
|
@@ -19,7 +19,11 @@ export default function ConversationHistory({ conversationHistory, isLoading, is
|
|
|
19
19
|
return () => cancelAnimationFrame(frameId);
|
|
20
20
|
}, [conversationHistory, isLoading]);
|
|
21
21
|
const lastEntry = conversationHistory[conversationHistory.length - 1];
|
|
22
|
-
|
|
22
|
+
// An entry answered before mount (a restored conversation) was not just answered: announcing
|
|
23
|
+
// "Answer ready" on page load would tell the shopper something happened when nothing did.
|
|
24
|
+
const [answeredOnMountId] = useState(() => (lastEntry?.answer ? lastEntry.id : undefined));
|
|
25
|
+
const hasNewAnswer = !!lastEntry?.answer && lastEntry.id !== answeredOnMountId;
|
|
26
|
+
const answerStatus = answerStatusMessage(isAnswerLoading, hasNewAnswer, translations);
|
|
23
27
|
const disclaimer = (React.createElement(Disclaimer, { learnMoreUrl: learnMoreUrl, translations: translations, componentOverride: componentOverrides?.disclaimer }));
|
|
24
28
|
return (React.createElement("div", { className: 'cio-pia-conversation-history' },
|
|
25
29
|
disclaimerPosition === 'top' && disclaimer,
|
|
@@ -35,7 +39,7 @@ export default function ConversationHistory({ conversationHistory, isLoading, is
|
|
|
35
39
|
isLast && !isLoading && error && (React.createElement(ErrorBlock, { key: error.message || translate('Unexpected error', translations), message: error.message, translations: translations, announce: false })),
|
|
36
40
|
entry.answer && (React.createElement("div", { className: 'cio-pia-answer-container' },
|
|
37
41
|
React.createElement(Answer, { text: entry.answer, componentOverride: componentOverrides?.answer }),
|
|
38
|
-
carouselItems && (React.createElement(PiaCustomCarousel, { items: carouselItems, componentOverrides: componentOverrides?.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: qnaResultId, translations: translations, priceCurrency: priceCurrency })),
|
|
42
|
+
carouselItems && (React.createElement(PiaCustomCarousel, { items: carouselItems, componentOverrides: componentOverrides?.carousel, callbacks: callbacks, onResultClick: onResultClick, question: entry.question, qnaResultId: entry.qnaResultId ?? (isLast ? qnaResultId : undefined), translations: translations, priceCurrency: priceCurrency })),
|
|
39
43
|
isLast && showFeedback && (React.createElement(Feedback, { translations: translations, onFeedback: handleFeedback, componentOverride: componentOverrides?.feedback }))))));
|
|
40
44
|
})),
|
|
41
45
|
disclaimerPosition === 'bottom' && disclaimer,
|
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
|
|
2
|
+
function isRestorableEntry(entry) {
|
|
3
|
+
if (typeof entry !== 'object' || entry === null)
|
|
4
|
+
return false;
|
|
5
|
+
const { question, answer, items } = entry;
|
|
6
|
+
return (typeof question === 'string' &&
|
|
7
|
+
typeof answer === 'string' &&
|
|
8
|
+
(items === undefined || items === null || Array.isArray(items)));
|
|
9
|
+
}
|
|
10
|
+
// The seed comes from the host's own storage, typed or not: a malformed one must not take the widget down.
|
|
11
|
+
function restoreHistory(seed) {
|
|
12
|
+
if (!Array.isArray(seed)) {
|
|
13
|
+
console.warn('[CioPia] initialConversationHistory must be an array. It was ignored.');
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const entries = seed.filter(isRestorableEntry);
|
|
17
|
+
if (entries.length < seed.length) {
|
|
18
|
+
console.warn(`[CioPia] ${seed.length - entries.length} initialConversationHistory entries were ignored. Each needs a string question and answer, and items must be an array.`);
|
|
19
|
+
}
|
|
20
|
+
// Seeded ids are replaced: `id` is the React key, so a caller's values cannot be trusted to be unique.
|
|
21
|
+
return entries.map((entry, index) => ({ ...entry, id: index + 1 }));
|
|
22
|
+
}
|
|
23
|
+
export default function useConversation({ pia, itemId, isConversation, callbacks, tracking, initialConversationHistory, }) {
|
|
3
24
|
const { suggestedQuestions, answers, threadId } = pia;
|
|
4
25
|
const { getAnswer } = answers;
|
|
5
26
|
const context = useMemo(() => ({ itemId, threadId }), [itemId, threadId]);
|
|
6
27
|
const [currentQuestion, setCurrentQuestion] = useState('');
|
|
7
28
|
const [displayedQuestions, setDisplayedQuestions] = useState([]);
|
|
8
|
-
const [conversationHistory, setConversationHistory] = useState(
|
|
9
|
-
|
|
29
|
+
const [conversationHistory, setConversationHistory] = useState(() => isConversation && initialConversationHistory !== undefined
|
|
30
|
+
? restoreHistory(initialConversationHistory)
|
|
31
|
+
: []);
|
|
32
|
+
const entryIdRef = useRef(conversationHistory.length);
|
|
33
|
+
const conversationHistoryRef = useRef(conversationHistory);
|
|
34
|
+
const prevItemIdRef = useRef(itemId);
|
|
10
35
|
const prevAnswerDataRef = useRef(answers.data);
|
|
11
36
|
const hasTrackedCurrentAnswerRef = useRef(false);
|
|
12
37
|
const answersRef = useRef(answers);
|
|
@@ -30,6 +55,9 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
30
55
|
useEffect(() => {
|
|
31
56
|
answersRef.current = answers;
|
|
32
57
|
}, [answers]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
conversationHistoryRef.current = conversationHistory;
|
|
60
|
+
}, [conversationHistory]);
|
|
33
61
|
const submitQuestion = useCallback((question, source) => {
|
|
34
62
|
lastSourceRef.current = source;
|
|
35
63
|
lastQuestionRef.current = question;
|
|
@@ -57,7 +85,10 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
57
85
|
callbacksRef.current?.onFocus?.(contextRef.current);
|
|
58
86
|
}, []);
|
|
59
87
|
const handleFeedback = useCallback((type) => {
|
|
60
|
-
|
|
88
|
+
// A seeded last entry has no live response behind it; its own id is the answer rated.
|
|
89
|
+
const history = conversationHistoryRef.current;
|
|
90
|
+
const qnaResultId = answersRef.current.data?.qna_result_id ?? history[history.length - 1]?.qnaResultId;
|
|
91
|
+
trackingRef.current?.trackAnswerFeedback(type, qnaResultId);
|
|
61
92
|
callbacksRef.current?.onFeedback?.(type);
|
|
62
93
|
}, []);
|
|
63
94
|
const resetState = useCallback(() => {
|
|
@@ -68,6 +99,10 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
68
99
|
showsFollowUpsRef.current = false;
|
|
69
100
|
}, [suggestedQuestions.data]);
|
|
70
101
|
useEffect(() => {
|
|
102
|
+
// Skip the mount run: it would wipe the seeded history.
|
|
103
|
+
if (prevItemIdRef.current === itemId)
|
|
104
|
+
return;
|
|
105
|
+
prevItemIdRef.current = itemId;
|
|
71
106
|
setCurrentQuestion('');
|
|
72
107
|
setDisplayedQuestions([]);
|
|
73
108
|
setConversationHistory([]);
|
package/lib/mjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '2.
|
|
1
|
+
export default '2.16.1';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { IncludeComponentOverrides, IncludeRenderProps } from '@constructor-io/constructorio-ui-components';
|
|
3
|
-
import { CioPiaRenderProps, CioPiaComponentOverrides, Callbacks, CioPiaAbTest, CioPiaDisplayConfigs, CioPiaTrackingConfigs, Translations, SuggestedQuestionsParameters, AnswerRequestParameters, RecsPodParameters, Formatters, ProductCardDisplayProps } from '../../types';
|
|
3
|
+
import { CioPiaRenderProps, CioPiaComponentOverrides, Callbacks, CioPiaAbTest, CioPiaDisplayConfigs, CioPiaTrackingConfigs, Translations, SuggestedQuestionsParameters, AnswerRequestParameters, RecsPodParameters, Formatters, ConversationEntry, ProductCardDisplayProps } from '../../types';
|
|
4
4
|
import type { CioClient } from '../../hooks/usePiaClient';
|
|
5
5
|
/**
|
|
6
6
|
* A single checkout entry point rendered inside the PIA `default` mode.
|
|
@@ -47,6 +47,15 @@ export interface CioPiaProps extends IncludeRenderProps<CioPiaRenderProps>, Incl
|
|
|
47
47
|
itemName: string;
|
|
48
48
|
/** Thread ID for conversation context. Must be a valid UUID (e.g., "550e8400-e29b-41d4-a716-446655440000"). */
|
|
49
49
|
threadId?: string;
|
|
50
|
+
/**
|
|
51
|
+
* A conversation you manage, such as one saved from `onAnswer` in the browser or on your own
|
|
52
|
+
* server. When provided, including as `[]`, these entries are the conversation shown before the
|
|
53
|
+
* first question; pass `[]` rather than omitting it when nothing is saved.
|
|
54
|
+
* Read once on mount, so render after it has loaded, and cleared when `itemId` changes. Pass the
|
|
55
|
+
* `threadId` the entries came from, or the agent will not remember them. Used by
|
|
56
|
+
* `mode: 'conversation'` and `type: 'modal'` only; the modal shows them once it opens.
|
|
57
|
+
*/
|
|
58
|
+
initialConversationHistory?: ConversationEntry[];
|
|
50
59
|
/** Optional variation ID for the product. */
|
|
51
60
|
variationId?: string;
|
|
52
61
|
/** Optional Constructor.io client instance. If not provided, one will be created internally. */
|
|
@@ -7,6 +7,7 @@ export interface UseConversationProps {
|
|
|
7
7
|
isConversation: boolean;
|
|
8
8
|
callbacks?: Callbacks;
|
|
9
9
|
tracking?: UseTrackingReturn;
|
|
10
|
+
initialConversationHistory?: ConversationEntry[];
|
|
10
11
|
}
|
|
11
12
|
export interface UseConversationReturn {
|
|
12
13
|
currentQuestion: string;
|
|
@@ -23,4 +24,4 @@ export interface UseConversationReturn {
|
|
|
23
24
|
handleFeedback: (type: FeedbackType) => void;
|
|
24
25
|
resetState: () => void;
|
|
25
26
|
}
|
|
26
|
-
export default function useConversation({ pia, itemId, isConversation, callbacks, tracking, }: UseConversationProps): UseConversationReturn;
|
|
27
|
+
export default function useConversation({ pia, itemId, isConversation, callbacks, tracking, initialConversationHistory, }: UseConversationProps): UseConversationReturn;
|
package/lib/types/types.d.ts
CHANGED
|
@@ -165,13 +165,18 @@ export interface Item extends Product, Record<string, any> {
|
|
|
165
165
|
url?: string;
|
|
166
166
|
matchedTerms?: string[];
|
|
167
167
|
}
|
|
168
|
+
/** One question and its answer, as passed to `onAnswer` and accepted by `initialConversationHistory`. */
|
|
168
169
|
export interface ConversationEntry {
|
|
170
|
+
/** Unique within the conversation. Assigned by the widget: ids in `initialConversationHistory` are replaced. */
|
|
169
171
|
id: number;
|
|
170
172
|
question: string;
|
|
171
173
|
answer: string;
|
|
174
|
+
/** Whether the shopper typed the question or clicked a suggested one. */
|
|
172
175
|
source: QuestionSource;
|
|
176
|
+
/** Products shown with the answer. */
|
|
173
177
|
items?: Item[] | null;
|
|
174
178
|
threadId?: string;
|
|
179
|
+
/** Identifies the answer for feedback and product click tracking. */
|
|
175
180
|
qnaResultId?: string;
|
|
176
181
|
}
|
|
177
182
|
/** Which kind of recommendations to fetch. */
|
package/lib/types/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "2.
|
|
1
|
+
declare const _default: "2.16.1";
|
|
2
2
|
export default _default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructor-io/constructorio-ui-pia",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.1",
|
|
4
4
|
"description": "Constructor.io AI Product Insights Agent UI library for web applications",
|
|
5
5
|
"author": "Constructor.io Corporation",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,10 +36,12 @@
|
|
|
36
36
|
"require": "./dist/constructorio-ui-pia-bundled.js"
|
|
37
37
|
},
|
|
38
38
|
".": {
|
|
39
|
+
"types": "./lib/types/index.d.ts",
|
|
39
40
|
"import": "./lib/mjs/index.js",
|
|
40
41
|
"require": "./lib/cjs/index.js"
|
|
41
42
|
},
|
|
42
43
|
"./cjs": {
|
|
44
|
+
"types": "./lib/types/index.d.ts",
|
|
43
45
|
"import": "./lib/cjs/index.js",
|
|
44
46
|
"require": "./lib/cjs/index.js"
|
|
45
47
|
}
|