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 +2 -0
- package/dist/index.cjs +240 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +240 -20
- package/dist/index.esm.js.map +1 -1
- package/dist/types/types/index.d.ts +13 -1
- package/package.json +2 -2
- package/src/AndyChat.tsx +56 -18
- package/src/hooks/useAndyChat.ts +3 -2
- package/src/types/index.ts +13 -1
package/dist/index.esm.js
CHANGED
|
@@ -869,7 +869,7 @@ const _mapApiDataToChatbotConfig = (apiData, defaultConfig) => {
|
|
|
869
869
|
throw new Error('Invalid chatbot configuration response.')
|
|
870
870
|
}
|
|
871
871
|
|
|
872
|
-
const { chatbot, faqs: rawFaqs } = apiData;
|
|
872
|
+
const { chatbot, faqs: rawFaqs, bootstrap } = apiData;
|
|
873
873
|
const appearance = chatbot.appearance || {};
|
|
874
874
|
const behavior = chatbot.behavior || {};
|
|
875
875
|
|
|
@@ -939,6 +939,7 @@ const _mapApiDataToChatbotConfig = (apiData, defaultConfig) => {
|
|
|
939
939
|
)
|
|
940
940
|
.map(faq => ({ question: faq.question.trim(), answer: faq.answer.trim() }));
|
|
941
941
|
}
|
|
942
|
+
config.bootstrap = bootstrap;
|
|
942
943
|
|
|
943
944
|
return config
|
|
944
945
|
};
|
|
@@ -988,7 +989,7 @@ const fetchChatbotConfig = async embedId => {
|
|
|
988
989
|
* @param {string} message - The user's message
|
|
989
990
|
* @returns {Promise<string>} The bot's response text
|
|
990
991
|
*/
|
|
991
|
-
const
|
|
992
|
+
const sendMessageForWidget = async (embedId, sessionId, message) => {
|
|
992
993
|
try {
|
|
993
994
|
const apiBaseUrl = getApiBaseUrl();
|
|
994
995
|
const response = await fetch(`${apiBaseUrl}/messages`, {
|
|
@@ -1009,10 +1010,13 @@ const sendMessage = async (embedId, sessionId, message) => {
|
|
|
1009
1010
|
if (data.error || data.limitStatus) {
|
|
1010
1011
|
throw new Error(data.error || 'Chat is temporarily unavailable.')
|
|
1011
1012
|
}
|
|
1013
|
+
if (data.handoffStatus === 'paused') {
|
|
1014
|
+
return { status: 'waiting' }
|
|
1015
|
+
}
|
|
1012
1016
|
if (typeof data.botResponse !== 'string' || !data.botResponse.trim()) {
|
|
1013
1017
|
throw new Error('Unexpected response format from server.')
|
|
1014
1018
|
}
|
|
1015
|
-
return data.botResponse
|
|
1019
|
+
return { status: 'agent', text: data.botResponse }
|
|
1016
1020
|
} else {
|
|
1017
1021
|
// Handle HTTP errors (e.g., 500, 404)
|
|
1018
1022
|
let errorData;
|
|
@@ -3429,6 +3433,34 @@ const SVG_ICONS = {
|
|
|
3429
3433
|
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
|
|
3430
3434
|
</svg>`};
|
|
3431
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
|
+
|
|
3432
3464
|
/**
|
|
3433
3465
|
* Keyboard detection utility for handling mobile virtual keyboards
|
|
3434
3466
|
* Provides cross-browser support for detecting keyboard open/close events
|
|
@@ -3694,12 +3726,172 @@ const isMobileDevice = () => {
|
|
|
3694
3726
|
return (hasTouch && isSmallScreen) || isMobileUserAgent || isMobileMediaQuery
|
|
3695
3727
|
};
|
|
3696
3728
|
|
|
3697
|
-
const
|
|
3729
|
+
const identityKey = chatbotId => `andy-browser:${chatbotId}`;
|
|
3730
|
+
|
|
3731
|
+
const randomIdentity = () => ({
|
|
3732
|
+
sessionId: window.crypto.randomUUID(),
|
|
3733
|
+
capability: btoa(String.fromCharCode(...(() => {
|
|
3734
|
+
const bytes = new Uint8Array(32);
|
|
3735
|
+
window.crypto.getRandomValues(bytes);
|
|
3736
|
+
return bytes
|
|
3737
|
+
})()))
|
|
3738
|
+
});
|
|
3739
|
+
|
|
3740
|
+
const callConvex = async (convexUrl, kind, path, wireArgs, keepalive = false) => {
|
|
3741
|
+
const response = await fetch(`${convexUrl}/api/${kind}`, {
|
|
3742
|
+
method: 'POST',
|
|
3743
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3744
|
+
body: JSON.stringify({ path, format: 'convex_encoded_json', args: [wireArgs] }),
|
|
3745
|
+
keepalive
|
|
3746
|
+
});
|
|
3747
|
+
if (!response.ok) throw new Error(`Browser handoff ${kind} failed: ${response.status}`)
|
|
3748
|
+
const data = await response.json();
|
|
3749
|
+
if (data.status !== 'success') throw new Error(data.errorMessage || `Browser handoff ${path} failed`)
|
|
3750
|
+
return data.value
|
|
3751
|
+
};
|
|
3752
|
+
|
|
3753
|
+
const sortMessages = messages => [...messages].sort((left, right) =>
|
|
3754
|
+
left.createdAt - right.createdAt || String(left.id).localeCompare(String(right.id)));
|
|
3755
|
+
|
|
3756
|
+
const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
|
|
3757
|
+
if (!bootstrap?.convexUrl || !bootstrap.surfaceKey || !bootstrap.surfaceProof) return null
|
|
3758
|
+
const storageKey = identityKey(chatbotId);
|
|
3759
|
+
let identity;
|
|
3760
|
+
try {
|
|
3761
|
+
const stored = JSON.parse(window.sessionStorage.getItem(storageKey) || 'null');
|
|
3762
|
+
identity = stored?.sessionId && stored?.capability && stored.surfaceKey === bootstrap.surfaceKey
|
|
3763
|
+
? { sessionId: stored.sessionId, capability: stored.capability }
|
|
3764
|
+
: randomIdentity();
|
|
3765
|
+
window.sessionStorage.setItem(storageKey, JSON.stringify({ ...identity, surfaceKey: bootstrap.surfaceKey }));
|
|
3766
|
+
} catch {
|
|
3767
|
+
identity = randomIdentity();
|
|
3768
|
+
}
|
|
3769
|
+
const wireArgs = () => ({ chatbotId, sessionId: identity.sessionId, capability: identity.capability, surfaceProof: bootstrap.surfaceProof });
|
|
3770
|
+
let active = true;
|
|
3771
|
+
let visible = document.visibilityState === 'visible';
|
|
3772
|
+
let cursor;
|
|
3773
|
+
let timer;
|
|
3774
|
+
const pending = new Set();
|
|
3775
|
+
const acknowledged = new Set();
|
|
3776
|
+
const acknowledging = new Set();
|
|
3777
|
+
const localMessages = new Map();
|
|
3778
|
+
const emit = projection => {
|
|
3779
|
+
if (!active) return
|
|
3780
|
+
if (projection?.ok === false && projection.code === 'browser_presence_unavailable') {
|
|
3781
|
+
onProjection({ offline: true, refreshBootstrap: Date.now() >= bootstrap.surfaceProofExpiresAt, identity, messages: [] });
|
|
3782
|
+
return
|
|
3783
|
+
}
|
|
3784
|
+
if (!projection?.ok) return
|
|
3785
|
+
cursor = projection.cursor || cursor;
|
|
3786
|
+
const transcript = projection.transcript || [];
|
|
3787
|
+
const projected = (projection.messages || []).map(message => ({ ...message, role: 'bot', sentByHuman: true }));
|
|
3788
|
+
const byId = new Map(transcript.map(message => [String(message.id), message]));
|
|
3789
|
+
for (const message of localMessages.values()) {
|
|
3790
|
+
const persisted = transcript.some(candidate => candidate.role === 'user' && candidate.content === message.content && Math.abs(candidate.createdAt - message.createdAt) < 30000);
|
|
3791
|
+
if (persisted) localMessages.delete(message.id);
|
|
3792
|
+
else byId.set(message.id, message);
|
|
3793
|
+
}
|
|
3794
|
+
projected.forEach(message => byId.set(String(message.id), message));
|
|
3795
|
+
const messages = sortMessages([...byId.values()]);
|
|
3796
|
+
const pendingIds = projected.map(message => String(message.id)).filter(id => !acknowledged.has(id));
|
|
3797
|
+
pendingIds.forEach(id => pending.add(id));
|
|
3798
|
+
onProjection({ identity, control: projection.control, messages, pendingProjectionIds: pendingIds });
|
|
3799
|
+
};
|
|
3800
|
+
const project = async () => {
|
|
3801
|
+
if (!active || !visible) return
|
|
3802
|
+
if (Date.now() >= bootstrap.surfaceProofExpiresAt) {
|
|
3803
|
+
emit({ ok: false, code: 'browser_presence_unavailable' });
|
|
3804
|
+
return
|
|
3805
|
+
}
|
|
3806
|
+
try {
|
|
3807
|
+
const result = await callConvex(bootstrap.convexUrl, 'query', 'browserHandoff:project', { ...wireArgs(), ...(cursor ? { cursor } : {}) });
|
|
3808
|
+
emit(result);
|
|
3809
|
+
} catch {
|
|
3810
|
+
if (active) onProjection({ offline: true, identity, messages: [] });
|
|
3811
|
+
}
|
|
3812
|
+
};
|
|
3813
|
+
const connect = async () => {
|
|
3814
|
+
if (!active || Date.now() >= bootstrap.surfaceProofExpiresAt) {
|
|
3815
|
+
emit({ ok: false, code: 'browser_presence_unavailable' });
|
|
3816
|
+
return
|
|
3817
|
+
}
|
|
3818
|
+
try {
|
|
3819
|
+
const result = await callConvex(bootstrap.convexUrl, 'mutation', 'browserHandoff:connect', wireArgs());
|
|
3820
|
+
if (result?.ok === false) return emit(result)
|
|
3821
|
+
await project();
|
|
3822
|
+
} catch {
|
|
3823
|
+
if (active) onProjection({ offline: true, identity, messages: [] });
|
|
3824
|
+
}
|
|
3825
|
+
};
|
|
3826
|
+
const release = () => callConvex(bootstrap.convexUrl, 'mutation', 'browserHandoff:release', wireArgs(), true).catch(() => undefined);
|
|
3827
|
+
const onVisibility = () => {
|
|
3828
|
+
visible = document.visibilityState === 'visible';
|
|
3829
|
+
if (visible) void connect();
|
|
3830
|
+
else void release();
|
|
3831
|
+
};
|
|
3832
|
+
document.addEventListener('visibilitychange', onVisibility);
|
|
3833
|
+
window.addEventListener('pagehide', release);
|
|
3834
|
+
void connect();
|
|
3835
|
+
timer = window.setInterval(async () => {
|
|
3836
|
+
if (!active || !visible) return
|
|
3837
|
+
if (Date.now() >= bootstrap.surfaceProofExpiresAt) {
|
|
3838
|
+
window.clearInterval(timer);
|
|
3839
|
+
emit({ ok: false, code: 'browser_presence_unavailable' });
|
|
3840
|
+
return
|
|
3841
|
+
}
|
|
3842
|
+
try {
|
|
3843
|
+
const result = await callConvex(bootstrap.convexUrl, 'mutation', 'browserHandoff:heartbeat', wireArgs());
|
|
3844
|
+
if (result?.ok === false) emit(result);
|
|
3845
|
+
else await project();
|
|
3846
|
+
} catch {
|
|
3847
|
+
if (active) onProjection({ offline: true, identity, messages: [] });
|
|
3848
|
+
}
|
|
3849
|
+
}, 15000);
|
|
3850
|
+
return {
|
|
3851
|
+
identity: { sessionId: identity.sessionId },
|
|
3852
|
+
recordVisitorMessage: content => {
|
|
3853
|
+
const id = `local-${window.crypto.randomUUID()}`;
|
|
3854
|
+
localMessages.set(id, { id, role: 'user', content, createdAt: Date.now(), sentByHuman: false });
|
|
3855
|
+
},
|
|
3856
|
+
markRendered: async messageId => {
|
|
3857
|
+
if (!active || !pending.has(messageId) || acknowledged.has(messageId) || acknowledging.has(messageId)) return
|
|
3858
|
+
acknowledging.add(messageId);
|
|
3859
|
+
try {
|
|
3860
|
+
const result = await callConvex(bootstrap.convexUrl, 'mutation', 'browserHandoff:acknowledge', { ...wireArgs(), messageId });
|
|
3861
|
+
if (result?.ok) {
|
|
3862
|
+
acknowledged.add(messageId);
|
|
3863
|
+
pending.delete(messageId);
|
|
3864
|
+
}
|
|
3865
|
+
} catch {
|
|
3866
|
+
if (active) onProjection({ offline: true, identity, messages: [] });
|
|
3867
|
+
} finally {
|
|
3868
|
+
acknowledging.delete(messageId);
|
|
3869
|
+
}
|
|
3870
|
+
},
|
|
3871
|
+
stop: () => {
|
|
3872
|
+
if (!active) return
|
|
3873
|
+
active = false;
|
|
3874
|
+
window.clearInterval(timer);
|
|
3875
|
+
document.removeEventListener('visibilitychange', onVisibility);
|
|
3876
|
+
window.removeEventListener('pagehide', release);
|
|
3877
|
+
void release();
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
};
|
|
3881
|
+
|
|
3882
|
+
const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessage, bubbleLabel, lang, predefinedQuestions, theme, orientation, brandColor, bubbleStyle, useBrandColor, logoUrl, previewMode = false, initialOpen = false }) => {
|
|
3698
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;
|
|
3699
3889
|
const [isOpen, setIsOpen] = useState(initialOpen);
|
|
3700
3890
|
const [messages, setMessages] = useState([]);
|
|
3701
3891
|
const [isLoading, setIsLoading] = useState(false);
|
|
3702
3892
|
const [sessionId, setSessionId] = useState(() => generateSessionId());
|
|
3893
|
+
const handoffRef = useRef(null);
|
|
3894
|
+
const pendingHandoffIdsRef = useRef([]);
|
|
3703
3895
|
const [shadowReady, setShadowReady] = useState(false);
|
|
3704
3896
|
const [showPredefinedQuestions, setShowPredefinedQuestions] = useState(true);
|
|
3705
3897
|
const [userInfoCollected, setUserInfoCollected] = useState(false);
|
|
@@ -3713,6 +3905,29 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
3713
3905
|
const messagesContainerRef = useRef(null);
|
|
3714
3906
|
const welcomePopupShownRef = useRef(false);
|
|
3715
3907
|
const welcomePopupTimeoutRef = useRef(null);
|
|
3908
|
+
useEffect(() => {
|
|
3909
|
+
if (!config?.bootstrap || !config.id)
|
|
3910
|
+
return;
|
|
3911
|
+
handoffRef.current?.stop();
|
|
3912
|
+
const handoff = createBrowserHandoff(config.id, config.bootstrap, projection => {
|
|
3913
|
+
if (projection.offline)
|
|
3914
|
+
return;
|
|
3915
|
+
setSessionId(projection.identity.sessionId);
|
|
3916
|
+
pendingHandoffIdsRef.current = projection.pendingProjectionIds || [];
|
|
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 }));
|
|
3918
|
+
setMessages(projection.control === 'requested' || projection.control === 'human'
|
|
3919
|
+
? [...projectedMessages, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]
|
|
3920
|
+
: projectedMessages);
|
|
3921
|
+
});
|
|
3922
|
+
handoffRef.current = handoff;
|
|
3923
|
+
return () => {
|
|
3924
|
+
handoff?.stop();
|
|
3925
|
+
handoffRef.current = null;
|
|
3926
|
+
};
|
|
3927
|
+
}, [config?.bootstrap, config?.id]);
|
|
3928
|
+
useEffect(() => {
|
|
3929
|
+
pendingHandoffIdsRef.current.forEach(messageId => void handoffRef.current?.markRendered(messageId));
|
|
3930
|
+
}, [messages]);
|
|
3716
3931
|
// Create shadow DOM container on mount
|
|
3717
3932
|
useEffect(() => {
|
|
3718
3933
|
if (!containerRef.current)
|
|
@@ -3890,12 +4105,12 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
3890
4105
|
bubbleStyle: bubbleStyle || 'icon',
|
|
3891
4106
|
bubbleLabel: bubbleLabel || null,
|
|
3892
4107
|
useBrandColor: useBrandColor !== undefined ? useBrandColor : true,
|
|
3893
|
-
defaultLang:
|
|
4108
|
+
defaultLang: uiLang,
|
|
3894
4109
|
showBranding: true,
|
|
3895
4110
|
showWelcomePopup: { enabled: false, seconds: 3, message: '' },
|
|
3896
4111
|
collectUserInfo: { enabled: false, message: '' },
|
|
3897
4112
|
welcomeMessage: welcomeMessage || 'Hi there! How can I help you today?',
|
|
3898
|
-
predefinedQuestions: [],
|
|
4113
|
+
predefinedQuestions: predefinedQuestions || [],
|
|
3899
4114
|
faqs: []
|
|
3900
4115
|
};
|
|
3901
4116
|
setConfig(previewConfig);
|
|
@@ -4039,16 +4254,21 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4039
4254
|
sender: 'user',
|
|
4040
4255
|
timestamp: new Date().toISOString()
|
|
4041
4256
|
};
|
|
4257
|
+
handoffRef.current?.recordVisitorMessage(messageText);
|
|
4042
4258
|
setMessages(prev => [...prev, userMessage]);
|
|
4043
4259
|
if (hidePredefined) {
|
|
4044
4260
|
setShowPredefinedQuestions(false);
|
|
4045
4261
|
}
|
|
4046
4262
|
try {
|
|
4047
4263
|
setIsLoading(true);
|
|
4048
|
-
const response = await
|
|
4264
|
+
const response = await sendMessageForWidget(embedId, sessionId, messageText);
|
|
4265
|
+
if (response.status === 'waiting') {
|
|
4266
|
+
setMessages(prev => prev.some(message => message.isWaiting) ? prev : [...prev, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]);
|
|
4267
|
+
return;
|
|
4268
|
+
}
|
|
4049
4269
|
const botMessage = {
|
|
4050
4270
|
id: timestamp + 1,
|
|
4051
|
-
content: response,
|
|
4271
|
+
content: response.text,
|
|
4052
4272
|
sender: 'bot',
|
|
4053
4273
|
timestamp: new Date().toISOString()
|
|
4054
4274
|
};
|
|
@@ -4070,9 +4290,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4070
4290
|
console.error('AndyChat: Message request failed.');
|
|
4071
4291
|
const errorMessage = {
|
|
4072
4292
|
id: timestamp + 3,
|
|
4073
|
-
content:
|
|
4074
|
-
? 'Lo siento, no pude responder. Inténtalo de nuevo.'
|
|
4075
|
-
: 'Sorry, I could not answer. Please try again.',
|
|
4293
|
+
content: strings.sendFailure,
|
|
4076
4294
|
sender: 'bot',
|
|
4077
4295
|
timestamp: new Date().toISOString(),
|
|
4078
4296
|
isError: true
|
|
@@ -4188,7 +4406,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4188
4406
|
bubble.addEventListener('click', () => setIsOpen(prev => !prev));
|
|
4189
4407
|
}
|
|
4190
4408
|
const isPillStyle = config?.bubbleStyle === 'pill';
|
|
4191
|
-
const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(
|
|
4409
|
+
const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(uiLang) : null);
|
|
4192
4410
|
const bubbleLogoUrl = config?.logoUrl || CHAT_BUBBLE_DEFAULT_ICON_URL;
|
|
4193
4411
|
if (bubble) {
|
|
4194
4412
|
bubble.className = 'andy-chat-bubble';
|
|
@@ -4241,10 +4459,10 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4241
4459
|
if (message.isLoading) {
|
|
4242
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));
|
|
4243
4461
|
}
|
|
4244
|
-
return (jsxs("div", { className: `andy-message-container andy-message-container--${message.sender}`, children: [message.sender === 'bot' && (jsxs("div", { className: `andy-message andy-message--bot ${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: 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));
|
|
4245
|
-
}), 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: [
|
|
4246
|
-
|
|
4247
|
-
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:
|
|
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 => {
|
|
4248
4466
|
if (e.key === 'Enter' && e.target.value.trim()) {
|
|
4249
4467
|
handleSendMessage(e.target.value);
|
|
4250
4468
|
e.target.value = '';
|
|
@@ -4346,7 +4564,7 @@ const useAndyChat = (embedId) => {
|
|
|
4346
4564
|
return newIsOpen;
|
|
4347
4565
|
});
|
|
4348
4566
|
}, [config, welcomePopupShown, userInfoCollected]);
|
|
4349
|
-
const sendMessage
|
|
4567
|
+
const sendMessage = useCallback(async (messageText) => {
|
|
4350
4568
|
if (!messageText.trim() || !config || !sessionId) {
|
|
4351
4569
|
return;
|
|
4352
4570
|
}
|
|
@@ -4358,10 +4576,12 @@ const useAndyChat = (embedId) => {
|
|
|
4358
4576
|
};
|
|
4359
4577
|
setMessages(prev => [...prev, userMessage]);
|
|
4360
4578
|
try {
|
|
4361
|
-
const response = await
|
|
4579
|
+
const response = await sendMessageForWidget(embedId, sessionId, messageText);
|
|
4580
|
+
if (response.status === 'waiting')
|
|
4581
|
+
return response;
|
|
4362
4582
|
const botMessage = {
|
|
4363
4583
|
id: Date.now() + 1,
|
|
4364
|
-
content: response || 'I apologize, but I encountered an error processing your request.',
|
|
4584
|
+
content: response.text || 'I apologize, but I encountered an error processing your request.',
|
|
4365
4585
|
sender: 'bot',
|
|
4366
4586
|
timestamp: new Date().toISOString()
|
|
4367
4587
|
};
|
|
@@ -4458,7 +4678,7 @@ const useAndyChat = (embedId) => {
|
|
|
4458
4678
|
error,
|
|
4459
4679
|
initializeChatbot,
|
|
4460
4680
|
toggleChat,
|
|
4461
|
-
sendMessage
|
|
4681
|
+
sendMessage,
|
|
4462
4682
|
showFaq,
|
|
4463
4683
|
hideFaq,
|
|
4464
4684
|
showWelcomePopup,
|