@constructor-io/constructorio-ui-pia 2.12.0 → 2.13.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/dist/constructorio-ui-pia-bundled.js +34 -34
- package/lib/cjs/components/CioPia/CioPiaQna.js +8 -3
- package/lib/cjs/components/CioPia/CioPiaStyles.css +5 -0
- package/lib/cjs/components/CioPia/PiaCustomCarousel.js +11 -1
- package/lib/cjs/components/ConversationHistory/ConversationHistory.js +10 -5
- package/lib/cjs/components/Error/ErrorBlock.js +7 -5
- package/lib/cjs/components/Feedback/Feedback.js +4 -4
- package/lib/cjs/components/Input/Input.js +2 -1
- package/lib/cjs/components/LoadingSkeleton/LoadingSkeleton.js +2 -1
- package/lib/cjs/components/PiaConversation/PiaModal.js +22 -4
- package/lib/cjs/components/StatusRegion/StatusRegion.js +34 -0
- package/lib/cjs/components/SuggestedQuestionsContainer/SuggestedQuestionsSkeleton.js +1 -1
- package/lib/cjs/components/icons/QuestionIcon.js +1 -1
- package/lib/cjs/components/icons/SendIcon.js +1 -1
- package/lib/cjs/components/icons/SparklesIcon.js +1 -1
- package/lib/cjs/hooks/useConversation.js +10 -8
- package/lib/cjs/utils/translate.js +12 -0
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/CioPia/CioPiaQna.js +8 -3
- package/lib/mjs/components/CioPia/CioPiaStyles.css +5 -0
- package/lib/mjs/components/CioPia/PiaCustomCarousel.js +11 -1
- package/lib/mjs/components/ConversationHistory/ConversationHistory.js +10 -5
- package/lib/mjs/components/Error/ErrorBlock.js +7 -5
- package/lib/mjs/components/Feedback/Feedback.js +4 -4
- package/lib/mjs/components/Input/Input.js +2 -1
- package/lib/mjs/components/LoadingSkeleton/LoadingSkeleton.js +2 -1
- package/lib/mjs/components/PiaConversation/PiaModal.js +22 -4
- package/lib/mjs/components/StatusRegion/StatusRegion.js +28 -0
- package/lib/mjs/components/SuggestedQuestionsContainer/SuggestedQuestionsSkeleton.js +1 -1
- package/lib/mjs/components/icons/QuestionIcon.js +1 -1
- package/lib/mjs/components/icons/SendIcon.js +1 -1
- package/lib/mjs/components/icons/SparklesIcon.js +1 -1
- package/lib/mjs/hooks/useConversation.js +7 -5
- package/lib/mjs/utils/translate.js +12 -0
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/ConversationHistory/ConversationHistory.d.ts +7 -1
- package/lib/types/components/Error/ErrorBlock.d.ts +9 -1
- package/lib/types/components/StatusRegion/StatusRegion.d.ts +13 -0
- package/lib/types/types.d.ts +10 -0
- package/lib/types/utils/translate.d.ts +3 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { translate } from '../../utils/translate';
|
|
2
3
|
function WarningIcon() {
|
|
3
|
-
return (React.createElement("svg", { width: '24', height: '24', viewBox: '0 0 24 24', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
|
|
4
|
+
return (React.createElement("svg", { width: '24', height: '24', viewBox: '0 0 24 24', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', "aria-hidden": 'true', focusable: 'false' },
|
|
4
5
|
React.createElement("path", { d: 'M12.0004 2C6.4804 2 2.0004 6.48 2.0004 12C2.0004 17.52 6.4804 22 12.0004 22C17.5204 22 22.0004 17.52 22.0004 12C22.0004 6.48 17.5204 2 12.0004 2ZM13.0004 17H11.0004V15H13.0004V17ZM13.0004 13H11.0004V7H13.0004V13Z', fill: '#F1923A' })));
|
|
5
6
|
}
|
|
6
7
|
function RetryIcon() {
|
|
7
|
-
return (React.createElement("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
|
|
8
|
+
return (React.createElement("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', "aria-hidden": 'true', focusable: 'false' },
|
|
8
9
|
React.createElement("path", { d: 'M11.7633 4.23329C10.7966 3.26663 9.46992 2.66663 7.99659 2.66663C5.04992 2.66663 2.66992 5.05329 2.66992 7.99996C2.66992 10.9466 5.04992 13.3333 7.99659 13.3333C10.4833 13.3333 12.5566 11.6333 13.1499 9.33329H11.7633C11.2166 10.8866 9.73659 12 7.99659 12C5.78992 12 3.99659 10.2066 3.99659 7.99996C3.99659 5.79329 5.78992 3.99996 7.99659 3.99996C9.10326 3.99996 10.0899 4.45996 10.8099 5.18663L8.66326 7.33329H13.3299V2.66663L11.7633 4.23329Z', fill: 'white' })));
|
|
9
10
|
}
|
|
10
|
-
function ErrorBlock({ message, onRetry = undefined }) {
|
|
11
|
+
function ErrorBlock({ message, onRetry = undefined, translations, announce = true, }) {
|
|
12
|
+
const resolvedMessage = message || translate('Unexpected error', translations);
|
|
11
13
|
return (React.createElement("div", { className: 'cio-pia-error-block-container', "data-testid": 'error-block' },
|
|
12
14
|
React.createElement("div", { className: 'cio-pia-error-block-warning-icon-container', "data-testid": 'error-block-warning-icon' },
|
|
13
15
|
React.createElement(WarningIcon, null)),
|
|
14
16
|
React.createElement("div", { className: 'cio-pia-error-block-text-container' },
|
|
15
|
-
React.createElement("p", { className: 'cio-pia-error-block-text' },
|
|
17
|
+
React.createElement("p", { className: 'cio-pia-error-block-text', role: announce ? 'alert' : undefined }, resolvedMessage),
|
|
16
18
|
onRetry && (React.createElement("button", { type: 'button', onClick: onRetry, className: 'cio-pia-error-block-retry-button' },
|
|
17
19
|
React.createElement(RetryIcon, null),
|
|
18
|
-
|
|
20
|
+
translate('Retry', translations))))));
|
|
19
21
|
}
|
|
20
22
|
export default ErrorBlock;
|
|
@@ -3,10 +3,10 @@ import { RenderPropsWrapper, } from '@constructor-io/constructorio-ui-components
|
|
|
3
3
|
import { FeedbackType } from '../../types';
|
|
4
4
|
import { translate } from '../../utils/translate';
|
|
5
5
|
function ThumbsUpIcon({ isSelected }) {
|
|
6
|
-
return (React.createElement("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' }, isSelected ? (React.createElement("path", { d: 'M3.33317 6.66667H0.666504V14.6667H3.33317V6.66667ZM5.99984 14.6667C5.2665 14.6667 4.6665 14.0667 4.6665 13.3333V6.66667C4.6665 6.3 4.81317 5.96667 5.05317 5.72667L9.4465 1.33333L10.1532 2.03333C10.3332 2.21333 10.4465 2.46667 10.4465 2.74L10.4265 2.95333L9.79317 6.00001H13.9998C14.7332 6.00001 15.3332 6.60001 15.3332 7.33334V8.66668C15.3332 8.84001 15.2998 9.00001 15.2398 9.15334L13.2265 13.8533C13.0265 14.3333 12.5532 14.6667 11.9998 14.6667H5.99984Z', fill: '#4CAF50', fillOpacity: '1' })) : (React.createElement("path", { d: 'M5.99984 14.6667H11.9998C12.5532 14.6667 13.0265 14.3333 13.2265 13.8533L15.2398 9.15334C15.2998 9.00001 15.3332 8.84001 15.3332 8.66668V7.33334C15.3332 6.60001 14.7332 6.00001 13.9998 6.00001H9.79317L10.4265 2.95334L10.4465 2.74001C10.4465 2.46668 10.3332 2.21334 10.1532 2.03334L9.4465 1.33334L5.05317 5.72668C4.81317 5.96668 4.6665 6.30001 4.6665 6.66668V13.3333C4.6665 14.0667 5.2665 14.6667 5.99984 14.6667ZM5.99984 6.66668L8.89317 3.77334L7.99984 7.33334H13.9998V8.66668L11.9998 13.3333H5.99984V6.66668ZM0.666504 6.66668H3.33317V14.6667H0.666504V6.66668Z', fill: '#0F1324', fillOpacity: '0.6' }))));
|
|
6
|
+
return (React.createElement("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', "aria-hidden": 'true', focusable: 'false' }, isSelected ? (React.createElement("path", { d: 'M3.33317 6.66667H0.666504V14.6667H3.33317V6.66667ZM5.99984 14.6667C5.2665 14.6667 4.6665 14.0667 4.6665 13.3333V6.66667C4.6665 6.3 4.81317 5.96667 5.05317 5.72667L9.4465 1.33333L10.1532 2.03333C10.3332 2.21333 10.4465 2.46667 10.4465 2.74L10.4265 2.95333L9.79317 6.00001H13.9998C14.7332 6.00001 15.3332 6.60001 15.3332 7.33334V8.66668C15.3332 8.84001 15.2998 9.00001 15.2398 9.15334L13.2265 13.8533C13.0265 14.3333 12.5532 14.6667 11.9998 14.6667H5.99984Z', fill: '#4CAF50', fillOpacity: '1' })) : (React.createElement("path", { d: 'M5.99984 14.6667H11.9998C12.5532 14.6667 13.0265 14.3333 13.2265 13.8533L15.2398 9.15334C15.2998 9.00001 15.3332 8.84001 15.3332 8.66668V7.33334C15.3332 6.60001 14.7332 6.00001 13.9998 6.00001H9.79317L10.4265 2.95334L10.4465 2.74001C10.4465 2.46668 10.3332 2.21334 10.1532 2.03334L9.4465 1.33334L5.05317 5.72668C4.81317 5.96668 4.6665 6.30001 4.6665 6.66668V13.3333C4.6665 14.0667 5.2665 14.6667 5.99984 14.6667ZM5.99984 6.66668L8.89317 3.77334L7.99984 7.33334H13.9998V8.66668L11.9998 13.3333H5.99984V6.66668ZM0.666504 6.66668H3.33317V14.6667H0.666504V6.66668Z', fill: '#0F1324', fillOpacity: '0.6' }))));
|
|
7
7
|
}
|
|
8
8
|
function ThumbsDownIcon({ isSelected }) {
|
|
9
|
-
return (React.createElement("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' }, isSelected ? (React.createElement("path", { d: 'M12.6665 9.33334H15.3332V1.33334H12.6665V9.33334ZM9.99984 1.33334C10.7332 1.33334 11.3332 1.93334 11.3332 2.66668V9.33334C11.3332 9.70001 11.1865 10.0333 10.9465 10.2733L6.55317 14.6667L5.8465 13.9667C5.6665 13.7867 5.55317 13.5333 5.55317 13.26L5.57317 13.0467L6.2065 10H1.99984C1.2665 10 0.666504 9.40001 0.666504 8.66668V7.33334C0.666504 7.16001 0.699837 7.00001 0.759837 6.84668L2.77317 2.14668C2.97317 1.66668 3.4465 1.33334 3.99984 1.33334H9.99984Z', fill: '#F44336', fillOpacity: '1' })) : (React.createElement("path", { d: 'M9.99984 1.33334H3.99984C3.4465 1.33334 2.97317 1.66668 2.77317 2.14668L0.759837 6.84668C0.699837 7.00001 0.666504 7.16001 0.666504 7.33334V8.66668C0.666504 9.40001 1.2665 10 1.99984 10H6.2065L5.57317 13.0467L5.55317 13.26C5.55317 13.5333 5.6665 13.7867 5.8465 13.9667L6.55317 14.6667L10.9465 10.2733C11.1865 10.0333 11.3332 9.70001 11.3332 9.33334V2.66668C11.3332 1.93334 10.7332 1.33334 9.99984 1.33334ZM9.99984 9.33334L7.1065 12.2267L7.99984 8.66668H1.99984V7.33334L3.99984 2.66668H9.99984V9.33334ZM12.6665 1.33334H15.3332V9.33334H12.6665V1.33334Z', fill: '#0F1324', fillOpacity: '0.6' }))));
|
|
9
|
+
return (React.createElement("svg", { width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', "aria-hidden": 'true', focusable: 'false' }, isSelected ? (React.createElement("path", { d: 'M12.6665 9.33334H15.3332V1.33334H12.6665V9.33334ZM9.99984 1.33334C10.7332 1.33334 11.3332 1.93334 11.3332 2.66668V9.33334C11.3332 9.70001 11.1865 10.0333 10.9465 10.2733L6.55317 14.6667L5.8465 13.9667C5.6665 13.7867 5.55317 13.5333 5.55317 13.26L5.57317 13.0467L6.2065 10H1.99984C1.2665 10 0.666504 9.40001 0.666504 8.66668V7.33334C0.666504 7.16001 0.699837 7.00001 0.759837 6.84668L2.77317 2.14668C2.97317 1.66668 3.4465 1.33334 3.99984 1.33334H9.99984Z', fill: '#F44336', fillOpacity: '1' })) : (React.createElement("path", { d: 'M9.99984 1.33334H3.99984C3.4465 1.33334 2.97317 1.66668 2.77317 2.14668L0.759837 6.84668C0.699837 7.00001 0.666504 7.16001 0.666504 7.33334V8.66668C0.666504 9.40001 1.2665 10 1.99984 10H6.2065L5.57317 13.0467L5.55317 13.26C5.55317 13.5333 5.6665 13.7867 5.8465 13.9667L6.55317 14.6667L10.9465 10.2733C11.1865 10.0333 11.3332 9.70001 11.3332 9.33334V2.66668C11.3332 1.93334 10.7332 1.33334 9.99984 1.33334ZM9.99984 9.33334L7.1065 12.2267L7.99984 8.66668H1.99984V7.33334L3.99984 2.66668H9.99984V9.33334ZM12.6665 1.33334H15.3332V9.33334H12.6665V1.33334Z', fill: '#0F1324', fillOpacity: '0.6' }))));
|
|
10
10
|
}
|
|
11
11
|
export default function Feedback({ translations, onFeedback, componentOverride }) {
|
|
12
12
|
const [feedback, setFeedback] = useState(null);
|
|
@@ -17,8 +17,8 @@ export default function Feedback({ translations, onFeedback, componentOverride }
|
|
|
17
17
|
return (React.createElement(RenderPropsWrapper, { props: { translations, onFeedback }, override: componentOverride?.reactNode },
|
|
18
18
|
React.createElement("div", { className: 'cio-pia-feedback-container' },
|
|
19
19
|
React.createElement("p", { className: 'cio-pia-feedback-text' }, translate('Is this answer useful?', translations)),
|
|
20
|
-
React.createElement("button", { type: 'button', className: 'cio-pia-feedback-button', "aria-label": 'thumbs up', onClick: () => handleFeedback(FeedbackType.UP) },
|
|
20
|
+
React.createElement("button", { type: 'button', className: 'cio-pia-feedback-button', "aria-label": translate('thumbs up', translations), "aria-pressed": feedback === FeedbackType.UP, onClick: () => handleFeedback(FeedbackType.UP) },
|
|
21
21
|
React.createElement(ThumbsUpIcon, { isSelected: feedback === FeedbackType.UP })),
|
|
22
|
-
React.createElement("button", { type: 'button', className: 'cio-pia-feedback-button', "aria-label": 'thumbs down', onClick: () => handleFeedback(FeedbackType.DOWN) },
|
|
22
|
+
React.createElement("button", { type: 'button', className: 'cio-pia-feedback-button', "aria-label": translate('thumbs down', translations), "aria-pressed": feedback === FeedbackType.DOWN, onClick: () => handleFeedback(FeedbackType.DOWN) },
|
|
23
23
|
React.createElement(ThumbsDownIcon, { isSelected: feedback === FeedbackType.DOWN })))));
|
|
24
24
|
}
|
|
@@ -42,6 +42,7 @@ function Input({ value: providedValue, placeholder, disabled = false, onSubmit,
|
|
|
42
42
|
const resolvedPlaceholder = translations?.[placeholderKey] !== undefined
|
|
43
43
|
? translate(placeholderKey, translations)
|
|
44
44
|
: (placeholder ?? translate(placeholderKey));
|
|
45
|
+
const accessibleName = resolvedPlaceholder || translate('Your question', translations);
|
|
45
46
|
return (React.createElement(RenderPropsWrapper, { props: {
|
|
46
47
|
disabled,
|
|
47
48
|
placeholder: resolvedPlaceholder,
|
|
@@ -52,7 +53,7 @@ function Input({ value: providedValue, placeholder, disabled = false, onSubmit,
|
|
|
52
53
|
}, override: componentOverride?.reactNode },
|
|
53
54
|
React.createElement(React.Fragment, null,
|
|
54
55
|
React.createElement("div", { className: 'cio-pia-input-container' },
|
|
55
|
-
React.createElement("input", { type: 'text', value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleSubmitOnEnter, onFocus: onFocus, placeholder: resolvedPlaceholder, disabled: disabled, className: 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 }),
|
|
56
|
+
React.createElement("input", { type: 'text', value: value, onChange: (e) => setValue(e.target.value), onKeyDown: handleSubmitOnEnter, onFocus: onFocus, placeholder: resolvedPlaceholder, "aria-label": accessibleName, disabled: disabled, className: 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 }),
|
|
56
57
|
showSendButton && (React.createElement("button", { type: 'button', onClick: () => handleSubmit(value), className: 'cio-pia-send-button', disabled: disabled },
|
|
57
58
|
translate('Send', translations),
|
|
58
59
|
React.createElement(SendIcon, null)))),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { RenderPropsWrapper, } from '@constructor-io/constructorio-ui-components';
|
|
3
|
-
|
|
3
|
+
// Decorative: loading is announced by the always-mounted status region.
|
|
4
|
+
const skeleton = (React.createElement("div", { className: 'cio-pia-loading-skeleton', "data-testid": 'loading-skeleton', "aria-hidden": 'true' },
|
|
4
5
|
React.createElement("div", { className: 'skeleton-bar' }),
|
|
5
6
|
React.createElement("div", { className: 'skeleton-bar' }),
|
|
6
7
|
React.createElement("div", { className: 'skeleton-bar skeleton-short' })));
|
|
@@ -4,13 +4,17 @@ import { translate } from '../../utils/translate';
|
|
|
4
4
|
import SuggestedQuestionsContainer from '../SuggestedQuestionsContainer/SuggestedQuestionsContainer';
|
|
5
5
|
const OVERFLOW_HIDDEN_CLASS = 'cio-pia-modal-open';
|
|
6
6
|
function CloseIcon() {
|
|
7
|
-
return (React.createElement("svg", { width: '20', height: '20', viewBox: '0 0 20 20', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
|
|
7
|
+
return (React.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' },
|
|
8
8
|
React.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' })));
|
|
9
9
|
}
|
|
10
10
|
export default function PiaModal({ initialQuestions, handleSubmitQuestion, handleQuestionClick, containerRef, isLoading, componentOverrides, translations, onInputFocus, onClose, children, }) {
|
|
11
11
|
const [isOpen, setIsOpen] = useState(false);
|
|
12
12
|
const dialogRef = useRef(null);
|
|
13
|
+
const triggerRef = useRef(null);
|
|
13
14
|
const openModal = useCallback(() => {
|
|
15
|
+
const active = document.activeElement;
|
|
16
|
+
const isRealTrigger = !!active && active !== document.body && active !== document.documentElement;
|
|
17
|
+
triggerRef.current = isRealTrigger ? active : null;
|
|
14
18
|
setIsOpen(true);
|
|
15
19
|
}, []);
|
|
16
20
|
const closeModal = useCallback(() => {
|
|
@@ -25,14 +29,28 @@ export default function PiaModal({ initialQuestions, handleSubmitQuestion, handl
|
|
|
25
29
|
dialog.showModal();
|
|
26
30
|
document.body.classList.add(OVERFLOW_HIDDEN_CLASS);
|
|
27
31
|
}
|
|
28
|
-
else
|
|
29
|
-
dialog.
|
|
32
|
+
else {
|
|
33
|
+
if (dialog.open)
|
|
34
|
+
dialog.close();
|
|
30
35
|
document.body.classList.remove(OVERFLOW_HIDDEN_CLASS);
|
|
31
36
|
}
|
|
32
37
|
return () => {
|
|
33
38
|
document.body.classList.remove(OVERFLOW_HIDDEN_CLASS);
|
|
34
39
|
};
|
|
35
40
|
}, [isOpen]);
|
|
41
|
+
// Restore focus to the trigger. Re-runs on `isLoading`: the input trigger is
|
|
42
|
+
// disabled while a request is in flight and `focus()` would no-op.
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (isOpen)
|
|
45
|
+
return;
|
|
46
|
+
const trigger = triggerRef.current;
|
|
47
|
+
if (!trigger || !trigger.isConnected)
|
|
48
|
+
return;
|
|
49
|
+
if (trigger.disabled)
|
|
50
|
+
return;
|
|
51
|
+
trigger.focus();
|
|
52
|
+
triggerRef.current = null;
|
|
53
|
+
}, [isOpen, isLoading]);
|
|
36
54
|
useEffect(() => {
|
|
37
55
|
const dialog = dialogRef.current;
|
|
38
56
|
if (!dialog)
|
|
@@ -69,7 +87,7 @@ export default function PiaModal({ initialQuestions, handleSubmitQuestion, handl
|
|
|
69
87
|
React.createElement("div", { className: 'cio-pia-modal-content' },
|
|
70
88
|
React.createElement("div", { className: 'cio-pia-modal-header' },
|
|
71
89
|
React.createElement("p", { className: 'cio-pia-title', id: 'cio-pia-modal-title' }, translate('Ask about this product', translations)),
|
|
72
|
-
React.createElement("button", { type: 'button', className: 'cio-pia-modal-close-button', "aria-label": 'Close', onClick: closeModal },
|
|
90
|
+
React.createElement("button", { type: 'button', className: 'cio-pia-modal-close-button', "aria-label": translate('Close', translations), onClick: closeModal },
|
|
73
91
|
React.createElement(CloseIcon, null))),
|
|
74
92
|
React.createElement("div", { className: 'cio-pia-modal-body' }, children)))));
|
|
75
93
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { translate } from '../../utils/translate';
|
|
3
|
+
// Inline so it stays hidden for consumers who do not load the stylesheet.
|
|
4
|
+
const SR_ONLY_STYLE = {
|
|
5
|
+
position: 'absolute',
|
|
6
|
+
width: 1,
|
|
7
|
+
height: 1,
|
|
8
|
+
padding: 0,
|
|
9
|
+
margin: -1,
|
|
10
|
+
overflow: 'hidden',
|
|
11
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
12
|
+
whiteSpace: 'nowrap',
|
|
13
|
+
border: 0,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Visually hidden `role='status'`. Keep it mounted and change only `message`:
|
|
17
|
+
* a live region created together with its text is announced inconsistently.
|
|
18
|
+
*/
|
|
19
|
+
export default function StatusRegion({ message, 'data-testid': testId }) {
|
|
20
|
+
return (React.createElement("div", { className: 'cio-pia-sr-only', style: SR_ONLY_STYLE, role: 'status', "data-testid": testId }, message));
|
|
21
|
+
}
|
|
22
|
+
export function answerStatusMessage(isLoading, hasAnswer, translations) {
|
|
23
|
+
if (isLoading)
|
|
24
|
+
return translate('Loading answer', translations);
|
|
25
|
+
if (hasAnswer)
|
|
26
|
+
return translate('Answer ready', translations);
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export default function SuggestedQuestionsSkeleton() {
|
|
3
|
-
return (React.createElement("div", { className: 'cio-pia-suggested-questions-container', "data-testid": 'suggested-questions-skeleton', "aria-
|
|
3
|
+
return (React.createElement("div", { className: 'cio-pia-suggested-questions-container', "data-testid": 'suggested-questions-skeleton', "aria-hidden": 'true' },
|
|
4
4
|
React.createElement("div", { className: 'cio-pia-suggested-question-skeleton' }),
|
|
5
5
|
React.createElement("div", { className: 'cio-pia-suggested-question-skeleton' }),
|
|
6
6
|
React.createElement("div", { className: 'cio-pia-suggested-question-skeleton' })));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export default function QuestionIcon() {
|
|
3
|
-
return (React.createElement("svg", { "aria-hidden": 'true', width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
|
|
3
|
+
return (React.createElement("svg", { "aria-hidden": 'true', focusable: 'false', width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
|
|
4
4
|
React.createElement("path", { d: 'M8.00004 1.33331C4.32004 1.33331 1.33337 4.31998 1.33337 7.99998C1.33337 11.68 4.32004 14.6666 8.00004 14.6666C11.68 14.6666 14.6667 11.68 14.6667 7.99998C14.6667 4.31998 11.68 1.33331 8.00004 1.33331ZM8.66671 12.6666H7.33337V11.3333H8.66671V12.6666ZM10.0467 7.49998L9.44671 8.11331C8.96671 8.59998 8.66671 8.99998 8.66671 9.99998H7.33337V9.66665C7.33337 8.93331 7.63337 8.26665 8.11337 7.77998L8.94004 6.93998C9.18671 6.69998 9.33337 6.36665 9.33337 5.99998C9.33337 5.26665 8.73337 4.66665 8.00004 4.66665C7.26671 4.66665 6.66671 5.26665 6.66671 5.99998H5.33337C5.33337 4.52665 6.52671 3.33331 8.00004 3.33331C9.47337 3.33331 10.6667 4.52665 10.6667 5.99998C10.6667 6.58665 10.4267 7.11998 10.0467 7.49998Z', fill: '#0F1324', fillOpacity: '0.6' })));
|
|
5
5
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export default function SendIcon() {
|
|
3
|
-
return (React.createElement("svg", { "aria-hidden": 'true', width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
|
|
3
|
+
return (React.createElement("svg", { "aria-hidden": 'true', focusable: 'false', width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
|
|
4
4
|
React.createElement("path", { d: 'M14.8934 7.09533L14.8884 7.09314L1.54219 1.55752C1.42993 1.51053 1.30778 1.49211 1.18666 1.50388C1.06554 1.51566 0.949225 1.55728 0.848125 1.62502C0.741311 1.69501 0.653573 1.79045 0.592805 1.90277C0.532037 2.01509 0.500145 2.14075 0.5 2.26845V5.80877C0.50006 5.98335 0.561019 6.15243 0.67237 6.28689C0.783722 6.42134 0.938491 6.51274 1.11 6.54533L8.38906 7.89127C8.41767 7.89669 8.44348 7.91193 8.46205 7.93435C8.48062 7.95677 8.49078 7.98497 8.49078 8.01408C8.49078 8.04319 8.48062 8.07139 8.46205 8.09381C8.44348 8.11623 8.41767 8.13147 8.38906 8.13689L1.11031 9.48283C0.938851 9.51533 0.784092 9.60661 0.67269 9.74094C0.561288 9.87528 0.500219 10.0443 0.5 10.2188V13.7597C0.499917 13.8816 0.530111 14.0017 0.587871 14.1091C0.645632 14.2165 0.729152 14.3079 0.830938 14.375C0.953375 14.4564 1.09706 14.4998 1.24406 14.5C1.34626 14.4999 1.4474 14.4794 1.54156 14.4397L14.8875 8.93564L14.8934 8.93283C15.0731 8.85563 15.2262 8.72745 15.3337 8.56415C15.4413 8.40086 15.4986 8.20961 15.4986 8.01408C15.4986 7.81855 15.4413 7.6273 15.3337 7.46401C15.2262 7.30071 15.0731 7.17253 14.8934 7.09533Z', fill: 'white' })));
|
|
5
5
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export default function SparklesIcon() {
|
|
3
|
-
return (React.createElement("svg", { "aria-hidden": 'true', width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
|
|
3
|
+
return (React.createElement("svg", { "aria-hidden": 'true', focusable: 'false', width: '16', height: '16', viewBox: '0 0 16 16', fill: 'none', xmlns: 'http://www.w3.org/2000/svg' },
|
|
4
4
|
React.createElement("path", { d: 'M12.6667 6.0013L13.5001 4.16797L15.3334 3.33464L13.5001 2.5013L12.6667 0.667969L11.8334 2.5013L10.0001 3.33464L11.8334 4.16797L12.6667 6.0013Z', fill: '#1C1917' }),
|
|
5
5
|
React.createElement("path", { d: 'M12.6667 10.0013L11.8334 11.8346L10.0001 12.668L11.8334 13.5013L12.6667 15.3346L13.5001 13.5013L15.3334 12.668L13.5001 11.8346L12.6667 10.0013Z', fill: '#1C1917' }),
|
|
6
6
|
React.createElement("path", { d: 'M7.66675 6.33464L6.00008 2.66797L4.33341 6.33464L0.666748 8.0013L4.33341 9.66797L6.00008 13.3346L7.66675 9.66797L11.3334 8.0013L7.66675 6.33464ZM6.66008 8.6613L6.00008 10.1146L5.34008 8.6613L3.88675 8.0013L5.34008 7.3413L6.00008 5.88797L6.66008 7.3413L8.11341 8.0013L6.66008 8.6613Z', fill: '#1C1917' })));
|
|
@@ -7,7 +7,7 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
7
7
|
const [displayedQuestions, setDisplayedQuestions] = useState([]);
|
|
8
8
|
const [conversationHistory, setConversationHistory] = useState([]);
|
|
9
9
|
const entryIdRef = useRef(0);
|
|
10
|
-
const
|
|
10
|
+
const prevAnswerDataRef = useRef(answers.data);
|
|
11
11
|
const hasTrackedCurrentAnswerRef = useRef(false);
|
|
12
12
|
const answersRef = useRef(answers);
|
|
13
13
|
const callbacksRef = useRef(callbacks);
|
|
@@ -61,13 +61,13 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
61
61
|
setCurrentQuestion('');
|
|
62
62
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
63
63
|
setConversationHistory([]);
|
|
64
|
-
|
|
64
|
+
prevAnswerDataRef.current = null;
|
|
65
65
|
}, [suggestedQuestions.data]);
|
|
66
66
|
useEffect(() => {
|
|
67
67
|
setCurrentQuestion('');
|
|
68
68
|
setDisplayedQuestions([]);
|
|
69
69
|
setConversationHistory([]);
|
|
70
|
-
|
|
70
|
+
prevAnswerDataRef.current = null;
|
|
71
71
|
}, [itemId]);
|
|
72
72
|
useEffect(() => {
|
|
73
73
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
@@ -84,9 +84,11 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
84
84
|
const answerValue = answers.data?.value ?? '';
|
|
85
85
|
if (!answerValue)
|
|
86
86
|
return;
|
|
87
|
-
|
|
87
|
+
// Compare by response object, not by text: two consecutive answers with
|
|
88
|
+
// identical text are still two answers and both must reach the history.
|
|
89
|
+
if (answers.data === prevAnswerDataRef.current)
|
|
88
90
|
return;
|
|
89
|
-
|
|
91
|
+
prevAnswerDataRef.current = answers.data;
|
|
90
92
|
const answerThreadId = answers.data?.thread_id;
|
|
91
93
|
const qnaResultId = answers.data?.qna_result_id;
|
|
92
94
|
if (isConversation) {
|
|
@@ -2,12 +2,21 @@ import { DISCLAIMER_TEXT, RECS_FALLBACK_TITLE, RECS_INPUT_PLACEHOLDER, RECS_LOAD
|
|
|
2
2
|
const defaultTranslations = {
|
|
3
3
|
'Any questions about this product?': 'Any questions about this product?',
|
|
4
4
|
'Ask anything': 'Ask anything',
|
|
5
|
+
'Your question': 'Your question',
|
|
5
6
|
Send: 'Send',
|
|
6
7
|
[DISCLAIMER_TEXT]: DISCLAIMER_TEXT,
|
|
7
8
|
'Is this answer useful?': 'Is this answer useful?',
|
|
8
9
|
'Learn More.': 'Learn More.',
|
|
9
10
|
'Ask about this product': 'Ask about this product',
|
|
10
11
|
'Add to Cart': 'Add to Cart',
|
|
12
|
+
'Unexpected error': 'Unexpected error',
|
|
13
|
+
'thumbs up': 'thumbs up',
|
|
14
|
+
'thumbs down': 'thumbs down',
|
|
15
|
+
Close: 'Close',
|
|
16
|
+
Retry: 'Retry',
|
|
17
|
+
'Conversation history': 'Conversation history',
|
|
18
|
+
'Loading answer': 'Loading answer',
|
|
19
|
+
'Answer ready': 'Answer ready',
|
|
11
20
|
[RECS_LOADING_TITLE]: RECS_LOADING_TITLE,
|
|
12
21
|
[RECS_FALLBACK_TITLE]: RECS_FALLBACK_TITLE,
|
|
13
22
|
[RECS_UNSUPPORTED_REQUEST]: RECS_UNSUPPORTED_REQUEST,
|
|
@@ -18,6 +27,9 @@ const defaultTranslations = {
|
|
|
18
27
|
* Translates a word using the provided translations object.
|
|
19
28
|
* Falls back to English defaults if translation is not provided.
|
|
20
29
|
*
|
|
30
|
+
* An explicitly empty string is returned as-is: blanking out a string is how
|
|
31
|
+
* consumers hide it.
|
|
32
|
+
*
|
|
21
33
|
* @param word - The key to translate
|
|
22
34
|
* @param translations - Optional user-provided translations object
|
|
23
35
|
* @returns The translated string or the original word if no translation exists
|
package/lib/mjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '2.
|
|
1
|
+
export default '2.13.0';
|
|
@@ -3,6 +3,12 @@ import { ConversationEntry, FeedbackType, Translations, Item, Callbacks, CioPiaC
|
|
|
3
3
|
export interface ConversationHistoryProps {
|
|
4
4
|
conversationHistory: ConversationEntry[];
|
|
5
5
|
isLoading: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Whether the answer request itself is in flight. `isLoading` also covers the follow-up
|
|
8
|
+
* questions request, which is fine for the visible skeleton but would make the status region
|
|
9
|
+
* announce "Loading answer" while only the questions are loading. Defaults to `isLoading`.
|
|
10
|
+
*/
|
|
11
|
+
isAnswerLoading?: boolean;
|
|
6
12
|
error: Error | null;
|
|
7
13
|
/**
|
|
8
14
|
* Items for the latest conversation entry's carousel.
|
|
@@ -27,4 +33,4 @@ export interface ConversationHistoryProps {
|
|
|
27
33
|
qnaResultId?: string;
|
|
28
34
|
priceCurrency?: string;
|
|
29
35
|
}
|
|
30
|
-
export default function ConversationHistory({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, disclaimerPosition, translations, callbacks, componentOverrides, handleFeedback, onResultClick, qnaResultId, priceCurrency, }: ConversationHistoryProps): React.JSX.Element;
|
|
36
|
+
export default function ConversationHistory({ conversationHistory, isLoading, isAnswerLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, disclaimerPosition, translations, callbacks, componentOverrides, handleFeedback, onResultClick, qnaResultId, priceCurrency, }: ConversationHistoryProps): React.JSX.Element;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { Translations } from '../../types';
|
|
2
3
|
interface ErrorBlockProps {
|
|
3
4
|
message: string;
|
|
4
5
|
onRetry?: () => void;
|
|
6
|
+
translations?: Translations;
|
|
7
|
+
/**
|
|
8
|
+
* Whether the message is a `role='alert'` live region. Turn off when the block is rendered
|
|
9
|
+
* inside another live region (such as the conversation `role='log'`), which already announces
|
|
10
|
+
* inserted text: nesting the two would announce the error twice. Defaults to true.
|
|
11
|
+
*/
|
|
12
|
+
announce?: boolean;
|
|
5
13
|
}
|
|
6
|
-
declare function ErrorBlock({ message, onRetry }: ErrorBlockProps): React.JSX.Element;
|
|
14
|
+
declare function ErrorBlock({ message, onRetry, translations, announce, }: ErrorBlockProps): React.JSX.Element;
|
|
7
15
|
export default ErrorBlock;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Translations } from '../../types';
|
|
3
|
+
interface StatusRegionProps {
|
|
4
|
+
message: string;
|
|
5
|
+
'data-testid'?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Visually hidden `role='status'`. Keep it mounted and change only `message`:
|
|
9
|
+
* a live region created together with its text is announced inconsistently.
|
|
10
|
+
*/
|
|
11
|
+
export default function StatusRegion({ message, 'data-testid': testId }: StatusRegionProps): React.JSX.Element;
|
|
12
|
+
export declare function answerStatusMessage(isLoading: boolean, hasAnswer: boolean, translations?: Translations): string;
|
|
13
|
+
export {};
|
package/lib/types/types.d.ts
CHANGED
|
@@ -60,12 +60,22 @@ export type CioPiaDisplayConfigs = {
|
|
|
60
60
|
export type Translations = {
|
|
61
61
|
'Any questions about this product?'?: string;
|
|
62
62
|
'Ask anything'?: string;
|
|
63
|
+
/** Accessible name of the question field when its placeholder is blanked out. */
|
|
64
|
+
'Your question'?: string;
|
|
63
65
|
Send?: string;
|
|
64
66
|
'AI-generated answers aim to help, but they may occasionally miss details or be inaccurate. Double-check important information before purchasing.'?: string;
|
|
65
67
|
'Is this answer useful?'?: string;
|
|
66
68
|
'Learn More.'?: string;
|
|
67
69
|
'Ask about this product'?: string;
|
|
68
70
|
'Add to Cart'?: string;
|
|
71
|
+
'Unexpected error'?: string;
|
|
72
|
+
'thumbs up'?: string;
|
|
73
|
+
'thumbs down'?: string;
|
|
74
|
+
Close?: string;
|
|
75
|
+
Retry?: string;
|
|
76
|
+
'Conversation history'?: string;
|
|
77
|
+
'Loading answer'?: string;
|
|
78
|
+
'Answer ready'?: string;
|
|
69
79
|
/** Recommendations pod title shown while a request is in flight. */
|
|
70
80
|
'Adapting recommendations to your preference'?: string;
|
|
71
81
|
/** Recommendations pod title shown when a request fails or comes back degraded. */
|
|
@@ -3,6 +3,9 @@ import { Translations } from '../types';
|
|
|
3
3
|
* Translates a word using the provided translations object.
|
|
4
4
|
* Falls back to English defaults if translation is not provided.
|
|
5
5
|
*
|
|
6
|
+
* An explicitly empty string is returned as-is: blanking out a string is how
|
|
7
|
+
* consumers hide it.
|
|
8
|
+
*
|
|
6
9
|
* @param word - The key to translate
|
|
7
10
|
* @param translations - Optional user-provided translations object
|
|
8
11
|
* @returns The translated string or the original word if no translation exists
|
package/lib/types/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "2.
|
|
1
|
+
declare const _default: "2.13.0";
|
|
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.13.0",
|
|
4
4
|
"description": "Constructor.io AI Product Insights Agent UI library for web applications",
|
|
5
5
|
"author": "Constructor.io Corporation",
|
|
6
6
|
"license": "MIT",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"tslib": "^2.4.0"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@axe-core/react": "^4.10.1",
|
|
81
80
|
"@babel/core": "^7.24.0",
|
|
82
81
|
"@babel/preset-env": "^7.24.0",
|
|
83
82
|
"@babel/preset-react": "^7.23.3",
|
|
@@ -104,6 +103,7 @@
|
|
|
104
103
|
"@types/jest": "^30.0.0",
|
|
105
104
|
"@types/react": "^19.0.10",
|
|
106
105
|
"@types/react-dom": "^19.0.4",
|
|
106
|
+
"axe-playwright": "^2.2.2",
|
|
107
107
|
"chromatic": "^11.27.0",
|
|
108
108
|
"eslint": "^9.39.4",
|
|
109
109
|
"eslint-config-prettier": "^10.1.1",
|