andy-widget-react 2.0.5 → 2.0.6
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/index.cjs +199 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +199 -10
- package/dist/index.esm.js.map +1 -1
- package/dist/types/types/index.d.ts +9 -1
- package/package.json +2 -2
- package/src/AndyChat.tsx +38 -5
- package/src/hooks/useAndyChat.ts +3 -2
- package/src/types/index.ts +9 -1
|
@@ -37,15 +37,23 @@ export interface ChatbotConfig {
|
|
|
37
37
|
collectUserInfo: CollectUserInfoConfig;
|
|
38
38
|
predefinedQuestions: PredefinedQuestion[];
|
|
39
39
|
faqs: FAQItem[];
|
|
40
|
+
bootstrap?: {
|
|
41
|
+
convexUrl: string;
|
|
42
|
+
surfaceKey: string;
|
|
43
|
+
surfaceProof: string;
|
|
44
|
+
surfaceProofExpiresAt: number;
|
|
45
|
+
};
|
|
40
46
|
}
|
|
41
47
|
export interface Message {
|
|
42
|
-
id: number;
|
|
48
|
+
id: string | number;
|
|
43
49
|
content: string;
|
|
44
50
|
sender: 'user' | 'bot';
|
|
45
51
|
timestamp: string;
|
|
46
52
|
isForm?: boolean;
|
|
47
53
|
isLoading?: boolean;
|
|
48
54
|
isError?: boolean;
|
|
55
|
+
isHuman?: boolean;
|
|
56
|
+
isWaiting?: boolean;
|
|
49
57
|
}
|
|
50
58
|
export interface UserInfo {
|
|
51
59
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "andy-widget-react",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Official React Widget for embeddable Andy agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -67,6 +67,6 @@
|
|
|
67
67
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"andy-widget-core": "2.0.
|
|
70
|
+
"andy-widget-core": "2.0.6"
|
|
71
71
|
}
|
|
72
72
|
}
|
package/src/AndyChat.tsx
CHANGED
|
@@ -5,11 +5,12 @@ import {
|
|
|
5
5
|
fetchChatbotConfig,
|
|
6
6
|
generateSessionId,
|
|
7
7
|
createGreetingMessage,
|
|
8
|
-
|
|
8
|
+
sendMessageForWidget as sendMessageToAPI,
|
|
9
9
|
submitUserInfo,
|
|
10
10
|
isValidEmbedId,
|
|
11
11
|
getFullStylesheet,
|
|
12
12
|
getHostClasses,
|
|
13
|
+
createBrowserHandoff,
|
|
13
14
|
parseMarkdown,
|
|
14
15
|
CHAT_BUBBLE_DEFAULT_ICON,
|
|
15
16
|
CHAT_BUBBLE_DEFAULT_ICON_URL,
|
|
@@ -87,6 +88,8 @@ const AndyChat: React.FC<AndyChatProps> = ({
|
|
|
87
88
|
const [messages, setMessages] = useState<Message[]>([])
|
|
88
89
|
const [isLoading, setIsLoading] = useState(false)
|
|
89
90
|
const [sessionId, setSessionId] = useState(() => generateSessionId())
|
|
91
|
+
const handoffRef = useRef<ReturnType<typeof createBrowserHandoff>>(null)
|
|
92
|
+
const pendingHandoffIdsRef = useRef<string[]>([])
|
|
90
93
|
const [shadowReady, setShadowReady] = useState(false)
|
|
91
94
|
const [showPredefinedQuestions, setShowPredefinedQuestions] = useState(true)
|
|
92
95
|
const [userInfoCollected, setUserInfoCollected] = useState(false)
|
|
@@ -102,6 +105,29 @@ const AndyChat: React.FC<AndyChatProps> = ({
|
|
|
102
105
|
const welcomePopupShownRef = useRef(false)
|
|
103
106
|
const welcomePopupTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
104
107
|
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!config?.bootstrap || !config.id) return
|
|
110
|
+
handoffRef.current?.stop()
|
|
111
|
+
const handoff = createBrowserHandoff(config.id, config.bootstrap, projection => {
|
|
112
|
+
if (projection.offline) return
|
|
113
|
+
setSessionId(projection.identity.sessionId)
|
|
114
|
+
pendingHandoffIdsRef.current = projection.pendingProjectionIds || []
|
|
115
|
+
const projectedMessages = projection.messages.map(message => ({ id: message.id, content: message.content, sender: message.role === 'user' ? 'user' as const : 'bot' as const, timestamp: new Date(message.createdAt).toISOString(), isHuman: message.sentByHuman }))
|
|
116
|
+
setMessages(projection.control === 'requested' || projection.control === 'human'
|
|
117
|
+
? [...projectedMessages, { id: 'handoff-waiting', content: config.defaultLang === 'es' ? 'Un miembro del equipo te responderá pronto.' : 'A team member will reply soon.', sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]
|
|
118
|
+
: projectedMessages)
|
|
119
|
+
})
|
|
120
|
+
handoffRef.current = handoff
|
|
121
|
+
return () => {
|
|
122
|
+
handoff?.stop()
|
|
123
|
+
handoffRef.current = null
|
|
124
|
+
}
|
|
125
|
+
}, [config?.bootstrap, config?.id])
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
pendingHandoffIdsRef.current.forEach(messageId => void handoffRef.current?.markRendered(messageId))
|
|
129
|
+
}, [messages])
|
|
130
|
+
|
|
105
131
|
// Create shadow DOM container on mount
|
|
106
132
|
useEffect(() => {
|
|
107
133
|
if (!containerRef.current) return
|
|
@@ -490,6 +516,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
|
|
|
490
516
|
sender: 'user',
|
|
491
517
|
timestamp: new Date().toISOString()
|
|
492
518
|
}
|
|
519
|
+
handoffRef.current?.recordVisitorMessage(messageText)
|
|
493
520
|
setMessages(prev => [...prev, userMessage])
|
|
494
521
|
|
|
495
522
|
if (hidePredefined) {
|
|
@@ -499,10 +526,14 @@ const AndyChat: React.FC<AndyChatProps> = ({
|
|
|
499
526
|
try {
|
|
500
527
|
setIsLoading(true)
|
|
501
528
|
const response = await sendMessageToAPI(embedId!, sessionId, messageText)
|
|
529
|
+
if (response.status === 'waiting') {
|
|
530
|
+
setMessages(prev => prev.some(message => message.isWaiting) ? prev : [...prev, { id: 'handoff-waiting', content: config?.defaultLang === 'es' ? 'Un miembro del equipo te responderá pronto.' : 'A team member will reply soon.', sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }])
|
|
531
|
+
return
|
|
532
|
+
}
|
|
502
533
|
|
|
503
534
|
const botMessage: Message = {
|
|
504
535
|
id: timestamp + 1,
|
|
505
|
-
content: response,
|
|
536
|
+
content: response.text,
|
|
506
537
|
sender: 'bot',
|
|
507
538
|
timestamp: new Date().toISOString()
|
|
508
539
|
}
|
|
@@ -897,11 +928,11 @@ const AndyChat: React.FC<AndyChatProps> = ({
|
|
|
897
928
|
return (
|
|
898
929
|
<div
|
|
899
930
|
key={message.id}
|
|
900
|
-
className={`andy-message-container andy-message-container--${message.sender}`}
|
|
931
|
+
className={`andy-message-container andy-message-container--${message.sender} ${message.isWaiting ? 'andy-message-container--waiting' : ''}`}
|
|
901
932
|
>
|
|
902
933
|
{message.sender === 'bot' && (
|
|
903
934
|
<div
|
|
904
|
-
|
|
935
|
+
className={`andy-message andy-message--bot ${message.isHuman ? 'andy-message--operator' : ''} ${message.isWaiting ? 'andy-message--waiting' : ''} ${message.isError ? 'andy-message--error' : ''}`}
|
|
905
936
|
>
|
|
906
937
|
<div className='andy-bot-header'>
|
|
907
938
|
<div className='andy-bot-header__icon'>
|
|
@@ -919,7 +950,9 @@ const AndyChat: React.FC<AndyChatProps> = ({
|
|
|
919
950
|
/>
|
|
920
951
|
)}
|
|
921
952
|
</div>
|
|
922
|
-
|
|
953
|
+
<span className='andy-bot-header__name'>
|
|
954
|
+
{message.isHuman ? (config?.defaultLang === 'es' ? 'Equipo' : 'Team') : config?.name || 'Andy bot'}
|
|
955
|
+
</span>
|
|
923
956
|
</div>
|
|
924
957
|
<div className='andy-message__content'>
|
|
925
958
|
<div dangerouslySetInnerHTML={{ __html: parseMarkdown(message.content) }} />
|
package/src/hooks/useAndyChat.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
fetchChatbotConfig,
|
|
4
4
|
generateSessionId,
|
|
5
5
|
createGreetingMessage,
|
|
6
|
-
|
|
6
|
+
sendMessageForWidget as sendMessageToAPI,
|
|
7
7
|
submitUserInfo as submitUserInfoAPI,
|
|
8
8
|
initializeConfig,
|
|
9
9
|
validateChatbotConfig,
|
|
@@ -142,10 +142,11 @@ export const useAndyChat = (embedId: string): UseAndyChatReturn => {
|
|
|
142
142
|
|
|
143
143
|
try {
|
|
144
144
|
const response = await sendMessageToAPI(embedId, sessionId, messageText)
|
|
145
|
+
if (response.status === 'waiting') return response
|
|
145
146
|
|
|
146
147
|
const botMessage: Message = {
|
|
147
148
|
id: Date.now() + 1,
|
|
148
|
-
content: response || 'I apologize, but I encountered an error processing your request.',
|
|
149
|
+
content: response.text || 'I apologize, but I encountered an error processing your request.',
|
|
149
150
|
sender: 'bot',
|
|
150
151
|
timestamp: new Date().toISOString()
|
|
151
152
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -47,17 +47,25 @@ export interface ChatbotConfig {
|
|
|
47
47
|
collectUserInfo: CollectUserInfoConfig
|
|
48
48
|
predefinedQuestions: PredefinedQuestion[]
|
|
49
49
|
faqs: FAQItem[]
|
|
50
|
+
bootstrap?: {
|
|
51
|
+
convexUrl: string
|
|
52
|
+
surfaceKey: string
|
|
53
|
+
surfaceProof: string
|
|
54
|
+
surfaceProofExpiresAt: number
|
|
55
|
+
}
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
// Message in the chat
|
|
53
59
|
export interface Message {
|
|
54
|
-
id: number
|
|
60
|
+
id: string | number
|
|
55
61
|
content: string
|
|
56
62
|
sender: 'user' | 'bot'
|
|
57
63
|
timestamp: string
|
|
58
64
|
isForm?: boolean
|
|
59
65
|
isLoading?: boolean
|
|
60
66
|
isError?: boolean
|
|
67
|
+
isHuman?: boolean
|
|
68
|
+
isWaiting?: boolean
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
// User info for form submission
|