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.
@@ -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.5",
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.5"
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
- sendMessage as sendMessageToAPI,
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
- className={`andy-message andy-message--bot ${message.isError ? 'andy-message--error' : ''}`}
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
- <span className='andy-bot-header__name'>{config?.name || 'Andy bot'}</span>
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) }} />
@@ -3,7 +3,7 @@ import {
3
3
  fetchChatbotConfig,
4
4
  generateSessionId,
5
5
  createGreetingMessage,
6
- sendMessage as sendMessageToAPI,
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
  }
@@ -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