andy-widget-react 2.0.6 → 2.1.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.
@@ -74,6 +74,10 @@ export interface AndyChatProps {
74
74
  welcomeMessage?: string;
75
75
  /** Optional custom label for pill-style bubble (overrides default) */
76
76
  bubbleLabel?: string;
77
+ /** Optional UI language for the widget's own chrome, overrides the Agent's defaultLang */
78
+ lang?: 'en' | 'es';
79
+ /** Optional starter questions (overrides API value) */
80
+ predefinedQuestions?: PredefinedQuestion[];
77
81
  /** Optional theme ('light' | 'dark' | 'auto') */
78
82
  theme?: 'light' | 'dark' | 'auto';
79
83
  /** Optional position ('left' | 'right') */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "andy-widget-react",
3
- "version": "2.0.6",
3
+ "version": "2.1.1",
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.6"
70
+ "andy-widget-core": "2.1.1"
71
71
  }
72
72
  }
package/src/AndyChat.tsx CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  FOOTER_TEXT,
19
19
  getFooterUrl,
20
20
  getDefaultBubbleLabel,
21
+ getUiStrings,
21
22
  getKeyboardDetector,
22
23
  updateKeyboardHeight,
23
24
  isMobileDevice
@@ -74,6 +75,8 @@ const AndyChat: React.FC<AndyChatProps> = ({
74
75
  description,
75
76
  welcomeMessage,
76
77
  bubbleLabel,
78
+ lang,
79
+ predefinedQuestions,
77
80
  theme,
78
81
  orientation,
79
82
  brandColor,
@@ -84,6 +87,11 @@ const AndyChat: React.FC<AndyChatProps> = ({
84
87
  initialOpen = false
85
88
  }) => {
86
89
  const [config, setConfig] = useState<ChatbotConfig | null>(null)
90
+ // The visitor's language wins over the Agent's own default: a Spanish Agent
91
+ // embedded on an English page must still render English chrome.
92
+ const uiLang = lang || config?.defaultLang || 'en'
93
+ const strings = getUiStrings(uiLang)
94
+ const activeQuestions = predefinedQuestions?.length ? predefinedQuestions : config?.predefinedQuestions
87
95
  const [isOpen, setIsOpen] = useState(initialOpen)
88
96
  const [messages, setMessages] = useState<Message[]>([])
89
97
  const [isLoading, setIsLoading] = useState(false)
@@ -113,8 +121,10 @@ const AndyChat: React.FC<AndyChatProps> = ({
113
121
  setSessionId(projection.identity.sessionId)
114
122
  pendingHandoffIdsRef.current = projection.pendingProjectionIds || []
115
123
  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 }))
124
+ // An empty agent projection does not mean the client-only greeting is gone.
125
+ if (projection.control === 'agent' && projectedMessages.length === 0) return
116
126
  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 }]
127
+ ? [...projectedMessages, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]
118
128
  : projectedMessages)
119
129
  })
120
130
  handoffRef.current = handoff
@@ -326,12 +336,12 @@ const AndyChat: React.FC<AndyChatProps> = ({
326
336
  bubbleStyle: bubbleStyle || 'icon',
327
337
  bubbleLabel: bubbleLabel || null,
328
338
  useBrandColor: useBrandColor !== undefined ? useBrandColor : true,
329
- defaultLang: 'en',
339
+ defaultLang: uiLang,
330
340
  showBranding: true,
331
341
  showWelcomePopup: { enabled: false, seconds: 3, message: '' },
332
342
  collectUserInfo: { enabled: false, message: '' },
333
343
  welcomeMessage: welcomeMessage || 'Hi there! How can I help you today?',
334
- predefinedQuestions: [],
344
+ predefinedQuestions: predefinedQuestions || [],
335
345
  faqs: []
336
346
  }
337
347
  setConfig(previewConfig)
@@ -495,7 +505,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
495
505
  setMessages([])
496
506
  }
497
507
 
498
- if (config?.predefinedQuestions && config.predefinedQuestions.length > 0) {
508
+ if (activeQuestions && activeQuestions.length > 0) {
499
509
  setShowPredefinedQuestions(true)
500
510
  }
501
511
  }
@@ -527,7 +537,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
527
537
  setIsLoading(true)
528
538
  const response = await sendMessageToAPI(embedId!, sessionId, messageText)
529
539
  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 }])
540
+ setMessages(prev => prev.some(message => message.isWaiting) ? prev : [...prev, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }])
531
541
  return
532
542
  }
533
543
 
@@ -556,10 +566,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
556
566
  console.error('AndyChat: Message request failed.')
557
567
  const errorMessage: Message = {
558
568
  id: timestamp + 3,
559
- content:
560
- config?.defaultLang === 'es'
561
- ? 'Lo siento, no pude responder. Inténtalo de nuevo.'
562
- : 'Sorry, I could not answer. Please try again.',
569
+ content: strings.sendFailure,
563
570
  sender: 'bot',
564
571
  timestamp: new Date().toISOString(),
565
572
  isError: true
@@ -691,7 +698,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
691
698
  bubble = document.createElement('button')
692
699
  bubble.type = 'button'
693
700
  bubble.className = 'andy-chat-bubble'
694
- bubble.setAttribute('aria-label', 'Open chat')
701
+ bubble.setAttribute('aria-label', strings.openChat)
695
702
 
696
703
  const tooltipHTML = tooltip
697
704
  ? `<span class="andy-chat-bubble__tooltip" role="tooltip">${tooltip}</span>`
@@ -704,10 +711,11 @@ const AndyChat: React.FC<AndyChatProps> = ({
704
711
 
705
712
  const isPillStyle = config?.bubbleStyle === 'pill'
706
713
  const displayLabel =
707
- bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(config?.defaultLang || 'en') : null)
714
+ bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(uiLang) : null)
708
715
  const bubbleLogoUrl = config?.logoUrl || CHAT_BUBBLE_DEFAULT_ICON_URL
709
716
 
710
717
  if (bubble) {
718
+ bubble.setAttribute('aria-label', isOpen ? strings.closeChat : strings.openChat)
711
719
  bubble.className = 'andy-chat-bubble'
712
720
  if (isPillStyle) {
713
721
  bubble.classList.add('andy-chat-bubble--pill')
@@ -770,7 +778,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
770
778
  type='button'
771
779
  className='andy-header__back'
772
780
  id='andy-back-button'
773
- aria-label='Go back'
781
+ aria-label={strings.goBack}
774
782
  onClick={() => setIsOpen(false)}
775
783
  >
776
784
  <svg
@@ -816,7 +824,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
816
824
  type='button'
817
825
  className='andy-header__close'
818
826
  id='andy-close-button'
819
- aria-label='Close chat'
827
+ aria-label={strings.closeChat}
820
828
  onClick={() => setIsOpen(false)}
821
829
  >
822
830
  <svg
@@ -951,7 +959,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
951
959
  )}
952
960
  </div>
953
961
  <span className='andy-bot-header__name'>
954
- {message.isHuman ? (config?.defaultLang === 'es' ? 'Equipo' : 'Team') : config?.name || 'Andy bot'}
962
+ {message.isHuman ? strings.teamName : config?.name || 'Andy bot'}
955
963
  </span>
956
964
  </div>
957
965
  <div className='andy-message__content'>
@@ -984,15 +992,15 @@ const AndyChat: React.FC<AndyChatProps> = ({
984
992
  </div>
985
993
 
986
994
  <div className='andy-chat-window__bottom'>
987
- {config?.predefinedQuestions &&
988
- config.predefinedQuestions.length > 0 &&
995
+ {activeQuestions &&
996
+ activeQuestions.length > 0 &&
989
997
  showPredefinedQuestions && (
990
998
  <div className='andy-predefined-questions' id='andy-predefined-questions'>
991
999
  <div className='andy-predefined-questions__header'>
992
- <p className='andy-predefined-questions__title'>What would you like to know?</p>
1000
+ <p className='andy-predefined-questions__title'>{strings.predefinedQuestionsTitle}</p>
993
1001
  </div>
994
1002
  <div className='andy-predefined-questions__list'>
995
- {config.predefinedQuestions.map((q, index) => (
1003
+ {activeQuestions.map((q, index) => (
996
1004
  <button
997
1005
  key={index}
998
1006
  type='button'
@@ -1015,8 +1023,8 @@ const AndyChat: React.FC<AndyChatProps> = ({
1015
1023
  type='text'
1016
1024
  className='andy-input__field'
1017
1025
  id='andy-message-input'
1018
- placeholder={config ? 'Type your message...' : '...'}
1019
- aria-label='Message input'
1026
+ placeholder={config ? strings.inputPlaceholder : '...'}
1027
+ aria-label={strings.messageInput}
1020
1028
  onKeyDown={e => {
1021
1029
  if (e.key === 'Enter' && (e.target as HTMLInputElement).value.trim()) {
1022
1030
  handleSendMessage((e.target as HTMLInputElement).value)
@@ -1029,7 +1037,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
1029
1037
  type='button'
1030
1038
  className='andy-input__button'
1031
1039
  id='andy-send-button'
1032
- aria-label='Send message'
1040
+ aria-label={strings.sendMessage}
1033
1041
  onClick={() => {
1034
1042
  const input = shadowRef.current?.getElementById(
1035
1043
  'andy-message-input'
@@ -1079,7 +1087,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
1079
1087
  className={`andy-welcome-popup ${config?.orientation === 'left' ? 'andychat-popup-left' : ''}`}
1080
1088
  style={{ display: 'block' }}
1081
1089
  role='dialog'
1082
- aria-label='Welcome message'
1090
+ aria-label={strings.welcomeMessage}
1083
1091
  >
1084
1092
  <div className='andy-welcome-popup__content'>
1085
1093
  <div className='andy-welcome-popup__message'>
@@ -1090,7 +1098,7 @@ const AndyChat: React.FC<AndyChatProps> = ({
1090
1098
  <button
1091
1099
  type='button'
1092
1100
  className='andy-welcome-popup__close'
1093
- aria-label='Close welcome message'
1101
+ aria-label={strings.closeWelcomeMessage}
1094
1102
  onClick={() => setShowWelcomePopup(false)}
1095
1103
  >
1096
1104
  <svg
@@ -90,6 +90,10 @@ export interface AndyChatProps {
90
90
  welcomeMessage?: string
91
91
  /** Optional custom label for pill-style bubble (overrides default) */
92
92
  bubbleLabel?: string
93
+ /** Optional UI language for the widget's own chrome, overrides the Agent's defaultLang */
94
+ lang?: 'en' | 'es'
95
+ /** Optional starter questions (overrides API value) */
96
+ predefinedQuestions?: PredefinedQuestion[]
93
97
  /** Optional theme ('light' | 'dark' | 'auto') */
94
98
  theme?: 'light' | 'dark' | 'auto'
95
99
  /** Optional position ('left' | 'right') */