@constructor-io/constructorio-ui-pia 2.11.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.
Files changed (69) hide show
  1. package/dist/constructorio-ui-pia-bundled.js +62 -62
  2. package/lib/cjs/components/CioPia/CioPiaQna.js +10 -4
  3. package/lib/cjs/components/CioPia/CioPiaStyles.css +5 -0
  4. package/lib/cjs/components/CioPia/PiaCustomCarousel.js +34 -29
  5. package/lib/cjs/components/ConversationHistory/ConversationHistory.js +10 -5
  6. package/lib/cjs/components/Error/ErrorBlock.js +7 -5
  7. package/lib/cjs/components/Feedback/Feedback.js +4 -4
  8. package/lib/cjs/components/Input/Input.js +2 -1
  9. package/lib/cjs/components/LoadingSkeleton/LoadingSkeleton.js +2 -1
  10. package/lib/cjs/components/PiaConversation/PiaModal.js +22 -4
  11. package/lib/cjs/components/StatusRegion/StatusRegion.js +34 -0
  12. package/lib/cjs/components/SuggestedQuestionsContainer/SuggestedQuestionsSkeleton.js +1 -1
  13. package/lib/cjs/components/icons/QuestionIcon.js +1 -1
  14. package/lib/cjs/components/icons/SendIcon.js +1 -1
  15. package/lib/cjs/components/icons/SparklesIcon.js +1 -1
  16. package/lib/cjs/hooks/useAnswerResults.js +4 -14
  17. package/lib/cjs/hooks/useCioPia.js +42 -4
  18. package/lib/cjs/hooks/useConversation.js +10 -8
  19. package/lib/cjs/hooks/usePiaClient.js +2 -7
  20. package/lib/cjs/hooks/useRecsPod.js +10 -5
  21. package/lib/cjs/hooks/useSuggestedQuestions.js +10 -26
  22. package/lib/cjs/types.js +0 -2
  23. package/lib/cjs/utils/translate.js +12 -0
  24. package/lib/cjs/version.js +1 -1
  25. package/lib/mjs/components/CioPia/CioPiaQna.js +10 -4
  26. package/lib/mjs/components/CioPia/CioPiaStyles.css +5 -0
  27. package/lib/mjs/components/CioPia/PiaCustomCarousel.js +53 -30
  28. package/lib/mjs/components/ConversationHistory/ConversationHistory.js +10 -5
  29. package/lib/mjs/components/Error/ErrorBlock.js +7 -5
  30. package/lib/mjs/components/Feedback/Feedback.js +4 -4
  31. package/lib/mjs/components/Input/Input.js +2 -1
  32. package/lib/mjs/components/LoadingSkeleton/LoadingSkeleton.js +2 -1
  33. package/lib/mjs/components/PiaConversation/PiaModal.js +22 -4
  34. package/lib/mjs/components/StatusRegion/StatusRegion.js +28 -0
  35. package/lib/mjs/components/SuggestedQuestionsContainer/SuggestedQuestionsSkeleton.js +1 -1
  36. package/lib/mjs/components/icons/QuestionIcon.js +1 -1
  37. package/lib/mjs/components/icons/SendIcon.js +1 -1
  38. package/lib/mjs/components/icons/SparklesIcon.js +1 -1
  39. package/lib/mjs/hooks/useAnswerResults.js +4 -13
  40. package/lib/mjs/hooks/useCioPia.js +43 -4
  41. package/lib/mjs/hooks/useConversation.js +7 -5
  42. package/lib/mjs/hooks/usePiaClient.js +2 -7
  43. package/lib/mjs/hooks/useRecsPod.js +9 -5
  44. package/lib/mjs/hooks/useSuggestedQuestions.js +12 -24
  45. package/lib/mjs/types.js +0 -1
  46. package/lib/mjs/utils/translate.js +12 -0
  47. package/lib/mjs/version.js +1 -1
  48. package/lib/types/components/CioPia/types.d.ts +20 -5
  49. package/lib/types/components/ConversationHistory/ConversationHistory.d.ts +7 -1
  50. package/lib/types/components/Error/ErrorBlock.d.ts +9 -1
  51. package/lib/types/components/StatusRegion/StatusRegion.d.ts +13 -0
  52. package/lib/types/hooks/useAnswerResults.d.ts +4 -4
  53. package/lib/types/hooks/useCioPia.d.ts +20 -5
  54. package/lib/types/hooks/usePiaClient.d.ts +4 -9
  55. package/lib/types/hooks/useRecsPod.d.ts +2 -2
  56. package/lib/types/hooks/useSuggestedQuestions.d.ts +3 -4
  57. package/lib/types/types.d.ts +53 -6
  58. package/lib/types/utils/translate.d.ts +3 -0
  59. package/lib/types/version.d.ts +1 -1
  60. package/package.json +3 -3
  61. package/lib/cjs/hooks/mocks/MockConstructorIOClient.js +0 -24
  62. package/lib/cjs/hooks/mocks/agent.js +0 -183
  63. package/lib/cjs/hooks/mocks/types.js +0 -2
  64. package/lib/mjs/hooks/mocks/MockConstructorIOClient.js +0 -38
  65. package/lib/mjs/hooks/mocks/agent.js +0 -173
  66. package/lib/mjs/hooks/mocks/types.js +0 -1
  67. package/lib/types/hooks/mocks/MockConstructorIOClient.d.ts +0 -14
  68. package/lib/types/hooks/mocks/agent.d.ts +0 -23
  69. package/lib/types/hooks/mocks/types.d.ts +0 -77
@@ -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-busy": 'true', "aria-label": 'Loading suggestions' },
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' })));
@@ -1,15 +1,5 @@
1
1
  import { useCallback, useMemo, useState } from 'react';
2
2
  import { extractAndTransformItems } from '../utils/transformers';
3
- const fetchAnswerResults = async ({ client, itemId, question, variationId, threadId, parameters, }) => {
4
- const response = await client.agent.getAnswerResults({
5
- itemId,
6
- variationId,
7
- threadId,
8
- question,
9
- parameters,
10
- });
11
- return response;
12
- };
13
3
  export default function useAnswerResults({ itemId, variationId, threadId, cioClient, parameters, formatImageUrl, }) {
14
4
  const [answerResults, setAnswerResults] = useState(null);
15
5
  const [isLoading, setIsLoading] = useState(false);
@@ -21,9 +11,10 @@ export default function useAnswerResults({ itemId, variationId, threadId, cioCli
21
11
  setIsLoading(true);
22
12
  setError(null);
23
13
  setAnswerResults(null);
24
- fetchAnswerResults({ client: cioClient, itemId, question, variationId, threadId, parameters })
25
- .then((fetchedAnswerResults) => {
26
- setAnswerResults(fetchedAnswerResults);
14
+ cioClient.agent.pia
15
+ .getAnswerResults(itemId, question, { threadId, variationId, ...parameters })
16
+ .then((response) => {
17
+ setAnswerResults(response);
27
18
  setError(null);
28
19
  })
29
20
  .catch((err) => {
@@ -1,27 +1,66 @@
1
1
  import useAnswerResults from './useAnswerResults';
2
2
  import usePiaClient from './usePiaClient';
3
3
  import useSuggestedQuestions from './useSuggestedQuestions';
4
+ // TODO: remove SUPPORTED_DEPRECATED_PARAMS and mapDeprecatedParameters once the
5
+ // deprecated `parameters` prop is removed from CioPiaProps.
6
+ const SUPPORTED_DEPRECATED_PARAMS = {
7
+ guard: { camelKey: 'guard', isObject: false },
8
+ num_results: { camelKey: 'numResults', isObject: false },
9
+ numResults: { camelKey: 'numResults', isObject: false },
10
+ pre_filter_expression: { camelKey: 'preFilterExpression', isObject: true },
11
+ preFilterExpression: { camelKey: 'preFilterExpression', isObject: true },
12
+ fmt_options: { camelKey: 'fmtOptions', isObject: true },
13
+ fmtOptions: { camelKey: 'fmtOptions', isObject: true },
14
+ };
15
+ function mapDeprecatedParameters(params) {
16
+ if (!params)
17
+ return undefined;
18
+ const mapped = {};
19
+ let hasEntries = false;
20
+ for (const [key, value] of Object.entries(params)) {
21
+ const config = SUPPORTED_DEPRECATED_PARAMS[key];
22
+ if (!config)
23
+ continue;
24
+ hasEntries = true;
25
+ if (config.isObject && typeof value === 'string') {
26
+ try {
27
+ mapped[config.camelKey] = JSON.parse(value);
28
+ }
29
+ catch {
30
+ mapped[config.camelKey] = value;
31
+ }
32
+ }
33
+ else {
34
+ mapped[config.camelKey] = value;
35
+ }
36
+ }
37
+ return hasEntries ? mapped : undefined;
38
+ }
4
39
  export default function useCioPia(props) {
5
- const { apiKey, itemId, variationId, threadId: providedThreadId, cioClient: providedClient, suggestedQuestionsParameters, parameters, formatImageUrl, } = props;
40
+ const { apiKey, itemId, variationId, threadId: providedThreadId, cioClient: providedClient, suggestedQuestionsParameters, answerParameters, parameters, formatImageUrl, } = props;
6
41
  const { cioClient: client, threadId } = usePiaClient({
7
42
  apiKey,
8
43
  threadId: providedThreadId,
9
44
  cioClient: providedClient,
10
45
  });
46
+ const mappedDeprecated = mapDeprecatedParameters(parameters);
47
+ const mergedSuggestedParams = mappedDeprecated || suggestedQuestionsParameters
48
+ ? { ...mappedDeprecated, ...suggestedQuestionsParameters }
49
+ : undefined;
50
+ const mergedAnswerParams = mappedDeprecated || answerParameters ? { ...mappedDeprecated, ...answerParameters } : undefined;
11
51
  const suggestedQuestions = useSuggestedQuestions({
12
52
  itemId,
13
53
  variationId,
14
54
  threadId,
15
55
  cioClient: client,
16
- parameters: suggestedQuestionsParameters,
17
- requestParameters: parameters,
56
+ parameters: mergedSuggestedParams,
18
57
  });
19
58
  const answers = useAnswerResults({
20
59
  itemId,
21
60
  variationId,
22
61
  threadId,
23
62
  cioClient: client,
24
- parameters,
63
+ parameters: mergedAnswerParams,
25
64
  formatImageUrl,
26
65
  });
27
66
  return {
@@ -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 prevAnswerValueRef = useRef(answers.data?.value);
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
- prevAnswerValueRef.current = undefined;
64
+ prevAnswerDataRef.current = null;
65
65
  }, [suggestedQuestions.data]);
66
66
  useEffect(() => {
67
67
  setCurrentQuestion('');
68
68
  setDisplayedQuestions([]);
69
69
  setConversationHistory([]);
70
- prevAnswerValueRef.current = undefined;
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
- if (answerValue === prevAnswerValueRef.current)
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
- prevAnswerValueRef.current = answerValue;
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) {
@@ -1,18 +1,13 @@
1
1
  import { useMemo, useState } from 'react';
2
- import MockConstructorIOClient from './mocks/MockConstructorIOClient';
2
+ import ConstructorIOClient from '@constructor-io/constructorio-client-javascript';
3
3
  import version from '../version';
4
- /**
5
- * Resolves the client and thread ID every mode needs: the caller's client when they supplied
6
- * one, otherwise a client built from the API key, plus a generated thread ID that stays stable
7
- * for the life of the component.
8
- */
9
4
  export default function usePiaClient({ apiKey, threadId: providedThreadId, cioClient: providedClient, }) {
10
5
  const [generatedThreadId] = useState(() => crypto.randomUUID());
11
6
  const threadId = providedThreadId || generatedThreadId;
12
7
  const client = useMemo(() => {
13
8
  if (providedClient)
14
9
  return providedClient;
15
- return new MockConstructorIOClient({
10
+ return new ConstructorIOClient({
16
11
  apiKey,
17
12
  sendTrackingEvents: true,
18
13
  version: `cio-ui-pia-${version}`,
@@ -30,9 +30,13 @@ export default function useRecsPod({ itemId, variationId, threadId, cioClient, p
30
30
  const strategy = parameters?.strategy || DEFAULT_STRATEGY;
31
31
  const { numResults, defaultTitle } = parameters || {};
32
32
  const fetchResult = useCallback((shopperInput, source) => {
33
- // Nothing to ask, so settle the loading state rather than leaving the caller in a skeleton
34
- // that never resolves.
35
- if (!cioClient) {
33
+ // TODO: once getRecs is added to the JS client SDK, replace this guard+cast
34
+ // with a direct call: cioClient.agent.getRecs({...})
35
+ const getRecs = cioClient?.agent?.getRecs;
36
+ if (!cioClient || !getRecs) {
37
+ if (cioClient && !getRecs) {
38
+ console.info('[CioPia] getRecs is not available on the client SDK yet.');
39
+ }
36
40
  setIsLoading(false);
37
41
  return;
38
42
  }
@@ -42,8 +46,8 @@ export default function useRecsPod({ itemId, variationId, threadId, cioClient, p
42
46
  setIsLoading(true);
43
47
  setError(null);
44
48
  setHasUnsupportedInput(false);
45
- cioClient.agent
46
- .getRecs({
49
+ getRecs
50
+ .call(cioClient.agent, {
47
51
  itemId,
48
52
  variationId,
49
53
  threadId,
@@ -1,35 +1,22 @@
1
- import { useCallback, useEffect, useState } from 'react';
2
- const fetchSuggestedQuestions = async ({ client, itemId, variationId, threadId, parameters, requestParameters, }) => {
3
- const response = await client.agent.getSuggestedQuestions({
4
- itemId,
5
- variationId,
6
- threadId,
7
- parameters,
8
- requestParameters,
9
- });
10
- return response.questions;
11
- };
12
- export default function useSuggestedQuestions({ itemId, variationId, threadId, cioClient, parameters, requestParameters, }) {
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ export default function useSuggestedQuestions({ itemId, variationId, threadId, cioClient, parameters, }) {
13
3
  const [questions, setQuestions] = useState([]);
14
4
  const [isLoading, setIsLoading] = useState(false);
15
5
  const [error, setError] = useState(null);
16
- // Serialize to a primitive so an object-literal prop doesn't retrigger the auto-fetch effect each render.
17
- const requestParametersKey = JSON.stringify(requestParameters);
6
+ const serializedParameters = useMemo(() => JSON.stringify(parameters), [parameters]);
18
7
  const fetchResult = useCallback(() => {
19
8
  if (!cioClient)
20
9
  return;
21
10
  setIsLoading(true);
22
11
  setError(null);
23
- fetchSuggestedQuestions({
24
- client: cioClient,
25
- itemId,
26
- variationId,
12
+ cioClient.agent.pia
13
+ .getSuggestedQuestions(itemId, {
27
14
  threadId,
28
- parameters,
29
- requestParameters,
15
+ variationId,
16
+ ...parameters,
30
17
  })
31
- .then((fetchedQuestions) => {
32
- setQuestions(fetchedQuestions);
18
+ .then((response) => {
19
+ setQuestions(response.questions);
33
20
  setError(null);
34
21
  })
35
22
  .catch((err) => {
@@ -38,8 +25,9 @@ export default function useSuggestedQuestions({ itemId, variationId, threadId, c
38
25
  .finally(() => {
39
26
  setIsLoading(false);
40
27
  });
41
- // eslint-disable-next-line react-hooks/exhaustive-deps -- primitive deps prevent refetch loop
42
- }, [cioClient, itemId, variationId, threadId, parameters?.numResults, requestParametersKey]);
28
+ // parameters is serialized via serializedParameters to prevent refetch on identity changes
29
+ // eslint-disable-next-line react-hooks/exhaustive-deps
30
+ }, [cioClient, itemId, variationId, threadId, serializedParameters]);
43
31
  useEffect(() => {
44
32
  fetchResult();
45
33
  }, [fetchResult]);
package/lib/mjs/types.js CHANGED
@@ -3,4 +3,3 @@ export var FeedbackType;
3
3
  FeedbackType["UP"] = "up";
4
4
  FeedbackType["DOWN"] = "down";
5
5
  })(FeedbackType || (FeedbackType = {}));
6
- export * from './hooks/mocks/types';
@@ -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
@@ -1 +1 @@
1
- export default '2.11.0';
1
+ export default '2.13.0';
@@ -1,6 +1,6 @@
1
1
  import { IncludeComponentOverrides, IncludeRenderProps } from '@constructor-io/constructorio-ui-components';
2
- import MockConstructorIOClient from '../../hooks/mocks/MockConstructorIOClient';
3
- import { CioPiaRenderProps, CioPiaComponentOverrides, Callbacks, CioPiaDisplayConfigs, CioPiaTrackingConfigs, Translations, SuggestedQuestionsParameters, RecsPodParameters, Formatters, ProductCardDisplayProps } from '../../types';
2
+ import { CioPiaRenderProps, CioPiaComponentOverrides, Callbacks, CioPiaDisplayConfigs, CioPiaTrackingConfigs, Translations, SuggestedQuestionsParameters, AnswerRequestParameters, RecsPodParameters, Formatters, ProductCardDisplayProps } from '../../types';
3
+ import type { CioClient } from '../../hooks/usePiaClient';
4
4
  export interface CioPiaProps extends IncludeRenderProps<CioPiaRenderProps>, IncludeComponentOverrides<CioPiaComponentOverrides> {
5
5
  /** Your Constructor.io API key. */
6
6
  apiKey: string;
@@ -13,7 +13,7 @@ export interface CioPiaProps extends IncludeRenderProps<CioPiaRenderProps>, Incl
13
13
  /** Optional variation ID for the product. */
14
14
  variationId?: string;
15
15
  /** Optional Constructor.io client instance. If not provided, one will be created internally. */
16
- cioClient?: MockConstructorIOClient;
16
+ cioClient?: CioClient;
17
17
  /** Display configuration options (mode, type, showFeedback, etc.). */
18
18
  displayConfigs?: CioPiaDisplayConfigs;
19
19
  /** Tracking configuration options (viewThreshold, etc.). */
@@ -30,11 +30,26 @@ export interface CioPiaProps extends IncludeRenderProps<CioPiaRenderProps>, Incl
30
30
  translations?: Translations;
31
31
  /** Parameters for the suggested questions request. */
32
32
  suggestedQuestionsParameters?: SuggestedQuestionsParameters;
33
+ /** Parameters for the answer request. */
34
+ answerParameters?: AnswerRequestParameters;
33
35
  /** Parameters for the recommendations request, used by `mode: 'recommendations'`. */
34
36
  recsPodParameters?: RecsPodParameters;
35
37
  /**
36
- * Extra query parameters appended to PIA API requests (e.g. `ef-*` test cell params).
37
- * Define outside the component or wrap with useMemo to avoid unnecessary re-renders.
38
+ * @deprecated Use `answerParameters` and `suggestedQuestionsParameters` instead.
39
+ *
40
+ * Only the following keys are forwarded (snake_case or camelCase accepted):
41
+ *
42
+ * Suggested-questions endpoint:
43
+ * - `num_results` / `numResults` → numResults
44
+ * - `pre_filter_expression` / `preFilterExpression` → preFilterExpression (JSON-parsed if string)
45
+ *
46
+ * Answer endpoint:
47
+ * - `guard` → guard
48
+ * - `pre_filter_expression` / `preFilterExpression` → preFilterExpression (JSON-parsed if string)
49
+ * - `fmt_options` / `fmtOptions` → fmtOptions (JSON-parsed if string)
50
+ *
51
+ * All other keys (e.g. `ef-*`) are silently dropped.
52
+ * Typed parameters take precedence over values specified here.
38
53
  */
39
54
  parameters?: Record<string, string | number | boolean>;
40
55
  }
@@ -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 {};
@@ -1,12 +1,12 @@
1
1
  import { Nullable } from '@constructor-io/constructorio-client-javascript';
2
- import MockConstructorIOClient from './mocks/MockConstructorIOClient';
3
- import { Formatters, Item, GetAnswerResultsResponse } from '../types';
2
+ import { AnswerRequestParameters, Formatters, Item, GetAnswerResultsResponse } from '../types';
3
+ import type { CioClient } from './usePiaClient';
4
4
  export interface UseAnswerResultsProps {
5
5
  itemId: string;
6
6
  variationId?: string;
7
7
  threadId?: string;
8
- cioClient: MockConstructorIOClient;
9
- parameters?: Record<string, string | number | boolean>;
8
+ cioClient: CioClient;
9
+ parameters?: AnswerRequestParameters;
10
10
  formatImageUrl?: Formatters['formatImageUrl'];
11
11
  }
12
12
  export interface UseAnswerResultsReturn {
@@ -1,18 +1,33 @@
1
1
  import { Tracker } from '@constructor-io/constructorio-client-javascript/lib/types/constructorio';
2
- import { Formatters, SuggestedQuestionsParameters } from '../types';
3
- import MockConstructorIOClient from './mocks/MockConstructorIOClient';
2
+ import { AnswerRequestParameters, Formatters, SuggestedQuestionsParameters } from '../types';
4
3
  import { UseAnswerResultsReturn } from './useAnswerResults';
4
+ import { CioClient } from './usePiaClient';
5
5
  import { UseSuggestedQuestionsReturn } from './useSuggestedQuestions';
6
+ export type { CioClient };
6
7
  export interface UseCioPiaProps {
7
8
  apiKey: string;
8
9
  itemId: string;
9
10
  variationId?: string;
10
11
  threadId?: string;
11
- cioClient?: MockConstructorIOClient;
12
+ cioClient?: CioClient;
12
13
  suggestedQuestionsParameters?: SuggestedQuestionsParameters;
14
+ answerParameters?: AnswerRequestParameters;
13
15
  /**
14
- * Extra query parameters appended to PIA API requests (e.g. `ef-*` test cell params).
15
- * Define outside the component or wrap with useMemo to avoid unnecessary re-renders.
16
+ * @deprecated Use `answerParameters` and `suggestedQuestionsParameters` instead.
17
+ *
18
+ * Only the following keys are forwarded (snake_case or camelCase accepted):
19
+ *
20
+ * Suggested-questions endpoint:
21
+ * - `num_results` / `numResults` → numResults
22
+ * - `pre_filter_expression` / `preFilterExpression` → preFilterExpression (JSON-parsed if string)
23
+ *
24
+ * Answer endpoint:
25
+ * - `guard` → guard
26
+ * - `pre_filter_expression` / `preFilterExpression` → preFilterExpression (JSON-parsed if string)
27
+ * - `fmt_options` / `fmtOptions` → fmtOptions (JSON-parsed if string)
28
+ *
29
+ * All other keys (e.g. `ef-*`) are silently dropped.
30
+ * Typed parameters take precedence over values specified here.
16
31
  */
17
32
  parameters?: Record<string, string | number | boolean>;
18
33
  /** Define outside the component or wrap with useCallback to avoid unnecessary re-renders. */
@@ -1,17 +1,12 @@
1
- import MockConstructorIOClient from './mocks/MockConstructorIOClient';
1
+ import ConstructorIOClient from '@constructor-io/constructorio-client-javascript';
2
+ export type CioClient = InstanceType<typeof ConstructorIOClient>;
2
3
  export interface UsePiaClientProps {
3
4
  apiKey: string;
4
- /** Thread ID for conversation context. Must be a valid UUID. One is generated when absent. */
5
5
  threadId?: string;
6
- cioClient?: MockConstructorIOClient;
6
+ cioClient?: CioClient;
7
7
  }
8
8
  export interface UsePiaClientReturn {
9
- cioClient: MockConstructorIOClient;
9
+ cioClient: CioClient;
10
10
  threadId: string;
11
11
  }
12
- /**
13
- * Resolves the client and thread ID every mode needs: the caller's client when they supplied
14
- * one, otherwise a client built from the API key, plus a generated thread ID that stays stable
15
- * for the life of the component.
16
- */
17
12
  export default function usePiaClient({ apiKey, threadId: providedThreadId, cioClient: providedClient, }: UsePiaClientProps): UsePiaClientReturn;
@@ -1,4 +1,4 @@
1
- import MockConstructorIOClient from './mocks/MockConstructorIOClient';
1
+ import type { CioClient } from './usePiaClient';
2
2
  import { Formatters, Item, RecsPodParameters, RecsRefinement, Translations } from '../types';
3
3
  /**
4
4
  * What the shopper did to ask for this refinement.
@@ -12,7 +12,7 @@ export interface UseRecsPodProps {
12
12
  itemId: string;
13
13
  variationId?: string;
14
14
  threadId?: string;
15
- cioClient?: MockConstructorIOClient;
15
+ cioClient?: CioClient;
16
16
  parameters?: RecsPodParameters;
17
17
  formatImageUrl?: Formatters['formatImageUrl'];
18
18
  translations?: Translations;
@@ -1,12 +1,11 @@
1
1
  import { Question, SuggestedQuestionsParameters } from '../types';
2
- import MockConstructorIOClient from './mocks/MockConstructorIOClient';
2
+ import type { CioClient } from './usePiaClient';
3
3
  export interface UseSuggestedQuestionsProps {
4
4
  itemId: string;
5
5
  variationId?: string;
6
6
  threadId?: string;
7
- cioClient?: MockConstructorIOClient;
7
+ cioClient?: CioClient;
8
8
  parameters?: SuggestedQuestionsParameters;
9
- requestParameters?: Record<string, string | number | boolean>;
10
9
  }
11
10
  export interface UseSuggestedQuestionsReturn {
12
11
  data: Array<Question>;
@@ -14,4 +13,4 @@ export interface UseSuggestedQuestionsReturn {
14
13
  error: Error | null;
15
14
  getSuggestedQuestions: () => void;
16
15
  }
17
- export default function useSuggestedQuestions({ itemId, variationId, threadId, cioClient, parameters, requestParameters, }: UseSuggestedQuestionsProps): UseSuggestedQuestionsReturn;
16
+ export default function useSuggestedQuestions({ itemId, variationId, threadId, cioClient, parameters, }: UseSuggestedQuestionsProps): UseSuggestedQuestionsReturn;