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.esm.js
CHANGED
|
@@ -758,6 +758,84 @@ const initializeConfig = (packageName = 'unknown') => {
|
|
|
758
758
|
}
|
|
759
759
|
};
|
|
760
760
|
|
|
761
|
+
/** Stream a reply while the server restores this session's conversation history. */
|
|
762
|
+
const sendMessageForWidget = async (embedId, sessionId, message, options = {}) => {
|
|
763
|
+
const response = await fetch(`${getApiBaseUrl()}/chat/stream`, {
|
|
764
|
+
method: 'POST',
|
|
765
|
+
headers: { 'Content-Type': 'application/json' },
|
|
766
|
+
signal: options.signal,
|
|
767
|
+
body: JSON.stringify({
|
|
768
|
+
chatbotId: embedId,
|
|
769
|
+
sessionId,
|
|
770
|
+
historySource: 'session',
|
|
771
|
+
messages: [{ role: 'user', parts: [{ type: 'text', text: message }] }]
|
|
772
|
+
})
|
|
773
|
+
});
|
|
774
|
+
if (!response.ok || !response.headers.get('content-type')?.includes('text/event-stream')) {
|
|
775
|
+
const data = await response.json().catch(() => null);
|
|
776
|
+
throw new Error(data?.error || data?.message || 'Chat is temporarily unavailable.')
|
|
777
|
+
}
|
|
778
|
+
if (!response.body) throw new Error('Missing reply stream.')
|
|
779
|
+
|
|
780
|
+
const reader = response.body.getReader();
|
|
781
|
+
const decoder = new TextDecoder();
|
|
782
|
+
let buffer = '';
|
|
783
|
+
let eventData = [];
|
|
784
|
+
let text = '';
|
|
785
|
+
let published = '';
|
|
786
|
+
let complete = false;
|
|
787
|
+
let waiting = false;
|
|
788
|
+
const event = () => {
|
|
789
|
+
if (!eventData.length) return
|
|
790
|
+
const data = eventData.join('\n');
|
|
791
|
+
eventData = [];
|
|
792
|
+
if (data === '[DONE]') { complete = true; return }
|
|
793
|
+
const part = JSON.parse(data);
|
|
794
|
+
if (part.type === 'error' || part.type === 'abort') {
|
|
795
|
+
throw new Error(part.errorText || 'Reply interrupted.')
|
|
796
|
+
}
|
|
797
|
+
if (part.type === 'data-handoff' && part.data?.status === 'waiting') {
|
|
798
|
+
waiting = true;
|
|
799
|
+
return
|
|
800
|
+
}
|
|
801
|
+
if (part.type === 'text-start' && text && !text.endsWith('\n')) text += '\n';
|
|
802
|
+
if (part.type === 'text-delta') {
|
|
803
|
+
if (typeof part.delta !== 'string') throw new Error('Invalid reply text.')
|
|
804
|
+
text += part.delta;
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
const line = value => {
|
|
808
|
+
const cleaned = value.endsWith('\r') ? value.slice(0, -1) : value;
|
|
809
|
+
if (!cleaned) event();
|
|
810
|
+
else if (cleaned.startsWith('data:')) eventData.push(cleaned.slice(5).replace(/^ /, ''));
|
|
811
|
+
};
|
|
812
|
+
try {
|
|
813
|
+
while (!complete) {
|
|
814
|
+
const { value, done } = await reader.read();
|
|
815
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
816
|
+
let newline;
|
|
817
|
+
while ((newline = buffer.indexOf('\n')) !== -1) {
|
|
818
|
+
line(buffer.slice(0, newline));
|
|
819
|
+
buffer = buffer.slice(newline + 1);
|
|
820
|
+
if (complete) break
|
|
821
|
+
}
|
|
822
|
+
// Publish once per received chunk, including the first useful text.
|
|
823
|
+
if (!waiting && text.trim() && text !== published) {
|
|
824
|
+
published = text;
|
|
825
|
+
options.onText?.(text);
|
|
826
|
+
}
|
|
827
|
+
if (done) break
|
|
828
|
+
}
|
|
829
|
+
if (!complete) throw new Error('Reply stream ended before completion.')
|
|
830
|
+
if (waiting) return { status: 'waiting' }
|
|
831
|
+
if (!text.trim()) throw new Error('The reply was empty.')
|
|
832
|
+
return { status: 'agent', text }
|
|
833
|
+
} finally {
|
|
834
|
+
await reader.cancel().catch(() => {});
|
|
835
|
+
reader.releaseLock();
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
|
|
761
839
|
/**
|
|
762
840
|
* AndyChat API client
|
|
763
841
|
* Handles all communication with the AndyChat backend API
|
|
@@ -982,60 +1060,6 @@ const fetchChatbotConfig = async embedId => {
|
|
|
982
1060
|
}
|
|
983
1061
|
};
|
|
984
1062
|
|
|
985
|
-
/**
|
|
986
|
-
* Sends a user message to the API
|
|
987
|
-
* @param {string} embedId - The chatbot embed ID
|
|
988
|
-
* @param {string} sessionId - The current session ID
|
|
989
|
-
* @param {string} message - The user's message
|
|
990
|
-
* @returns {Promise<string>} The bot's response text
|
|
991
|
-
*/
|
|
992
|
-
const sendMessageForWidget = async (embedId, sessionId, message) => {
|
|
993
|
-
try {
|
|
994
|
-
const apiBaseUrl = getApiBaseUrl();
|
|
995
|
-
const response = await fetch(`${apiBaseUrl}/messages`, {
|
|
996
|
-
method: 'POST',
|
|
997
|
-
headers: { 'Content-Type': 'application/json' },
|
|
998
|
-
body: JSON.stringify({
|
|
999
|
-
chatbotId: embedId,
|
|
1000
|
-
sessionId: sessionId,
|
|
1001
|
-
currentUserMessage: {
|
|
1002
|
-
role: 'user',
|
|
1003
|
-
content: message
|
|
1004
|
-
}
|
|
1005
|
-
})
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1008
|
-
if (response.ok) {
|
|
1009
|
-
const data = await response.json();
|
|
1010
|
-
if (data.error || data.limitStatus) {
|
|
1011
|
-
throw new Error(data.error || 'Chat is temporarily unavailable.')
|
|
1012
|
-
}
|
|
1013
|
-
if (data.handoffStatus === 'paused') {
|
|
1014
|
-
return { status: 'waiting' }
|
|
1015
|
-
}
|
|
1016
|
-
if (typeof data.botResponse !== 'string' || !data.botResponse.trim()) {
|
|
1017
|
-
throw new Error('Unexpected response format from server.')
|
|
1018
|
-
}
|
|
1019
|
-
return { status: 'agent', text: data.botResponse }
|
|
1020
|
-
} else {
|
|
1021
|
-
// Handle HTTP errors (e.g., 500, 404)
|
|
1022
|
-
let errorData;
|
|
1023
|
-
try {
|
|
1024
|
-
errorData = await response.json();
|
|
1025
|
-
} catch (e) {
|
|
1026
|
-
// If parsing error JSON fails, use status text
|
|
1027
|
-
// Also, if response.text() is available and gives more info, consider using it.
|
|
1028
|
-
const responseText = response.text ? await response.text() : '';
|
|
1029
|
-
throw new Error(response.statusText || `Server returned an error. ${responseText}`.trim())
|
|
1030
|
-
}
|
|
1031
|
-
throw new Error(errorData.error || errorData.message || response.statusText || 'Server returned an error.')
|
|
1032
|
-
}
|
|
1033
|
-
} catch (error) {
|
|
1034
|
-
console.error('AndyChat: Message request failed.');
|
|
1035
|
-
throw error
|
|
1036
|
-
}
|
|
1037
|
-
};
|
|
1038
|
-
|
|
1039
1063
|
/**
|
|
1040
1064
|
* Visitor information collection is disabled until Andy exposes a durable public submission
|
|
1041
1065
|
* contract. This explicit failure prevents a Widget from reporting a mocked success.
|
|
@@ -2129,6 +2153,10 @@ const getCoreStructuralCssString = () => {
|
|
|
2129
2153
|
}
|
|
2130
2154
|
|
|
2131
2155
|
/* Message Sub-Components */
|
|
2156
|
+
.andy-message__stream {
|
|
2157
|
+
white-space: pre-wrap;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2132
2160
|
.andy-message__content {
|
|
2133
2161
|
line-height: 1.4;
|
|
2134
2162
|
margin: 0;
|
|
@@ -3438,6 +3466,13 @@ const SVG_ICONS = {
|
|
|
3438
3466
|
// Keyed by language so React and Vanilla render identical text for a visitor.
|
|
3439
3467
|
const UI_STRINGS = {
|
|
3440
3468
|
en: {
|
|
3469
|
+
openChat: 'Open chat',
|
|
3470
|
+
closeChat: 'Close chat',
|
|
3471
|
+
goBack: 'Go back',
|
|
3472
|
+
messageInput: 'Message input',
|
|
3473
|
+
sendMessage: 'Send message',
|
|
3474
|
+
welcomeMessage: 'Welcome message',
|
|
3475
|
+
closeWelcomeMessage: 'Close welcome message',
|
|
3441
3476
|
inputPlaceholder: 'Type your message...',
|
|
3442
3477
|
predefinedQuestionsTitle: 'What would you like to know?',
|
|
3443
3478
|
sendFailure: 'Sorry, I could not answer. Please try again.',
|
|
@@ -3445,6 +3480,13 @@ const UI_STRINGS = {
|
|
|
3445
3480
|
teamName: 'Team'
|
|
3446
3481
|
},
|
|
3447
3482
|
es: {
|
|
3483
|
+
openChat: 'Abrir chat',
|
|
3484
|
+
closeChat: 'Cerrar chat',
|
|
3485
|
+
goBack: 'Volver',
|
|
3486
|
+
messageInput: 'Entrada de mensaje',
|
|
3487
|
+
sendMessage: 'Enviar mensaje',
|
|
3488
|
+
welcomeMessage: 'Mensaje de bienvenida',
|
|
3489
|
+
closeWelcomeMessage: 'Cerrar mensaje de bienvenida',
|
|
3448
3490
|
inputPlaceholder: 'Escribe tu mensaje...',
|
|
3449
3491
|
predefinedQuestionsTitle: '¿Qué te gustaría saber?',
|
|
3450
3492
|
sendFailure: 'Lo siento, no pude responder. Inténtalo de nuevo.',
|
|
@@ -3753,12 +3795,12 @@ const callConvex = async (convexUrl, kind, path, wireArgs, keepalive = false) =>
|
|
|
3753
3795
|
const sortMessages = messages => [...messages].sort((left, right) =>
|
|
3754
3796
|
left.createdAt - right.createdAt || String(left.id).localeCompare(String(right.id)));
|
|
3755
3797
|
|
|
3756
|
-
const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
|
|
3798
|
+
const createBrowserHandoff = (chatbotId, bootstrap, onProjection, fresh = false) => {
|
|
3757
3799
|
if (!bootstrap?.convexUrl || !bootstrap.surfaceKey || !bootstrap.surfaceProof) return null
|
|
3758
3800
|
const storageKey = identityKey(chatbotId);
|
|
3759
3801
|
let identity;
|
|
3760
3802
|
try {
|
|
3761
|
-
const stored = JSON.parse(window.sessionStorage.getItem(storageKey) || 'null');
|
|
3803
|
+
const stored = fresh ? null : JSON.parse(window.sessionStorage.getItem(storageKey) || 'null');
|
|
3762
3804
|
identity = stored?.sessionId && stored?.capability && stored.surfaceKey === bootstrap.surfaceKey
|
|
3763
3805
|
? { sessionId: stored.sessionId, capability: stored.capability }
|
|
3764
3806
|
: randomIdentity();
|
|
@@ -3847,6 +3889,14 @@ const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
|
|
|
3847
3889
|
if (active) onProjection({ offline: true, identity, messages: [] });
|
|
3848
3890
|
}
|
|
3849
3891
|
}, 15000);
|
|
3892
|
+
const stop = () => {
|
|
3893
|
+
if (!active) return
|
|
3894
|
+
active = false;
|
|
3895
|
+
window.clearInterval(timer);
|
|
3896
|
+
document.removeEventListener('visibilitychange', onVisibility);
|
|
3897
|
+
window.removeEventListener('pagehide', release);
|
|
3898
|
+
void release();
|
|
3899
|
+
};
|
|
3850
3900
|
return {
|
|
3851
3901
|
identity: { sessionId: identity.sessionId },
|
|
3852
3902
|
recordVisitorMessage: content => {
|
|
@@ -3868,14 +3918,11 @@ const createBrowserHandoff = (chatbotId, bootstrap, onProjection) => {
|
|
|
3868
3918
|
acknowledging.delete(messageId);
|
|
3869
3919
|
}
|
|
3870
3920
|
},
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
window.removeEventListener('pagehide', release);
|
|
3877
|
-
void release();
|
|
3878
|
-
}
|
|
3921
|
+
restart: () => {
|
|
3922
|
+
stop();
|
|
3923
|
+
return createBrowserHandoff(chatbotId, bootstrap, onProjection, true)
|
|
3924
|
+
},
|
|
3925
|
+
stop
|
|
3879
3926
|
}
|
|
3880
3927
|
};
|
|
3881
3928
|
|
|
@@ -3889,6 +3936,8 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
3889
3936
|
const [isOpen, setIsOpen] = useState(initialOpen);
|
|
3890
3937
|
const [messages, setMessages] = useState([]);
|
|
3891
3938
|
const [isLoading, setIsLoading] = useState(false);
|
|
3939
|
+
const [replyStarted, setReplyStarted] = useState(false);
|
|
3940
|
+
const activeReplyRef = useRef(null);
|
|
3892
3941
|
const [sessionId, setSessionId] = useState(() => generateSessionId());
|
|
3893
3942
|
const handoffRef = useRef(null);
|
|
3894
3943
|
const pendingHandoffIdsRef = useRef([]);
|
|
@@ -3912,16 +3961,27 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
3912
3961
|
const handoff = createBrowserHandoff(config.id, config.bootstrap, projection => {
|
|
3913
3962
|
if (projection.offline)
|
|
3914
3963
|
return;
|
|
3964
|
+
const paused = projection.control === 'requested' || projection.control === 'human';
|
|
3965
|
+
if (paused)
|
|
3966
|
+
activeReplyRef.current?.abort();
|
|
3967
|
+
else if (activeReplyRef.current)
|
|
3968
|
+
return;
|
|
3915
3969
|
setSessionId(projection.identity.sessionId);
|
|
3916
3970
|
pendingHandoffIdsRef.current = projection.pendingProjectionIds || [];
|
|
3917
3971
|
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 }));
|
|
3972
|
+
// An empty agent projection does not mean the client-only greeting is gone.
|
|
3973
|
+
if (projection.control === 'agent' && projectedMessages.length === 0)
|
|
3974
|
+
return;
|
|
3918
3975
|
setMessages(projection.control === 'requested' || projection.control === 'human'
|
|
3919
3976
|
? [...projectedMessages, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }]
|
|
3920
3977
|
: projectedMessages);
|
|
3921
3978
|
});
|
|
3922
3979
|
handoffRef.current = handoff;
|
|
3980
|
+
if (handoff)
|
|
3981
|
+
setSessionId(handoff.identity.sessionId);
|
|
3923
3982
|
return () => {
|
|
3924
|
-
|
|
3983
|
+
activeReplyRef.current?.abort();
|
|
3984
|
+
handoffRef.current?.stop();
|
|
3925
3985
|
handoffRef.current = null;
|
|
3926
3986
|
};
|
|
3927
3987
|
}, [config?.bootstrap, config?.id]);
|
|
@@ -4243,11 +4303,13 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4243
4303
|
}, [isCollectingUserInfo, messages]);
|
|
4244
4304
|
const sendUserMessage = useCallback(async (rawMessageText, options = {}) => {
|
|
4245
4305
|
const messageText = typeof rawMessageText === 'string' ? rawMessageText.trim() : '';
|
|
4246
|
-
if (!messageText) {
|
|
4306
|
+
if (!messageText || activeReplyRef.current) {
|
|
4247
4307
|
return;
|
|
4248
4308
|
}
|
|
4249
4309
|
const { hidePredefined = true } = options;
|
|
4250
4310
|
const timestamp = Date.now();
|
|
4311
|
+
const controller = new AbortController();
|
|
4312
|
+
activeReplyRef.current = controller;
|
|
4251
4313
|
const userMessage = {
|
|
4252
4314
|
id: timestamp,
|
|
4253
4315
|
content: messageText,
|
|
@@ -4261,18 +4323,30 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4261
4323
|
}
|
|
4262
4324
|
try {
|
|
4263
4325
|
setIsLoading(true);
|
|
4264
|
-
|
|
4326
|
+
setReplyStarted(false);
|
|
4327
|
+
const showReply = (content, isStreaming) => {
|
|
4328
|
+
if (controller.signal.aborted)
|
|
4329
|
+
return;
|
|
4330
|
+
setReplyStarted(true);
|
|
4331
|
+
const reply = { id: timestamp + 1, content, sender: 'bot', timestamp: new Date(timestamp).toISOString(), isStreaming };
|
|
4332
|
+
setMessages(prev => prev.some(message => message.id === reply.id)
|
|
4333
|
+
? prev.map(message => message.id === reply.id ? reply : message)
|
|
4334
|
+
: [...prev, reply]);
|
|
4335
|
+
};
|
|
4336
|
+
const response = await sendMessageForWidget(embedId, sessionId, messageText, {
|
|
4337
|
+
signal: controller.signal,
|
|
4338
|
+
onText: text => showReply(text, true)
|
|
4339
|
+
});
|
|
4340
|
+
if (controller.signal.aborted)
|
|
4341
|
+
return;
|
|
4265
4342
|
if (response.status === 'waiting') {
|
|
4266
|
-
setMessages(prev =>
|
|
4343
|
+
setMessages(prev => {
|
|
4344
|
+
const visible = prev.filter(message => message.id !== timestamp + 1);
|
|
4345
|
+
return visible.some(message => message.isWaiting) ? visible : [...visible, { id: 'handoff-waiting', content: strings.handoffWaiting, sender: 'bot', timestamp: new Date().toISOString(), isWaiting: true }];
|
|
4346
|
+
});
|
|
4267
4347
|
return;
|
|
4268
4348
|
}
|
|
4269
|
-
|
|
4270
|
-
id: timestamp + 1,
|
|
4271
|
-
content: response.text,
|
|
4272
|
-
sender: 'bot',
|
|
4273
|
-
timestamp: new Date().toISOString()
|
|
4274
|
-
};
|
|
4275
|
-
setMessages(prev => [...prev, botMessage]);
|
|
4349
|
+
showReply(response.text, false);
|
|
4276
4350
|
if (config?.collectUserInfo?.enabled && !userInfoCollected && !isCollectingUserInfo) {
|
|
4277
4351
|
setPendingMessage(messageText);
|
|
4278
4352
|
setIsCollectingUserInfo(true);
|
|
@@ -4287,6 +4361,8 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4287
4361
|
}
|
|
4288
4362
|
}
|
|
4289
4363
|
catch {
|
|
4364
|
+
if (controller.signal.aborted)
|
|
4365
|
+
return;
|
|
4290
4366
|
console.error('AndyChat: Message request failed.');
|
|
4291
4367
|
const errorMessage = {
|
|
4292
4368
|
id: timestamp + 3,
|
|
@@ -4295,10 +4371,14 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4295
4371
|
timestamp: new Date().toISOString(),
|
|
4296
4372
|
isError: true
|
|
4297
4373
|
};
|
|
4298
|
-
setMessages(prev => [...prev, errorMessage]);
|
|
4374
|
+
setMessages(prev => [...prev.filter(message => message.id !== timestamp + 1), errorMessage]);
|
|
4299
4375
|
}
|
|
4300
4376
|
finally {
|
|
4301
|
-
|
|
4377
|
+
if (activeReplyRef.current === controller) {
|
|
4378
|
+
activeReplyRef.current = null;
|
|
4379
|
+
setIsLoading(false);
|
|
4380
|
+
setReplyStarted(false);
|
|
4381
|
+
}
|
|
4302
4382
|
}
|
|
4303
4383
|
}, [config, embedId, sessionId, userInfoCollected, isCollectingUserInfo]);
|
|
4304
4384
|
const handleSendMessage = useCallback((messageText) => {
|
|
@@ -4397,7 +4477,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4397
4477
|
bubble = document.createElement('button');
|
|
4398
4478
|
bubble.type = 'button';
|
|
4399
4479
|
bubble.className = 'andy-chat-bubble';
|
|
4400
|
-
bubble.setAttribute('aria-label',
|
|
4480
|
+
bubble.setAttribute('aria-label', strings.openChat);
|
|
4401
4481
|
const tooltipHTML = tooltip
|
|
4402
4482
|
? `<span class="andy-chat-bubble__tooltip" role="tooltip">${tooltip}</span>`
|
|
4403
4483
|
: '';
|
|
@@ -4409,6 +4489,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4409
4489
|
const displayLabel = bubbleLabel || config?.bubbleLabel || (isPillStyle ? getDefaultBubbleLabel(uiLang) : null);
|
|
4410
4490
|
const bubbleLogoUrl = config?.logoUrl || CHAT_BUBBLE_DEFAULT_ICON_URL;
|
|
4411
4491
|
if (bubble) {
|
|
4492
|
+
bubble.setAttribute('aria-label', isOpen ? strings.closeChat : strings.openChat);
|
|
4412
4493
|
bubble.className = 'andy-chat-bubble';
|
|
4413
4494
|
if (isPillStyle) {
|
|
4414
4495
|
bubble.classList.add('andy-chat-bubble--pill');
|
|
@@ -4451,7 +4532,7 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4451
4532
|
}
|
|
4452
4533
|
}
|
|
4453
4534
|
}, [shadowReady, config, isOpen, tooltip, bubbleLabel]);
|
|
4454
|
-
return (jsx("div", { ref: containerRef, id: 'andy-chatbot-container', children: portalRef.current && shadowReady && config && (jsxs(Fragment, { children: [ReactDOM.createPortal(jsxs("div", { className: `andy-chat-window ${isOpen ? 'andy-chat-window--open' : ''}`, children: [jsxs("header", { className: 'andy-header', children: [jsx("button", { type: 'button', className: 'andy-header__back', id: 'andy-back-button', "aria-label":
|
|
4535
|
+
return (jsx("div", { ref: containerRef, id: 'andy-chatbot-container', children: portalRef.current && shadowReady && config && (jsxs(Fragment, { children: [ReactDOM.createPortal(jsxs("div", { className: `andy-chat-window ${isOpen ? 'andy-chat-window--open' : ''}`, children: [jsxs("header", { className: 'andy-header', children: [jsx("button", { type: 'button', className: 'andy-header__back', id: 'andy-back-button', "aria-label": strings.goBack, onClick: () => setIsOpen(false), children: jsx("svg", { className: 'andy-header__back-icon', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none', children: jsx("path", { d: 'M15 18l-6-6 6-6', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' }) }) }), jsxs("div", { className: 'andy-header__bot-info', children: [jsx("div", { className: 'andy-header__avatar', children: config?.logoUrl ? (jsx("img", { src: config.logoUrl, alt: 'Bot Avatar', className: 'andy-header__avatar-image' })) : (jsx("img", { src: CHAT_BUBBLE_DEFAULT_ICON_URL, alt: 'Bot Avatar', className: 'andy-header__avatar-icon andy-header__avatar-icon--default' })) }), jsxs("div", { className: 'andy-header__text', children: [jsx("h1", { className: 'andy-header__name', children: config?.name || 'Andy bot' }), config?.headline && (jsx("p", { className: 'andy-header__subtitle', title: config.headline, children: config.headline }))] })] }), jsx("button", { type: 'button', className: 'andy-header__close', id: 'andy-close-button', "aria-label": strings.closeChat, onClick: () => setIsOpen(false), children: jsx("svg", { className: 'andy-header__close-icon', width: '20', height: '20', viewBox: '0 0 24 24', fill: 'none', children: jsx("path", { d: 'M18 6L6 18M6 6l12 12', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' }) }) })] }), jsxs("div", { className: 'andy-chat-window__messages', id: 'andy-messages', ref: messagesContainerRef, children: [config?.description && (jsx("div", { className: 'andy-chat-subtitle', children: jsx("p", { className: 'andy-chat-subtitle__text', children: config.description }) })), messages.map(message => {
|
|
4455
4536
|
if (message.isForm) {
|
|
4456
4537
|
return (jsxs("div", { className: 'andy-message-container andy-message-container--form', children: [jsx("div", { className: 'andy-avatar', children: 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: 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' }) }) }), jsx("div", { className: 'andy-message andy-message--bot andy-message--form', children: jsx("div", { className: 'andy-user-info-form', children: jsxs("div", { className: 'andy-user-info-form__content', children: [jsx("h3", { className: 'andy-user-info-form__title', children: config?.collectUserInfo?.message ||
|
|
4457
4538
|
'Please provide your information to continue:' }), jsxs("form", { className: 'andy-user-info-form__form', id: 'andy-user-info-form', children: [jsxs("div", { className: 'andy-form-group', children: [jsx("label", { htmlFor: 'andy-user-name', className: 'andy-form-group__label', children: "Name" }), jsx("input", { type: 'text', id: 'andy-user-name', className: 'andy-form-group__input', placeholder: 'Your Name', required: true })] }), jsxs("div", { className: 'andy-form-group', children: [jsx("label", { htmlFor: 'andy-user-email', className: 'andy-form-group__label', children: "Email" }), jsx("input", { type: 'email', id: 'andy-user-email', className: 'andy-form-group__input', placeholder: 'Your Email', required: true })] }), jsx("button", { type: 'submit', className: 'andy-button andy-button--primary andy-user-info-form__submit', children: "Submit" })] })] }) }) })] }, message.id));
|
|
@@ -4459,25 +4540,31 @@ const AndyChat = ({ embedId, tooltip, name, headline, description, welcomeMessag
|
|
|
4459
4540
|
if (message.isLoading) {
|
|
4460
4541
|
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));
|
|
4461
4542
|
}
|
|
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 &&
|
|
4543
|
+
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: message.isStreaming ? jsx("div", { className: 'andy-message__stream', children: message.content }) : 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));
|
|
4544
|
+
}), isLoading && !replyStarted && (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
4545
|
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":
|
|
4546
|
+
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": strings.messageInput, onKeyDown: e => {
|
|
4547
|
+
if (e.key === 'Enter' && activeReplyRef.current) {
|
|
4548
|
+
e.preventDefault();
|
|
4549
|
+
return;
|
|
4550
|
+
}
|
|
4466
4551
|
if (e.key === 'Enter' && e.target.value.trim()) {
|
|
4467
4552
|
handleSendMessage(e.target.value);
|
|
4468
4553
|
e.target.value = '';
|
|
4469
4554
|
}
|
|
4470
|
-
}, disabled: isCollectingUserInfo || !config }), jsx("button", { type: 'button', className: 'andy-input__button', id: 'andy-send-button', "aria-label":
|
|
4555
|
+
}, disabled: isCollectingUserInfo || !config }), jsx("button", { type: 'button', className: 'andy-input__button', id: 'andy-send-button', "aria-label": strings.sendMessage, onClick: () => {
|
|
4556
|
+
if (activeReplyRef.current)
|
|
4557
|
+
return;
|
|
4471
4558
|
const input = shadowRef.current?.getElementById('andy-message-input');
|
|
4472
4559
|
if (input && input.value.trim()) {
|
|
4473
4560
|
handleSendMessage(input.value);
|
|
4474
4561
|
input.value = '';
|
|
4475
4562
|
}
|
|
4476
|
-
}, disabled: isCollectingUserInfo || !config, children: 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: jsx("path", { d: 'M2.01 21L23 12 2.01 3 2 10l15 2-15 2z' }) }) })] }) }) }), config?.showBranding !== false && (jsx("footer", { className: 'andy-chat-window__footer', children: jsx("a", { href: getFooterUrl(), target: '_blank', rel: 'noopener noreferrer', className: 'andy-chat-window__footer-link', children: jsx("span", { className: 'andy-chat-window__footer-text', children: FOOTER_TEXT }) }) }))] })] }), portalRef.current), showWelcomePopup &&
|
|
4563
|
+
}, disabled: isCollectingUserInfo || isLoading || !config, children: 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: jsx("path", { d: 'M2.01 21L23 12 2.01 3 2 10l15 2-15 2z' }) }) })] }) }) }), config?.showBranding !== false && (jsx("footer", { className: 'andy-chat-window__footer', children: jsx("a", { href: getFooterUrl(), target: '_blank', rel: 'noopener noreferrer', className: 'andy-chat-window__footer-link', children: jsx("span", { className: 'andy-chat-window__footer-text', children: FOOTER_TEXT }) }) }))] })] }), portalRef.current), showWelcomePopup &&
|
|
4477
4564
|
!isOpen &&
|
|
4478
|
-
ReactDOM.createPortal(jsxs("div", { className: `andy-welcome-popup ${config?.orientation === 'left' ? 'andychat-popup-left' : ''}`, style: { display: 'block' }, role: 'dialog', "aria-label":
|
|
4565
|
+
ReactDOM.createPortal(jsxs("div", { className: `andy-welcome-popup ${config?.orientation === 'left' ? 'andychat-popup-left' : ''}`, style: { display: 'block' }, role: 'dialog', "aria-label": strings.welcomeMessage, children: [jsxs("div", { className: 'andy-welcome-popup__content', children: [jsx("div", { className: 'andy-welcome-popup__message', children: config?.showWelcomePopup?.message ||
|
|
4479
4566
|
config?.welcomeMessage ||
|
|
4480
|
-
'Welcome! How can I help you today?' }), jsx("button", { type: 'button', className: 'andy-welcome-popup__close', "aria-label":
|
|
4567
|
+
'Welcome! How can I help you today?' }), jsx("button", { type: 'button', className: 'andy-welcome-popup__close', "aria-label": strings.closeWelcomeMessage, onClick: () => setShowWelcomePopup(false), children: 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: jsx("path", { d: 'M18 6L6 18M6 6l12 12', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' }) }) })] }), jsx("div", { className: 'andy-welcome-popup__arrow' })] }), portalRef.current)] })) }));
|
|
4481
4568
|
};
|
|
4482
4569
|
|
|
4483
4570
|
const { SESSION_STORAGE_KEY, FORM_DISPLAY_DELAY_MS, PERSIST_SESSION } = CONFIG;
|
|
@@ -4494,6 +4581,11 @@ const useAndyChat = (embedId) => {
|
|
|
4494
4581
|
const [showingWelcomePopup, setShowingWelcomePopup] = useState(false);
|
|
4495
4582
|
const [showingUserInfoForm, setShowingUserInfoForm] = useState(false);
|
|
4496
4583
|
const [welcomePopupShown, setWelcomePopupShown] = useState(false);
|
|
4584
|
+
const activeReplyRef = useRef(null);
|
|
4585
|
+
useEffect(() => () => {
|
|
4586
|
+
activeReplyRef.current?.abort();
|
|
4587
|
+
activeReplyRef.current = null;
|
|
4588
|
+
}, [embedId]);
|
|
4497
4589
|
const welcomeTimeoutRef = useRef(null);
|
|
4498
4590
|
const userInfoTimeoutRef = useRef(null);
|
|
4499
4591
|
const initializeChatbot = useCallback(async () => {
|
|
@@ -4565,9 +4657,11 @@ const useAndyChat = (embedId) => {
|
|
|
4565
4657
|
});
|
|
4566
4658
|
}, [config, welcomePopupShown, userInfoCollected]);
|
|
4567
4659
|
const sendMessage = useCallback(async (messageText) => {
|
|
4568
|
-
if (!messageText.trim() || !config || !sessionId) {
|
|
4660
|
+
if (!messageText.trim() || !config || !sessionId || activeReplyRef.current) {
|
|
4569
4661
|
return;
|
|
4570
4662
|
}
|
|
4663
|
+
const controller = new AbortController();
|
|
4664
|
+
activeReplyRef.current = controller;
|
|
4571
4665
|
const userMessage = {
|
|
4572
4666
|
id: Date.now(),
|
|
4573
4667
|
content: messageText,
|
|
@@ -4575,20 +4669,29 @@ const useAndyChat = (embedId) => {
|
|
|
4575
4669
|
timestamp: new Date().toISOString()
|
|
4576
4670
|
};
|
|
4577
4671
|
setMessages(prev => [...prev, userMessage]);
|
|
4672
|
+
const replyId = Date.now() + 1;
|
|
4673
|
+
const showReply = (content, isStreaming) => {
|
|
4674
|
+
if (controller.signal.aborted)
|
|
4675
|
+
return;
|
|
4676
|
+
const reply = { id: replyId, content, sender: 'bot', timestamp: new Date().toISOString(), isStreaming };
|
|
4677
|
+
setMessages(prev => prev.some(message => message.id === replyId)
|
|
4678
|
+
? prev.map(message => message.id === replyId ? reply : message)
|
|
4679
|
+
: [...prev, reply]);
|
|
4680
|
+
};
|
|
4578
4681
|
try {
|
|
4579
|
-
const response = await sendMessageForWidget(embedId, sessionId, messageText);
|
|
4580
|
-
if (
|
|
4682
|
+
const response = await sendMessageForWidget(embedId, sessionId, messageText, { signal: controller.signal, onText: text => showReply(text, true) });
|
|
4683
|
+
if (controller.signal.aborted)
|
|
4684
|
+
return;
|
|
4685
|
+
if (response.status === 'waiting') {
|
|
4686
|
+
setMessages(prev => prev.filter(message => message.id !== replyId));
|
|
4581
4687
|
return response;
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
content: response.text || 'I apologize, but I encountered an error processing your request.',
|
|
4585
|
-
sender: 'bot',
|
|
4586
|
-
timestamp: new Date().toISOString()
|
|
4587
|
-
};
|
|
4588
|
-
setMessages(prev => [...prev, botMessage]);
|
|
4688
|
+
}
|
|
4689
|
+
showReply(response.text, false);
|
|
4589
4690
|
return response;
|
|
4590
4691
|
}
|
|
4591
4692
|
catch (err) {
|
|
4693
|
+
if (controller.signal.aborted)
|
|
4694
|
+
return;
|
|
4592
4695
|
console.error('Error sending message:', err);
|
|
4593
4696
|
const errorMessage = {
|
|
4594
4697
|
id: Date.now() + 1,
|
|
@@ -4597,9 +4700,13 @@ const useAndyChat = (embedId) => {
|
|
|
4597
4700
|
timestamp: new Date().toISOString(),
|
|
4598
4701
|
isError: true
|
|
4599
4702
|
};
|
|
4600
|
-
setMessages(prev => [...prev, errorMessage]);
|
|
4703
|
+
setMessages(prev => [...prev.filter(message => message.id !== replyId), errorMessage]);
|
|
4601
4704
|
throw err;
|
|
4602
4705
|
}
|
|
4706
|
+
finally {
|
|
4707
|
+
if (activeReplyRef.current === controller)
|
|
4708
|
+
activeReplyRef.current = null;
|
|
4709
|
+
}
|
|
4603
4710
|
}, [embedId, sessionId, config]);
|
|
4604
4711
|
const showFaq = useCallback(() => {
|
|
4605
4712
|
setShowingFaq(true);
|
|
@@ -4638,6 +4745,8 @@ const useAndyChat = (embedId) => {
|
|
|
4638
4745
|
}
|
|
4639
4746
|
}, [embedId, sessionId]);
|
|
4640
4747
|
const startNewSession = useCallback(() => {
|
|
4748
|
+
activeReplyRef.current?.abort();
|
|
4749
|
+
activeReplyRef.current = null;
|
|
4641
4750
|
const newSessionId = generateSessionId();
|
|
4642
4751
|
setSessionId(newSessionId);
|
|
4643
4752
|
{
|
|
@@ -4707,9 +4816,11 @@ const MessageComponent = ({ message, botName }) => {
|
|
|
4707
4816
|
.filter(Boolean)
|
|
4708
4817
|
.join(' ');
|
|
4709
4818
|
const messageClasses = ['andy-message', `andy-message--${message.sender}`].filter(Boolean).join(' ');
|
|
4710
|
-
const messageContent = isBot ? parseMarkdown(message.content) : message.content;
|
|
4819
|
+
const messageContent = isBot && !message.isStreaming ? parseMarkdown(message.content) : message.content;
|
|
4711
4820
|
if (isBot) {
|
|
4712
|
-
return (jsxs("div", { className: containerClasses, children: [jsxs("div", { className: 'andy-bot-header', children: [jsx("div", { className: 'andy-bot-header__icon', children: 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: [jsx("path", { d: 'M12 6V2H8' }), 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' }), jsx("path", { d: 'M2 12h2' }), jsx("path", { d: 'M9 11v2' }), jsx("path", { d: 'M15 11v2' }), jsx("path", { d: 'M20 12h2' })] }) }), jsx("span", { className: 'andy-bot-header__name', children: botName || 'Andy bot' })] }), jsx("div", { className: messageClasses, children:
|
|
4821
|
+
return (jsxs("div", { className: containerClasses, children: [jsxs("div", { className: 'andy-bot-header', children: [jsx("div", { className: 'andy-bot-header__icon', children: 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: [jsx("path", { d: 'M12 6V2H8' }), 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' }), jsx("path", { d: 'M2 12h2' }), jsx("path", { d: 'M9 11v2' }), jsx("path", { d: 'M15 11v2' }), jsx("path", { d: 'M20 12h2' })] }) }), jsx("span", { className: 'andy-bot-header__name', children: botName || 'Andy bot' })] }), jsx("div", { className: messageClasses, children: message.isStreaming
|
|
4822
|
+
? jsx("div", { className: 'andy-message__content andy-message__stream', children: message.content })
|
|
4823
|
+
: jsx("div", { className: 'andy-message__content', dangerouslySetInnerHTML: { __html: messageContent } }) })] }));
|
|
4713
4824
|
}
|
|
4714
4825
|
return (jsx("div", { className: containerClasses, children: jsx("div", { className: messageClasses, children: jsx("div", { className: 'andy-message__content', children: jsx("p", { children: messageContent }) }) }) }));
|
|
4715
4826
|
};
|
|
@@ -4724,7 +4835,7 @@ const MessageList = ({ messages, isLoading, logoUrl, botName }) => {
|
|
|
4724
4835
|
});
|
|
4725
4836
|
}
|
|
4726
4837
|
}, [messages, isLoading]);
|
|
4727
|
-
return (jsxs(Fragment, { children: [messages.map(message => (jsx(MessageComponent, { message: message, logoUrl: logoUrl, botName: botName }, message.id || message.timestamp))), isLoading && jsx(TypingIndicator, {}), messages.length === 0 && !isLoading && (jsxs("div", { className: 'andy-empty-state', children: [jsx("div", { className: 'andy-empty-state__icon', children: "\uD83D\uDCAC" }), jsx("div", { className: 'andy-empty-state__text', children: "Start a conversation by typing a message below" })] })), jsx("div", { ref: messagesEndRef })] }));
|
|
4838
|
+
return (jsxs(Fragment, { children: [messages.map(message => (jsx(MessageComponent, { message: message, logoUrl: logoUrl, botName: botName }, message.id || message.timestamp))), isLoading && !messages.some(message => message.isStreaming) && jsx(TypingIndicator, {}), messages.length === 0 && !isLoading && (jsxs("div", { className: 'andy-empty-state', children: [jsx("div", { className: 'andy-empty-state__icon', children: "\uD83D\uDCAC" }), jsx("div", { className: 'andy-empty-state__text', children: "Start a conversation by typing a message below" })] })), jsx("div", { ref: messagesEndRef })] }));
|
|
4728
4839
|
};
|
|
4729
4840
|
|
|
4730
4841
|
const MessageInput = ({ onSendMessage, disabled = false, placeholder = 'Type your message here...' }) => {
|
|
@@ -4739,14 +4850,15 @@ const MessageInput = ({ onSendMessage, disabled = false, placeholder = 'Type you
|
|
|
4739
4850
|
}
|
|
4740
4851
|
try {
|
|
4741
4852
|
setIsSubmitting(true);
|
|
4742
|
-
await onSendMessage(trimmedMessage);
|
|
4743
4853
|
setMessage('');
|
|
4854
|
+
await onSendMessage(trimmedMessage);
|
|
4744
4855
|
if (inputRef.current) {
|
|
4745
4856
|
inputRef.current.focus();
|
|
4746
4857
|
}
|
|
4747
4858
|
}
|
|
4748
4859
|
catch (error) {
|
|
4749
4860
|
console.error('Error sending message:', error);
|
|
4861
|
+
setMessage(current => current || trimmedMessage);
|
|
4750
4862
|
}
|
|
4751
4863
|
finally {
|
|
4752
4864
|
setIsSubmitting(false);
|
|
@@ -4763,7 +4875,7 @@ const MessageInput = ({ onSendMessage, disabled = false, placeholder = 'Type you
|
|
|
4763
4875
|
};
|
|
4764
4876
|
const isInputDisabled = disabled || isSubmitting;
|
|
4765
4877
|
const canSend = message.trim().length > 0 && !isInputDisabled;
|
|
4766
|
-
return (jsxs("div", { className: 'andy-input', children: [jsx("input", { ref: inputRef, type: 'text', value: message, onChange: handleInputChange, onKeyDown: handleKeyDown, placeholder: placeholder, disabled:
|
|
4878
|
+
return (jsxs("div", { className: 'andy-input', children: [jsx("input", { ref: inputRef, type: 'text', value: message, onChange: handleInputChange, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, maxLength: 1000, autoComplete: 'off', id: 'andy-input' }), jsx("button", { type: 'button', onClick: () => handleSubmit({ preventDefault: () => { } }), disabled: !canSend, id: 'andy-send', children: isSubmitting ? ('...') : (jsx("svg", { width: '18', height: '18', viewBox: '0 0 24 24', fill: 'none', xmlns: 'http://www.w3.org/2000/svg', children: jsx("path", { d: 'M2.01 21L23 12 2.01 3 2 10l15 2-15 2z', fill: 'white' }) })) })] }));
|
|
4767
4879
|
};
|
|
4768
4880
|
|
|
4769
4881
|
const ChatWindow = ({ config, messages, isLoading, onClose, onSendMessage }) => {
|