andy-widget-react 2.0.6 → 2.1.0
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 +44 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +44 -13
- 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 +21 -16
- package/src/types/index.ts +4 -0
package/README.md
CHANGED
|
@@ -46,6 +46,8 @@ Widget as a short, language-aware retry message.
|
|
|
46
46
|
| `description` | `string` | Override the configured description |
|
|
47
47
|
| `welcomeMessage` | `string` | Override the configured first message |
|
|
48
48
|
| `bubbleLabel` | `string` | Override the configured pill label |
|
|
49
|
+
| `lang` | `'en' \| 'es'` | Language for the Widget's own chrome (placeholder, starter-question title, error and handoff text). Overrides the Agent's `defaultLang`, so a Spanish Agent on an English page still renders English chrome |
|
|
50
|
+
| `predefinedQuestions` | `{ order: number, question: string }[]` | Override the configured starter questions |
|
|
49
51
|
| `theme` | `'light' \| 'dark' \| 'auto'` | Override the configured theme |
|
|
50
52
|
| `orientation` | `'left' \| 'right'` | Override the configured position |
|
|
51
53
|
| `brandColor` | `string` | Override the configured six-digit hex color |
|
package/dist/index.cjs
CHANGED
|
@@ -3437,6 +3437,34 @@ const SVG_ICONS = {
|
|
|
3437
3437
|
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
|
|
3438
3438
|
</svg>`};
|
|
3439
3439
|
|
|
3440
|
+
// Localized UI Strings
|
|
3441
|
+
// Chrome the widget renders itself, as opposed to copy the Agent supplies.
|
|
3442
|
+
// Keyed by language so React and Vanilla render identical text for a visitor.
|
|
3443
|
+
const UI_STRINGS = {
|
|
3444
|
+
en: {
|
|
3445
|
+
inputPlaceholder: 'Type your message...',
|
|
3446
|
+
predefinedQuestionsTitle: 'What would you like to know?',
|
|
3447
|
+
sendFailure: 'Sorry, I could not answer. Please try again.',
|
|
3448
|
+
handoffWaiting: 'A team member will reply soon.',
|
|
3449
|
+
teamName: 'Team'
|
|
3450
|
+
},
|
|
3451
|
+
es: {
|
|
3452
|
+
inputPlaceholder: 'Escribe tu mensaje...',
|
|
3453
|
+
predefinedQuestionsTitle: '¿Qué te gustaría saber?',
|
|
3454
|
+
sendFailure: 'Lo siento, no pude responder. Inténtalo de nuevo.',
|
|
3455
|
+
handoffWaiting: 'Un miembro del equipo te responderá pronto.',
|
|
3456
|
+
teamName: 'Equipo'
|
|
3457
|
+
}
|
|
3458
|
+
};
|
|
3459
|
+
|
|
3460
|
+
/**
|
|
3461
|
+
* Get the widget's own UI strings for a language.
|
|
3462
|
+
* Unknown languages fall back to English rather than rendering blanks.
|
|
3463
|
+
* @param {string} [lang] - Language code ('en' or 'es')
|
|
3464
|
+
* @returns {typeof UI_STRINGS.en} The localized string set
|
|
3465
|
+
*/
|
|
3466
|
+
const getUiStrings = lang => UI_STRINGS[lang] || UI_STRINGS.en;
|
|
3467
|
+
|
|
3440
3468
|
/**
|
|
3441
3469
|
* Keyboard detection utility for handling mobile virtual keyboards
|
|
3442
3470
|
* Provides cross-browser support for detecting keyboard open/close events
|
|
@@ -3855,8 +3883,13 @@ const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
|
|
|
3855
3883
|
}
|
|
3856
3884
|
};
|
|
3857
3885
|
|
|
3858
|
-
const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessage, bubbleLabel, theme, orientation, brandColor, bubbleStyle, useBrandColor, logoUrl, previewMode = false, initialOpen = false }) => {
|
|
3886
|
+
const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessage, bubbleLabel, lang, predefinedQuestions, theme, orientation, brandColor, bubbleStyle, useBrandColor, logoUrl, previewMode = false, initialOpen = false }) => {
|
|
3859
3887
|
const [config, setConfig] = react.useState(null);
|
|
3888
|
+
// The visitor's language wins over the Agent's own default: a Spanish Agent
|
|
3889
|
+
// embedded on an English page must still render English chrome.
|
|
3890
|
+
const uiLang = lang || config?.defaultLang || 'en';
|
|
3891
|
+
const strings = getUiStrings(uiLang);
|
|
3892
|
+
const activeQuestions = predefinedQuestions?.length ? predefinedQuestions : config?.predefinedQuestions;
|
|
3860
3893
|
const [isOpen, setIsOpen] = react.useState(initialOpen);
|
|
3861
3894
|
const [messages, setMessages] = react.useState([]);
|
|
3862
3895
|
const [isLoading, setIsLoading] = react.useState(false);
|
|
@@ -3887,7 +3920,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
3887
3920
|
pendingHandoffIdsRef.current = projection.pendingProjectionIds || [];
|
|
3888
3921
|
const projectedMessages = projection.messages.map(message => ({ id: message.id, content: message.content, sender: message.role === 'user' ? 'user' : 'bot', timestamp: new Date(message.createdAt).toISOString(), isHuman: message.sentByHuman }));
|
|
3889
3922
|
setMessages(projection.control === 'requested' || projection.control === 'human'
|
|
3890
|
-
? [...projectedMessages, { id: 'handoff-waiting', content:
|
|
3923
|
+
? [...projectedMessages, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]
|
|
3891
3924
|
: projectedMessages);
|
|
3892
3925
|
});
|
|
3893
3926
|
handoffRef.current = handoff;
|
|
@@ -4076,12 +4109,12 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4076
4109
|
bubbleStyle: bubbleStyle || 'icon',
|
|
4077
4110
|
bubbleLabel: bubbleLabel || null,
|
|
4078
4111
|
useBrandColor: useBrandColor !== undefined ? useBrandColor : true,
|
|
4079
|
-
defaultLang:
|
|
4112
|
+
defaultLang: uiLang,
|
|
4080
4113
|
showBranding: true,
|
|
4081
4114
|
showWelcomePopup: { enabled: false, seconds: 3, message: '' },
|
|
4082
4115
|
collectUserInfo: { enabled: false, message: '' },
|
|
4083
4116
|
welcomeMessage: welcomeMessage || 'Hi there! How can I help you today?',
|
|
4084
|
-
predefinedQuestions: [],
|
|
4117
|
+
predefinedQuestions: predefinedQuestions || [],
|
|
4085
4118
|
faqs: []
|
|
4086
4119
|
};
|
|
4087
4120
|
setConfig(previewConfig);
|
|
@@ -4234,7 +4267,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4234
4267
|
setIsLoading(true);
|
|
4235
4268
|
const response = await sendMessageForWidget(embedId, sessionId, messageText);
|
|
4236
4269
|
if (response.status === 'waiting') {
|
|
4237
|
-
setMessages(prev => prev.some(message => message.isWaiting) ? prev : [...prev, { id: 'handoff-waiting', content:
|
|
4270
|
+
setMessages(prev => prev.some(message => message.isWaiting) ? prev : [...prev, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]);
|
|
4238
4271
|
return;
|
|
4239
4272
|
}
|
|
4240
4273
|
const botMessage = {
|
|
@@ -4261,9 +4294,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4261
4294
|
console.error('AndyChat: Message request failed.');
|
|
4262
4295
|
const errorMessage = {
|
|
4263
4296
|
id: timestamp + 3,
|
|
4264
|
-
content:
|
|
4265
|
-
? 'Lo siento, no pude responder. Inténtalo de nuevo.'
|
|
4266
|
-
: 'Sorry, I could not answer. Please try again.',
|
|
4297
|
+
content: strings.sendFailure,
|
|
4267
4298
|
sender: 'bot',
|
|
4268
4299
|
timestamp: new Date().toISOString(),
|
|
4269
4300
|
isError: true
|
|
@@ -4379,7 +4410,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4379
4410
|
bubble.addEventListener('click', () => setIsOpen(prev => !prev));
|
|
4380
4411
|
}
|
|
4381
4412
|
const isPillStyle = config?.bubbleStyle === 'pill';
|
|
4382
|
-
const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(
|
|
4413
|
+
const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(uiLang) : null);
|
|
4383
4414
|
const bubbleLogoUrl = config?.logoUrl || CHAT_BUBBLE_DEFAULT_ICON_URL;
|
|
4384
4415
|
if (bubble) {
|
|
4385
4416
|
bubble.className = 'andy-chat-bubble';
|
|
@@ -4432,10 +4463,10 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4432
4463
|
if (message.isLoading) {
|
|
4433
4464
|
return (jsxRuntime.jsx("div", { className: 'andy-message-container andy-message-container--bot andy-message-container--loading', children: jsxRuntime.jsx("div", { className: 'andy-message andy-message--bot', children: jsxRuntime.jsxs("div", { className: 'andy-typing-indicator', children: [jsxRuntime.jsx("div", { className: 'andy-typing-indicator__dot' }), jsxRuntime.jsx("div", { className: 'andy-typing-indicator__dot' }), jsxRuntime.jsx("div", { className: 'andy-typing-indicator__dot' })] }) }) }, message.id));
|
|
4434
4465
|
}
|
|
4435
|
-
return (jsxRuntime.jsxs("div", { className: `andy-message-container andy-message-container--${message.sender} ${message.isWaiting ? 'andy-message-container--waiting' : ''}`, children: [message.sender === 'bot' && (jsxRuntime.jsxs("div", { className: `andy-message andy-message--bot ${message.isHuman ? 'andy-message--operator' : ''} ${message.isWaiting ? 'andy-message--waiting' : ''} ${message.isError ? 'andy-message--error' : ''}`, children: [jsxRuntime.jsxs("div", { className: 'andy-bot-header', children: [jsxRuntime.jsx("div", { className: 'andy-bot-header__icon', children: config?.logoUrl ? (jsxRuntime.jsx("img", { src: config.logoUrl, alt: 'Bot Avatar', className: 'andy-bot-header__icon-image' })) : (jsxRuntime.jsx("img", { src: CHAT_BUBBLE_DEFAULT_ICON_URL, alt: 'Bot Avatar', className: 'andy-bot-header__icon-image andy-bot-header__icon-image--default' })) }), jsxRuntime.jsx("span", { className: 'andy-bot-header__name', children: message.isHuman ?
|
|
4436
|
-
}), isLoading && (jsxRuntime.jsx("div", { className: 'andy-message-container andy-message-container--bot andy-message-container--typing', children: jsxRuntime.jsx("div", { className: 'andy-message andy-message--bot andy-message--typing', children: jsxRuntime.jsxs("div", { className: 'andy-typing-indicator', children: [jsxRuntime.jsx("div", { className: 'andy-typing-indicator__dot' }), jsxRuntime.jsx("div", { className: 'andy-typing-indicator__dot' }), jsxRuntime.jsx("div", { className: 'andy-typing-indicator__dot' })] }) }) }))] }), jsxRuntime.jsxs("div", { className: 'andy-chat-window__bottom', children: [
|
|
4437
|
-
|
|
4438
|
-
showPredefinedQuestions && (jsxRuntime.jsxs("div", { className: 'andy-predefined-questions', id: 'andy-predefined-questions', children: [jsxRuntime.jsx("div", { className: 'andy-predefined-questions__header', children: jsxRuntime.jsx("p", { className: 'andy-predefined-questions__title', children:
|
|
4466
|
+
return (jsxRuntime.jsxs("div", { className: `andy-message-container andy-message-container--${message.sender} ${message.isWaiting ? 'andy-message-container--waiting' : ''}`, children: [message.sender === 'bot' && (jsxRuntime.jsxs("div", { className: `andy-message andy-message--bot ${message.isHuman ? 'andy-message--operator' : ''} ${message.isWaiting ? 'andy-message--waiting' : ''} ${message.isError ? 'andy-message--error' : ''}`, children: [jsxRuntime.jsxs("div", { className: 'andy-bot-header', children: [jsxRuntime.jsx("div", { className: 'andy-bot-header__icon', children: config?.logoUrl ? (jsxRuntime.jsx("img", { src: config.logoUrl, alt: 'Bot Avatar', className: 'andy-bot-header__icon-image' })) : (jsxRuntime.jsx("img", { src: CHAT_BUBBLE_DEFAULT_ICON_URL, alt: 'Bot Avatar', className: 'andy-bot-header__icon-image andy-bot-header__icon-image--default' })) }), jsxRuntime.jsx("span", { className: 'andy-bot-header__name', children: message.isHuman ? strings.teamName : config?.name || 'Andy bot' })] }), jsxRuntime.jsx("div", { className: 'andy-message__content', children: jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: parseMarkdown(message.content) } }) })] })), message.sender === 'user' && (jsxRuntime.jsx("div", { className: 'andy-message andy-message--user', children: jsxRuntime.jsx("div", { className: 'andy-message__content', children: jsxRuntime.jsx("p", { children: message.content }) }) }))] }, message.id));
|
|
4467
|
+
}), isLoading && (jsxRuntime.jsx("div", { className: 'andy-message-container andy-message-container--bot andy-message-container--typing', children: jsxRuntime.jsx("div", { className: 'andy-message andy-message--bot andy-message--typing', children: jsxRuntime.jsxs("div", { className: 'andy-typing-indicator', children: [jsxRuntime.jsx("div", { className: 'andy-typing-indicator__dot' }), jsxRuntime.jsx("div", { className: 'andy-typing-indicator__dot' }), jsxRuntime.jsx("div", { className: 'andy-typing-indicator__dot' })] }) }) }))] }), jsxRuntime.jsxs("div", { className: 'andy-chat-window__bottom', children: [activeQuestions &&
|
|
4468
|
+
activeQuestions.length > 0 &&
|
|
4469
|
+
showPredefinedQuestions && (jsxRuntime.jsxs("div", { className: 'andy-predefined-questions', id: 'andy-predefined-questions', children: [jsxRuntime.jsx("div", { className: 'andy-predefined-questions__header', children: jsxRuntime.jsx("p", { className: 'andy-predefined-questions__title', children: strings.predefinedQuestionsTitle }) }), jsxRuntime.jsx("div", { className: 'andy-predefined-questions__list', children: activeQuestions.map((q, index) => (jsxRuntime.jsxs("button", { type: 'button', className: 'andy-predefined-question', "data-question": q.question, disabled: isCollectingUserInfo || isLoading || !config, children: [jsxRuntime.jsx("span", { className: 'andy-predefined-question__icon', children: "\uD83D\uDCAC" }), jsxRuntime.jsx("span", { className: 'andy-predefined-question__text', children: q.question })] }, index))) })] })), jsxRuntime.jsx("div", { className: 'andy-chat-window__input', children: jsxRuntime.jsx("div", { className: 'andy-input-container', children: jsxRuntime.jsxs("div", { className: 'andy-input', children: [jsxRuntime.jsx("input", { type: 'text', className: 'andy-input__field', id: 'andy-message-input', placeholder: config ? strings.inputPlaceholder : '...', "aria-label": 'Message input', onKeyDown: e => {
|
|
4439
4470
|
if (e.key === 'Enter' && e.target.value.trim()) {
|
|
4440
4471
|
handleSendMessage(e.target.value);
|
|
4441
4472
|
e.target.value = '';
|