@constructor-io/constructorio-ui-pia 2.16.1 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -13
- package/dist/constructorio-ui-pia-bundled.js +51 -51
- package/lib/cjs/components/CioPia/CioPiaQna.js +26 -6
- package/lib/cjs/components/CioPiaRecs/RecsPodRefinement.js +13 -3
- package/lib/cjs/components/Input/Input.js +3 -2
- package/lib/cjs/components/PiaConversation/PiaConversation.js +19 -3
- package/lib/cjs/components/PiaConversation/PiaModal.js +24 -1
- package/lib/cjs/hooks/useAnswerResults.js +17 -1
- package/lib/cjs/hooks/useCioPia.js +2 -1
- package/lib/cjs/hooks/useConversation.js +40 -18
- package/lib/cjs/hooks/usePersistedConversation.js +71 -0
- package/lib/cjs/hooks/usePiaClient.js +8 -1
- package/lib/cjs/hooks/useReturnFocusToInput.js +28 -0
- package/lib/cjs/index.js +4 -1
- package/lib/cjs/utils/conversationStorage.js +141 -0
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/CioPia/CioPiaQna.js +24 -4
- package/lib/mjs/components/CioPiaRecs/RecsPodRefinement.js +13 -3
- package/lib/mjs/components/Input/Input.js +3 -2
- package/lib/mjs/components/PiaConversation/PiaConversation.js +19 -3
- package/lib/mjs/components/PiaConversation/PiaModal.js +23 -2
- package/lib/mjs/hooks/useAnswerResults.js +17 -1
- package/lib/mjs/hooks/useCioPia.js +2 -1
- package/lib/mjs/hooks/useConversation.js +38 -16
- package/lib/mjs/hooks/usePersistedConversation.js +73 -0
- package/lib/mjs/hooks/usePiaClient.js +8 -1
- package/lib/mjs/hooks/useReturnFocusToInput.js +25 -0
- package/lib/mjs/index.js +2 -0
- package/lib/mjs/utils/conversationStorage.js +129 -0
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/CioPia/types.d.ts +16 -1
- package/lib/types/components/Input/Input.d.ts +2 -1
- package/lib/types/components/PiaConversation/PiaModal.d.ts +2 -0
- package/lib/types/hooks/useAnswerResults.d.ts +2 -0
- package/lib/types/hooks/useCioPia.d.ts +2 -0
- package/lib/types/hooks/useConversation.d.ts +4 -1
- package/lib/types/hooks/usePersistedConversation.d.ts +18 -0
- package/lib/types/hooks/usePiaClient.d.ts +3 -1
- package/lib/types/hooks/useReturnFocusToInput.d.ts +9 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/types.d.ts +19 -0
- package/lib/types/utils/conversationStorage.d.ts +20 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -8,9 +8,11 @@ const SuggestedQuestionsContainer_1 = tslib_1.__importDefault(require("../Sugges
|
|
|
8
8
|
const SuggestedQuestionsSkeleton_1 = tslib_1.__importDefault(require("../SuggestedQuestionsContainer/SuggestedQuestionsSkeleton"));
|
|
9
9
|
const useCioPia_1 = tslib_1.__importDefault(require("../../hooks/useCioPia"));
|
|
10
10
|
const useConversation_1 = tslib_1.__importDefault(require("../../hooks/useConversation"));
|
|
11
|
+
const usePersistedConversation_1 = tslib_1.__importDefault(require("../../hooks/usePersistedConversation"));
|
|
11
12
|
const useTracking_1 = tslib_1.__importDefault(require("../../hooks/useTracking"));
|
|
12
13
|
const useViewportTracking_1 = tslib_1.__importDefault(require("../../hooks/useViewportTracking"));
|
|
13
14
|
const useViewportCallbacks_1 = tslib_1.__importDefault(require("../../hooks/useViewportCallbacks"));
|
|
15
|
+
const useReturnFocusToInput_1 = tslib_1.__importDefault(require("../../hooks/useReturnFocusToInput"));
|
|
14
16
|
const ErrorBlock_1 = tslib_1.__importDefault(require("../Error/ErrorBlock"));
|
|
15
17
|
const LoadingSkeleton_1 = tslib_1.__importDefault(require("../LoadingSkeleton/LoadingSkeleton"));
|
|
16
18
|
const translate_1 = require("../../utils/translate");
|
|
@@ -22,15 +24,22 @@ const CheckoutTriggerBar_1 = tslib_1.__importDefault(require("./CheckoutTriggerB
|
|
|
22
24
|
/** The question-and-answer experience: `mode: 'default'`, `mode: 'conversation'`, and the modal. */
|
|
23
25
|
// eslint-disable-next-line complexity
|
|
24
26
|
function CioPiaQna(props) {
|
|
25
|
-
var _a;
|
|
26
|
-
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, checkoutTriggers, abTest, initialConversationHistory, } = props;
|
|
27
|
+
var _a, _b;
|
|
28
|
+
const { apiKey, itemId, itemName, threadId, variationId, cioClient, displayConfigs, componentOverrides, callbacks, formatters, productCardProps, children, translations, suggestedQuestionsParameters, answerParameters, parameters, trackingConfigs, checkoutTriggers, abTest, initialConversationHistory, persistConversation, userId, } = props;
|
|
27
29
|
const { learnMoreUrl, showFeedback, mode = 'default', type = 'inline', showPreviousItems, disclaimerPosition = 'bottom', } = displayConfigs || {};
|
|
28
30
|
const isConversation = mode === 'conversation' || type === 'modal';
|
|
29
31
|
const { priceCurrency } = productCardProps || {};
|
|
30
|
-
const
|
|
32
|
+
const persisted = (0, usePersistedConversation_1.default)({
|
|
33
|
+
enabled: Boolean(persistConversation === null || persistConversation === void 0 ? void 0 : persistConversation.enabled) && isConversation,
|
|
31
34
|
apiKey,
|
|
35
|
+
userId,
|
|
32
36
|
itemId,
|
|
33
37
|
threadId,
|
|
38
|
+
});
|
|
39
|
+
const pia = (0, useCioPia_1.default)({
|
|
40
|
+
apiKey,
|
|
41
|
+
itemId,
|
|
42
|
+
threadId: (_a = persisted === null || persisted === void 0 ? void 0 : persisted.threadId) !== null && _a !== void 0 ? _a : threadId,
|
|
34
43
|
variationId,
|
|
35
44
|
cioClient,
|
|
36
45
|
suggestedQuestionsParameters,
|
|
@@ -38,6 +47,7 @@ function CioPiaQna(props) {
|
|
|
38
47
|
parameters,
|
|
39
48
|
formatImageUrl: formatters === null || formatters === void 0 ? void 0 : formatters.formatImageUrl,
|
|
40
49
|
testCells: abTest === null || abTest === void 0 ? void 0 : abTest.testCells,
|
|
50
|
+
userId,
|
|
41
51
|
});
|
|
42
52
|
const tracking = (0, useTracking_1.default)({
|
|
43
53
|
cioClient: pia.cioClient,
|
|
@@ -53,7 +63,17 @@ function CioPiaQna(props) {
|
|
|
53
63
|
callbacks,
|
|
54
64
|
tracking,
|
|
55
65
|
initialConversationHistory,
|
|
66
|
+
persisted,
|
|
56
67
|
});
|
|
68
|
+
const { inputRef, returnFocusAfterLoading } = (0, useReturnFocusToInput_1.default)(isLoading);
|
|
69
|
+
const handleInlineSubmit = (0, react_1.useCallback)((question) => {
|
|
70
|
+
handleSubmitQuestion(question);
|
|
71
|
+
returnFocusAfterLoading();
|
|
72
|
+
}, [handleSubmitQuestion, returnFocusAfterLoading]);
|
|
73
|
+
const handleInlineQuestionClick = (0, react_1.useCallback)((question) => {
|
|
74
|
+
handleQuestionClick(question);
|
|
75
|
+
returnFocusAfterLoading();
|
|
76
|
+
}, [handleQuestionClick, returnFocusAfterLoading]);
|
|
57
77
|
const { containerRef: viewportContainerRef } = (0, useViewportTracking_1.default)({
|
|
58
78
|
tracking,
|
|
59
79
|
questions: displayedQuestions,
|
|
@@ -64,7 +84,7 @@ function CioPiaQna(props) {
|
|
|
64
84
|
viewportContainerRef(node);
|
|
65
85
|
callbackContainerRef(node);
|
|
66
86
|
}, [viewportContainerRef, callbackContainerRef]);
|
|
67
|
-
const qnaResultId = (
|
|
87
|
+
const qnaResultId = (_b = pia.answers.data) === null || _b === void 0 ? void 0 : _b.qna_result_id;
|
|
68
88
|
// The text bars and the "Loading answer" status track the answer request only. The question
|
|
69
89
|
// row keeps the combined `isLoading`: the follow-up questions that refill it arrive with the
|
|
70
90
|
// answer, so the row is waiting on either request.
|
|
@@ -117,12 +137,12 @@ function CioPiaQna(props) {
|
|
|
117
137
|
return (react_1.default.createElement("div", { ref: containerRef, className: 'cio-pia-container', "data-testid": 'cio-pia-container' },
|
|
118
138
|
react_1.default.createElement(constructorio_ui_components_1.RenderPropsWrapper, { props: renderProps, override: children || (componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.reactNode) },
|
|
119
139
|
react_1.default.createElement("p", { className: 'cio-pia-title', "data-testid": 'cio-pia-title' }, (0, translate_1.translate)('Any questions about this product?', translations)),
|
|
120
|
-
react_1.default.createElement(Input_1.default, { onSubmit:
|
|
140
|
+
react_1.default.createElement(Input_1.default, { onSubmit: handleInlineSubmit, onFocus: handleInputFocus, inputRef: inputRef, disabled: isLoading, value: currentQuestion, translations: translations, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.input }),
|
|
121
141
|
isAnswerLoading && react_1.default.createElement(LoadingSkeleton_1.default, { componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.loading }),
|
|
122
142
|
!isAnswerLoading && error && (react_1.default.createElement(ErrorBlock_1.default, { key: error.message || (0, translate_1.translate)('Unexpected error', translations), message: error.message, translations: translations })),
|
|
123
143
|
!isAnswerLoading && !error && currentAnswer && (react_1.default.createElement(PiaInlineAnswer_1.default, { currentAnswer: currentAnswer, currentItems: currentItems, showFeedback: showFeedback, learnMoreUrl: learnMoreUrl, disclaimerPosition: disclaimerPosition, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides, onFeedback: handleFeedback, onResultClick: tracking.trackResultClick, question: currentQuestion, qnaResultId: qnaResultId, priceCurrency: priceCurrency })),
|
|
124
144
|
isLoading && react_1.default.createElement(SuggestedQuestionsSkeleton_1.default, null),
|
|
125
|
-
!isLoading && !error && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: displayedQuestions, onQuestionClick:
|
|
145
|
+
!isLoading && !error && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: displayedQuestions, onQuestionClick: handleInlineQuestionClick, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.suggestedQuestions })),
|
|
126
146
|
checkoutElement),
|
|
127
147
|
react_1.default.createElement(StatusRegion_1.default, { message: answerStatus, "data-testid": 'answer-status' })));
|
|
128
148
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
|
-
const react_1 = tslib_1.
|
|
4
|
+
const react_1 = tslib_1.__importStar(require("react"));
|
|
5
5
|
const Input_1 = tslib_1.__importDefault(require("../Input/Input"));
|
|
6
6
|
const SuggestedQuestion_1 = tslib_1.__importDefault(require("../SuggestedQuestion/SuggestedQuestion"));
|
|
7
7
|
const icons_1 = require("../icons");
|
|
8
8
|
const RecsOptionsSkeleton_1 = tslib_1.__importDefault(require("./RecsOptionsSkeleton"));
|
|
9
9
|
const recs_1 = require("../../utils/recs");
|
|
10
10
|
const constants_1 = require("../../constants");
|
|
11
|
+
const useReturnFocusToInput_1 = tslib_1.__importDefault(require("../../hooks/useReturnFocusToInput"));
|
|
11
12
|
/**
|
|
12
13
|
* The row under the products: a prompt line, the short options the API suggested, and a box for
|
|
13
14
|
* anything it did not think of. The prompt line is held still while the rest reloads, so the row
|
|
@@ -15,11 +16,20 @@ const constants_1 = require("../../constants");
|
|
|
15
16
|
*/
|
|
16
17
|
function RecsPodRefinement({ refinement, isLoading, inputError, showInput, translations, componentOverrides, onRefine, onSubmit, onInputFocus, }) {
|
|
17
18
|
const options = (refinement === null || refinement === void 0 ? void 0 : refinement.options) || [];
|
|
19
|
+
const { inputRef, returnFocusAfterLoading } = (0, useReturnFocusToInput_1.default)(isLoading);
|
|
20
|
+
const refine = (0, react_1.useCallback)((text) => {
|
|
21
|
+
onRefine(text);
|
|
22
|
+
returnFocusAfterLoading();
|
|
23
|
+
}, [onRefine, returnFocusAfterLoading]);
|
|
24
|
+
const submit = (0, react_1.useCallback)((value) => {
|
|
25
|
+
onSubmit(value);
|
|
26
|
+
returnFocusAfterLoading();
|
|
27
|
+
}, [onSubmit, returnFocusAfterLoading]);
|
|
18
28
|
return (react_1.default.createElement("div", { className: 'cio-pia-recs-pod__refinement' },
|
|
19
29
|
react_1.default.createElement("span", { className: 'cio-pia-recs-pod__refinement-label' }, (0, recs_1.resolveRefinementQuestion)(refinement, translations)),
|
|
20
|
-
isLoading ? (react_1.default.createElement(RecsOptionsSkeleton_1.default, { count: options.length || undefined })) : (options.map((option) => (react_1.default.createElement(SuggestedQuestion_1.default, { key: option, question: option, icon: react_1.default.createElement(icons_1.SparklesIcon, null), onClick: () =>
|
|
30
|
+
isLoading ? (react_1.default.createElement(RecsOptionsSkeleton_1.default, { count: options.length || undefined })) : (options.map((option) => (react_1.default.createElement(SuggestedQuestion_1.default, { key: option, question: option, icon: react_1.default.createElement(icons_1.SparklesIcon, null), onClick: () => refine(option) })))),
|
|
21
31
|
showInput && (react_1.default.createElement("div", { className: 'cio-pia-recs-pod__input' },
|
|
22
|
-
react_1.default.createElement(Input_1.default, { onSubmit:
|
|
32
|
+
react_1.default.createElement(Input_1.default, { onSubmit: submit, onFocus: onInputFocus, inputRef: inputRef, disabled: isLoading, error: inputError !== null && inputError !== void 0 ? inputError : undefined, placeholderKey: constants_1.RECS_INPUT_PLACEHOLDER,
|
|
23
33
|
// The pod's box sits in a row beside the options and submits on Enter. Q&A keeps
|
|
24
34
|
// its button; this one has none in the mocks.
|
|
25
35
|
showSendButton: false, translations: translations, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.input })))));
|
|
@@ -16,7 +16,7 @@ const icons_1 = require("../icons");
|
|
|
16
16
|
* across its re-renders.
|
|
17
17
|
*/
|
|
18
18
|
let inputIdCounter = 0;
|
|
19
|
-
function Input({ value: providedValue, placeholder, disabled = false, onSubmit, onFocus, translations, componentOverride, error, placeholderKey = 'Ask anything', showSendButton = true, }) {
|
|
19
|
+
function Input({ value: providedValue, placeholder, disabled = false, onSubmit, onFocus, translations, componentOverride, error, inputRef, placeholderKey = 'Ask anything', showSendButton = true, }) {
|
|
20
20
|
const [value, setValue] = (0, react_1.useState)(providedValue || '');
|
|
21
21
|
const [errorId] = (0, react_1.useState)(() => {
|
|
22
22
|
inputIdCounter += 1;
|
|
@@ -53,10 +53,11 @@ function Input({ value: providedValue, placeholder, disabled = false, onSubmit,
|
|
|
53
53
|
onFocus,
|
|
54
54
|
translations,
|
|
55
55
|
error,
|
|
56
|
+
inputRef,
|
|
56
57
|
}, override: componentOverride === null || componentOverride === void 0 ? void 0 : componentOverride.reactNode },
|
|
57
58
|
react_1.default.createElement(react_1.default.Fragment, null,
|
|
58
59
|
react_1.default.createElement("div", { className: 'cio-pia-input-container' },
|
|
59
|
-
react_1.default.createElement("input", { type: 'text', value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleSubmitOnEnter, onFocus: onFocus, placeholder: resolvedPlaceholder, "aria-label": accessibleName, disabled: disabled, className: (0, classNames_1.cx)('cio-pia-input', error && 'cio-pia-input--error', !showSendButton && 'cio-pia-input--no-send'), "aria-invalid": error ? true : undefined, "aria-describedby": error ? errorId : undefined }),
|
|
60
|
+
react_1.default.createElement("input", { ref: inputRef, type: 'text', value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleSubmitOnEnter, onFocus: onFocus, placeholder: resolvedPlaceholder, "aria-label": accessibleName, disabled: disabled, className: (0, classNames_1.cx)('cio-pia-input', error && 'cio-pia-input--error', !showSendButton && 'cio-pia-input--no-send'), "aria-invalid": error ? true : undefined, "aria-describedby": error ? errorId : undefined }),
|
|
60
61
|
showSendButton && (react_1.default.createElement("button", { type: 'button', onClick: () => handleSubmit(value), className: 'cio-pia-send-button', disabled: disabled },
|
|
61
62
|
(0, translate_1.translate)('Send', translations),
|
|
62
63
|
react_1.default.createElement(icons_1.SendIcon, null)))),
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
|
-
const react_1 = tslib_1.
|
|
4
|
+
const react_1 = tslib_1.__importStar(require("react"));
|
|
5
5
|
const Input_1 = tslib_1.__importDefault(require("../Input/Input"));
|
|
6
6
|
const SuggestedQuestionsContainer_1 = tslib_1.__importDefault(require("../SuggestedQuestionsContainer/SuggestedQuestionsContainer"));
|
|
7
7
|
const SuggestedQuestionsSkeleton_1 = tslib_1.__importDefault(require("../SuggestedQuestionsContainer/SuggestedQuestionsSkeleton"));
|
|
8
8
|
const translate_1 = require("../../utils/translate");
|
|
9
|
+
const useReturnFocusToInput_1 = tslib_1.__importDefault(require("../../hooks/useReturnFocusToInput"));
|
|
10
|
+
const PiaModal_1 = require("./PiaModal");
|
|
9
11
|
const ConversationHistory_1 = tslib_1.__importDefault(require("../ConversationHistory/ConversationHistory"));
|
|
10
12
|
function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, disclaimerPosition, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, handleQuestionClick, containerRef, handleFeedback, onResultClick, qnaResultId, onInputFocus, priceCurrency, checkoutElement, }) {
|
|
11
13
|
const hasHistory = conversationHistory.length > 0;
|
|
14
|
+
const { inputRef, returnFocusAfterLoading } = (0, useReturnFocusToInput_1.default)(isLoading);
|
|
15
|
+
const modalInputRef = (0, react_1.useContext)(PiaModal_1.ModalInputRefContext);
|
|
16
|
+
const attachInput = (0, react_1.useCallback)((node) => {
|
|
17
|
+
inputRef(node);
|
|
18
|
+
modalInputRef === null || modalInputRef === void 0 ? void 0 : modalInputRef(node);
|
|
19
|
+
}, [inputRef, modalInputRef]);
|
|
20
|
+
const submitQuestion = (0, react_1.useCallback)((question) => {
|
|
21
|
+
handleSubmitQuestion(question);
|
|
22
|
+
returnFocusAfterLoading();
|
|
23
|
+
}, [handleSubmitQuestion, returnFocusAfterLoading]);
|
|
24
|
+
const clickQuestion = (0, react_1.useCallback)((question) => {
|
|
25
|
+
handleQuestionClick(question);
|
|
26
|
+
returnFocusAfterLoading();
|
|
27
|
+
}, [handleQuestionClick, returnFocusAfterLoading]);
|
|
12
28
|
return (react_1.default.createElement("div", { ref: containerRef, className: 'cio-pia-container cio-pia-conversation', "data-testid": 'cio-pia-container' },
|
|
13
29
|
!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
30
|
react_1.default.createElement(ConversationHistory_1.default, { conversationHistory: conversationHistory, isLoading: isLoading, error: error, currentItems: currentItems, showFeedback: showFeedback, showPreviousItems: showPreviousItems, learnMoreUrl: learnMoreUrl, disclaimerPosition: disclaimerPosition, translations: translations, callbacks: callbacks, componentOverrides: componentOverrides, handleFeedback: handleFeedback, onResultClick: onResultClick, qnaResultId: qnaResultId, priceCurrency: priceCurrency }),
|
|
15
31
|
checkoutElement && react_1.default.createElement("div", { className: 'cio-pia-conversation-checkout' }, checkoutElement),
|
|
16
32
|
react_1.default.createElement("div", { className: 'cio-pia-conversation-footer' },
|
|
17
33
|
isLoading && !error && react_1.default.createElement(SuggestedQuestionsSkeleton_1.default, null),
|
|
18
|
-
!isLoading && !error && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: displayedQuestions, onQuestionClick:
|
|
19
|
-
react_1.default.createElement(Input_1.default, { onSubmit:
|
|
34
|
+
!isLoading && !error && (react_1.default.createElement(SuggestedQuestionsContainer_1.default, { questions: displayedQuestions, onQuestionClick: clickQuestion, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.suggestedQuestions })),
|
|
35
|
+
react_1.default.createElement(Input_1.default, { onSubmit: submitQuestion, onFocus: onInputFocus, inputRef: attachInput, disabled: isLoading, translations: translations, componentOverride: componentOverrides === null || componentOverrides === void 0 ? void 0 : componentOverrides.input }))));
|
|
20
36
|
}
|
|
21
37
|
exports.default = PiaConversation;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ModalInputRefContext = void 0;
|
|
3
4
|
const tslib_1 = require("tslib");
|
|
4
5
|
const react_1 = tslib_1.__importStar(require("react"));
|
|
5
6
|
const Input_1 = tslib_1.__importDefault(require("../Input/Input"));
|
|
6
7
|
const translate_1 = require("../../utils/translate");
|
|
7
8
|
const SuggestedQuestionsContainer_1 = tslib_1.__importDefault(require("../SuggestedQuestionsContainer/SuggestedQuestionsContainer"));
|
|
8
9
|
const OVERFLOW_HIDDEN_CLASS = 'cio-pia-modal-open';
|
|
10
|
+
/** Lets the conversation rendered as the modal's children hand it the node of its input. */
|
|
11
|
+
exports.ModalInputRefContext = (0, react_1.createContext)(undefined);
|
|
9
12
|
function CloseIcon() {
|
|
10
13
|
return (react_1.default.createElement("svg", { width: '20', height: '20', viewBox: '0 0 20 20', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', "aria-hidden": 'true', focusable: 'false' },
|
|
11
14
|
react_1.default.createElement("path", { d: 'M15.8334 5.34166L14.6584 4.16666L10.0001 8.82499L5.34175 4.16666L4.16675 5.34166L8.82508 9.99999L4.16675 14.6583L5.34175 15.8333L10.0001 11.175L14.6584 15.8333L15.8334 14.6583L11.1751 9.99999L15.8334 5.34166Z', fill: 'currentColor' })));
|
|
@@ -14,6 +17,11 @@ function PiaModal({ initialQuestions, handleSubmitQuestion, handleQuestionClick,
|
|
|
14
17
|
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
15
18
|
const dialogRef = (0, react_1.useRef)(null);
|
|
16
19
|
const triggerRef = (0, react_1.useRef)(null);
|
|
20
|
+
const initialFocusRef = (0, react_1.useRef)(null);
|
|
21
|
+
const inputNodeRef = (0, react_1.useRef)(null);
|
|
22
|
+
const setInputNode = (0, react_1.useCallback)((node) => {
|
|
23
|
+
inputNodeRef.current = node;
|
|
24
|
+
}, []);
|
|
17
25
|
const openModal = (0, react_1.useCallback)(() => {
|
|
18
26
|
const active = document.activeElement;
|
|
19
27
|
const isRealTrigger = !!active && active !== document.body && active !== document.documentElement;
|
|
@@ -30,6 +38,7 @@ function PiaModal({ initialQuestions, handleSubmitQuestion, handleQuestionClick,
|
|
|
30
38
|
return undefined;
|
|
31
39
|
if (isOpen) {
|
|
32
40
|
dialog.showModal();
|
|
41
|
+
initialFocusRef.current = document.activeElement;
|
|
33
42
|
document.body.classList.add(OVERFLOW_HIDDEN_CLASS);
|
|
34
43
|
}
|
|
35
44
|
else {
|
|
@@ -41,6 +50,19 @@ function PiaModal({ initialQuestions, handleSubmitQuestion, handleQuestionClick,
|
|
|
41
50
|
document.body.classList.remove(OVERFLOW_HIDDEN_CLASS);
|
|
42
51
|
};
|
|
43
52
|
}, [isOpen]);
|
|
53
|
+
// The input is disabled when the dialog opens, so showModal() parks focus on the first enabled
|
|
54
|
+
// control. Hand it to the input once the answer has loaded, unless the shopper has moved it.
|
|
55
|
+
(0, react_1.useEffect)(() => {
|
|
56
|
+
var _a;
|
|
57
|
+
if (!isOpen || isLoading || !initialFocusRef.current)
|
|
58
|
+
return;
|
|
59
|
+
const placed = initialFocusRef.current;
|
|
60
|
+
initialFocusRef.current = null;
|
|
61
|
+
const active = document.activeElement;
|
|
62
|
+
if (active !== placed && active !== document.body)
|
|
63
|
+
return;
|
|
64
|
+
(_a = inputNodeRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
65
|
+
}, [isOpen, isLoading]);
|
|
44
66
|
// Restore focus to the trigger. Re-runs on `isLoading`: the input trigger is
|
|
45
67
|
// disabled while a request is in flight and `focus()` would no-op.
|
|
46
68
|
(0, react_1.useEffect)(() => {
|
|
@@ -92,6 +114,7 @@ function PiaModal({ initialQuestions, handleSubmitQuestion, handleQuestionClick,
|
|
|
92
114
|
react_1.default.createElement("p", { className: 'cio-pia-title', id: 'cio-pia-modal-title' }, (0, translate_1.translate)('Ask about this product', translations)),
|
|
93
115
|
react_1.default.createElement("button", { type: 'button', className: 'cio-pia-modal-close-button', "aria-label": (0, translate_1.translate)('Close', translations), onClick: closeModal },
|
|
94
116
|
react_1.default.createElement(CloseIcon, null))),
|
|
95
|
-
react_1.default.createElement("div", { className: 'cio-pia-modal-body' },
|
|
117
|
+
react_1.default.createElement("div", { className: 'cio-pia-modal-body' },
|
|
118
|
+
react_1.default.createElement(exports.ModalInputRefContext.Provider, { value: setInputNode }, children))))));
|
|
96
119
|
}
|
|
97
120
|
exports.default = PiaModal;
|
|
@@ -13,35 +13,51 @@ function useAnswerResults({ itemId, variationId, threadId, cioClient, parameters
|
|
|
13
13
|
(0, react_1.useEffect)(() => {
|
|
14
14
|
cioClientRef.current = cioClient;
|
|
15
15
|
}, [cioClient]);
|
|
16
|
+
const requestIdRef = (0, react_1.useRef)(0);
|
|
16
17
|
const fetchResult = (0, react_1.useCallback)((question) => {
|
|
17
18
|
const client = cioClientRef.current;
|
|
18
19
|
if (!client)
|
|
19
20
|
return;
|
|
21
|
+
requestIdRef.current += 1;
|
|
22
|
+
const requestId = requestIdRef.current;
|
|
23
|
+
const isCurrent = () => requestId === requestIdRef.current;
|
|
20
24
|
setIsLoading(true);
|
|
21
25
|
setError(null);
|
|
22
26
|
setAnswerResults(null);
|
|
23
27
|
client.agent.pia
|
|
24
28
|
.getAnswerResults(itemId, question, Object.assign({ threadId, variationId }, parameters))
|
|
25
29
|
.then((response) => {
|
|
30
|
+
if (!isCurrent())
|
|
31
|
+
return;
|
|
26
32
|
setAnswerResults(response);
|
|
27
33
|
setError(null);
|
|
28
34
|
})
|
|
29
35
|
.catch((err) => {
|
|
36
|
+
if (!isCurrent())
|
|
37
|
+
return;
|
|
30
38
|
setError(err instanceof Error ? err : new Error('Error fetching answer'));
|
|
31
39
|
setAnswerResults(null);
|
|
32
40
|
})
|
|
33
41
|
.finally(() => {
|
|
34
|
-
|
|
42
|
+
if (isCurrent())
|
|
43
|
+
setIsLoading(false);
|
|
35
44
|
});
|
|
36
45
|
},
|
|
37
46
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
47
|
[itemId, variationId, threadId, serializedParameters]);
|
|
48
|
+
const clear = (0, react_1.useCallback)(() => {
|
|
49
|
+
requestIdRef.current += 1;
|
|
50
|
+
setAnswerResults(null);
|
|
51
|
+
setError(null);
|
|
52
|
+
setIsLoading(false);
|
|
53
|
+
}, []);
|
|
39
54
|
return {
|
|
40
55
|
data: answerResults,
|
|
41
56
|
items,
|
|
42
57
|
isLoading,
|
|
43
58
|
error,
|
|
44
59
|
getAnswer: fetchResult,
|
|
60
|
+
clear,
|
|
45
61
|
};
|
|
46
62
|
}
|
|
47
63
|
exports.default = useAnswerResults;
|
|
@@ -40,12 +40,13 @@ function mapDeprecatedParameters(params) {
|
|
|
40
40
|
return hasEntries ? mapped : undefined;
|
|
41
41
|
}
|
|
42
42
|
function useCioPia(props) {
|
|
43
|
-
const { apiKey, itemId, variationId, threadId: providedThreadId, cioClient: providedClient, suggestedQuestionsParameters, answerParameters, parameters, formatImageUrl, testCells, } = props;
|
|
43
|
+
const { apiKey, itemId, variationId, threadId: providedThreadId, cioClient: providedClient, suggestedQuestionsParameters, answerParameters, parameters, formatImageUrl, testCells, userId, } = props;
|
|
44
44
|
const { cioClient: client, threadId } = (0, usePiaClient_1.default)({
|
|
45
45
|
apiKey,
|
|
46
46
|
threadId: providedThreadId,
|
|
47
47
|
cioClient: providedClient,
|
|
48
48
|
testCells,
|
|
49
|
+
userId,
|
|
49
50
|
});
|
|
50
51
|
const mappedDeprecated = mapDeprecatedParameters(parameters);
|
|
51
52
|
const mergedSuggestedParams = mappedDeprecated || suggestedQuestionsParameters
|
|
@@ -1,40 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const react_1 = require("react");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const { question, answer, items } = entry;
|
|
8
|
-
return (typeof question === 'string' &&
|
|
9
|
-
typeof answer === 'string' &&
|
|
10
|
-
(items === undefined || items === null || Array.isArray(items)));
|
|
11
|
-
}
|
|
4
|
+
const conversationStorage_1 = require("../utils/conversationStorage");
|
|
5
|
+
// `id` is the React key, so ids from outside the widget cannot be trusted to be unique.
|
|
6
|
+
const renumber = (entries) => entries.map((entry, index) => (Object.assign(Object.assign({}, entry), { id: index + 1 })));
|
|
12
7
|
// The seed comes from the host's own storage, typed or not: a malformed one must not take the widget down.
|
|
13
8
|
function restoreHistory(seed) {
|
|
14
9
|
if (!Array.isArray(seed)) {
|
|
15
10
|
console.warn('[CioPia] initialConversationHistory must be an array. It was ignored.');
|
|
16
11
|
return [];
|
|
17
12
|
}
|
|
18
|
-
const entries = seed.filter(isRestorableEntry);
|
|
13
|
+
const entries = seed.filter(conversationStorage_1.isRestorableEntry);
|
|
19
14
|
if (entries.length < seed.length) {
|
|
20
15
|
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
16
|
}
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
return renumber(entries);
|
|
18
|
+
}
|
|
19
|
+
function initialHistory(isConversation, seed, persisted) {
|
|
20
|
+
if (!isConversation)
|
|
21
|
+
return [];
|
|
22
|
+
if (seed !== undefined)
|
|
23
|
+
return restoreHistory(seed);
|
|
24
|
+
return persisted ? renumber(persisted.history) : [];
|
|
24
25
|
}
|
|
25
|
-
function useConversation({ pia, itemId, isConversation, callbacks, tracking, initialConversationHistory, }) {
|
|
26
|
+
function useConversation({ pia, itemId, isConversation, callbacks, tracking, initialConversationHistory, persisted, }) {
|
|
26
27
|
var _a, _b, _c;
|
|
27
28
|
const { suggestedQuestions, answers, threadId } = pia;
|
|
28
29
|
const { getAnswer } = answers;
|
|
29
30
|
const context = (0, react_1.useMemo)(() => ({ itemId, threadId }), [itemId, threadId]);
|
|
30
31
|
const [currentQuestion, setCurrentQuestion] = (0, react_1.useState)('');
|
|
31
32
|
const [displayedQuestions, setDisplayedQuestions] = (0, react_1.useState)([]);
|
|
32
|
-
const [conversationHistory, setConversationHistory] = (0, react_1.useState)(() => isConversation
|
|
33
|
-
? restoreHistory(initialConversationHistory)
|
|
34
|
-
: []);
|
|
33
|
+
const [conversationHistory, setConversationHistory] = (0, react_1.useState)(() => initialHistory(isConversation, initialConversationHistory, persisted));
|
|
35
34
|
const entryIdRef = (0, react_1.useRef)(conversationHistory.length);
|
|
36
35
|
const conversationHistoryRef = (0, react_1.useRef)(conversationHistory);
|
|
37
36
|
const prevItemIdRef = (0, react_1.useRef)(itemId);
|
|
37
|
+
const persistedRef = (0, react_1.useRef)(persisted);
|
|
38
|
+
const prevPersistedKeyRef = (0, react_1.useRef)(persisted === null || persisted === void 0 ? void 0 : persisted.key);
|
|
38
39
|
const prevAnswerDataRef = (0, react_1.useRef)(answers.data);
|
|
39
40
|
const hasTrackedCurrentAnswerRef = (0, react_1.useRef)(false);
|
|
40
41
|
const answersRef = (0, react_1.useRef)(answers);
|
|
@@ -61,6 +62,9 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, ini
|
|
|
61
62
|
(0, react_1.useEffect)(() => {
|
|
62
63
|
conversationHistoryRef.current = conversationHistory;
|
|
63
64
|
}, [conversationHistory]);
|
|
65
|
+
(0, react_1.useEffect)(() => {
|
|
66
|
+
persistedRef.current = persisted;
|
|
67
|
+
}, [persisted]);
|
|
64
68
|
const submitQuestion = (0, react_1.useCallback)((question, source) => {
|
|
65
69
|
lastSourceRef.current = source;
|
|
66
70
|
lastQuestionRef.current = question;
|
|
@@ -101,7 +105,9 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, ini
|
|
|
101
105
|
const resetState = (0, react_1.useCallback)(() => {
|
|
102
106
|
setCurrentQuestion('');
|
|
103
107
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
104
|
-
|
|
108
|
+
// A stored conversation outlives the modal: it is there again when the modal reopens.
|
|
109
|
+
if (!persistedRef.current)
|
|
110
|
+
setConversationHistory([]);
|
|
105
111
|
prevAnswerDataRef.current = null;
|
|
106
112
|
showsFollowUpsRef.current = false;
|
|
107
113
|
}, [suggestedQuestions.data]);
|
|
@@ -116,6 +122,19 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, ini
|
|
|
116
122
|
prevAnswerDataRef.current = null;
|
|
117
123
|
showsFollowUpsRef.current = false;
|
|
118
124
|
}, [itemId]);
|
|
125
|
+
(0, react_1.useEffect)(() => {
|
|
126
|
+
// Runs after the itemId reset above, so another product's stored conversation replaces the empty one.
|
|
127
|
+
if (!persisted || prevPersistedKeyRef.current === persisted.key)
|
|
128
|
+
return;
|
|
129
|
+
prevPersistedKeyRef.current = persisted.key;
|
|
130
|
+
// The live answer belongs to the conversation being replaced: left in place, the restored last
|
|
131
|
+
// turn would show its products and credit its feedback.
|
|
132
|
+
answersRef.current.clear();
|
|
133
|
+
const restored = renumber(persisted.history);
|
|
134
|
+
entryIdRef.current = restored.length;
|
|
135
|
+
setConversationHistory(restored);
|
|
136
|
+
setCurrentQuestion('');
|
|
137
|
+
}, [persisted]);
|
|
119
138
|
(0, react_1.useEffect)(() => {
|
|
120
139
|
// The ref, not `answers.data`, which outlives the item it answered.
|
|
121
140
|
if (showsFollowUpsRef.current)
|
|
@@ -149,12 +168,15 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, ini
|
|
|
149
168
|
const qnaResultId = (_d = answers.data) === null || _d === void 0 ? void 0 : _d.qna_result_id;
|
|
150
169
|
if (isConversation) {
|
|
151
170
|
setConversationHistory((prev) => {
|
|
152
|
-
var _a, _b;
|
|
153
|
-
|
|
171
|
+
var _a, _b, _c;
|
|
172
|
+
// Only a question still waiting is answered: after a switch of product or shopper, a late
|
|
173
|
+
// answer must not overwrite a settled turn of the conversation that replaced it.
|
|
174
|
+
if (prev.length === 0 || prev[prev.length - 1].answer !== '')
|
|
154
175
|
return prev;
|
|
155
176
|
const updated = [...prev];
|
|
156
177
|
updated[updated.length - 1] = Object.assign(Object.assign({}, updated[updated.length - 1]), { answer: answerValue, items: answers.items, threadId: answerThreadId, qnaResultId });
|
|
157
178
|
(_b = (_a = callbacksRef.current) === null || _a === void 0 ? void 0 : _a.onAnswer) === null || _b === void 0 ? void 0 : _b.call(_a, updated, contextRef.current);
|
|
179
|
+
(_c = persistedRef.current) === null || _c === void 0 ? void 0 : _c.save(updated);
|
|
158
180
|
return updated;
|
|
159
181
|
});
|
|
160
182
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
const conversationStorage_1 = require("../utils/conversationStorage");
|
|
5
|
+
function loadIfEnabled({ enabled, apiKey, userId, itemId, threadIdProp, }) {
|
|
6
|
+
if (!enabled)
|
|
7
|
+
return { threadId: '', history: [] };
|
|
8
|
+
const stored = (0, conversationStorage_1.loadConversation)({ apiKey, userId }, itemId);
|
|
9
|
+
if (stored && (!threadIdProp || stored.threadId === threadIdProp)) {
|
|
10
|
+
return { threadId: stored.threadId, history: stored.entries };
|
|
11
|
+
}
|
|
12
|
+
// A product with nothing stored starts its own thread, so returning to another product resumes
|
|
13
|
+
// a thread that only ever heard about that product.
|
|
14
|
+
return { threadId: threadIdProp || crypto.randomUUID(), history: [] };
|
|
15
|
+
}
|
|
16
|
+
/** The stored conversation for this product and shopper, or `undefined` while persistence is off. */
|
|
17
|
+
function usePersistedConversation({ enabled, apiKey, userId: userIdProp, itemId, threadId: threadIdProp, }) {
|
|
18
|
+
const userId = (0, conversationStorage_1.isGuest)(userIdProp) ? undefined : String(userIdProp);
|
|
19
|
+
const userIdOmitted = userIdProp === undefined;
|
|
20
|
+
// An omitted id stores a signed-in shopper as a guest, so say so rather than fail silently.
|
|
21
|
+
(0, react_1.useEffect)(() => {
|
|
22
|
+
if (!enabled || !userIdOmitted)
|
|
23
|
+
return;
|
|
24
|
+
console.warn('[CioPia] persistConversation is on but no userId was passed, so conversations are stored as a guest. Pass the signed-in shopper id, or null for a guest.');
|
|
25
|
+
}, [enabled, userIdOmitted]);
|
|
26
|
+
const target = { enabled, apiKey, userId, itemId, threadIdProp };
|
|
27
|
+
const [shown, setShown] = (0, react_1.useState)(() => (Object.assign(Object.assign(Object.assign({}, target), { key: 0, loggedIn: false }), loadIfEnabled(target))));
|
|
28
|
+
// Adjusted during render, not in an effect: React re-renders before committing, so the first
|
|
29
|
+
// request after a switch of product or shopper already goes out on the new thread. See
|
|
30
|
+
// https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
|
|
31
|
+
let current = shown;
|
|
32
|
+
if (shown.enabled !== enabled ||
|
|
33
|
+
shown.apiKey !== apiKey ||
|
|
34
|
+
shown.userId !== userId ||
|
|
35
|
+
shown.itemId !== itemId ||
|
|
36
|
+
shown.threadIdProp !== threadIdProp) {
|
|
37
|
+
const loggedIn = enabled &&
|
|
38
|
+
shown.enabled &&
|
|
39
|
+
shown.apiKey === apiKey &&
|
|
40
|
+
shown.userId === undefined &&
|
|
41
|
+
userId !== undefined;
|
|
42
|
+
// A login continues the guest's conversation as the shopper's own. With none to carry over,
|
|
43
|
+
// the shopper's own stored conversation is shown, rather than replaced by an empty one.
|
|
44
|
+
const keepsConversation = loggedIn &&
|
|
45
|
+
shown.itemId === itemId &&
|
|
46
|
+
shown.threadIdProp === threadIdProp &&
|
|
47
|
+
(0, conversationStorage_1.loadConversation)({ apiKey }, itemId) !== undefined;
|
|
48
|
+
const conversation = keepsConversation
|
|
49
|
+
? { key: shown.key, threadId: shown.threadId, history: shown.history }
|
|
50
|
+
: Object.assign({ key: shown.key + 1 }, loadIfEnabled(target));
|
|
51
|
+
current = Object.assign(Object.assign(Object.assign({}, target), { loggedIn }), conversation);
|
|
52
|
+
setShown(current);
|
|
53
|
+
}
|
|
54
|
+
(0, react_1.useEffect)(() => {
|
|
55
|
+
if (shown.loggedIn && shown.userId)
|
|
56
|
+
(0, conversationStorage_1.moveGuestConversations)(shown.apiKey, shown.userId);
|
|
57
|
+
}, [shown]);
|
|
58
|
+
const shownRef = (0, react_1.useRef)(shown);
|
|
59
|
+
(0, react_1.useEffect)(() => {
|
|
60
|
+
shownRef.current = shown;
|
|
61
|
+
}, [shown]);
|
|
62
|
+
const save = (0, react_1.useCallback)((history) => {
|
|
63
|
+
const latest = shownRef.current;
|
|
64
|
+
if (!latest.enabled)
|
|
65
|
+
return;
|
|
66
|
+
(0, conversationStorage_1.saveConversation)({ apiKey: latest.apiKey, userId: latest.userId }, latest.itemId, latest.threadId, history);
|
|
67
|
+
}, []);
|
|
68
|
+
const { key, threadId, history } = current;
|
|
69
|
+
return (0, react_1.useMemo)(() => (current.enabled ? { key, threadId, history, save } : undefined), [current.enabled, key, threadId, history, save]);
|
|
70
|
+
}
|
|
71
|
+
exports.default = usePersistedConversation;
|
|
@@ -4,7 +4,7 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const constructorio_client_javascript_1 = tslib_1.__importDefault(require("@constructor-io/constructorio-client-javascript"));
|
|
6
6
|
const version_1 = tslib_1.__importDefault(require("../version"));
|
|
7
|
-
function usePiaClient({ apiKey, threadId: providedThreadId, cioClient: providedClient, testCells, }) {
|
|
7
|
+
function usePiaClient({ apiKey, threadId: providedThreadId, cioClient: providedClient, testCells, userId, }) {
|
|
8
8
|
const [generatedThreadId] = (0, react_1.useState)(() => crypto.randomUUID());
|
|
9
9
|
const threadId = providedThreadId || generatedThreadId;
|
|
10
10
|
const serializedTestCells = (0, react_1.useMemo)(() => JSON.stringify(testCells !== null && testCells !== void 0 ? testCells : null), [testCells]);
|
|
@@ -15,6 +15,13 @@ function usePiaClient({ apiKey, threadId: providedThreadId, cioClient: providedC
|
|
|
15
15
|
// testCells is compared by serializedTestCells so an inline object does not rebuild the client
|
|
16
16
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
17
17
|
}, [apiKey, providedClient, serializedTestCells]);
|
|
18
|
+
// Set rather than passed to the constructor, so a login does not rebuild the client.
|
|
19
|
+
(0, react_1.useEffect)(() => {
|
|
20
|
+
if (providedClient || userId === undefined)
|
|
21
|
+
return;
|
|
22
|
+
// The typings require `apiKey`; the client merges a partial update.
|
|
23
|
+
client.setClientOptions({ userId: userId || undefined });
|
|
24
|
+
}, [client, providedClient, userId]);
|
|
18
25
|
// A caller's client owns its own options, so say so rather than dropping these silently.
|
|
19
26
|
(0, react_1.useEffect)(() => {
|
|
20
27
|
if (!providedClient || !testCells || Object.keys(testCells).length === 0)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const react_1 = require("react");
|
|
4
|
+
/**
|
|
5
|
+
* While an answer loads, the control the shopper used to ask is disabled or swapped for a
|
|
6
|
+
* skeleton, and the browser drops focus to <body>.
|
|
7
|
+
*/
|
|
8
|
+
function useReturnFocusToInput(isLoading) {
|
|
9
|
+
const inputNodeRef = (0, react_1.useRef)(null);
|
|
10
|
+
const pendingRef = (0, react_1.useRef)(false);
|
|
11
|
+
(0, react_1.useEffect)(() => {
|
|
12
|
+
if (isLoading || !pendingRef.current)
|
|
13
|
+
return;
|
|
14
|
+
pendingRef.current = false;
|
|
15
|
+
const active = document.activeElement;
|
|
16
|
+
const focusWasDropped = !active || active === document.body;
|
|
17
|
+
if (inputNodeRef.current && focusWasDropped)
|
|
18
|
+
inputNodeRef.current.focus();
|
|
19
|
+
}, [isLoading]);
|
|
20
|
+
const inputRef = (0, react_1.useCallback)((node) => {
|
|
21
|
+
inputNodeRef.current = node;
|
|
22
|
+
}, []);
|
|
23
|
+
const returnFocusAfterLoading = (0, react_1.useCallback)(() => {
|
|
24
|
+
pendingRef.current = true;
|
|
25
|
+
}, []);
|
|
26
|
+
return { inputRef, returnFocusAfterLoading };
|
|
27
|
+
}
|
|
28
|
+
exports.default = useReturnFocusToInput;
|
package/lib/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AgentRequestError = exports.renderMarkdown = exports.sanitizeHtml = exports.CioPia = void 0;
|
|
3
|
+
exports.AgentRequestError = exports.clearPersistedConversations = exports.renderMarkdown = exports.sanitizeHtml = exports.CioPia = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const CioPia_1 = tslib_1.__importDefault(require("./components/CioPia"));
|
|
6
6
|
// Components
|
|
@@ -10,6 +10,9 @@ Object.defineProperty(exports, "CioPia", { enumerable: true, get: function () {
|
|
|
10
10
|
var contentTransformers_1 = require("./utils/contentTransformers");
|
|
11
11
|
Object.defineProperty(exports, "sanitizeHtml", { enumerable: true, get: function () { return contentTransformers_1.sanitizeHtml; } });
|
|
12
12
|
Object.defineProperty(exports, "renderMarkdown", { enumerable: true, get: function () { return contentTransformers_1.renderMarkdown; } });
|
|
13
|
+
// Persistence
|
|
14
|
+
var conversationStorage_1 = require("./utils/conversationStorage");
|
|
15
|
+
Object.defineProperty(exports, "clearPersistedConversations", { enumerable: true, get: function () { return conversationStorage_1.clearPersistedConversations; } });
|
|
13
16
|
// Errors
|
|
14
17
|
var errors_1 = require("./errors");
|
|
15
18
|
Object.defineProperty(exports, "AgentRequestError", { enumerable: true, get: function () { return errors_1.AgentRequestError; } });
|