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.
- package/README.md +2 -0
- package/dist/index.cjs +67 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +67 -18
- package/dist/index.esm.js.map +1 -1
- package/dist/types/types/index.d.ts +4 -0
- package/package.json +2 -2
- package/src/AndyChat.tsx +31 -23
- package/src/types/index.ts +4 -0
|
@@ -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.
|
|
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.
|
|
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:
|
|
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:
|
|
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 (
|
|
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:
|
|
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',
|
|
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(
|
|
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=
|
|
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=
|
|
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 ?
|
|
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
|
-
{
|
|
988
|
-
|
|
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'>
|
|
1000
|
+
<p className='andy-predefined-questions__title'>{strings.predefinedQuestionsTitle}</p>
|
|
993
1001
|
</div>
|
|
994
1002
|
<div className='andy-predefined-questions__list'>
|
|
995
|
-
{
|
|
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 ?
|
|
1019
|
-
aria-label=
|
|
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=
|
|
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=
|
|
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=
|
|
1101
|
+
aria-label={strings.closeWelcomeMessage}
|
|
1094
1102
|
onClick={() => setShowWelcomePopup(false)}
|
|
1095
1103
|
>
|
|
1096
1104
|
<svg
|
package/src/types/index.ts
CHANGED
|
@@ -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') */
|