andy-widget-react 2.1.0 → 2.2.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.cjs +214 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +214 -102
- package/dist/index.esm.js.map +1 -1
- package/dist/types/types/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/AndyChat.tsx +59 -24
- package/src/components/MessageInput.tsx +3 -2
- package/src/components/MessageList.tsx +5 -3
- package/src/hooks/useAndyChat.ts +30 -13
- package/src/types/index.ts +1 -0
package/dist/index.cjs
CHANGED
|
@@ -762,6 +762,84 @@ const initializeConfig = (packageName = 'unknown') => {
|
|
|
762
762
|
}
|
|
763
763
|
};
|
|
764
764
|
|
|
765
|
+
/** Stream a reply while the server restores this session's conversation history. */
|
|
766
|
+
const sendMessageForWidget = async (embedId, sessionId, message, options = {}) => {
|
|
767
|
+
const response = await fetch(`${getApiBaseUrl()}/chat/stream`, {
|
|
768
|
+
method: 'POST',
|
|
769
|
+
headers: { 'Content-Type': 'application/json' },
|
|
770
|
+
signal: options.signal,
|
|
771
|
+
body: JSON.stringify({
|
|
772
|
+
chatbotId: embedId,
|
|
773
|
+
sessionId,
|
|
774
|
+
historySource: 'session',
|
|
775
|
+
messages: [{ role: 'user', parts: [{ type: 'text', text: message }] }]
|
|
776
|
+
})
|
|
777
|
+
});
|
|
778
|
+
if (!response.ok || !response.headers.get('content-type')?.includes('text/event-stream')) {
|
|
779
|
+
const data = await response.json().catch(() => null);
|
|
780
|
+
throw new Error(data?.error || data?.message || 'Chat is temporarily unavailable.')
|
|
781
|
+
}
|
|
782
|
+
if (!response.body) throw new Error('Missing reply stream.')
|
|
783
|
+
|
|
784
|
+
const reader = response.body.getReader();
|
|
785
|
+
const decoder = new TextDecoder();
|
|
786
|
+
let buffer = '';
|
|
787
|
+
let eventData = [];
|
|
788
|
+
let text = '';
|
|
789
|
+
let published = '';
|
|
790
|
+
let complete = false;
|
|
791
|
+
let waiting = false;
|
|
792
|
+
const event = () => {
|
|
793
|
+
if (!eventData.length) return
|
|
794
|
+
const data = eventData.join('\n');
|
|
795
|
+
eventData = [];
|
|
796
|
+
if (data === '[DONE]') { complete = true; return }
|
|
797
|
+
const part = JSON.parse(data);
|
|
798
|
+
if (part.type === 'error' || part.type === 'abort') {
|
|
799
|
+
throw new Error(part.errorText || 'Reply interrupted.')
|
|
800
|
+
}
|
|
801
|
+
if (part.type === 'data-handoff' && part.data?.status === 'waiting') {
|
|
802
|
+
waiting = true;
|
|
803
|
+
return
|
|
804
|
+
}
|
|
805
|
+
if (part.type === 'text-start' && text && !text.endsWith('\n')) text += '\n';
|
|
806
|
+
if (part.type === 'text-delta') {
|
|
807
|
+
if (typeof part.delta !== 'string') throw new Error('Invalid reply text.')
|
|
808
|
+
text += part.delta;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
const line = value => {
|
|
812
|
+
const cleaned = value.endsWith('\r') ? value.slice(0, -1) : value;
|
|
813
|
+
if (!cleaned) event();
|
|
814
|
+
else if (cleaned.startsWith('data:')) eventData.push(cleaned.slice(5).replace(/^ /, ''));
|
|
815
|
+
};
|
|
816
|
+
try {
|
|
817
|
+
while (!complete) {
|
|
818
|
+
const { value, done } = await reader.read();
|
|
819
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
820
|
+
let newline;
|
|
821
|
+
while ((newline = buffer.indexOf('\n')) !== -1) {
|
|
822
|
+
line(buffer.slice(0, newline));
|
|
823
|
+
buffer = buffer.slice(newline + 1);
|
|
824
|
+
if (complete) break
|
|
825
|
+
}
|
|
826
|
+
// Publish once per received chunk, including the first useful text.
|
|
827
|
+
if (!waiting && text.trim() && text !== published) {
|
|
828
|
+
published = text;
|
|
829
|
+
options.onText?.(text);
|
|
830
|
+
}
|
|
831
|
+
if (done) break
|
|
832
|
+
}
|
|
833
|
+
if (!complete) throw new Error('Reply stream ended before completion.')
|
|
834
|
+
if (waiting) return { status: 'waiting' }
|
|
835
|
+
if (!text.trim()) throw new Error('The reply was empty.')
|
|
836
|
+
return { status: 'agent', text }
|
|
837
|
+
} finally {
|
|
838
|
+
await reader.cancel().catch(() => {});
|
|
839
|
+
reader.releaseLock();
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
|
|
765
843
|
/**
|
|
766
844
|
* AndyChat API client
|
|
767
845
|
* Handles all communication with the AndyChat backend API
|
|
@@ -986,60 +1064,6 @@ const fetchChatbotConfig = async embedId => {
|
|
|
986
1064
|
}
|
|
987
1065
|
};
|
|
988
1066
|
|
|
989
|
-
/**
|
|
990
|
-
* Sends a user message to the API
|
|
991
|
-
* @param {string} embedId - The chatbot embed ID
|
|
992
|
-
* @param {string} sessionId - The current session ID
|
|
993
|
-
* @param {string} message - The user's message
|
|
994
|
-
* @returns {Promise<string>} The bot's response text
|
|
995
|
-
*/
|
|
996
|
-
const sendMessageForWidget = async (embedId, sessionId, message) => {
|
|
997
|
-
try {
|
|
998
|
-
const apiBaseUrl = getApiBaseUrl();
|
|
999
|
-
const response = await fetch(`${apiBaseUrl}/messages`, {
|
|
1000
|
-
method: 'POST',
|
|
1001
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1002
|
-
body: JSON.stringify({
|
|
1003
|
-
chatbotId: embedId,
|
|
1004
|
-
sessionId: sessionId,
|
|
1005
|
-
currentUserMessage: {
|
|
1006
|
-
role: 'user',
|
|
1007
|
-
content: message
|
|
1008
|
-
}
|
|
1009
|
-
})
|
|
1010
|
-
});
|
|
1011
|
-
|
|
1012
|
-
if (response.ok) {
|
|
1013
|
-
const data = await response.json();
|
|
1014
|
-
if (data.error || data.limitStatus) {
|
|
1015
|
-
throw new Error(data.error || 'Chat is temporarily unavailable.')
|
|
1016
|
-
}
|
|
1017
|
-
if (data.handoffStatus === 'paused') {
|
|
1018
|
-
return { status: 'waiting' }
|
|
1019
|
-
}
|
|
1020
|
-
if (typeof data.botResponse !== 'string' || !data.botResponse.trim()) {
|
|
1021
|
-
throw new Error('Unexpected response format from server.')
|
|
1022
|
-
}
|
|
1023
|
-
return { status: 'agent', text: data.botResponse }
|
|
1024
|
-
} else {
|
|
1025
|
-
// Handle HTTP errors (e.g., 500, 404)
|
|
1026
|
-
let errorData;
|
|
1027
|
-
try {
|
|
1028
|
-
errorData = await response.json();
|
|
1029
|
-
} catch (e) {
|
|
1030
|
-
// If parsing error JSON fails, use status text
|
|
1031
|
-
// Also, if response.text() is available and gives more info, consider using it.
|
|
1032
|
-
const responseText = response.text ? await response.text() : '';
|
|
1033
|
-
throw new Error(response.statusText || `Server returned an error. ${responseText}`.trim())
|
|
1034
|
-
}
|
|
1035
|
-
throw new Error(errorData.error || errorData.message || response.statusText || 'Server returned an error.')
|
|
1036
|
-
}
|
|
1037
|
-
} catch (error) {
|
|
1038
|
-
console.error('AndyChat: Message request failed.');
|
|
1039
|
-
throw error
|
|
1040
|
-
}
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
1067
|
/**
|
|
1044
1068
|
* Visitor information collection is disabled until Andy exposes a durable public submission
|
|
1045
1069
|
* contract. This explicit failure prevents a Widget from reporting a mocked success.
|
|
@@ -2133,6 +2157,10 @@ const getCoreStructuralCssString = () => {
|
|
|
2133
2157
|
}
|
|
2134
2158
|
|
|
2135
2159
|
/* Message Sub-Components */
|
|
2160
|
+
.andy-message__stream {
|
|
2161
|
+
white-space: pre-wrap;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2136
2164
|
.andy-message__content {
|
|
2137
2165
|
line-height: 1.4;
|
|
2138
2166
|
margin: 0;
|
|
@@ -3442,6 +3470,13 @@ const SVG_ICONS = {
|
|
|
3442
3470
|
// Keyed by language so React and Vanilla render identical text for a visitor.
|
|
3443
3471
|
const UI_STRINGS = {
|
|
3444
3472
|
en: {
|
|
3473
|
+
openChat: 'Open chat',
|
|
3474
|
+
closeChat: 'Close chat',
|
|
3475
|
+
goBack: 'Go back',
|
|
3476
|
+
messageInput: 'Message input',
|
|
3477
|
+
sendMessage: 'Send message',
|
|
3478
|
+
welcomeMessage: 'Welcome message',
|
|
3479
|
+
closeWelcomeMessage: 'Close welcome message',
|
|
3445
3480
|
inputPlaceholder: 'Type your message...',
|
|
3446
3481
|
predefinedQuestionsTitle: 'What would you like to know?',
|
|
3447
3482
|
sendFailure: 'Sorry, I could not answer. Please try again.',
|
|
@@ -3449,6 +3484,13 @@ const UI_STRINGS = {
|
|
|
3449
3484
|
teamName: 'Team'
|
|
3450
3485
|
},
|
|
3451
3486
|
es: {
|
|
3487
|
+
openChat: 'Abrir chat',
|
|
3488
|
+
closeChat: 'Cerrar chat',
|
|
3489
|
+
goBack: 'Volver',
|
|
3490
|
+
messageInput: 'Entrada de mensaje',
|
|
3491
|
+
sendMessage: 'Enviar mensaje',
|
|
3492
|
+
welcomeMessage: 'Mensaje de bienvenida',
|
|
3493
|
+
closeWelcomeMessage: 'Cerrar mensaje de bienvenida',
|
|
3452
3494
|
inputPlaceholder: 'Escribe tu mensaje...',
|
|
3453
3495
|
predefinedQuestionsTitle: '¿Qué te gustaría saber?',
|
|
3454
3496
|
sendFailure: 'Lo siento, no pude responder. Inténtalo de nuevo.',
|
|
@@ -3757,12 +3799,12 @@ const callConvex = async (convexUrl, kind, path, wireArgs, keepalive = false) =>
|
|
|
3757
3799
|
const sortMessages = messages => [...messages].sort((left, right) =>
|
|
3758
3800
|
left.createdAt - right.createdAt || String(left.id).localeCompare(String(right.id)));
|
|
3759
3801
|
|
|
3760
|
-
const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
|
|
3802
|
+
const createBrowserHandoff = (chatbotId, bootstrap, onProjection, fresh = false) => {
|
|
3761
3803
|
if (!bootstrap?.convexUrl || !bootstrap.surfaceKey || !bootstrap.surfaceProof) return null
|
|
3762
3804
|
const storageKey = identityKey(chatbotId);
|
|
3763
3805
|
let identity;
|
|
3764
3806
|
try {
|
|
3765
|
-
const stored = JSON.parse(window.sessionStorage.getItem(storageKey) || 'null');
|
|
3807
|
+
const stored = fresh ? null : JSON.parse(window.sessionStorage.getItem(storageKey) || 'null');
|
|
3766
3808
|
identity = stored?.sessionId && stored?.capability && stored.surfaceKey === bootstrap.surfaceKey
|
|
3767
3809
|
? { sessionId: stored.sessionId, capability: stored.capability }
|
|
3768
3810
|
: randomIdentity();
|
|
@@ -3851,6 +3893,14 @@ const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
|
|
|
3851
3893
|
if (active) onProjection({ offline: true, identity, messages: [] });
|
|
3852
3894
|
}
|
|
3853
3895
|
}, 15000);
|
|
3896
|
+
const stop = () => {
|
|
3897
|
+
if (!active) return
|
|
3898
|
+
active = false;
|
|
3899
|
+
window.clearInterval(timer);
|
|
3900
|
+
document.removeEventListener('visibilitychange', onVisibility);
|
|
3901
|
+
window.removeEventListener('pagehide', release);
|
|
3902
|
+
void release();
|
|
3903
|
+
};
|
|
3854
3904
|
return {
|
|
3855
3905
|
identity: { sessionId: identity.sessionId },
|
|
3856
3906
|
recordVisitorMessage: content => {
|
|
@@ -3872,14 +3922,11 @@ const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
|
|
|
3872
3922
|
acknowledging.delete(messageId);
|
|
3873
3923
|
}
|
|
3874
3924
|
},
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
window.removeEventListener('pagehide', release);
|
|
3881
|
-
void release();
|
|
3882
|
-
}
|
|
3925
|
+
restart: () => {
|
|
3926
|
+
stop();
|
|
3927
|
+
return createBrowserHandoff(chatbotId, bootstrap, onProjection, true)
|
|
3928
|
+
},
|
|
3929
|
+
stop
|
|
3883
3930
|
}
|
|
3884
3931
|
};
|
|
3885
3932
|
|
|
@@ -3893,6 +3940,8 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
3893
3940
|
const [isOpen, setIsOpen] = react.useState(initialOpen);
|
|
3894
3941
|
const [messages, setMessages] = react.useState([]);
|
|
3895
3942
|
const [isLoading, setIsLoading] = react.useState(false);
|
|
3943
|
+
const [replyStarted, setReplyStarted] = react.useState(false);
|
|
3944
|
+
const activeReplyRef = react.useRef(null);
|
|
3896
3945
|
const [sessionId, setSessionId] = react.useState(() => generateSessionId());
|
|
3897
3946
|
const handoffRef = react.useRef(null);
|
|
3898
3947
|
const pendingHandoffIdsRef = react.useRef([]);
|
|
@@ -3916,16 +3965,27 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
3916
3965
|
const handoff = createBrowserHandoff(config.id, config.bootstrap, projection => {
|
|
3917
3966
|
if (projection.offline)
|
|
3918
3967
|
return;
|
|
3968
|
+
const paused = projection.control === 'requested' || projection.control === 'human';
|
|
3969
|
+
if (paused)
|
|
3970
|
+
activeReplyRef.current?.abort();
|
|
3971
|
+
else if (activeReplyRef.current)
|
|
3972
|
+
return;
|
|
3919
3973
|
setSessionId(projection.identity.sessionId);
|
|
3920
3974
|
pendingHandoffIdsRef.current = projection.pendingProjectionIds || [];
|
|
3921
3975
|
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 }));
|
|
3976
|
+
// An empty agent projection does not mean the client-only greeting is gone.
|
|
3977
|
+
if (projection.control === 'agent' && projectedMessages.length === 0)
|
|
3978
|
+
return;
|
|
3922
3979
|
setMessages(projection.control === 'requested' || projection.control === 'human'
|
|
3923
3980
|
? [...projectedMessages, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]
|
|
3924
3981
|
: projectedMessages);
|
|
3925
3982
|
});
|
|
3926
3983
|
handoffRef.current = handoff;
|
|
3984
|
+
if (handoff)
|
|
3985
|
+
setSessionId(handoff.identity.sessionId);
|
|
3927
3986
|
return () => {
|
|
3928
|
-
|
|
3987
|
+
activeReplyRef.current?.abort();
|
|
3988
|
+
handoffRef.current?.stop();
|
|
3929
3989
|
handoffRef.current = null;
|
|
3930
3990
|
};
|
|
3931
3991
|
}, [config?.bootstrap, config?.id]);
|
|
@@ -4247,11 +4307,13 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4247
4307
|
}, [isCollectingUserInfo, messages]);
|
|
4248
4308
|
const sendUserMessage = react.useCallback(async (rawMessageText, options = {}) => {
|
|
4249
4309
|
const messageText = typeof rawMessageText === 'string' ? rawMessageText.trim() : '';
|
|
4250
|
-
if (!messageText) {
|
|
4310
|
+
if (!messageText || activeReplyRef.current) {
|
|
4251
4311
|
return;
|
|
4252
4312
|
}
|
|
4253
4313
|
const { hidePredefined = true } = options;
|
|
4254
4314
|
const timestamp = Date.now();
|
|
4315
|
+
const controller = new AbortController();
|
|
4316
|
+
activeReplyRef.current = controller;
|
|
4255
4317
|
const userMessage = {
|
|
4256
4318
|
id: timestamp,
|
|
4257
4319
|
content: messageText,
|
|
@@ -4265,18 +4327,30 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4265
4327
|
}
|
|
4266
4328
|
try {
|
|
4267
4329
|
setIsLoading(true);
|
|
4268
|
-
|
|
4330
|
+
setReplyStarted(false);
|
|
4331
|
+
const showReply = (content, isStreaming) => {
|
|
4332
|
+
if (controller.signal.aborted)
|
|
4333
|
+
return;
|
|
4334
|
+
setReplyStarted(true);
|
|
4335
|
+
const reply = { id: timestamp + 1, content, sender: 'bot', timestamp: new Date(timestamp).toISOString(), isStreaming };
|
|
4336
|
+
setMessages(prev => prev.some(message => message.id === reply.id)
|
|
4337
|
+
? prev.map(message => message.id === reply.id ? reply : message)
|
|
4338
|
+
: [...prev, reply]);
|
|
4339
|
+
};
|
|
4340
|
+
const response = await sendMessageForWidget(embedId, sessionId, messageText, {
|
|
4341
|
+
signal: controller.signal,
|
|
4342
|
+
onText: text => showReply(text, true)
|
|
4343
|
+
});
|
|
4344
|
+
if (controller.signal.aborted)
|
|
4345
|
+
return;
|
|
4269
4346
|
if (response.status === 'waiting') {
|
|
4270
|
-
setMessages(prev =>
|
|
4347
|
+
setMessages(prev => {
|
|
4348
|
+
const visible = prev.filter(message => message.id !== timestamp + 1);
|
|
4349
|
+
return visible.some(message => message.isWaiting) ? visible : [...visible, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }];
|
|
4350
|
+
});
|
|
4271
4351
|
return;
|
|
4272
4352
|
}
|
|
4273
|
-
|
|
4274
|
-
id: timestamp + 1,
|
|
4275
|
-
content: response.text,
|
|
4276
|
-
sender: 'bot',
|
|
4277
|
-
timestamp: new Date().toISOString()
|
|
4278
|
-
};
|
|
4279
|
-
setMessages(prev => [...prev, botMessage]);
|
|
4353
|
+
showReply(response.text, false);
|
|
4280
4354
|
if (config?.collectUserInfo?.enabled && !userInfoCollected && !isCollectingUserInfo) {
|
|
4281
4355
|
setPendingMessage(messageText);
|
|
4282
4356
|
setIsCollectingUserInfo(true);
|
|
@@ -4291,6 +4365,8 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4291
4365
|
}
|
|
4292
4366
|
}
|
|
4293
4367
|
catch {
|
|
4368
|
+
if (controller.signal.aborted)
|
|
4369
|
+
return;
|
|
4294
4370
|
console.error('AndyChat: Message request failed.');
|
|
4295
4371
|
const errorMessage = {
|
|
4296
4372
|
id: timestamp + 3,
|
|
@@ -4299,10 +4375,14 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4299
4375
|
timestamp: new Date().toISOString(),
|
|
4300
4376
|
isError: true
|
|
4301
4377
|
};
|
|
4302
|
-
setMessages(prev => [...prev, errorMessage]);
|
|
4378
|
+
setMessages(prev => [...prev.filter(message => message.id !== timestamp + 1), errorMessage]);
|
|
4303
4379
|
}
|
|
4304
4380
|
finally {
|
|
4305
|
-
|
|
4381
|
+
if (activeReplyRef.current === controller) {
|
|
4382
|
+
activeReplyRef.current = null;
|
|
4383
|
+
setIsLoading(false);
|
|
4384
|
+
setReplyStarted(false);
|
|
4385
|
+
}
|
|
4306
4386
|
}
|
|
4307
4387
|
}, [config, embedId, sessionId, userInfoCollected, isCollectingUserInfo]);
|
|
4308
4388
|
const handleSendMessage = react.useCallback((messageText) => {
|
|
@@ -4401,7 +4481,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4401
4481
|
bubble = document.createElement('button');
|
|
4402
4482
|
bubble.type = 'button';
|
|
4403
4483
|
bubble.className = 'andy-chat-bubble';
|
|
4404
|
-
bubble.setAttribute('aria-label',
|
|
4484
|
+
bubble.setAttribute('aria-label', strings.openChat);
|
|
4405
4485
|
const tooltipHTML = tooltip
|
|
4406
4486
|
? `<span class="andy-chat-bubble__tooltip" role="tooltip">${tooltip}</span>`
|
|
4407
4487
|
: '';
|
|
@@ -4413,6 +4493,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4413
4493
|
const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(uiLang) : null);
|
|
4414
4494
|
const bubbleLogoUrl = config?.logoUrl || CHAT_BUBBLE_DEFAULT_ICON_URL;
|
|
4415
4495
|
if (bubble) {
|
|
4496
|
+
bubble.setAttribute('aria-label', isOpen ? strings.closeChat : strings.openChat);
|
|
4416
4497
|
bubble.className = 'andy-chat-bubble';
|
|
4417
4498
|
if (isPillStyle) {
|
|
4418
4499
|
bubble.classList.add('andy-chat-bubble--pill');
|
|
@@ -4455,7 +4536,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4455
4536
|
}
|
|
4456
4537
|
}
|
|
4457
4538
|
}, [shadowReady, config, isOpen, tooltip, bubbleLabel]);
|
|
4458
|
-
return (jsxRuntime.jsx("div", { ref: containerRef, id: 'andy-chatbot-container', children: portalRef.current && shadowReady && config && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ReactDOM.createPortal(jsxRuntime.jsxs("div", { className: `andy-chat-window ${isOpen ? 'andy-chat-window--open' : ''}`, children: [jsxRuntime.jsxs("header", { className: 'andy-header', children: [jsxRuntime.jsx("button", { type: 'button', className: 'andy-header__back', id: 'andy-back-button', "aria-label":
|
|
4539
|
+
return (jsxRuntime.jsx("div", { ref: containerRef, id: 'andy-chatbot-container', children: portalRef.current && shadowReady && config && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ReactDOM.createPortal(jsxRuntime.jsxs("div", { className: `andy-chat-window ${isOpen ? 'andy-chat-window--open' : ''}`, children: [jsxRuntime.jsxs("header", { className: 'andy-header', children: [jsxRuntime.jsx("button", { type: 'button', className: 'andy-header__back', id: 'andy-back-button', "aria-label": strings.goBack, onClick: () => setIsOpen(false), children: jsxRuntime.jsx("svg", { className: 'andy-header__back-icon', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none', children: jsxRuntime.jsx("path", { d: 'M15 18l-6-6 6-6', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' }) }) }), jsxRuntime.jsxs("div", { className: 'andy-header__bot-info', children: [jsxRuntime.jsx("div", { className: 'andy-header__avatar', children: config?.logoUrl ? (jsxRuntime.jsx("img", { src: config.logoUrl, alt: 'Bot Avatar', className: 'andy-header__avatar-image' })) : (jsxRuntime.jsx("img", { src: CHAT_BUBBLE_DEFAULT_ICON_URL, alt: 'Bot Avatar', className: 'andy-header__avatar-icon andy-header__avatar-icon--default' })) }), jsxRuntime.jsxs("div", { className: 'andy-header__text', children: [jsxRuntime.jsx("h1", { className: 'andy-header__name', children: config?.name || 'Andy bot' }), config?.headline && (jsxRuntime.jsx("p", { className: 'andy-header__subtitle', title: config.headline, children: config.headline }))] })] }), jsxRuntime.jsx("button", { type: 'button', className: 'andy-header__close', id: 'andy-close-button', "aria-label": strings.closeChat, onClick: () => setIsOpen(false), children: jsxRuntime.jsx("svg", { className: 'andy-header__close-icon', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none', children: jsxRuntime.jsx("path", { d: 'M18 6L6 18M6 6l12 12', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' }) }) })] }), jsxRuntime.jsxs("div", { className: 'andy-chat-window__messages', id: 'andy-messages', ref: messagesContainerRef, children: [config?.description && (jsxRuntime.jsx("div", { className: 'andy-chat-subtitle', children: jsxRuntime.jsx("p", { className: 'andy-chat-subtitle__text', children: config.description }) })), messages.map(message => {
|
|
4459
4540
|
if (message.isForm) {
|
|
4460
4541
|
return (jsxRuntime.jsxs("div", { className: 'andy-message-container andy-message-container--form', children: [jsxRuntime.jsx("div", { className: 'andy-avatar', children: jsxRuntime.jsx("svg", { className: 'andy-avatar__icon', width: '24', height: '24', viewBox: '0 0 24 24', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', children: jsxRuntime.jsx("path", { d: 'M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2Z', fill: 'currentColor' }) }) }), jsxRuntime.jsx("div", { className: 'andy-message andy-message--bot andy-message--form', children: jsxRuntime.jsx("div", { className: 'andy-user-info-form', children: jsxRuntime.jsxs("div", { className: 'andy-user-info-form__content', children: [jsxRuntime.jsx("h3", { className: 'andy-user-info-form__title', children: config?.collectUserInfo?.message ||
|
|
4461
4542
|
'Please provide your information to continue:' }), jsxRuntime.jsxs("form", { className: 'andy-user-info-form__form', id: 'andy-user-info-form', children: [jsxRuntime.jsxs("div", { className: 'andy-form-group', children: [jsxRuntime.jsx("label", { htmlFor: 'andy-user-name', className: 'andy-form-group__label', children: "Name" }), jsxRuntime.jsx("input", { type: 'text', id: 'andy-user-name', className: 'andy-form-group__input', placeholder: 'Your Name', required: true })] }), jsxRuntime.jsxs("div", { className: 'andy-form-group', children: [jsxRuntime.jsx("label", { htmlFor: 'andy-user-email', className: 'andy-form-group__label', children: "Email" }), jsxRuntime.jsx("input", { type: 'email', id: 'andy-user-email', className: 'andy-form-group__input', placeholder: 'Your Email', required: true })] }), jsxRuntime.jsx("button", { type: 'submit', className: 'andy-button andy-button--primary andy-user-info-form__submit', children: "Submit" })] })] }) }) })] }, message.id));
|
|
@@ -4463,25 +4544,31 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4463
4544
|
if (message.isLoading) {
|
|
4464
4545
|
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));
|
|
4465
4546
|
}
|
|
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 &&
|
|
4547
|
+
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: message.isStreaming ? jsxRuntime.jsx("div", { className: 'andy-message__stream', children: message.content }) : 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));
|
|
4548
|
+
}), isLoading && !replyStarted && (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
4549
|
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":
|
|
4550
|
+
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": strings.messageInput, onKeyDown: e => {
|
|
4551
|
+
if (e.key === 'Enter' && activeReplyRef.current) {
|
|
4552
|
+
e.preventDefault();
|
|
4553
|
+
return;
|
|
4554
|
+
}
|
|
4470
4555
|
if (e.key === 'Enter' && e.target.value.trim()) {
|
|
4471
4556
|
handleSendMessage(e.target.value);
|
|
4472
4557
|
e.target.value = '';
|
|
4473
4558
|
}
|
|
4474
|
-
}, disabled: isCollectingUserInfo || !config }), jsxRuntime.jsx("button", { type: 'button', className: 'andy-input__button', id: 'andy-send-button', "aria-label":
|
|
4559
|
+
}, disabled: isCollectingUserInfo || !config }), jsxRuntime.jsx("button", { type: 'button', className: 'andy-input__button', id: 'andy-send-button', "aria-label": strings.sendMessage, onClick: () => {
|
|
4560
|
+
if (activeReplyRef.current)
|
|
4561
|
+
return;
|
|
4475
4562
|
const input = shadowRef.current?.getElementById('andy-message-input');
|
|
4476
4563
|
if (input && input.value.trim()) {
|
|
4477
4564
|
handleSendMessage(input.value);
|
|
4478
4565
|
input.value = '';
|
|
4479
4566
|
}
|
|
4480
|
-
}, disabled: isCollectingUserInfo || !config, children: jsxRuntime.jsx("svg", { className: 'andy-input__button-icon', width: '18', height: '18', viewBox: '0 0 24 24', fill: 'currentColor', xmlns: 'http://www.w3.org/2000/svg', children: jsxRuntime.jsx("path", { d: 'M2.01 21L23 12 2.01 3 2 10l15 2-15 2z' }) }) })] }) }) }), config?.showBranding !== false && (jsxRuntime.jsx("footer", { className: 'andy-chat-window__footer', children: jsxRuntime.jsx("a", { href: getFooterUrl(), target: '_blank', rel: 'noopener noreferrer', className: 'andy-chat-window__footer-link', children: jsxRuntime.jsx("span", { className: 'andy-chat-window__footer-text', children: FOOTER_TEXT }) }) }))] })] }), portalRef.current), showWelcomePopup &&
|
|
4567
|
+
}, disabled: isCollectingUserInfo || isLoading || !config, children: jsxRuntime.jsx("svg", { className: 'andy-input__button-icon', width: '18', height: '18', viewBox: '0 0 24 24', fill: 'currentColor', xmlns: 'http://www.w3.org/2000/svg', children: jsxRuntime.jsx("path", { d: 'M2.01 21L23 12 2.01 3 2 10l15 2-15 2z' }) }) })] }) }) }), config?.showBranding !== false && (jsxRuntime.jsx("footer", { className: 'andy-chat-window__footer', children: jsxRuntime.jsx("a", { href: getFooterUrl(), target: '_blank', rel: 'noopener noreferrer', className: 'andy-chat-window__footer-link', children: jsxRuntime.jsx("span", { className: 'andy-chat-window__footer-text', children: FOOTER_TEXT }) }) }))] })] }), portalRef.current), showWelcomePopup &&
|
|
4481
4568
|
!isOpen &&
|
|
4482
|
-
ReactDOM.createPortal(jsxRuntime.jsxs("div", { className: `andy-welcome-popup ${config?.orientation === 'left' ? 'andychat-popup-left' : ''}`, style: { display: 'block' }, role: 'dialog', "aria-label":
|
|
4569
|
+
ReactDOM.createPortal(jsxRuntime.jsxs("div", { className: `andy-welcome-popup ${config?.orientation === 'left' ? 'andychat-popup-left' : ''}`, style: { display: 'block' }, role: 'dialog', "aria-label": strings.welcomeMessage, children: [jsxRuntime.jsxs("div", { className: 'andy-welcome-popup__content', children: [jsxRuntime.jsx("div", { className: 'andy-welcome-popup__message', children: config?.showWelcomePopup?.message ||
|
|
4483
4570
|
config?.welcomeMessage ||
|
|
4484
|
-
'Welcome! How can I help you today?' }), jsxRuntime.jsx("button", { type: 'button', className: 'andy-welcome-popup__close', "aria-label":
|
|
4571
|
+
'Welcome! How can I help you today?' }), jsxRuntime.jsx("button", { type: 'button', className: 'andy-welcome-popup__close', "aria-label": strings.closeWelcomeMessage, onClick: () => setShowWelcomePopup(false), children: jsxRuntime.jsx("svg", { className: 'andy-welcome-popup__close-icon', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', children: jsxRuntime.jsx("path", { d: 'M18 6L6 18M6 6l12 12', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' }) }) })] }), jsxRuntime.jsx("div", { className: 'andy-welcome-popup__arrow' })] }), portalRef.current)] })) }));
|
|
4485
4572
|
};
|
|
4486
4573
|
|
|
4487
4574
|
const { SESSION_STORAGE_KEY, FORM_DISPLAY_DELAY_MS, PERSIST_SESSION } = CONFIG;
|
|
@@ -4498,6 +4585,11 @@ const useAndyChat = (embedId) => {
|
|
|
4498
4585
|
const [showingWelcomePopup, setShowingWelcomePopup] = react.useState(false);
|
|
4499
4586
|
const [showingUserInfoForm, setShowingUserInfoForm] = react.useState(false);
|
|
4500
4587
|
const [welcomePopupShown, setWelcomePopupShown] = react.useState(false);
|
|
4588
|
+
const activeReplyRef = react.useRef(null);
|
|
4589
|
+
react.useEffect(() => () => {
|
|
4590
|
+
activeReplyRef.current?.abort();
|
|
4591
|
+
activeReplyRef.current = null;
|
|
4592
|
+
}, [embedId]);
|
|
4501
4593
|
const welcomeTimeoutRef = react.useRef(null);
|
|
4502
4594
|
const userInfoTimeoutRef = react.useRef(null);
|
|
4503
4595
|
const initializeChatbot = react.useCallback(async () => {
|
|
@@ -4569,9 +4661,11 @@ const useAndyChat = (embedId) => {
|
|
|
4569
4661
|
});
|
|
4570
4662
|
}, [config, welcomePopupShown, userInfoCollected]);
|
|
4571
4663
|
const sendMessage = react.useCallback(async (messageText) => {
|
|
4572
|
-
if (!messageText.trim() || !config || !sessionId) {
|
|
4664
|
+
if (!messageText.trim() || !config || !sessionId || activeReplyRef.current) {
|
|
4573
4665
|
return;
|
|
4574
4666
|
}
|
|
4667
|
+
const controller = new AbortController();
|
|
4668
|
+
activeReplyRef.current = controller;
|
|
4575
4669
|
const userMessage = {
|
|
4576
4670
|
id: Date.now(),
|
|
4577
4671
|
content: messageText,
|
|
@@ -4579,20 +4673,29 @@ const useAndyChat = (embedId) => {
|
|
|
4579
4673
|
timestamp: new Date().toISOString()
|
|
4580
4674
|
};
|
|
4581
4675
|
setMessages(prev => [...prev, userMessage]);
|
|
4676
|
+
const replyId = Date.now() + 1;
|
|
4677
|
+
const showReply = (content, isStreaming) => {
|
|
4678
|
+
if (controller.signal.aborted)
|
|
4679
|
+
return;
|
|
4680
|
+
const reply = { id: replyId, content, sender: 'bot', timestamp: new Date().toISOString(), isStreaming };
|
|
4681
|
+
setMessages(prev => prev.some(message => message.id === replyId)
|
|
4682
|
+
? prev.map(message => message.id === replyId ? reply : message)
|
|
4683
|
+
: [...prev, reply]);
|
|
4684
|
+
};
|
|
4582
4685
|
try {
|
|
4583
|
-
const response = await sendMessageForWidget(embedId, sessionId, messageText);
|
|
4584
|
-
if (
|
|
4686
|
+
const response = await sendMessageForWidget(embedId, sessionId, messageText, { signal: controller.signal, onText: text => showReply(text, true) });
|
|
4687
|
+
if (controller.signal.aborted)
|
|
4688
|
+
return;
|
|
4689
|
+
if (response.status === 'waiting') {
|
|
4690
|
+
setMessages(prev => prev.filter(message => message.id !== replyId));
|
|
4585
4691
|
return response;
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
content: response.text || 'I apologize, but I encountered an error processing your request.',
|
|
4589
|
-
sender: 'bot',
|
|
4590
|
-
timestamp: new Date().toISOString()
|
|
4591
|
-
};
|
|
4592
|
-
setMessages(prev => [...prev, botMessage]);
|
|
4692
|
+
}
|
|
4693
|
+
showReply(response.text, false);
|
|
4593
4694
|
return response;
|
|
4594
4695
|
}
|
|
4595
4696
|
catch (err) {
|
|
4697
|
+
if (controller.signal.aborted)
|
|
4698
|
+
return;
|
|
4596
4699
|
console.error('Error sending message:', err);
|
|
4597
4700
|
const errorMessage = {
|
|
4598
4701
|
id: Date.now() + 1,
|
|
@@ -4601,9 +4704,13 @@ const useAndyChat = (embedId) => {
|
|
|
4601
4704
|
timestamp: new Date().toISOString(),
|
|
4602
4705
|
isError: true
|
|
4603
4706
|
};
|
|
4604
|
-
setMessages(prev => [...prev, errorMessage]);
|
|
4707
|
+
setMessages(prev => [...prev.filter(message => message.id !== replyId), errorMessage]);
|
|
4605
4708
|
throw err;
|
|
4606
4709
|
}
|
|
4710
|
+
finally {
|
|
4711
|
+
if (activeReplyRef.current === controller)
|
|
4712
|
+
activeReplyRef.current = null;
|
|
4713
|
+
}
|
|
4607
4714
|
}, [embedId, sessionId, config]);
|
|
4608
4715
|
const showFaq = react.useCallback(() => {
|
|
4609
4716
|
setShowingFaq(true);
|
|
@@ -4642,6 +4749,8 @@ const useAndyChat = (embedId) => {
|
|
|
4642
4749
|
}
|
|
4643
4750
|
}, [embedId, sessionId]);
|
|
4644
4751
|
const startNewSession = react.useCallback(() => {
|
|
4752
|
+
activeReplyRef.current?.abort();
|
|
4753
|
+
activeReplyRef.current = null;
|
|
4645
4754
|
const newSessionId = generateSessionId();
|
|
4646
4755
|
setSessionId(newSessionId);
|
|
4647
4756
|
{
|
|
@@ -4711,9 +4820,11 @@ const MessageComponent = ({ message, botName }) => {
|
|
|
4711
4820
|
.filter(Boolean)
|
|
4712
4821
|
.join(' ');
|
|
4713
4822
|
const messageClasses = ['andy-message', `andy-message--${message.sender}`].filter(Boolean).join(' ');
|
|
4714
|
-
const messageContent = isBot ? parseMarkdown(message.content) : message.content;
|
|
4823
|
+
const messageContent = isBot && !message.isStreaming ? parseMarkdown(message.content) : message.content;
|
|
4715
4824
|
if (isBot) {
|
|
4716
|
-
return (jsxRuntime.jsxs("div", { className: containerClasses, children: [jsxRuntime.jsxs("div", { className: 'andy-bot-header', children: [jsxRuntime.jsx("div", { className: 'andy-bot-header__icon', children: jsxRuntime.jsxs("svg", { width: '16', height: '16', viewBox: '0 0 24 24', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', children: [jsxRuntime.jsx("path", { d: 'M12 6V2H8' }), jsxRuntime.jsx("path", { d: 'm8 18-4 4V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2Z' }), jsxRuntime.jsx("path", { d: 'M2 12h2' }), jsxRuntime.jsx("path", { d: 'M9 11v2' }), jsxRuntime.jsx("path", { d: 'M15 11v2' }), jsxRuntime.jsx("path", { d: 'M20 12h2' })] }) }), jsxRuntime.jsx("span", { className: 'andy-bot-header__name', children: botName || 'Andy bot' })] }), jsxRuntime.jsx("div", { className: messageClasses, children:
|
|
4825
|
+
return (jsxRuntime.jsxs("div", { className: containerClasses, children: [jsxRuntime.jsxs("div", { className: 'andy-bot-header', children: [jsxRuntime.jsx("div", { className: 'andy-bot-header__icon', children: jsxRuntime.jsxs("svg", { width: '16', height: '16', viewBox: '0 0 24 24', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', children: [jsxRuntime.jsx("path", { d: 'M12 6V2H8' }), jsxRuntime.jsx("path", { d: 'm8 18-4 4V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2Z' }), jsxRuntime.jsx("path", { d: 'M2 12h2' }), jsxRuntime.jsx("path", { d: 'M9 11v2' }), jsxRuntime.jsx("path", { d: 'M15 11v2' }), jsxRuntime.jsx("path", { d: 'M20 12h2' })] }) }), jsxRuntime.jsx("span", { className: 'andy-bot-header__name', children: botName || 'Andy bot' })] }), jsxRuntime.jsx("div", { className: messageClasses, children: message.isStreaming
|
|
4826
|
+
? jsxRuntime.jsx("div", { className: 'andy-message__content andy-message__stream', children: message.content })
|
|
4827
|
+
: jsxRuntime.jsx("div", { className: 'andy-message__content', dangerouslySetInnerHTML: { __html: messageContent } }) })] }));
|
|
4717
4828
|
}
|
|
4718
4829
|
return (jsxRuntime.jsx("div", { className: containerClasses, children: jsxRuntime.jsx("div", { className: messageClasses, children: jsxRuntime.jsx("div", { className: 'andy-message__content', children: jsxRuntime.jsx("p", { children: messageContent }) }) }) }));
|
|
4719
4830
|
};
|
|
@@ -4728,7 +4839,7 @@ const MessageList = ({ messages, isLoading, logoUrl, botName }) => {
|
|
|
4728
4839
|
});
|
|
4729
4840
|
}
|
|
4730
4841
|
}, [messages, isLoading]);
|
|
4731
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [messages.map(message => (jsxRuntime.jsx(MessageComponent, { message: message, logoUrl: logoUrl, botName: botName }, message.id || message.timestamp))), isLoading && jsxRuntime.jsx(TypingIndicator, {}), messages.length === 0 && !isLoading && (jsxRuntime.jsxs("div", { className: 'andy-empty-state', children: [jsxRuntime.jsx("div", { className: 'andy-empty-state__icon', children: "\uD83D\uDCAC" }), jsxRuntime.jsx("div", { className: 'andy-empty-state__text', children: "Start a conversation by typing a message below" })] })), jsxRuntime.jsx("div", { ref: messagesEndRef })] }));
|
|
4842
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [messages.map(message => (jsxRuntime.jsx(MessageComponent, { message: message, logoUrl: logoUrl, botName: botName }, message.id || message.timestamp))), isLoading && !messages.some(message => message.isStreaming) && jsxRuntime.jsx(TypingIndicator, {}), messages.length === 0 && !isLoading && (jsxRuntime.jsxs("div", { className: 'andy-empty-state', children: [jsxRuntime.jsx("div", { className: 'andy-empty-state__icon', children: "\uD83D\uDCAC" }), jsxRuntime.jsx("div", { className: 'andy-empty-state__text', children: "Start a conversation by typing a message below" })] })), jsxRuntime.jsx("div", { ref: messagesEndRef })] }));
|
|
4732
4843
|
};
|
|
4733
4844
|
|
|
4734
4845
|
const MessageInput = ({ onSendMessage, disabled = false, placeholder = 'Type your message here...' }) => {
|
|
@@ -4743,14 +4854,15 @@ const MessageInput = ({ onSendMessage, disabled = false, placeholder = 'Type you
|
|
|
4743
4854
|
}
|
|
4744
4855
|
try {
|
|
4745
4856
|
setIsSubmitting(true);
|
|
4746
|
-
await onSendMessage(trimmedMessage);
|
|
4747
4857
|
setMessage('');
|
|
4858
|
+
await onSendMessage(trimmedMessage);
|
|
4748
4859
|
if (inputRef.current) {
|
|
4749
4860
|
inputRef.current.focus();
|
|
4750
4861
|
}
|
|
4751
4862
|
}
|
|
4752
4863
|
catch (error) {
|
|
4753
4864
|
console.error('Error sending message:', error);
|
|
4865
|
+
setMessage(current => current || trimmedMessage);
|
|
4754
4866
|
}
|
|
4755
4867
|
finally {
|
|
4756
4868
|
setIsSubmitting(false);
|
|
@@ -4767,7 +4879,7 @@ const MessageInput = ({ onSendMessage, disabled = false, placeholder = 'Type you
|
|
|
4767
4879
|
};
|
|
4768
4880
|
const isInputDisabled = disabled || isSubmitting;
|
|
4769
4881
|
const canSend = message.trim().length > 0 && !isInputDisabled;
|
|
4770
|
-
return (jsxRuntime.jsxs("div", { className: 'andy-input', children: [jsxRuntime.jsx("input", { ref: inputRef, type: 'text', value: message, onChange: handleInputChange, onKeyDown: handleKeyDown, placeholder: placeholder, disabled:
|
|
4882
|
+
return (jsxRuntime.jsxs("div", { className: 'andy-input', children: [jsxRuntime.jsx("input", { ref: inputRef, type: 'text', value: message, onChange: handleInputChange, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, maxLength: 1000, autoComplete: 'off', id: 'andy-input' }), jsxRuntime.jsx("button", { type: 'button', onClick: () => handleSubmit({ preventDefault: () => { } }), disabled: !canSend, id: 'andy-send', children: isSubmitting ? ('...') : (jsxRuntime.jsx("svg", { width: '18', height: '18', viewBox: '0 0 24 24', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', children: jsxRuntime.jsx("path", { d: 'M2.01 21L23 12 2.01 3 2 10l15 2-15 2z', fill: 'white' }) })) })] }));
|
|
4771
4883
|
};
|
|
4772
4884
|
|
|
4773
4885
|
const ChatWindow = ({ config, messages, isLoading, onClose, onSendMessage }) => {
|