andy-widget-react 2.0.5 → 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 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
@@ -873,7 +873,7 @@ const _mapApiDataToChatbotConfig = (apiData, defaultConfig) => {
873
873
  throw new Error('Invalid chatbot configuration response.')
874
874
  }
875
875
 
876
- const { chatbot, faqs: rawFaqs } = apiData;
876
+ const { chatbot, faqs: rawFaqs, bootstrap } = apiData;
877
877
  const appearance = chatbot.appearance || {};
878
878
  const behavior = chatbot.behavior || {};
879
879
 
@@ -943,6 +943,7 @@ const _mapApiDataToChatbotConfig = (apiData, defaultConfig) => {
943
943
  )
944
944
  .map(faq => ({ question: faq.question.trim(), answer: faq.answer.trim() }));
945
945
  }
946
+ config.bootstrap = bootstrap;
946
947
 
947
948
  return config
948
949
  };
@@ -992,7 +993,7 @@ const fetchChatbotConfig = async embedId => {
992
993
  * @param {string} message - The user's message
993
994
  * @returns {Promise<string>} The bot's response text
994
995
  */
995
- const sendMessage = async (embedId, sessionId, message) => {
996
+ const sendMessageForWidget = async (embedId, sessionId, message) => {
996
997
  try {
997
998
  const apiBaseUrl = getApiBaseUrl();
998
999
  const response = await fetch(`${apiBaseUrl}/messages`, {
@@ -1013,10 +1014,13 @@ const sendMessage = async (embedId, sessionId, message) => {
1013
1014
  if (data.error || data.limitStatus) {
1014
1015
  throw new Error(data.error || 'Chat is temporarily unavailable.')
1015
1016
  }
1017
+ if (data.handoffStatus === 'paused') {
1018
+ return { status: 'waiting' }
1019
+ }
1016
1020
  if (typeof data.botResponse !== 'string' || !data.botResponse.trim()) {
1017
1021
  throw new Error('Unexpected response format from server.')
1018
1022
  }
1019
- return data.botResponse
1023
+ return { status: 'agent', text: data.botResponse }
1020
1024
  } else {
1021
1025
  // Handle HTTP errors (e.g., 500, 404)
1022
1026
  let errorData;
@@ -3433,6 +3437,34 @@ const SVG_ICONS = {
3433
3437
  <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
3434
3438
  </svg>`};
3435
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
+
3436
3468
  /**
3437
3469
  * Keyboard detection utility for handling mobile virtual keyboards
3438
3470
  * Provides cross-browser support for detecting keyboard open/close events
@@ -3698,12 +3730,172 @@ const isMobileDevice = () => {
3698
3730
  return (hasTouch && isSmallScreen) || isMobileUserAgent || isMobileMediaQuery
3699
3731
  };
3700
3732
 
3701
- const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessage, bubbleLabel, theme, orientation, brandColor, bubbleStyle, useBrandColor, logoUrl, previewMode = false, initialOpen = false }) => {
3733
+ const identityKey = chatbotId => `andy-browser:${chatbotId}`;
3734
+
3735
+ const randomIdentity = () => ({
3736
+ sessionId: window.crypto.randomUUID(),
3737
+ capability: btoa(String.fromCharCode(...(() => {
3738
+ const bytes = new Uint8Array(32);
3739
+ window.crypto.getRandomValues(bytes);
3740
+ return bytes
3741
+ })()))
3742
+ });
3743
+
3744
+ const callConvex = async (convexUrl, kind, path, wireArgs, keepalive = false) => {
3745
+ const response = await fetch(`${convexUrl}/api/${kind}`, {
3746
+ method: 'POST',
3747
+ headers: { 'Content-Type': 'application/json' },
3748
+ body: JSON.stringify({ path, format: 'convex_encoded_json', args: [wireArgs] }),
3749
+ keepalive
3750
+ });
3751
+ if (!response.ok) throw new Error(`Browser handoff ${kind} failed: ${response.status}`)
3752
+ const data = await response.json();
3753
+ if (data.status !== 'success') throw new Error(data.errorMessage || `Browser handoff ${path} failed`)
3754
+ return data.value
3755
+ };
3756
+
3757
+ const sortMessages = messages => [...messages].sort((left, right) =>
3758
+ left.createdAt - right.createdAt || String(left.id).localeCompare(String(right.id)));
3759
+
3760
+ const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
3761
+ if (!bootstrap?.convexUrl || !bootstrap.surfaceKey || !bootstrap.surfaceProof) return null
3762
+ const storageKey = identityKey(chatbotId);
3763
+ let identity;
3764
+ try {
3765
+ const stored = JSON.parse(window.sessionStorage.getItem(storageKey) || 'null');
3766
+ identity = stored?.sessionId && stored?.capability && stored.surfaceKey === bootstrap.surfaceKey
3767
+ ? { sessionId: stored.sessionId, capability: stored.capability }
3768
+ : randomIdentity();
3769
+ window.sessionStorage.setItem(storageKey, JSON.stringify({ ...identity, surfaceKey: bootstrap.surfaceKey }));
3770
+ } catch {
3771
+ identity = randomIdentity();
3772
+ }
3773
+ const wireArgs = () => ({ chatbotId, sessionId: identity.sessionId, capability: identity.capability, surfaceProof: bootstrap.surfaceProof });
3774
+ let active = true;
3775
+ let visible = document.visibilityState === 'visible';
3776
+ let cursor;
3777
+ let timer;
3778
+ const pending = new Set();
3779
+ const acknowledged = new Set();
3780
+ const acknowledging = new Set();
3781
+ const localMessages = new Map();
3782
+ const emit = projection => {
3783
+ if (!active) return
3784
+ if (projection?.ok === false && projection.code === 'browser_presence_unavailable') {
3785
+ onProjection({ offline: true, refreshBootstrap: Date.now() >= bootstrap.surfaceProofExpiresAt, identity, messages: [] });
3786
+ return
3787
+ }
3788
+ if (!projection?.ok) return
3789
+ cursor = projection.cursor || cursor;
3790
+ const transcript = projection.transcript || [];
3791
+ const projected = (projection.messages || []).map(message => ({ ...message, role: 'bot', sentByHuman: true }));
3792
+ const byId = new Map(transcript.map(message => [String(message.id), message]));
3793
+ for (const message of localMessages.values()) {
3794
+ const persisted = transcript.some(candidate => candidate.role === 'user' && candidate.content === message.content && Math.abs(candidate.createdAt - message.createdAt) < 30000);
3795
+ if (persisted) localMessages.delete(message.id);
3796
+ else byId.set(message.id, message);
3797
+ }
3798
+ projected.forEach(message => byId.set(String(message.id), message));
3799
+ const messages = sortMessages([...byId.values()]);
3800
+ const pendingIds = projected.map(message => String(message.id)).filter(id => !acknowledged.has(id));
3801
+ pendingIds.forEach(id => pending.add(id));
3802
+ onProjection({ identity, control: projection.control, messages, pendingProjectionIds: pendingIds });
3803
+ };
3804
+ const project = async () => {
3805
+ if (!active || !visible) return
3806
+ if (Date.now() >= bootstrap.surfaceProofExpiresAt) {
3807
+ emit({ ok: false, code: 'browser_presence_unavailable' });
3808
+ return
3809
+ }
3810
+ try {
3811
+ const result = await callConvex(bootstrap.convexUrl, 'query', 'browserHandoff:project', { ...wireArgs(), ...(cursor ? { cursor } : {}) });
3812
+ emit(result);
3813
+ } catch {
3814
+ if (active) onProjection({ offline: true, identity, messages: [] });
3815
+ }
3816
+ };
3817
+ const connect = async () => {
3818
+ if (!active || Date.now() >= bootstrap.surfaceProofExpiresAt) {
3819
+ emit({ ok: false, code: 'browser_presence_unavailable' });
3820
+ return
3821
+ }
3822
+ try {
3823
+ const result = await callConvex(bootstrap.convexUrl, 'mutation', 'browserHandoff:connect', wireArgs());
3824
+ if (result?.ok === false) return emit(result)
3825
+ await project();
3826
+ } catch {
3827
+ if (active) onProjection({ offline: true, identity, messages: [] });
3828
+ }
3829
+ };
3830
+ const release = () => callConvex(bootstrap.convexUrl, 'mutation', 'browserHandoff:release', wireArgs(), true).catch(() => undefined);
3831
+ const onVisibility = () => {
3832
+ visible = document.visibilityState === 'visible';
3833
+ if (visible) void connect();
3834
+ else void release();
3835
+ };
3836
+ document.addEventListener('visibilitychange', onVisibility);
3837
+ window.addEventListener('pagehide', release);
3838
+ void connect();
3839
+ timer = window.setInterval(async () => {
3840
+ if (!active || !visible) return
3841
+ if (Date.now() >= bootstrap.surfaceProofExpiresAt) {
3842
+ window.clearInterval(timer);
3843
+ emit({ ok: false, code: 'browser_presence_unavailable' });
3844
+ return
3845
+ }
3846
+ try {
3847
+ const result = await callConvex(bootstrap.convexUrl, 'mutation', 'browserHandoff:heartbeat', wireArgs());
3848
+ if (result?.ok === false) emit(result);
3849
+ else await project();
3850
+ } catch {
3851
+ if (active) onProjection({ offline: true, identity, messages: [] });
3852
+ }
3853
+ }, 15000);
3854
+ return {
3855
+ identity: { sessionId: identity.sessionId },
3856
+ recordVisitorMessage: content => {
3857
+ const id = `local-${window.crypto.randomUUID()}`;
3858
+ localMessages.set(id, { id, role: 'user', content, createdAt: Date.now(), sentByHuman: false });
3859
+ },
3860
+ markRendered: async messageId => {
3861
+ if (!active || !pending.has(messageId) || acknowledged.has(messageId) || acknowledging.has(messageId)) return
3862
+ acknowledging.add(messageId);
3863
+ try {
3864
+ const result = await callConvex(bootstrap.convexUrl, 'mutation', 'browserHandoff:acknowledge', { ...wireArgs(), messageId });
3865
+ if (result?.ok) {
3866
+ acknowledged.add(messageId);
3867
+ pending.delete(messageId);
3868
+ }
3869
+ } catch {
3870
+ if (active) onProjection({ offline: true, identity, messages: [] });
3871
+ } finally {
3872
+ acknowledging.delete(messageId);
3873
+ }
3874
+ },
3875
+ stop: () => {
3876
+ if (!active) return
3877
+ active = false;
3878
+ window.clearInterval(timer);
3879
+ document.removeEventListener('visibilitychange', onVisibility);
3880
+ window.removeEventListener('pagehide', release);
3881
+ void release();
3882
+ }
3883
+ }
3884
+ };
3885
+
3886
+ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessage, bubbleLabel, lang, predefinedQuestions, theme, orientation, brandColor, bubbleStyle, useBrandColor, logoUrl, previewMode = false, initialOpen = false }) => {
3702
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;
3703
3893
  const [isOpen, setIsOpen] = react.useState(initialOpen);
3704
3894
  const [messages, setMessages] = react.useState([]);
3705
3895
  const [isLoading, setIsLoading] = react.useState(false);
3706
3896
  const [sessionId, setSessionId] = react.useState(() => generateSessionId());
3897
+ const handoffRef = react.useRef(null);
3898
+ const pendingHandoffIdsRef = react.useRef([]);
3707
3899
  const [shadowReady, setShadowReady] = react.useState(false);
3708
3900
  const [showPredefinedQuestions, setShowPredefinedQuestions] = react.useState(true);
3709
3901
  const [userInfoCollected, setUserInfoCollected] = react.useState(false);
@@ -3717,6 +3909,29 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
3717
3909
  const messagesContainerRef = react.useRef(null);
3718
3910
  const welcomePopupShownRef = react.useRef(false);
3719
3911
  const welcomePopupTimeoutRef = react.useRef(null);
3912
+ react.useEffect(() => {
3913
+ if (!config?.bootstrap || !config.id)
3914
+ return;
3915
+ handoffRef.current?.stop();
3916
+ const handoff = createBrowserHandoff(config.id, config.bootstrap, projection => {
3917
+ if (projection.offline)
3918
+ return;
3919
+ setSessionId(projection.identity.sessionId);
3920
+ pendingHandoffIdsRef.current = projection.pendingProjectionIds || [];
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 }));
3922
+ setMessages(projection.control === 'requested' || projection.control === 'human'
3923
+ ? [...projectedMessages, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]
3924
+ : projectedMessages);
3925
+ });
3926
+ handoffRef.current = handoff;
3927
+ return () => {
3928
+ handoff?.stop();
3929
+ handoffRef.current = null;
3930
+ };
3931
+ }, [config?.bootstrap, config?.id]);
3932
+ react.useEffect(() => {
3933
+ pendingHandoffIdsRef.current.forEach(messageId => void handoffRef.current?.markRendered(messageId));
3934
+ }, [messages]);
3720
3935
  // Create shadow DOM container on mount
3721
3936
  react.useEffect(() => {
3722
3937
  if (!containerRef.current)
@@ -3894,12 +4109,12 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
3894
4109
  bubbleStyle: bubbleStyle || 'icon',
3895
4110
  bubbleLabel: bubbleLabel || null,
3896
4111
  useBrandColor: useBrandColor !== undefined ? useBrandColor : true,
3897
- defaultLang: 'en',
4112
+ defaultLang: uiLang,
3898
4113
  showBranding: true,
3899
4114
  showWelcomePopup: { enabled: false, seconds: 3, message: '' },
3900
4115
  collectUserInfo: { enabled: false, message: '' },
3901
4116
  welcomeMessage: welcomeMessage || 'Hi there! How can I help you today?',
3902
- predefinedQuestions: [],
4117
+ predefinedQuestions: predefinedQuestions || [],
3903
4118
  faqs: []
3904
4119
  };
3905
4120
  setConfig(previewConfig);
@@ -4043,16 +4258,21 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
4043
4258
  sender: 'user',
4044
4259
  timestamp: new Date().toISOString()
4045
4260
  };
4261
+ handoffRef.current?.recordVisitorMessage(messageText);
4046
4262
  setMessages(prev => [...prev, userMessage]);
4047
4263
  if (hidePredefined) {
4048
4264
  setShowPredefinedQuestions(false);
4049
4265
  }
4050
4266
  try {
4051
4267
  setIsLoading(true);
4052
- const response = await sendMessage(embedId, sessionId, messageText);
4268
+ const response = await sendMessageForWidget(embedId, sessionId, messageText);
4269
+ if (response.status === 'waiting') {
4270
+ setMessages(prev => prev.some(message => message.isWaiting) ? prev : [...prev, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]);
4271
+ return;
4272
+ }
4053
4273
  const botMessage = {
4054
4274
  id: timestamp + 1,
4055
- content: response,
4275
+ content: response.text,
4056
4276
  sender: 'bot',
4057
4277
  timestamp: new Date().toISOString()
4058
4278
  };
@@ -4074,9 +4294,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
4074
4294
  console.error('AndyChat: Message request failed.');
4075
4295
  const errorMessage = {
4076
4296
  id: timestamp + 3,
4077
- content: config?.defaultLang === 'es'
4078
- ? 'Lo siento, no pude responder. Inténtalo de nuevo.'
4079
- : 'Sorry, I could not answer. Please try again.',
4297
+ content: strings.sendFailure,
4080
4298
  sender: 'bot',
4081
4299
  timestamp: new Date().toISOString(),
4082
4300
  isError: true
@@ -4192,7 +4410,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
4192
4410
  bubble.addEventListener('click', () => setIsOpen(prev => !prev));
4193
4411
  }
4194
4412
  const isPillStyle = config?.bubbleStyle === 'pill';
4195
- const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(config?.defaultLang || 'en') : null);
4413
+ const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(uiLang) : null);
4196
4414
  const bubbleLogoUrl = config?.logoUrl || CHAT_BUBBLE_DEFAULT_ICON_URL;
4197
4415
  if (bubble) {
4198
4416
  bubble.className = 'andy-chat-bubble';
@@ -4245,10 +4463,10 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
4245
4463
  if (message.isLoading) {
4246
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));
4247
4465
  }
4248
- return (jsxRuntime.jsxs("div", { className: `andy-message-container andy-message-container--${message.sender}`, children: [message.sender === 'bot' && (jsxRuntime.jsxs("div", { className: `andy-message andy-message--bot ${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: 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));
4249
- }), 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: [config?.predefinedQuestions &&
4250
- config.predefinedQuestions.length > 0 &&
4251
- 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: "What would you like to know?" }) }), jsxRuntime.jsx("div", { className: 'andy-predefined-questions__list', children: config.predefinedQuestions.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 ? 'Type your message...' : '...', "aria-label": 'Message input', onKeyDown: e => {
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 => {
4252
4470
  if (e.key === 'Enter' && e.target.value.trim()) {
4253
4471
  handleSendMessage(e.target.value);
4254
4472
  e.target.value = '';
@@ -4350,7 +4568,7 @@ const useAndyChat = (embedId) => {
4350
4568
  return newIsOpen;
4351
4569
  });
4352
4570
  }, [config, welcomePopupShown, userInfoCollected]);
4353
- const sendMessage$1 = react.useCallback(async (messageText) => {
4571
+ const sendMessage = react.useCallback(async (messageText) => {
4354
4572
  if (!messageText.trim() || !config || !sessionId) {
4355
4573
  return;
4356
4574
  }
@@ -4362,10 +4580,12 @@ const useAndyChat = (embedId) => {
4362
4580
  };
4363
4581
  setMessages(prev => [...prev, userMessage]);
4364
4582
  try {
4365
- const response = await sendMessage(embedId, sessionId, messageText);
4583
+ const response = await sendMessageForWidget(embedId, sessionId, messageText);
4584
+ if (response.status === 'waiting')
4585
+ return response;
4366
4586
  const botMessage = {
4367
4587
  id: Date.now() + 1,
4368
- content: response || 'I apologize, but I encountered an error processing your request.',
4588
+ content: response.text || 'I apologize, but I encountered an error processing your request.',
4369
4589
  sender: 'bot',
4370
4590
  timestamp: new Date().toISOString()
4371
4591
  };
@@ -4462,7 +4682,7 @@ const useAndyChat = (embedId) => {
4462
4682
  error,
4463
4683
  initializeChatbot,
4464
4684
  toggleChat,
4465
- sendMessage: sendMessage$1,
4685
+ sendMessage,
4466
4686
  showFaq,
4467
4687
  hideFaq,
4468
4688
  showWelcomePopup,