@constructor-io/constructorio-ui-pia 2.13.0 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constructorio-ui-pia-bundled.js +51 -51
- package/lib/cjs/hooks/useAnswerResults.js +12 -3
- package/lib/cjs/hooks/useConversation.js +14 -2
- package/lib/cjs/hooks/useRecsPod.js +15 -7
- package/lib/cjs/hooks/useSuggestedQuestions.js +12 -3
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/hooks/useAnswerResults.js +13 -4
- package/lib/mjs/hooks/useConversation.js +14 -2
- package/lib/mjs/hooks/useRecsPod.js +15 -7
- package/lib/mjs/hooks/useSuggestedQuestions.js +13 -4
- package/lib/mjs/version.js +1 -1
- package/lib/types/utils/contentTransformers.d.ts +2 -2
- package/lib/types/version.d.ts +1 -1
- package/package.json +6 -5
|
@@ -6,14 +6,21 @@ function useAnswerResults({ itemId, variationId, threadId, cioClient, parameters
|
|
|
6
6
|
const [answerResults, setAnswerResults] = (0, react_1.useState)(null);
|
|
7
7
|
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
8
8
|
const [error, setError] = (0, react_1.useState)(null);
|
|
9
|
+
const serializedParameters = (0, react_1.useMemo)(() => JSON.stringify(parameters), [parameters]);
|
|
9
10
|
const items = (0, react_1.useMemo)(() => (0, transformers_1.extractAndTransformItems)(answerResults, formatImageUrl), [answerResults, formatImageUrl]);
|
|
11
|
+
// A host that rebuilds the client on render must not change getAnswer's identity.
|
|
12
|
+
const cioClientRef = (0, react_1.useRef)(cioClient);
|
|
13
|
+
(0, react_1.useEffect)(() => {
|
|
14
|
+
cioClientRef.current = cioClient;
|
|
15
|
+
}, [cioClient]);
|
|
10
16
|
const fetchResult = (0, react_1.useCallback)((question) => {
|
|
11
|
-
|
|
17
|
+
const client = cioClientRef.current;
|
|
18
|
+
if (!client)
|
|
12
19
|
return;
|
|
13
20
|
setIsLoading(true);
|
|
14
21
|
setError(null);
|
|
15
22
|
setAnswerResults(null);
|
|
16
|
-
|
|
23
|
+
client.agent.pia
|
|
17
24
|
.getAnswerResults(itemId, question, Object.assign({ threadId, variationId }, parameters))
|
|
18
25
|
.then((response) => {
|
|
19
26
|
setAnswerResults(response);
|
|
@@ -26,7 +33,9 @@ function useAnswerResults({ itemId, variationId, threadId, cioClient, parameters
|
|
|
26
33
|
.finally(() => {
|
|
27
34
|
setIsLoading(false);
|
|
28
35
|
});
|
|
29
|
-
},
|
|
36
|
+
},
|
|
37
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
|
+
[itemId, variationId, threadId, serializedParameters]);
|
|
30
39
|
return {
|
|
31
40
|
data: answerResults,
|
|
32
41
|
items,
|
|
@@ -18,6 +18,9 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, })
|
|
|
18
18
|
const trackingRef = (0, react_1.useRef)(tracking);
|
|
19
19
|
const lastQuestionRef = (0, react_1.useRef)('');
|
|
20
20
|
const lastSourceRef = (0, react_1.useRef)('user');
|
|
21
|
+
// Set once an answer owns the question row, so a later suggested-questions
|
|
22
|
+
// response cannot take it back.
|
|
23
|
+
const showsFollowUpsRef = (0, react_1.useRef)(false);
|
|
21
24
|
(0, react_1.useEffect)(() => {
|
|
22
25
|
callbacksRef.current = callbacks;
|
|
23
26
|
}, [callbacks]);
|
|
@@ -69,20 +72,29 @@ function useConversation({ pia, itemId, isConversation, callbacks, tracking, })
|
|
|
69
72
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
70
73
|
setConversationHistory([]);
|
|
71
74
|
prevAnswerDataRef.current = null;
|
|
75
|
+
showsFollowUpsRef.current = false;
|
|
72
76
|
}, [suggestedQuestions.data]);
|
|
73
77
|
(0, react_1.useEffect)(() => {
|
|
74
78
|
setCurrentQuestion('');
|
|
75
79
|
setDisplayedQuestions([]);
|
|
76
80
|
setConversationHistory([]);
|
|
77
81
|
prevAnswerDataRef.current = null;
|
|
82
|
+
showsFollowUpsRef.current = false;
|
|
78
83
|
}, [itemId]);
|
|
79
84
|
(0, react_1.useEffect)(() => {
|
|
85
|
+
// The ref, not `answers.data`, which outlives the item it answered.
|
|
86
|
+
if (showsFollowUpsRef.current)
|
|
87
|
+
return;
|
|
80
88
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
81
89
|
}, [suggestedQuestions.data]);
|
|
82
90
|
(0, react_1.useEffect)(() => {
|
|
83
91
|
var _a, _b;
|
|
84
|
-
if (
|
|
85
|
-
|
|
92
|
+
if (answers.data) {
|
|
93
|
+
// An answer with no follow-ups empties the row: the pre-generated questions
|
|
94
|
+
// include the one just asked.
|
|
95
|
+
setDisplayedQuestions((_a = answers.data.follow_up_questions) !== null && _a !== void 0 ? _a : []);
|
|
96
|
+
showsFollowUpsRef.current = true;
|
|
97
|
+
}
|
|
86
98
|
if (answers.data && lastQuestionRef.current && !hasTrackedCurrentAnswerRef.current) {
|
|
87
99
|
hasTrackedCurrentAnswerRef.current = true;
|
|
88
100
|
(_b = trackingRef.current) === null || _b === void 0 ? void 0 : _b.trackAnswerView(lastQuestionRef.current, answers.data, answers.items);
|
|
@@ -30,15 +30,23 @@ function useRecsPod({ itemId, variationId, threadId, cioClient, parameters, form
|
|
|
30
30
|
(0, react_1.useEffect)(() => {
|
|
31
31
|
formatImageUrlRef.current = formatImageUrl;
|
|
32
32
|
}, [formatImageUrl]);
|
|
33
|
+
// A host that rebuilds the client on render must not restart the request.
|
|
34
|
+
const cioClientRef = (0, react_1.useRef)(cioClient);
|
|
35
|
+
(0, react_1.useEffect)(() => {
|
|
36
|
+
cioClientRef.current = cioClient;
|
|
37
|
+
}, [cioClient]);
|
|
38
|
+
// A client arriving where there was none still warrants a fetch.
|
|
39
|
+
const hasClient = !!cioClient;
|
|
33
40
|
const strategy = (parameters === null || parameters === void 0 ? void 0 : parameters.strategy) || DEFAULT_STRATEGY;
|
|
34
41
|
const { numResults, defaultTitle } = parameters || {};
|
|
35
42
|
const fetchResult = (0, react_1.useCallback)((shopperInput, source) => {
|
|
36
43
|
var _a;
|
|
37
44
|
// TODO: once getRecs is added to the JS client SDK, replace this guard+cast
|
|
38
45
|
// with a direct call: cioClient.agent.getRecs({...})
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
const client = cioClientRef.current;
|
|
47
|
+
const getRecs = (_a = client === null || client === void 0 ? void 0 : client.agent) === null || _a === void 0 ? void 0 : _a.getRecs;
|
|
48
|
+
if (!client || !getRecs) {
|
|
49
|
+
if (client && !getRecs) {
|
|
42
50
|
console.info('[CioPia] getRecs is not available on the client SDK yet.');
|
|
43
51
|
}
|
|
44
52
|
setIsLoading(false);
|
|
@@ -51,7 +59,7 @@ function useRecsPod({ itemId, variationId, threadId, cioClient, parameters, form
|
|
|
51
59
|
setError(null);
|
|
52
60
|
setHasUnsupportedInput(false);
|
|
53
61
|
getRecs
|
|
54
|
-
.call(
|
|
62
|
+
.call(client.agent, {
|
|
55
63
|
itemId,
|
|
56
64
|
variationId,
|
|
57
65
|
threadId,
|
|
@@ -91,9 +99,9 @@ function useRecsPod({ itemId, variationId, threadId, cioClient, parameters, form
|
|
|
91
99
|
setIsLoading(false);
|
|
92
100
|
});
|
|
93
101
|
},
|
|
94
|
-
// Every dependency is a primitive
|
|
95
|
-
//
|
|
96
|
-
[
|
|
102
|
+
// Every dependency is a primitive; `formatImageUrl` and the client are read through refs.
|
|
103
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
104
|
+
[hasClient, itemId, variationId, threadId, strategy, numResults]);
|
|
97
105
|
(0, react_1.useEffect)(() => {
|
|
98
106
|
fetchResult();
|
|
99
107
|
}, [fetchResult]);
|
|
@@ -6,12 +6,21 @@ function useSuggestedQuestions({ itemId, variationId, threadId, cioClient, param
|
|
|
6
6
|
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
7
7
|
const [error, setError] = (0, react_1.useState)(null);
|
|
8
8
|
const serializedParameters = (0, react_1.useMemo)(() => JSON.stringify(parameters), [parameters]);
|
|
9
|
+
// The client is how the question is asked, not part of the question: a host that
|
|
10
|
+
// rebuilds it on render must not make the widget ask the same thing again.
|
|
11
|
+
const cioClientRef = (0, react_1.useRef)(cioClient);
|
|
12
|
+
(0, react_1.useEffect)(() => {
|
|
13
|
+
cioClientRef.current = cioClient;
|
|
14
|
+
}, [cioClient]);
|
|
15
|
+
// A client arriving where there was none is the one client change worth a fetch.
|
|
16
|
+
const hasClient = !!cioClient;
|
|
9
17
|
const fetchResult = (0, react_1.useCallback)(() => {
|
|
10
|
-
|
|
18
|
+
const client = cioClientRef.current;
|
|
19
|
+
if (!client)
|
|
11
20
|
return;
|
|
12
21
|
setIsLoading(true);
|
|
13
22
|
setError(null);
|
|
14
|
-
|
|
23
|
+
client.agent.pia
|
|
15
24
|
.getSuggestedQuestions(itemId, Object.assign({ threadId,
|
|
16
25
|
variationId }, parameters))
|
|
17
26
|
.then((response) => {
|
|
@@ -26,7 +35,7 @@ function useSuggestedQuestions({ itemId, variationId, threadId, cioClient, param
|
|
|
26
35
|
});
|
|
27
36
|
// parameters is serialized via serializedParameters to prevent refetch on identity changes
|
|
28
37
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
-
}, [
|
|
38
|
+
}, [hasClient, itemId, variationId, threadId, serializedParameters]);
|
|
30
39
|
(0, react_1.useEffect)(() => {
|
|
31
40
|
fetchResult();
|
|
32
41
|
}, [fetchResult]);
|
package/lib/cjs/version.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { extractAndTransformItems } from '../utils/transformers';
|
|
3
3
|
export default function useAnswerResults({ itemId, variationId, threadId, cioClient, parameters, formatImageUrl, }) {
|
|
4
4
|
const [answerResults, setAnswerResults] = useState(null);
|
|
5
5
|
const [isLoading, setIsLoading] = useState(false);
|
|
6
6
|
const [error, setError] = useState(null);
|
|
7
|
+
const serializedParameters = useMemo(() => JSON.stringify(parameters), [parameters]);
|
|
7
8
|
const items = useMemo(() => extractAndTransformItems(answerResults, formatImageUrl), [answerResults, formatImageUrl]);
|
|
9
|
+
// A host that rebuilds the client on render must not change getAnswer's identity.
|
|
10
|
+
const cioClientRef = useRef(cioClient);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
cioClientRef.current = cioClient;
|
|
13
|
+
}, [cioClient]);
|
|
8
14
|
const fetchResult = useCallback((question) => {
|
|
9
|
-
|
|
15
|
+
const client = cioClientRef.current;
|
|
16
|
+
if (!client)
|
|
10
17
|
return;
|
|
11
18
|
setIsLoading(true);
|
|
12
19
|
setError(null);
|
|
13
20
|
setAnswerResults(null);
|
|
14
|
-
|
|
21
|
+
client.agent.pia
|
|
15
22
|
.getAnswerResults(itemId, question, { threadId, variationId, ...parameters })
|
|
16
23
|
.then((response) => {
|
|
17
24
|
setAnswerResults(response);
|
|
@@ -24,7 +31,9 @@ export default function useAnswerResults({ itemId, variationId, threadId, cioCli
|
|
|
24
31
|
.finally(() => {
|
|
25
32
|
setIsLoading(false);
|
|
26
33
|
});
|
|
27
|
-
},
|
|
34
|
+
},
|
|
35
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
|
+
[itemId, variationId, threadId, serializedParameters]);
|
|
28
37
|
return {
|
|
29
38
|
data: answerResults,
|
|
30
39
|
items,
|
|
@@ -15,6 +15,9 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
15
15
|
const trackingRef = useRef(tracking);
|
|
16
16
|
const lastQuestionRef = useRef('');
|
|
17
17
|
const lastSourceRef = useRef('user');
|
|
18
|
+
// Set once an answer owns the question row, so a later suggested-questions
|
|
19
|
+
// response cannot take it back.
|
|
20
|
+
const showsFollowUpsRef = useRef(false);
|
|
18
21
|
useEffect(() => {
|
|
19
22
|
callbacksRef.current = callbacks;
|
|
20
23
|
}, [callbacks]);
|
|
@@ -62,19 +65,28 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
62
65
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
63
66
|
setConversationHistory([]);
|
|
64
67
|
prevAnswerDataRef.current = null;
|
|
68
|
+
showsFollowUpsRef.current = false;
|
|
65
69
|
}, [suggestedQuestions.data]);
|
|
66
70
|
useEffect(() => {
|
|
67
71
|
setCurrentQuestion('');
|
|
68
72
|
setDisplayedQuestions([]);
|
|
69
73
|
setConversationHistory([]);
|
|
70
74
|
prevAnswerDataRef.current = null;
|
|
75
|
+
showsFollowUpsRef.current = false;
|
|
71
76
|
}, [itemId]);
|
|
72
77
|
useEffect(() => {
|
|
78
|
+
// The ref, not `answers.data`, which outlives the item it answered.
|
|
79
|
+
if (showsFollowUpsRef.current)
|
|
80
|
+
return;
|
|
73
81
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
74
82
|
}, [suggestedQuestions.data]);
|
|
75
83
|
useEffect(() => {
|
|
76
|
-
if (answers.data
|
|
77
|
-
|
|
84
|
+
if (answers.data) {
|
|
85
|
+
// An answer with no follow-ups empties the row: the pre-generated questions
|
|
86
|
+
// include the one just asked.
|
|
87
|
+
setDisplayedQuestions(answers.data.follow_up_questions ?? []);
|
|
88
|
+
showsFollowUpsRef.current = true;
|
|
89
|
+
}
|
|
78
90
|
if (answers.data && lastQuestionRef.current && !hasTrackedCurrentAnswerRef.current) {
|
|
79
91
|
hasTrackedCurrentAnswerRef.current = true;
|
|
80
92
|
trackingRef.current?.trackAnswerView(lastQuestionRef.current, answers.data, answers.items);
|
|
@@ -27,14 +27,22 @@ export default function useRecsPod({ itemId, variationId, threadId, cioClient, p
|
|
|
27
27
|
useEffect(() => {
|
|
28
28
|
formatImageUrlRef.current = formatImageUrl;
|
|
29
29
|
}, [formatImageUrl]);
|
|
30
|
+
// A host that rebuilds the client on render must not restart the request.
|
|
31
|
+
const cioClientRef = useRef(cioClient);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
cioClientRef.current = cioClient;
|
|
34
|
+
}, [cioClient]);
|
|
35
|
+
// A client arriving where there was none still warrants a fetch.
|
|
36
|
+
const hasClient = !!cioClient;
|
|
30
37
|
const strategy = parameters?.strategy || DEFAULT_STRATEGY;
|
|
31
38
|
const { numResults, defaultTitle } = parameters || {};
|
|
32
39
|
const fetchResult = useCallback((shopperInput, source) => {
|
|
33
40
|
// TODO: once getRecs is added to the JS client SDK, replace this guard+cast
|
|
34
41
|
// with a direct call: cioClient.agent.getRecs({...})
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
const client = cioClientRef.current;
|
|
43
|
+
const getRecs = client?.agent?.getRecs;
|
|
44
|
+
if (!client || !getRecs) {
|
|
45
|
+
if (client && !getRecs) {
|
|
38
46
|
console.info('[CioPia] getRecs is not available on the client SDK yet.');
|
|
39
47
|
}
|
|
40
48
|
setIsLoading(false);
|
|
@@ -47,7 +55,7 @@ export default function useRecsPod({ itemId, variationId, threadId, cioClient, p
|
|
|
47
55
|
setError(null);
|
|
48
56
|
setHasUnsupportedInput(false);
|
|
49
57
|
getRecs
|
|
50
|
-
.call(
|
|
58
|
+
.call(client.agent, {
|
|
51
59
|
itemId,
|
|
52
60
|
variationId,
|
|
53
61
|
threadId,
|
|
@@ -87,9 +95,9 @@ export default function useRecsPod({ itemId, variationId, threadId, cioClient, p
|
|
|
87
95
|
setIsLoading(false);
|
|
88
96
|
});
|
|
89
97
|
},
|
|
90
|
-
// Every dependency is a primitive
|
|
91
|
-
//
|
|
92
|
-
[
|
|
98
|
+
// Every dependency is a primitive; `formatImageUrl` and the client are read through refs.
|
|
99
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
100
|
+
[hasClient, itemId, variationId, threadId, strategy, numResults]);
|
|
93
101
|
useEffect(() => {
|
|
94
102
|
fetchResult();
|
|
95
103
|
}, [fetchResult]);
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
export default function useSuggestedQuestions({ itemId, variationId, threadId, cioClient, parameters, }) {
|
|
3
3
|
const [questions, setQuestions] = useState([]);
|
|
4
4
|
const [isLoading, setIsLoading] = useState(false);
|
|
5
5
|
const [error, setError] = useState(null);
|
|
6
6
|
const serializedParameters = useMemo(() => JSON.stringify(parameters), [parameters]);
|
|
7
|
+
// The client is how the question is asked, not part of the question: a host that
|
|
8
|
+
// rebuilds it on render must not make the widget ask the same thing again.
|
|
9
|
+
const cioClientRef = useRef(cioClient);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
cioClientRef.current = cioClient;
|
|
12
|
+
}, [cioClient]);
|
|
13
|
+
// A client arriving where there was none is the one client change worth a fetch.
|
|
14
|
+
const hasClient = !!cioClient;
|
|
7
15
|
const fetchResult = useCallback(() => {
|
|
8
|
-
|
|
16
|
+
const client = cioClientRef.current;
|
|
17
|
+
if (!client)
|
|
9
18
|
return;
|
|
10
19
|
setIsLoading(true);
|
|
11
20
|
setError(null);
|
|
12
|
-
|
|
21
|
+
client.agent.pia
|
|
13
22
|
.getSuggestedQuestions(itemId, {
|
|
14
23
|
threadId,
|
|
15
24
|
variationId,
|
|
@@ -27,7 +36,7 @@ export default function useSuggestedQuestions({ itemId, variationId, threadId, c
|
|
|
27
36
|
});
|
|
28
37
|
// parameters is serialized via serializedParameters to prevent refetch on identity changes
|
|
29
38
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
-
}, [
|
|
39
|
+
}, [hasClient, itemId, variationId, threadId, serializedParameters]);
|
|
31
40
|
useEffect(() => {
|
|
32
41
|
fetchResult();
|
|
33
42
|
}, [fetchResult]);
|
package/lib/mjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '2.13.
|
|
1
|
+
export default '2.13.1';
|
package/lib/types/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "2.13.
|
|
1
|
+
declare const _default: "2.13.1";
|
|
2
2
|
export default _default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructor-io/constructorio-ui-pia",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"description": "Constructor.io AI Product Insights Agent UI library for web applications",
|
|
5
5
|
"author": "Constructor.io Corporation",
|
|
6
6
|
"license": "MIT",
|
|
@@ -99,7 +99,6 @@
|
|
|
99
99
|
"@storybook/testing-library": "^0.2.1",
|
|
100
100
|
"@testing-library/jest-dom": "^6.6.3",
|
|
101
101
|
"@testing-library/react": "^16.2.0",
|
|
102
|
-
"@types/dompurify": "^3.0.5",
|
|
103
102
|
"@types/jest": "^30.0.0",
|
|
104
103
|
"@types/react": "^19.0.10",
|
|
105
104
|
"@types/react-dom": "^19.0.4",
|
|
@@ -127,17 +126,19 @@
|
|
|
127
126
|
"typescript-eslint": "^8.59.2",
|
|
128
127
|
"vite": "^6.4.3",
|
|
129
128
|
"vite-plugin-css-injected-by-js": "^3.5.2",
|
|
130
|
-
"webpack": "^5.
|
|
129
|
+
"webpack": "^5.104.1"
|
|
131
130
|
},
|
|
132
131
|
"volta": {
|
|
133
132
|
"node": "22.18.0",
|
|
134
133
|
"npm": "10.9.3"
|
|
135
134
|
},
|
|
136
135
|
"dependencies": {
|
|
137
|
-
"dompurify": "^3.4.
|
|
136
|
+
"dompurify": "^3.4.15",
|
|
138
137
|
"marked": "^15.0.12"
|
|
139
138
|
},
|
|
140
139
|
"overrides": {
|
|
141
|
-
"uuid": "^11.1.1"
|
|
140
|
+
"uuid": "^11.1.1",
|
|
141
|
+
"browserslist": "^4.28.9",
|
|
142
|
+
"postcss-selector-parser": "^7.1.6"
|
|
142
143
|
}
|
|
143
144
|
}
|