@constructor-io/constructorio-ui-pia 2.4.0 → 2.6.2
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 +50 -50
- package/lib/cjs/components/CioPia/CioPia.js +10 -5
- package/lib/cjs/components/Input/Input.js +2 -2
- package/lib/cjs/components/PiaConversation/PiaConversation.js +4 -4
- package/lib/cjs/components/PiaConversation/PiaModal.js +8 -4
- package/lib/cjs/hooks/useAnswerResults.js +2 -4
- package/lib/cjs/hooks/useCioPia.js +5 -1
- package/lib/cjs/hooks/useConversation.js +61 -15
- package/lib/cjs/hooks/useViewportCallbacks.js +50 -0
- package/lib/cjs/stories/assets/storybook-styles.css +89 -0
- package/lib/cjs/utils/contentTransformers.js +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/mjs/components/CioPia/CioPia.js +10 -5
- package/lib/mjs/components/Input/Input.js +2 -2
- package/lib/mjs/components/PiaConversation/PiaConversation.js +4 -4
- package/lib/mjs/components/PiaConversation/PiaModal.js +8 -4
- package/lib/mjs/hooks/useAnswerResults.js +3 -5
- package/lib/mjs/hooks/useCioPia.js +5 -1
- package/lib/mjs/hooks/useConversation.js +60 -15
- package/lib/mjs/hooks/useViewportCallbacks.js +45 -0
- package/lib/mjs/stories/assets/storybook-styles.css +89 -0
- package/lib/mjs/utils/contentTransformers.js +1 -1
- package/lib/mjs/version.js +1 -1
- package/lib/types/components/Input/Input.d.ts +2 -1
- package/lib/types/components/PiaConversation/PiaConversation.d.ts +4 -1
- package/lib/types/components/PiaConversation/PiaModal.d.ts +4 -1
- package/lib/types/hooks/mocks/MockConstructorIOClient.d.ts +1 -1
- package/lib/types/hooks/useCioPia.d.ts +1 -0
- package/lib/types/hooks/useConversation.d.ts +4 -1
- package/lib/types/hooks/useViewportCallbacks.d.ts +9 -0
- package/lib/types/types.d.ts +18 -2
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,22 +1,45 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
export default function useConversation({ pia, itemId, isConversation, callbacks, }) {
|
|
3
|
-
const { suggestedQuestions, answers } = pia;
|
|
3
|
+
const { suggestedQuestions, answers, threadId } = pia;
|
|
4
4
|
const { getAnswer } = answers;
|
|
5
|
+
const context = useMemo(() => ({ itemId, threadId }), [itemId, threadId]);
|
|
5
6
|
const [currentQuestion, setCurrentQuestion] = useState('');
|
|
6
7
|
const [displayedQuestions, setDisplayedQuestions] = useState([]);
|
|
7
8
|
const [conversationHistory, setConversationHistory] = useState([]);
|
|
8
9
|
const entryIdRef = useRef(0);
|
|
9
10
|
const prevAnswerValueRef = useRef(answers.data?.value);
|
|
10
|
-
const
|
|
11
|
-
|
|
11
|
+
const callbacksRef = useRef(callbacks);
|
|
12
|
+
const contextRef = useRef(context);
|
|
13
|
+
const lastQuestionRef = useRef('');
|
|
14
|
+
const lastSourceRef = useRef('user');
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
callbacksRef.current = callbacks;
|
|
17
|
+
}, [callbacks]);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
contextRef.current = context;
|
|
20
|
+
}, [context]);
|
|
21
|
+
const submitQuestion = useCallback((question, source) => {
|
|
22
|
+
lastSourceRef.current = source;
|
|
23
|
+
lastQuestionRef.current = question;
|
|
12
24
|
setCurrentQuestion(question);
|
|
13
25
|
getAnswer(question);
|
|
14
26
|
if (isConversation) {
|
|
15
27
|
entryIdRef.current += 1;
|
|
16
28
|
const id = entryIdRef.current;
|
|
17
|
-
setConversationHistory((prev) => [...prev, { id, question, answer: '' }]);
|
|
29
|
+
setConversationHistory((prev) => [...prev, { id, question, answer: '', source }]);
|
|
18
30
|
}
|
|
19
|
-
}, [getAnswer, isConversation
|
|
31
|
+
}, [getAnswer, isConversation]);
|
|
32
|
+
const handleSubmitQuestion = useCallback((question) => {
|
|
33
|
+
callbacksRef.current?.onQuestionSubmit?.(question, contextRef.current, 'user');
|
|
34
|
+
submitQuestion(question, 'user');
|
|
35
|
+
}, [submitQuestion]);
|
|
36
|
+
const handleQuestionClick = useCallback((question) => {
|
|
37
|
+
callbacksRef.current?.onQuestionSubmit?.(question, contextRef.current, 'suggestion');
|
|
38
|
+
submitQuestion(question, 'suggestion');
|
|
39
|
+
}, [submitQuestion]);
|
|
40
|
+
const handleInputFocus = useCallback(() => {
|
|
41
|
+
callbacksRef.current?.onFocus?.(contextRef.current);
|
|
42
|
+
}, []);
|
|
20
43
|
const resetState = useCallback(() => {
|
|
21
44
|
setCurrentQuestion('');
|
|
22
45
|
setDisplayedQuestions(suggestedQuestions.data);
|
|
@@ -38,22 +61,41 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
38
61
|
}, [answers.data]);
|
|
39
62
|
useEffect(() => {
|
|
40
63
|
const answerValue = answers.data?.value ?? '';
|
|
41
|
-
if (!
|
|
64
|
+
if (!answerValue)
|
|
42
65
|
return;
|
|
43
66
|
if (answerValue === prevAnswerValueRef.current)
|
|
44
67
|
return;
|
|
45
68
|
prevAnswerValueRef.current = answerValue;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
const answerThreadId = answers.data?.thread_id;
|
|
70
|
+
const qnaResultId = answers.data?.qna_result_id;
|
|
71
|
+
if (isConversation) {
|
|
72
|
+
setConversationHistory((prev) => {
|
|
73
|
+
if (prev.length === 0)
|
|
74
|
+
return prev;
|
|
75
|
+
const updated = [...prev];
|
|
76
|
+
updated[updated.length - 1] = {
|
|
77
|
+
...updated[updated.length - 1],
|
|
78
|
+
answer: answerValue,
|
|
79
|
+
items: answers.items,
|
|
80
|
+
threadId: answerThreadId,
|
|
81
|
+
qnaResultId,
|
|
82
|
+
};
|
|
83
|
+
callbacksRef.current?.onAnswer?.(updated, contextRef.current);
|
|
84
|
+
return updated;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const entry = {
|
|
89
|
+
id: entryIdRef.current,
|
|
90
|
+
question: lastQuestionRef.current,
|
|
52
91
|
answer: answerValue,
|
|
92
|
+
source: lastSourceRef.current,
|
|
53
93
|
items: answers.items,
|
|
94
|
+
threadId: answerThreadId,
|
|
95
|
+
qnaResultId,
|
|
54
96
|
};
|
|
55
|
-
|
|
56
|
-
}
|
|
97
|
+
callbacksRef.current?.onAnswer?.([entry], contextRef.current);
|
|
98
|
+
}
|
|
57
99
|
}, [isConversation, answers.data, answers.items]);
|
|
58
100
|
const currentAnswer = answers.data?.value ?? '';
|
|
59
101
|
const currentItems = answers.items ?? null;
|
|
@@ -67,7 +109,10 @@ export default function useConversation({ pia, itemId, isConversation, callbacks
|
|
|
67
109
|
currentItems,
|
|
68
110
|
isLoading,
|
|
69
111
|
error,
|
|
112
|
+
context,
|
|
70
113
|
handleSubmitQuestion,
|
|
114
|
+
handleQuestionClick,
|
|
115
|
+
handleInputFocus,
|
|
71
116
|
resetState,
|
|
72
117
|
};
|
|
73
118
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
export default function useViewportCallbacks({ callbacks, context, }) {
|
|
3
|
+
const callbacksRef = useRef(callbacks);
|
|
4
|
+
const contextRef = useRef(context);
|
|
5
|
+
const hasBeenVisibleRef = useRef(false);
|
|
6
|
+
const hasFiredOutOfViewRef = useRef(false);
|
|
7
|
+
const observerRef = useRef(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
callbacksRef.current = callbacks;
|
|
10
|
+
}, [callbacks]);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
contextRef.current = context;
|
|
13
|
+
}, [context]);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
return () => {
|
|
16
|
+
observerRef.current?.disconnect();
|
|
17
|
+
};
|
|
18
|
+
}, []);
|
|
19
|
+
const containerRef = useCallback((node) => {
|
|
20
|
+
if (observerRef.current) {
|
|
21
|
+
observerRef.current.disconnect();
|
|
22
|
+
observerRef.current = null;
|
|
23
|
+
}
|
|
24
|
+
if (!node)
|
|
25
|
+
return;
|
|
26
|
+
hasBeenVisibleRef.current = false;
|
|
27
|
+
hasFiredOutOfViewRef.current = false;
|
|
28
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
29
|
+
if (entry.isIntersecting && !hasBeenVisibleRef.current) {
|
|
30
|
+
hasBeenVisibleRef.current = true;
|
|
31
|
+
callbacksRef.current?.onView?.(contextRef.current);
|
|
32
|
+
}
|
|
33
|
+
else if (!entry.isIntersecting &&
|
|
34
|
+
hasBeenVisibleRef.current &&
|
|
35
|
+
!hasFiredOutOfViewRef.current) {
|
|
36
|
+
hasFiredOutOfViewRef.current = true;
|
|
37
|
+
callbacksRef.current?.onOutOfView?.(contextRef.current);
|
|
38
|
+
observer.disconnect();
|
|
39
|
+
}
|
|
40
|
+
}, { threshold: 0.5 });
|
|
41
|
+
observer.observe(node);
|
|
42
|
+
observerRef.current = observer;
|
|
43
|
+
}, []);
|
|
44
|
+
return { containerRef };
|
|
45
|
+
}
|
|
@@ -110,3 +110,92 @@ div.badge-wrapper code {
|
|
|
110
110
|
background: #d8e8fd;
|
|
111
111
|
color: #3c70bf;
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
.cio-flow-diagram {
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-direction: column;
|
|
117
|
+
gap: 4px;
|
|
118
|
+
margin: 24px 0 0;
|
|
119
|
+
font-family: system-ui, sans-serif;
|
|
120
|
+
font-size: 14px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.cio-flow-diagram .flow-step {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
gap: 12px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.cio-flow-diagram .flow-box {
|
|
130
|
+
padding: 10px 16px;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
min-width: 220px;
|
|
133
|
+
text-align: center;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.cio-flow-diagram .flow-box--consumer {
|
|
137
|
+
background: #eff6ff;
|
|
138
|
+
border: 1px solid #bfdbfe;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.cio-flow-diagram .flow-box--internal {
|
|
142
|
+
background: #f5f3ff;
|
|
143
|
+
border: 1px solid #ddd6fe;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.cio-flow-diagram .flow-box--output {
|
|
147
|
+
background: #f0fdf4;
|
|
148
|
+
border: 1px solid #bbf7d0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.cio-flow-diagram .flow-arrow {
|
|
152
|
+
margin-left: 110px;
|
|
153
|
+
color: #9ca3af;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.cio-flow-diagram .flow-label {
|
|
157
|
+
font-size: 12px;
|
|
158
|
+
color: #9ca3af;
|
|
159
|
+
font-style: italic;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.cio-flow-diagram .flow-callback {
|
|
163
|
+
font-size: 12px;
|
|
164
|
+
color: #059669;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.cio-flow-diagram .flow-box-subtitle {
|
|
168
|
+
font-size: 12px;
|
|
169
|
+
color: #6b7280;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.cio-flow-legend {
|
|
173
|
+
display: flex;
|
|
174
|
+
gap: 16px;
|
|
175
|
+
margin: 32px 0 16px;
|
|
176
|
+
font-size: 12px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.cio-flow-legend .legend-swatch {
|
|
180
|
+
display: inline-block;
|
|
181
|
+
width: 12px;
|
|
182
|
+
height: 12px;
|
|
183
|
+
border-radius: 3px;
|
|
184
|
+
margin-right: 4px;
|
|
185
|
+
vertical-align: middle;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.cio-flow-legend .legend-swatch--consumer {
|
|
189
|
+
background: #eff6ff;
|
|
190
|
+
border: 1px solid #bfdbfe;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.cio-flow-legend .legend-swatch--internal {
|
|
194
|
+
background: #f5f3ff;
|
|
195
|
+
border: 1px solid #ddd6fe;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.cio-flow-legend .legend-swatch--output {
|
|
199
|
+
background: #f0fdf4;
|
|
200
|
+
border: 1px solid #bbf7d0;
|
|
201
|
+
}
|
|
@@ -11,6 +11,6 @@ export function sanitizeHtml(html) {
|
|
|
11
11
|
export function renderMarkdown(content) {
|
|
12
12
|
if (!content)
|
|
13
13
|
return '';
|
|
14
|
-
const html = marked.parse(content, { async: false });
|
|
14
|
+
const html = marked.parse(content, { async: false, breaks: true });
|
|
15
15
|
return DOMPurify.sanitize(html, purifyConfig);
|
|
16
16
|
}
|
package/lib/mjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '2.
|
|
1
|
+
export default '2.6.2';
|
|
@@ -6,7 +6,8 @@ interface InputProps {
|
|
|
6
6
|
/** @deprecated Use the `translations` prop instead. */
|
|
7
7
|
placeholder?: string;
|
|
8
8
|
onSubmit: (value: string) => void;
|
|
9
|
+
onFocus?: () => void;
|
|
9
10
|
translations?: Translations;
|
|
10
11
|
}
|
|
11
|
-
declare function Input({ value: providedValue, placeholder, disabled, onSubmit, translations, }: InputProps): React.JSX.Element;
|
|
12
|
+
declare function Input({ value: providedValue, placeholder, disabled, onSubmit, onFocus, translations, }: InputProps): React.JSX.Element;
|
|
12
13
|
export default Input;
|
|
@@ -4,5 +4,8 @@ import { ConversationHistoryProps } from '../ConversationHistory/ConversationHis
|
|
|
4
4
|
export interface PiaConversationProps extends ConversationHistoryProps {
|
|
5
5
|
displayedQuestions: Question[];
|
|
6
6
|
handleSubmitQuestion: (question: string) => void;
|
|
7
|
+
handleQuestionClick: (question: string) => void;
|
|
8
|
+
containerRef?: (node: HTMLDivElement | null) => void;
|
|
9
|
+
onInputFocus?: () => void;
|
|
7
10
|
}
|
|
8
|
-
export default function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, disclaimerPosition, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, }: PiaConversationProps): React.JSX.Element;
|
|
11
|
+
export default function PiaConversation({ conversationHistory, isLoading, error, currentItems, showFeedback, showPreviousItems, learnMoreUrl, disclaimerPosition, translations, callbacks, componentOverrides, displayedQuestions, handleSubmitQuestion, handleQuestionClick, containerRef, onInputFocus, }: PiaConversationProps): React.JSX.Element;
|
|
@@ -3,10 +3,13 @@ import { Translations, Question, CioPiaComponentOverrides } from '../../types';
|
|
|
3
3
|
interface PiaModalProps {
|
|
4
4
|
initialQuestions: Question[];
|
|
5
5
|
handleSubmitQuestion: (question: string) => void;
|
|
6
|
+
handleQuestionClick: (question: string) => void;
|
|
7
|
+
containerRef?: (node: HTMLDivElement | null) => void;
|
|
6
8
|
isLoading: boolean;
|
|
7
9
|
componentOverrides?: CioPiaComponentOverrides;
|
|
8
10
|
translations?: Translations;
|
|
11
|
+
onInputFocus?: () => void;
|
|
9
12
|
onClose?: () => void;
|
|
10
13
|
}
|
|
11
|
-
export default function PiaModal({ initialQuestions, handleSubmitQuestion, isLoading, componentOverrides, translations, onClose, children, }: PropsWithChildren<PiaModalProps>): React.JSX.Element;
|
|
14
|
+
export default function PiaModal({ initialQuestions, handleSubmitQuestion, handleQuestionClick, containerRef, isLoading, componentOverrides, translations, onInputFocus, onClose, children, }: PropsWithChildren<PiaModalProps>): React.JSX.Element;
|
|
12
15
|
export {};
|
|
@@ -2,7 +2,7 @@ import { ConstructorClientOptions } from '@constructor-io/constructorio-client-j
|
|
|
2
2
|
import { Recommendations, Browse, Search, Tracker, Quizzes } from '@constructor-io/constructorio-client-javascript/lib/types/constructorio';
|
|
3
3
|
import MockAgent from './agent';
|
|
4
4
|
declare class MockConstructorIOClient {
|
|
5
|
-
|
|
5
|
+
options: ConstructorClientOptions;
|
|
6
6
|
search: Search;
|
|
7
7
|
browse: Browse;
|
|
8
8
|
recommendations: Recommendations;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Callbacks, ConversationEntry, Question, Item } from '../types';
|
|
1
|
+
import { Callbacks, ConversationEntry, PiaCallbackContext, Question, Item } from '../types';
|
|
2
2
|
import { UseCioPiaReturn } from './useCioPia';
|
|
3
3
|
export interface UseConversationProps {
|
|
4
4
|
pia: UseCioPiaReturn;
|
|
@@ -14,7 +14,10 @@ export interface UseConversationReturn {
|
|
|
14
14
|
currentItems: Item[] | null;
|
|
15
15
|
isLoading: boolean;
|
|
16
16
|
error: Error | null;
|
|
17
|
+
context: PiaCallbackContext;
|
|
17
18
|
handleSubmitQuestion: (question: string) => void;
|
|
19
|
+
handleQuestionClick: (question: string) => void;
|
|
20
|
+
handleInputFocus: () => void;
|
|
18
21
|
resetState: () => void;
|
|
19
22
|
}
|
|
20
23
|
export default function useConversation({ pia, itemId, isConversation, callbacks, }: UseConversationProps): UseConversationReturn;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Callbacks, PiaCallbackContext } from '../types';
|
|
2
|
+
export interface UseViewportCallbacksProps {
|
|
3
|
+
callbacks?: Callbacks;
|
|
4
|
+
context: PiaCallbackContext;
|
|
5
|
+
}
|
|
6
|
+
export interface UseViewportCallbacksReturn {
|
|
7
|
+
containerRef: (node: HTMLDivElement | null) => void;
|
|
8
|
+
}
|
|
9
|
+
export default function useViewportCallbacks({ callbacks, context, }: UseViewportCallbacksProps): UseViewportCallbacksReturn;
|
package/lib/types/types.d.ts
CHANGED
|
@@ -60,13 +60,26 @@ export type Translations = {
|
|
|
60
60
|
'Learn More.'?: string;
|
|
61
61
|
'Ask about this product'?: string;
|
|
62
62
|
};
|
|
63
|
+
export type QuestionSource = 'user' | 'suggestion';
|
|
64
|
+
export interface PiaCallbackContext {
|
|
65
|
+
itemId: string;
|
|
66
|
+
threadId: string;
|
|
67
|
+
}
|
|
63
68
|
export interface Callbacks {
|
|
64
|
-
/** Called when a question is submitted (
|
|
65
|
-
onQuestionSubmit?: (question: string) => void;
|
|
69
|
+
/** Called when a question is submitted (typed or suggested question clicked). */
|
|
70
|
+
onQuestionSubmit?: (question: string, context: PiaCallbackContext, source: QuestionSource) => void;
|
|
66
71
|
/** Called when a product card in the carousel is clicked. */
|
|
67
72
|
onProductCardClick?: (item: Item) => void;
|
|
68
73
|
/** Called when the user submits positive or negative feedback on an answer. */
|
|
69
74
|
onFeedback?: (type: FeedbackType) => void;
|
|
75
|
+
/** Called when a new answer is received. Passes the full conversation history. */
|
|
76
|
+
onAnswer?: (history: ConversationEntry[], context: PiaCallbackContext) => void;
|
|
77
|
+
/** Called when the user focuses the input field. */
|
|
78
|
+
onFocus?: (context: PiaCallbackContext) => void;
|
|
79
|
+
/** Called when the widget enters the viewport. */
|
|
80
|
+
onView?: (context: PiaCallbackContext) => void;
|
|
81
|
+
/** Called when the widget leaves the viewport. */
|
|
82
|
+
onOutOfView?: (context: PiaCallbackContext) => void;
|
|
70
83
|
}
|
|
71
84
|
/** Formatter functions for transforming data before display. */
|
|
72
85
|
export interface Formatters {
|
|
@@ -82,7 +95,10 @@ export interface ConversationEntry {
|
|
|
82
95
|
id: number;
|
|
83
96
|
question: string;
|
|
84
97
|
answer: string;
|
|
98
|
+
source: QuestionSource;
|
|
85
99
|
items?: Item[] | null;
|
|
100
|
+
threadId?: string;
|
|
101
|
+
qnaResultId?: string;
|
|
86
102
|
}
|
|
87
103
|
/**
|
|
88
104
|
* Render props passed to CioPia children function
|
package/lib/types/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "2.
|
|
1
|
+
declare const _default: "2.6.2";
|
|
2
2
|
export default _default;
|
package/package.json
CHANGED