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/dist/index.esm.js CHANGED
@@ -3433,6 +3433,34 @@ const SVG_ICONS = {
3433
3433
  <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
3434
3434
  </svg>`};
3435
3435
 
3436
+ // Localized UI Strings
3437
+ // Chrome the widget renders itself, as opposed to copy the Agent supplies.
3438
+ // Keyed by language so React and Vanilla render identical text for a visitor.
3439
+ const UI_STRINGS = {
3440
+ en: {
3441
+ inputPlaceholder: 'Type your message...',
3442
+ predefinedQuestionsTitle: 'What would you like to know?',
3443
+ sendFailure: 'Sorry, I could not answer. Please try again.',
3444
+ handoffWaiting: 'A team member will reply soon.',
3445
+ teamName: 'Team'
3446
+ },
3447
+ es: {
3448
+ inputPlaceholder: 'Escribe tu mensaje...',
3449
+ predefinedQuestionsTitle: '¿Qué te gustaría saber?',
3450
+ sendFailure: 'Lo siento, no pude responder. Inténtalo de nuevo.',
3451
+ handoffWaiting: 'Un miembro del equipo te responderá pronto.',
3452
+ teamName: 'Equipo'
3453
+ }
3454
+ };
3455
+
3456
+ /**
3457
+ * Get the widget's own UI strings for a language.
3458
+ * Unknown languages fall back to English rather than rendering blanks.
3459
+ * @param {string} [lang] - Language code ('en' or 'es')
3460
+ * @returns {typeof UI_STRINGS.en} The localized string set
3461
+ */
3462
+ const getUiStrings = lang => UI_STRINGS[lang] || UI_STRINGS.en;
3463
+
3436
3464
  /**
3437
3465
  * Keyboard detection utility for handling mobile virtual keyboards
3438
3466
  * Provides cross-browser support for detecting keyboard open/close events
@@ -3851,8 +3879,13 @@ const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
3851
3879
  }
3852
3880
  };
3853
3881
 
3854
- const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessage, bubbleLabel, theme, orientation, brandColor, bubbleStyle, useBrandColor, logoUrl, previewMode = false, initialOpen = false }) => {
3882
+ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessage, bubbleLabel, lang, predefinedQuestions, theme, orientation, brandColor, bubbleStyle, useBrandColor, logoUrl, previewMode = false, initialOpen = false }) => {
3855
3883
  const [config, setConfig] = useState(null);
3884
+ // The visitor's language wins over the Agent's own default: a Spanish Agent
3885
+ // embedded on an English page must still render English chrome.
3886
+ const uiLang = lang || config?.defaultLang || 'en';
3887
+ const strings = getUiStrings(uiLang);
3888
+ const activeQuestions = predefinedQuestions?.length ? predefinedQuestions : config?.predefinedQuestions;
3856
3889
  const [isOpen, setIsOpen] = useState(initialOpen);
3857
3890
  const [messages, setMessages] = useState([]);
3858
3891
  const [isLoading, setIsLoading] = useState(false);
@@ -3883,7 +3916,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
3883
3916
  pendingHandoffIdsRef.current = projection.pendingProjectionIds || [];
3884
3917
  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 }));
3885
3918
  setMessages(projection.control === 'requested' || projection.control === 'human'
3886
- ? [...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 }]
3919
+ ? [...projectedMessages, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]
3887
3920
  : projectedMessages);
3888
3921
  });
3889
3922
  handoffRef.current = handoff;
@@ -4072,12 +4105,12 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
4072
4105
  bubbleStyle: bubbleStyle || 'icon',
4073
4106
  bubbleLabel: bubbleLabel || null,
4074
4107
  useBrandColor: useBrandColor !== undefined ? useBrandColor : true,
4075
- defaultLang: 'en',
4108
+ defaultLang: uiLang,
4076
4109
  showBranding: true,
4077
4110
  showWelcomePopup: { enabled: false, seconds: 3, message: '' },
4078
4111
  collectUserInfo: { enabled: false, message: '' },
4079
4112
  welcomeMessage: welcomeMessage || 'Hi there! How can I help you today?',
4080
- predefinedQuestions: [],
4113
+ predefinedQuestions: predefinedQuestions || [],
4081
4114
  faqs: []
4082
4115
  };
4083
4116
  setConfig(previewConfig);
@@ -4230,7 +4263,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
4230
4263
  setIsLoading(true);
4231
4264
  const response = await sendMessageForWidget(embedId, sessionId, messageText);
4232
4265
  if (response.status === 'waiting') {
4233
- 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 }]);
4266
+ setMessages(prev => prev.some(message => message.isWaiting) ? prev : [...prev, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]);
4234
4267
  return;
4235
4268
  }
4236
4269
  const botMessage = {
@@ -4257,9 +4290,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
4257
4290
  console.error('AndyChat: Message request failed.');
4258
4291
  const errorMessage = {
4259
4292
  id: timestamp + 3,
4260
- content: config?.defaultLang === 'es'
4261
- ? 'Lo siento, no pude responder. Inténtalo de nuevo.'
4262
- : 'Sorry, I could not answer. Please try again.',
4293
+ content: strings.sendFailure,
4263
4294
  sender: 'bot',
4264
4295
  timestamp: new Date().toISOString(),
4265
4296
  isError: true
@@ -4375,7 +4406,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
4375
4406
  bubble.addEventListener('click', () => setIsOpen(prev => !prev));
4376
4407
  }
4377
4408
  const isPillStyle = config?.bubbleStyle === 'pill';
4378
- const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(config?.defaultLang || 'en') : null);
4409
+ const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(uiLang) : null);
4379
4410
  const bubbleLogoUrl = config?.logoUrl || CHAT_BUBBLE_DEFAULT_ICON_URL;
4380
4411
  if (bubble) {
4381
4412
  bubble.className = 'andy-chat-bubble';
@@ -4428,10 +4459,10 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
4428
4459
  if (message.isLoading) {
4429
4460
  return (jsx("div", { className: 'andy-message-container andy-message-container--bot andy-message-container--loading', children: jsx("div", { className: 'andy-message andy-message--bot', children: jsxs("div", { className: 'andy-typing-indicator', children: [jsx("div", { className: 'andy-typing-indicator__dot' }), jsx("div", { className: 'andy-typing-indicator__dot' }), jsx("div", { className: 'andy-typing-indicator__dot' })] }) }) }, message.id));
4430
4461
  }
4431
- return (jsxs("div", { className: `andy-message-container andy-message-container--${message.sender} ${message.isWaiting ? 'andy-message-container--waiting' : ''}`, children: [message.sender === 'bot' && (jsxs("div", { className: `andy-message andy-message--bot ${message.isHuman ? 'andy-message--operator' : ''} ${message.isWaiting ? 'andy-message--waiting' : ''} ${message.isError ? 'andy-message--error' : ''}`, children: [jsxs("div", { className: 'andy-bot-header', children: [jsx("div", { className: 'andy-bot-header__icon', children: config?.logoUrl ? (jsx("img", { src: config.logoUrl, alt: 'Bot Avatar', className: 'andy-bot-header__icon-image' })) : (jsx("img", { src: CHAT_BUBBLE_DEFAULT_ICON_URL, alt: 'Bot Avatar', className: 'andy-bot-header__icon-image andy-bot-header__icon-image--default' })) }), jsx("span", { className: 'andy-bot-header__name', children: message.isHuman ? (config?.defaultLang === 'es' ? 'Equipo' : 'Team') : config?.name || 'Andy bot' })] }), jsx("div", { className: 'andy-message__content', children: jsx("div", { dangerouslySetInnerHTML: { __html: parseMarkdown(message.content) } }) })] })), message.sender === 'user' && (jsx("div", { className: 'andy-message andy-message--user', children: jsx("div", { className: 'andy-message__content', children: jsx("p", { children: message.content }) }) }))] }, message.id));
4432
- }), isLoading && (jsx("div", { className: 'andy-message-container andy-message-container--bot andy-message-container--typing', children: jsx("div", { className: 'andy-message andy-message--bot andy-message--typing', children: jsxs("div", { className: 'andy-typing-indicator', children: [jsx("div", { className: 'andy-typing-indicator__dot' }), jsx("div", { className: 'andy-typing-indicator__dot' }), jsx("div", { className: 'andy-typing-indicator__dot' })] }) }) }))] }), jsxs("div", { className: 'andy-chat-window__bottom', children: [config?.predefinedQuestions &&
4433
- config.predefinedQuestions.length > 0 &&
4434
- showPredefinedQuestions && (jsxs("div", { className: 'andy-predefined-questions', id: 'andy-predefined-questions', children: [jsx("div", { className: 'andy-predefined-questions__header', children: jsx("p", { className: 'andy-predefined-questions__title', children: "What would you like to know?" }) }), jsx("div", { className: 'andy-predefined-questions__list', children: config.predefinedQuestions.map((q, index) => (jsxs("button", { type: 'button', className: 'andy-predefined-question', "data-question": q.question, disabled: isCollectingUserInfo || isLoading || !config, children: [jsx("span", { className: 'andy-predefined-question__icon', children: "\uD83D\uDCAC" }), jsx("span", { className: 'andy-predefined-question__text', children: q.question })] }, index))) })] })), jsx("div", { className: 'andy-chat-window__input', children: jsx("div", { className: 'andy-input-container', children: jsxs("div", { className: 'andy-input', children: [jsx("input", { type: 'text', className: 'andy-input__field', id: 'andy-message-input', placeholder: config ? 'Type your message...' : '...', "aria-label": 'Message input', onKeyDown: e => {
4462
+ return (jsxs("div", { className: `andy-message-container andy-message-container--${message.sender} ${message.isWaiting ? 'andy-message-container--waiting' : ''}`, children: [message.sender === 'bot' && (jsxs("div", { className: `andy-message andy-message--bot ${message.isHuman ? 'andy-message--operator' : ''} ${message.isWaiting ? 'andy-message--waiting' : ''} ${message.isError ? 'andy-message--error' : ''}`, children: [jsxs("div", { className: 'andy-bot-header', children: [jsx("div", { className: 'andy-bot-header__icon', children: config?.logoUrl ? (jsx("img", { src: config.logoUrl, alt: 'Bot Avatar', className: 'andy-bot-header__icon-image' })) : (jsx("img", { src: CHAT_BUBBLE_DEFAULT_ICON_URL, alt: 'Bot Avatar', className: 'andy-bot-header__icon-image andy-bot-header__icon-image--default' })) }), jsx("span", { className: 'andy-bot-header__name', children: message.isHuman ? strings.teamName : config?.name || 'Andy bot' })] }), jsx("div", { className: 'andy-message__content', children: jsx("div", { dangerouslySetInnerHTML: { __html: parseMarkdown(message.content) } }) })] })), message.sender === 'user' && (jsx("div", { className: 'andy-message andy-message--user', children: jsx("div", { className: 'andy-message__content', children: jsx("p", { children: message.content }) }) }))] }, message.id));
4463
+ }), isLoading && (jsx("div", { className: 'andy-message-container andy-message-container--bot andy-message-container--typing', children: jsx("div", { className: 'andy-message andy-message--bot andy-message--typing', children: jsxs("div", { className: 'andy-typing-indicator', children: [jsx("div", { className: 'andy-typing-indicator__dot' }), jsx("div", { className: 'andy-typing-indicator__dot' }), jsx("div", { className: 'andy-typing-indicator__dot' })] }) }) }))] }), jsxs("div", { className: 'andy-chat-window__bottom', children: [activeQuestions &&
4464
+ activeQuestions.length > 0 &&
4465
+ showPredefinedQuestions && (jsxs("div", { className: 'andy-predefined-questions', id: 'andy-predefined-questions', children: [jsx("div", { className: 'andy-predefined-questions__header', children: jsx("p", { className: 'andy-predefined-questions__title', children: strings.predefinedQuestionsTitle }) }), jsx("div", { className: 'andy-predefined-questions__list', children: activeQuestions.map((q, index) => (jsxs("button", { type: 'button', className: 'andy-predefined-question', "data-question": q.question, disabled: isCollectingUserInfo || isLoading || !config, children: [jsx("span", { className: 'andy-predefined-question__icon', children: "\uD83D\uDCAC" }), jsx("span", { className: 'andy-predefined-question__text', children: q.question })] }, index))) })] })), jsx("div", { className: 'andy-chat-window__input', children: jsx("div", { className: 'andy-input-container', children: jsxs("div", { className: 'andy-input', children: [jsx("input", { type: 'text', className: 'andy-input__field', id: 'andy-message-input', placeholder: config ? strings.inputPlaceholder : '...', "aria-label": 'Message input', onKeyDown: e => {
4435
4466
  if (e.key === 'Enter' && e.target.value.trim()) {
4436
4467
  handleSendMessage(e.target.value);
4437
4468
  e.target.value = '';