@xcelsior/ui-chat 2.0.3 → 2.0.5
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/.storybook/preview.tsx +2 -1
- package/dist/index.d.mts +58 -5
- package/dist/index.d.ts +58 -5
- package/dist/index.js +1073 -473
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1060 -463
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/components/BookingCancelledCard.tsx +103 -0
- package/src/components/BookingCards.stories.tsx +102 -0
- package/src/components/BookingConfirmationCard.tsx +170 -0
- package/src/components/BookingSlotPicker.stories.tsx +87 -0
- package/src/components/BookingSlotPicker.tsx +253 -0
- package/src/components/BrandIcons.stories.tsx +32 -1
- package/src/components/BrandIcons.tsx +21 -17
- package/src/components/Chat.tsx +78 -83
- package/src/components/ChatWidget.tsx +30 -3
- package/src/components/MessageItem.tsx +83 -72
- package/src/components/MessageList.tsx +4 -0
- package/src/hooks/useDraggablePosition.ts +147 -42
- package/src/hooks/useMessages.ts +119 -45
- package/src/hooks/useWebSocket.ts +17 -4
- package/src/index.tsx +11 -0
- package/src/types.ts +39 -2
- package/src/utils/api.ts +1 -0
- package/storybook-static/assets/BookingCancelledCard-XHuB-Ebp.js +31 -0
- package/storybook-static/assets/BookingCards.stories-DfJ482RS.js +66 -0
- package/storybook-static/assets/BookingSlotPicker-BkfssueW.js +1 -0
- package/storybook-static/assets/BookingSlotPicker.stories-fYlg1zLg.js +50 -0
- package/storybook-static/assets/BrandIcons-BsRAdWzL.js +4 -0
- package/storybook-static/assets/BrandIcons.stories-C6gBovfU.js +106 -0
- package/storybook-static/assets/Chat.stories-BrR7LHsz.js +830 -0
- package/storybook-static/assets/{Color-YHDXOIA2-BMnd3YrF.js → Color-YHDXOIA2-azE51u2m.js} +1 -1
- package/storybook-static/assets/{DocsRenderer-CFRXHY34-i_W8iCu9.js → DocsRenderer-CFRXHY34-jTmzKIDk.js} +3 -3
- package/storybook-static/assets/MessageItem-pEOwuLyh.js +34 -0
- package/storybook-static/assets/MessageItem.stories-Cs5Vtkle.js +422 -0
- package/storybook-static/assets/{entry-preview-oDnntGcx.js → entry-preview-vcpiajAT.js} +1 -1
- package/storybook-static/assets/globe-BtMvkLMD.js +31 -0
- package/storybook-static/assets/{iframe-CGBtu2Se.js → iframe-Cx1n-SeE.js} +2 -2
- package/storybook-static/assets/preview-B8y-wc-n.css +1 -0
- package/storybook-static/assets/preview-CC4t7T7W.js +1 -0
- package/storybook-static/assets/{preview-BRpahs9B.js → preview-Do3b3dZv.js} +2 -2
- package/storybook-static/iframe.html +1 -1
- package/storybook-static/index.json +1 -1
- package/storybook-static/project.json +1 -1
- package/tsconfig.json +4 -0
- package/storybook-static/assets/BrandIcons-Cjy5INAp.js +0 -4
- package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +0 -64
- package/storybook-static/assets/Chat.stories-J_Yp51wU.js +0 -803
- package/storybook-static/assets/MessageItem-DAaKZ9s9.js +0 -14
- package/storybook-static/assets/MessageItem.stories-Ckr1_scc.js +0 -255
- package/storybook-static/assets/ToastContext-Bty1K7ya.js +0 -1
- package/storybook-static/assets/preview-DUOvJmsz.js +0 -1
- package/storybook-static/assets/preview-DcGwT3kv.css +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/ChatWidget.tsx
|
|
2
|
-
import { useCallback as
|
|
2
|
+
import { useCallback as useCallback6, useEffect as useEffect8, useRef as useRef7 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/hooks/useWebSocket.ts
|
|
5
5
|
import { useEffect, useRef, useState, useCallback } from "react";
|
|
@@ -12,6 +12,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
12
12
|
const reconnectAttemptsRef = useRef(0);
|
|
13
13
|
const messageHandlerRef = useRef(null);
|
|
14
14
|
const abortedRef = useRef(false);
|
|
15
|
+
const connectionGenerationRef = useRef(0);
|
|
15
16
|
const maxReconnectAttempts = 5;
|
|
16
17
|
const reconnectDelay = 3e3;
|
|
17
18
|
const isUsingExternalWs = !!externalWebSocket;
|
|
@@ -23,9 +24,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
23
24
|
try {
|
|
24
25
|
const message = JSON.parse(event.data);
|
|
25
26
|
setLastMessage(message);
|
|
26
|
-
if (message.type === "
|
|
27
|
-
config.onMessageReceived?.(message.data);
|
|
28
|
-
} else if (message.type === "error") {
|
|
27
|
+
if (message.type === "error") {
|
|
29
28
|
const err = new Error(message.data?.message || "WebSocket error");
|
|
30
29
|
setError(err);
|
|
31
30
|
config.onError?.(err);
|
|
@@ -45,6 +44,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
45
44
|
}, []);
|
|
46
45
|
const connect = useCallback(() => {
|
|
47
46
|
if (abortedRef.current) return;
|
|
47
|
+
const generation = ++connectionGenerationRef.current;
|
|
48
48
|
console.log("connecting to WebSocket...", config.currentUser, config.conversationId);
|
|
49
49
|
try {
|
|
50
50
|
if (wsRef.current) {
|
|
@@ -64,6 +64,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
64
64
|
}
|
|
65
65
|
const ws = new WebSocket(url.toString());
|
|
66
66
|
ws.onopen = () => {
|
|
67
|
+
if (generation !== connectionGenerationRef.current) return;
|
|
67
68
|
if (abortedRef.current) {
|
|
68
69
|
ws.close(1e3, "Effect cleaned up");
|
|
69
70
|
return;
|
|
@@ -75,6 +76,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
75
76
|
config.onConnectionChange?.(true);
|
|
76
77
|
};
|
|
77
78
|
ws.onerror = (event) => {
|
|
79
|
+
if (generation !== connectionGenerationRef.current) return;
|
|
78
80
|
if (abortedRef.current) return;
|
|
79
81
|
console.error("WebSocket error:", event);
|
|
80
82
|
const err = new Error("WebSocket connection error");
|
|
@@ -82,6 +84,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
82
84
|
config.onError?.(err);
|
|
83
85
|
};
|
|
84
86
|
ws.onclose = (event) => {
|
|
87
|
+
if (generation !== connectionGenerationRef.current) return;
|
|
85
88
|
if (abortedRef.current) return;
|
|
86
89
|
console.log("WebSocket closed:", event.code, event.reason);
|
|
87
90
|
setIsConnected(false);
|
|
@@ -168,7 +171,7 @@ function useWebSocket(config, externalWebSocket) {
|
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
// src/hooks/useMessages.ts
|
|
171
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
174
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
172
175
|
|
|
173
176
|
// src/utils/api.ts
|
|
174
177
|
import axios from "axios";
|
|
@@ -183,7 +186,8 @@ async function fetchMessages(baseUrl, params, headers) {
|
|
|
183
186
|
headers: {
|
|
184
187
|
"Content-Type": "application/json",
|
|
185
188
|
...headers
|
|
186
|
-
}
|
|
189
|
+
},
|
|
190
|
+
timeout: 8e3
|
|
187
191
|
});
|
|
188
192
|
return {
|
|
189
193
|
data: response.data.data ?? [],
|
|
@@ -200,6 +204,8 @@ async function fetchMessages(baseUrl, params, headers) {
|
|
|
200
204
|
}
|
|
201
205
|
|
|
202
206
|
// src/hooks/useMessages.ts
|
|
207
|
+
var BOT_THINKING_TIMEOUT = 15e3;
|
|
208
|
+
var AGENT_STATUSES = /* @__PURE__ */ new Set(["agent_active", "pending_agent"]);
|
|
203
209
|
function useMessages(websocket, config) {
|
|
204
210
|
const [messages, setMessages] = useState2([]);
|
|
205
211
|
const [isLoading, setIsLoading] = useState2(false);
|
|
@@ -208,6 +214,8 @@ function useMessages(websocket, config) {
|
|
|
208
214
|
const [hasMore, setHasMore] = useState2(true);
|
|
209
215
|
const [isLoadingMore, setIsLoadingMore] = useState2(false);
|
|
210
216
|
const [isBotThinking, setIsBotThinking] = useState2(false);
|
|
217
|
+
const botThinkingTimerRef = useRef2(null);
|
|
218
|
+
const agentActiveRef = useRef2(false);
|
|
211
219
|
const { httpApiUrl, conversationId, headers, onError, toast } = config;
|
|
212
220
|
const headersWithApiKey = useMemo(
|
|
213
221
|
() => ({
|
|
@@ -216,11 +224,33 @@ function useMessages(websocket, config) {
|
|
|
216
224
|
}),
|
|
217
225
|
[headers, config.apiKey]
|
|
218
226
|
);
|
|
227
|
+
const clearBotThinking = useCallback2(() => {
|
|
228
|
+
setIsBotThinking(false);
|
|
229
|
+
if (botThinkingTimerRef.current) {
|
|
230
|
+
clearTimeout(botThinkingTimerRef.current);
|
|
231
|
+
botThinkingTimerRef.current = null;
|
|
232
|
+
}
|
|
233
|
+
}, []);
|
|
219
234
|
useEffect2(() => {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
235
|
+
if (!httpApiUrl || !conversationId) return;
|
|
236
|
+
const fetchStatus = async () => {
|
|
237
|
+
try {
|
|
238
|
+
const url = `${httpApiUrl}/conversation?conversationId=${encodeURIComponent(conversationId)}`;
|
|
239
|
+
const res = await fetch(url, { headers: headersWithApiKey });
|
|
240
|
+
if (!res.ok) return;
|
|
241
|
+
const json = await res.json();
|
|
242
|
+
const conv = json.data?.conversation ?? json.data;
|
|
243
|
+
if (conv?.status && AGENT_STATUSES.has(conv.status)) {
|
|
244
|
+
agentActiveRef.current = true;
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
223
247
|
}
|
|
248
|
+
};
|
|
249
|
+
fetchStatus();
|
|
250
|
+
}, [httpApiUrl, conversationId, headersWithApiKey]);
|
|
251
|
+
useEffect2(() => {
|
|
252
|
+
if (!httpApiUrl || !conversationId) return;
|
|
253
|
+
const loadMessages = async () => {
|
|
224
254
|
setIsLoading(true);
|
|
225
255
|
setError(null);
|
|
226
256
|
try {
|
|
@@ -232,6 +262,9 @@ function useMessages(websocket, config) {
|
|
|
232
262
|
setMessages(result.data);
|
|
233
263
|
setNextPageToken(result.nextPageToken);
|
|
234
264
|
setHasMore(!!result.nextPageToken);
|
|
265
|
+
if (result.data.some((m) => m.senderType === "agent")) {
|
|
266
|
+
agentActiveRef.current = true;
|
|
267
|
+
}
|
|
235
268
|
} catch (err) {
|
|
236
269
|
const error2 = err instanceof Error ? err : new Error("Failed to load messages");
|
|
237
270
|
setError(error2);
|
|
@@ -247,30 +280,60 @@ function useMessages(websocket, config) {
|
|
|
247
280
|
useEffect2(() => {
|
|
248
281
|
if (websocket.lastMessage?.type === "message" && websocket.lastMessage.data) {
|
|
249
282
|
const newMessage = websocket.lastMessage.data;
|
|
250
|
-
if (conversationId && newMessage.conversationId !== conversationId)
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
283
|
+
if (conversationId && newMessage.conversationId !== conversationId) return;
|
|
253
284
|
setMessages((prev) => {
|
|
254
|
-
if (prev.some((msg) => msg.id === newMessage.id))
|
|
255
|
-
|
|
285
|
+
if (prev.some((msg) => msg.id === newMessage.id)) return prev;
|
|
286
|
+
const newMsgTime = new Date(newMessage.createdAt).getTime();
|
|
287
|
+
const tempIdx = prev.findIndex(
|
|
288
|
+
(msg) => msg.id.startsWith("temp-") && msg.content === newMessage.content && msg.senderId === newMessage.senderId && Math.abs(new Date(msg.createdAt).getTime() - newMsgTime) < 1e4
|
|
289
|
+
);
|
|
290
|
+
if (tempIdx >= 0) {
|
|
291
|
+
const next = [...prev];
|
|
292
|
+
next[tempIdx] = newMessage;
|
|
293
|
+
return next;
|
|
256
294
|
}
|
|
257
295
|
return [...prev, newMessage];
|
|
258
296
|
});
|
|
259
|
-
if (newMessage.senderType === "
|
|
260
|
-
|
|
297
|
+
if (newMessage.senderType === "agent") {
|
|
298
|
+
agentActiveRef.current = true;
|
|
299
|
+
}
|
|
300
|
+
if (newMessage.senderType !== "customer") {
|
|
301
|
+
clearBotThinking();
|
|
261
302
|
}
|
|
262
303
|
onMessageReceived?.(newMessage);
|
|
263
304
|
}
|
|
264
|
-
}, [websocket.lastMessage, onMessageReceived, conversationId]);
|
|
305
|
+
}, [websocket.lastMessage, onMessageReceived, conversationId, clearBotThinking]);
|
|
306
|
+
useEffect2(() => {
|
|
307
|
+
if (!websocket.lastMessage) return;
|
|
308
|
+
const { type, data } = websocket.lastMessage;
|
|
309
|
+
if (type === "bot_not_responding") {
|
|
310
|
+
if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
|
|
311
|
+
clearBotThinking();
|
|
312
|
+
agentActiveRef.current = true;
|
|
313
|
+
}
|
|
314
|
+
if (type === "conversation_updated") {
|
|
315
|
+
if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
|
|
316
|
+
if (data?.status && AGENT_STATUSES.has(data.status)) {
|
|
317
|
+
agentActiveRef.current = true;
|
|
318
|
+
clearBotThinking();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}, [websocket.lastMessage, conversationId, clearBotThinking]);
|
|
265
322
|
const addMessage = useCallback2((message) => {
|
|
323
|
+
const isAgentActive = agentActiveRef.current;
|
|
266
324
|
setMessages((prev) => {
|
|
267
|
-
if (prev.some((msg) => msg.id === message.id))
|
|
268
|
-
return prev;
|
|
269
|
-
}
|
|
325
|
+
if (prev.some((msg) => msg.id === message.id)) return prev;
|
|
270
326
|
return [...prev, message];
|
|
271
327
|
});
|
|
272
|
-
if (message.senderType === "
|
|
328
|
+
if (message.senderType === "agent") {
|
|
329
|
+
agentActiveRef.current = true;
|
|
330
|
+
}
|
|
331
|
+
if (message.senderType === "customer" && !isAgentActive) {
|
|
273
332
|
setIsBotThinking(true);
|
|
333
|
+
if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
|
|
334
|
+
botThinkingTimerRef.current = setTimeout(() => {
|
|
335
|
+
setIsBotThinking(false);
|
|
336
|
+
}, BOT_THINKING_TIMEOUT);
|
|
274
337
|
}
|
|
275
338
|
}, []);
|
|
276
339
|
const updateMessageStatus = useCallback2((messageId, status) => {
|
|
@@ -278,21 +341,16 @@ function useMessages(websocket, config) {
|
|
|
278
341
|
}, []);
|
|
279
342
|
const clearMessages = useCallback2(() => {
|
|
280
343
|
setMessages([]);
|
|
344
|
+
agentActiveRef.current = false;
|
|
281
345
|
}, []);
|
|
282
346
|
const loadMore = useCallback2(async () => {
|
|
283
|
-
if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken)
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
347
|
+
if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken) return;
|
|
286
348
|
setIsLoadingMore(true);
|
|
287
349
|
setError(null);
|
|
288
350
|
try {
|
|
289
351
|
const result = await fetchMessages(
|
|
290
352
|
httpApiUrl,
|
|
291
|
-
{
|
|
292
|
-
conversationId,
|
|
293
|
-
limit: 20,
|
|
294
|
-
pageToken: nextPageToken
|
|
295
|
-
},
|
|
353
|
+
{ conversationId, limit: 20, pageToken: nextPageToken },
|
|
296
354
|
headersWithApiKey
|
|
297
355
|
);
|
|
298
356
|
setMessages((prev) => [...result.data, ...prev]);
|
|
@@ -305,15 +363,12 @@ function useMessages(websocket, config) {
|
|
|
305
363
|
} finally {
|
|
306
364
|
setIsLoadingMore(false);
|
|
307
365
|
}
|
|
308
|
-
}, [
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
headersWithApiKey,
|
|
315
|
-
onError
|
|
316
|
-
]);
|
|
366
|
+
}, [hasMore, isLoadingMore, httpApiUrl, conversationId, nextPageToken, headersWithApiKey, onError]);
|
|
367
|
+
useEffect2(() => {
|
|
368
|
+
return () => {
|
|
369
|
+
if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
|
|
370
|
+
};
|
|
371
|
+
}, []);
|
|
317
372
|
return {
|
|
318
373
|
messages,
|
|
319
374
|
addMessage,
|
|
@@ -462,7 +517,7 @@ function useTypingIndicator(websocket) {
|
|
|
462
517
|
}
|
|
463
518
|
|
|
464
519
|
// src/hooks/useResizableWidget.ts
|
|
465
|
-
import { useCallback as useCallback3, useEffect as useEffect4, useRef as
|
|
520
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
|
|
466
521
|
var STORAGE_KEY = "xcelsior-chat-size";
|
|
467
522
|
var EDGE_ZONE = 8;
|
|
468
523
|
var CURSOR_MAP = {
|
|
@@ -530,10 +585,10 @@ function useResizableWidget({
|
|
|
530
585
|
const [isResizing, setIsResizing] = useState5(false);
|
|
531
586
|
const [isNearEdge, setIsNearEdge] = useState5(false);
|
|
532
587
|
const [activeEdge, setActiveEdge] = useState5(null);
|
|
533
|
-
const sizeRef =
|
|
588
|
+
const sizeRef = useRef3(size);
|
|
534
589
|
sizeRef.current = size;
|
|
535
|
-
const dragRef =
|
|
536
|
-
const containerRef =
|
|
590
|
+
const dragRef = useRef3(null);
|
|
591
|
+
const containerRef = useRef3(null);
|
|
537
592
|
const clamp = useCallback3(
|
|
538
593
|
(w, h) => {
|
|
539
594
|
const mxW = Math.min(maxWidth, window.innerWidth - 24);
|
|
@@ -741,24 +796,20 @@ function XcelsiorSymbol({ size = 24, color = "white", className = "", style }) {
|
|
|
741
796
|
}
|
|
742
797
|
);
|
|
743
798
|
}
|
|
744
|
-
function ChatBubbleIcon({ size =
|
|
745
|
-
return /* @__PURE__ */
|
|
746
|
-
"
|
|
747
|
-
{
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
"aria-hidden": "true",
|
|
759
|
-
children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
|
|
760
|
-
}
|
|
761
|
-
);
|
|
799
|
+
function ChatBubbleIcon({ size = 36, color = "white", className = "", style }) {
|
|
800
|
+
return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", xmlns: "http://www.w3.org/2000/svg", className, style, "aria-hidden": "true", children: [
|
|
801
|
+
/* @__PURE__ */ jsx("circle", { cx: "16", cy: "3.5", r: "3", fill: "#67e8f9", opacity: 0.4 }),
|
|
802
|
+
/* @__PURE__ */ jsx("circle", { cx: "16", cy: "3.5", r: "1.5", fill: "#67e8f9" }),
|
|
803
|
+
/* @__PURE__ */ jsx("rect", { x: "15", y: "4.5", width: "2", height: "4", rx: "1", fill: color }),
|
|
804
|
+
/* @__PURE__ */ jsx("rect", { x: "1", y: "15", width: "4", height: "6", rx: "2", fill: color, opacity: 0.5 }),
|
|
805
|
+
/* @__PURE__ */ jsx("rect", { x: "27", y: "15", width: "4", height: "6", rx: "2", fill: color, opacity: 0.5 }),
|
|
806
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "8.5", width: "22", height: "19", rx: "5", fill: color }),
|
|
807
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "16.5", r: "3", fill: "#1a4fd0" }),
|
|
808
|
+
/* @__PURE__ */ jsx("circle", { cx: "20", cy: "16.5", r: "3", fill: "#1a4fd0" }),
|
|
809
|
+
/* @__PURE__ */ jsx("circle", { cx: "13", cy: "15.5", r: "1.2", fill: color }),
|
|
810
|
+
/* @__PURE__ */ jsx("circle", { cx: "21", cy: "15.5", r: "1.2", fill: color }),
|
|
811
|
+
/* @__PURE__ */ jsx("path", { d: "M13 22.5q3 2.5 6 0", stroke: "#1a4fd0", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
812
|
+
] });
|
|
762
813
|
}
|
|
763
814
|
function XcelsiorAvatar({ size = 40, className = "" }) {
|
|
764
815
|
const iconSize = Math.round(size * 0.55);
|
|
@@ -922,7 +973,7 @@ function ChatHeader({ agent, onClose, onMinimize, theme }) {
|
|
|
922
973
|
}
|
|
923
974
|
|
|
924
975
|
// src/components/MessageList.tsx
|
|
925
|
-
import { useCallback as
|
|
976
|
+
import { useCallback as useCallback5, useEffect as useEffect6, useRef as useRef5 } from "react";
|
|
926
977
|
|
|
927
978
|
// src/components/Spinner.tsx
|
|
928
979
|
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -970,6 +1021,7 @@ function Spinner({ size = "md", color = "currentColor" }) {
|
|
|
970
1021
|
|
|
971
1022
|
// src/components/MessageItem.tsx
|
|
972
1023
|
import { formatDistanceToNow } from "date-fns";
|
|
1024
|
+
import { Check, CheckCheck, Headphones, Paperclip } from "lucide-react";
|
|
973
1025
|
|
|
974
1026
|
// src/components/MarkdownMessage.tsx
|
|
975
1027
|
import ReactMarkdown from "react-markdown";
|
|
@@ -1209,14 +1261,481 @@ function MarkdownMessage({ content, theme }) {
|
|
|
1209
1261
|
) });
|
|
1210
1262
|
}
|
|
1211
1263
|
|
|
1264
|
+
// src/components/BookingSlotPicker.tsx
|
|
1265
|
+
import { useState as useState6, useCallback as useCallback4 } from "react";
|
|
1266
|
+
import { Calendar, Clock, Globe } from "lucide-react";
|
|
1267
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1268
|
+
function BookingSlotPicker({ data, theme, onSlotSelected, disabled = false }) {
|
|
1269
|
+
const [selectedDateIndex, setSelectedDateIndex] = useState6(0);
|
|
1270
|
+
const [selectedSlot, setSelectedSlot] = useState6(null);
|
|
1271
|
+
const bgColor = theme?.background || "#00001a";
|
|
1272
|
+
const isLightTheme = (() => {
|
|
1273
|
+
if (!bgColor.startsWith("#")) return false;
|
|
1274
|
+
const hex = bgColor.replace("#", "");
|
|
1275
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1276
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1277
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1278
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1279
|
+
})();
|
|
1280
|
+
const primaryColor = theme?.primary || "#337eff";
|
|
1281
|
+
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1282
|
+
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
|
|
1283
|
+
const cardStyle = isLightTheme ? {
|
|
1284
|
+
backgroundColor: "rgba(0,0,0,0.03)",
|
|
1285
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)",
|
|
1286
|
+
borderRadius: "14px",
|
|
1287
|
+
padding: "14px"
|
|
1288
|
+
} : {
|
|
1289
|
+
backgroundColor: "rgba(255,255,255,0.04)",
|
|
1290
|
+
boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",
|
|
1291
|
+
borderRadius: "14px",
|
|
1292
|
+
padding: "14px"
|
|
1293
|
+
};
|
|
1294
|
+
const activePillStyle = {
|
|
1295
|
+
backgroundColor: primaryColor,
|
|
1296
|
+
color: "#ffffff",
|
|
1297
|
+
borderRadius: "20px",
|
|
1298
|
+
padding: "5px 12px",
|
|
1299
|
+
fontSize: "12px",
|
|
1300
|
+
fontWeight: "600",
|
|
1301
|
+
letterSpacing: "0.01em",
|
|
1302
|
+
cursor: disabled ? "default" : "pointer",
|
|
1303
|
+
border: "none",
|
|
1304
|
+
flexShrink: 0
|
|
1305
|
+
};
|
|
1306
|
+
const inactivePillStyle = isLightTheme ? {
|
|
1307
|
+
backgroundColor: "rgba(0,0,0,0.04)",
|
|
1308
|
+
color: textColor,
|
|
1309
|
+
borderRadius: "20px",
|
|
1310
|
+
padding: "5px 12px",
|
|
1311
|
+
fontSize: "12px",
|
|
1312
|
+
fontWeight: "500",
|
|
1313
|
+
letterSpacing: "0.01em",
|
|
1314
|
+
cursor: disabled ? "default" : "pointer",
|
|
1315
|
+
border: "none",
|
|
1316
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.1)",
|
|
1317
|
+
flexShrink: 0
|
|
1318
|
+
} : {
|
|
1319
|
+
backgroundColor: "rgba(255,255,255,0.05)",
|
|
1320
|
+
color: textColor,
|
|
1321
|
+
borderRadius: "20px",
|
|
1322
|
+
padding: "5px 12px",
|
|
1323
|
+
fontSize: "12px",
|
|
1324
|
+
fontWeight: "500",
|
|
1325
|
+
letterSpacing: "0.01em",
|
|
1326
|
+
cursor: disabled ? "default" : "pointer",
|
|
1327
|
+
border: "none",
|
|
1328
|
+
boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.1)",
|
|
1329
|
+
flexShrink: 0
|
|
1330
|
+
};
|
|
1331
|
+
const activeSlot = selectedSlot?.date === data.availableDates[selectedDateIndex]?.date ? selectedSlot.time : null;
|
|
1332
|
+
const [confirming, setConfirming] = useState6(false);
|
|
1333
|
+
const handleSlotClick = (date, time) => {
|
|
1334
|
+
if (disabled || confirming) return;
|
|
1335
|
+
setSelectedSlot({ date, time });
|
|
1336
|
+
};
|
|
1337
|
+
const handleConfirm = useCallback4(() => {
|
|
1338
|
+
if (!selectedSlot || disabled || confirming) return;
|
|
1339
|
+
setConfirming(true);
|
|
1340
|
+
onSlotSelected(selectedSlot.date, selectedSlot.time);
|
|
1341
|
+
}, [selectedSlot, disabled, confirming, onSlotSelected]);
|
|
1342
|
+
const currentDate = data.availableDates[selectedDateIndex];
|
|
1343
|
+
return /* @__PURE__ */ jsxs4("div", { style: cardStyle, children: [
|
|
1344
|
+
/* @__PURE__ */ jsx5(
|
|
1345
|
+
"p",
|
|
1346
|
+
{
|
|
1347
|
+
style: {
|
|
1348
|
+
fontSize: "11px",
|
|
1349
|
+
fontWeight: "600",
|
|
1350
|
+
letterSpacing: "0.06em",
|
|
1351
|
+
textTransform: "uppercase",
|
|
1352
|
+
color: textMuted,
|
|
1353
|
+
marginBottom: "10px"
|
|
1354
|
+
},
|
|
1355
|
+
children: "Select a date"
|
|
1356
|
+
}
|
|
1357
|
+
),
|
|
1358
|
+
/* @__PURE__ */ jsx5(
|
|
1359
|
+
"div",
|
|
1360
|
+
{
|
|
1361
|
+
style: {
|
|
1362
|
+
display: "flex",
|
|
1363
|
+
gap: "6px",
|
|
1364
|
+
overflowX: "auto",
|
|
1365
|
+
paddingBottom: "2px",
|
|
1366
|
+
scrollbarWidth: "none",
|
|
1367
|
+
msOverflowStyle: "none",
|
|
1368
|
+
marginBottom: "14px"
|
|
1369
|
+
},
|
|
1370
|
+
children: data.availableDates.map((dateSlot, index) => /* @__PURE__ */ jsx5(
|
|
1371
|
+
"button",
|
|
1372
|
+
{
|
|
1373
|
+
type: "button",
|
|
1374
|
+
onClick: () => !disabled && setSelectedDateIndex(index),
|
|
1375
|
+
style: index === selectedDateIndex ? activePillStyle : inactivePillStyle,
|
|
1376
|
+
title: dateSlot.dayName,
|
|
1377
|
+
"aria-pressed": index === selectedDateIndex,
|
|
1378
|
+
disabled,
|
|
1379
|
+
children: dateSlot.dayLabel
|
|
1380
|
+
},
|
|
1381
|
+
dateSlot.date
|
|
1382
|
+
))
|
|
1383
|
+
}
|
|
1384
|
+
),
|
|
1385
|
+
currentDate && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1386
|
+
/* @__PURE__ */ jsx5(
|
|
1387
|
+
"p",
|
|
1388
|
+
{
|
|
1389
|
+
style: {
|
|
1390
|
+
fontSize: "11px",
|
|
1391
|
+
fontWeight: "600",
|
|
1392
|
+
letterSpacing: "0.06em",
|
|
1393
|
+
textTransform: "uppercase",
|
|
1394
|
+
color: textMuted,
|
|
1395
|
+
marginBottom: "8px"
|
|
1396
|
+
},
|
|
1397
|
+
children: currentDate.dayName
|
|
1398
|
+
}
|
|
1399
|
+
),
|
|
1400
|
+
/* @__PURE__ */ jsx5(
|
|
1401
|
+
"div",
|
|
1402
|
+
{
|
|
1403
|
+
style: {
|
|
1404
|
+
display: "grid",
|
|
1405
|
+
gridTemplateColumns: "repeat(3, 1fr)",
|
|
1406
|
+
gap: "6px",
|
|
1407
|
+
marginBottom: "14px"
|
|
1408
|
+
},
|
|
1409
|
+
children: currentDate.slots.map((time) => {
|
|
1410
|
+
const isSelected = activeSlot === time;
|
|
1411
|
+
const isConfirmed = selectedSlot?.date === currentDate.date && selectedSlot.time === time;
|
|
1412
|
+
return /* @__PURE__ */ jsx5(
|
|
1413
|
+
"button",
|
|
1414
|
+
{
|
|
1415
|
+
type: "button",
|
|
1416
|
+
onClick: () => handleSlotClick(currentDate.date, time),
|
|
1417
|
+
disabled: disabled && !isConfirmed,
|
|
1418
|
+
"aria-pressed": isSelected,
|
|
1419
|
+
style: {
|
|
1420
|
+
...isSelected ? activePillStyle : inactivePillStyle,
|
|
1421
|
+
padding: "7px 8px",
|
|
1422
|
+
borderRadius: "8px",
|
|
1423
|
+
fontSize: "13px",
|
|
1424
|
+
fontWeight: isSelected ? "600" : "400",
|
|
1425
|
+
textAlign: "center",
|
|
1426
|
+
opacity: disabled && !isConfirmed ? 0.4 : 1,
|
|
1427
|
+
transition: "opacity 0.15s ease"
|
|
1428
|
+
},
|
|
1429
|
+
children: time
|
|
1430
|
+
},
|
|
1431
|
+
time
|
|
1432
|
+
);
|
|
1433
|
+
})
|
|
1434
|
+
}
|
|
1435
|
+
)
|
|
1436
|
+
] }),
|
|
1437
|
+
selectedSlot && !disabled && /* @__PURE__ */ jsxs4(
|
|
1438
|
+
"button",
|
|
1439
|
+
{
|
|
1440
|
+
type: "button",
|
|
1441
|
+
onClick: handleConfirm,
|
|
1442
|
+
disabled: confirming,
|
|
1443
|
+
style: {
|
|
1444
|
+
width: "100%",
|
|
1445
|
+
padding: "10px 16px",
|
|
1446
|
+
borderRadius: "10px",
|
|
1447
|
+
border: "none",
|
|
1448
|
+
backgroundColor: confirming ? `${primaryColor}80` : primaryColor,
|
|
1449
|
+
color: "#ffffff",
|
|
1450
|
+
fontSize: "13px",
|
|
1451
|
+
fontWeight: "600",
|
|
1452
|
+
letterSpacing: "0.01em",
|
|
1453
|
+
cursor: confirming ? "default" : "pointer",
|
|
1454
|
+
marginBottom: "12px",
|
|
1455
|
+
display: "flex",
|
|
1456
|
+
alignItems: "center",
|
|
1457
|
+
justifyContent: "center",
|
|
1458
|
+
gap: "6px",
|
|
1459
|
+
transition: "background-color 0.15s ease"
|
|
1460
|
+
},
|
|
1461
|
+
children: [
|
|
1462
|
+
/* @__PURE__ */ jsx5(Calendar, { size: 14, "aria-hidden": "true" }),
|
|
1463
|
+
confirming ? "Booking..." : `Book ${selectedSlot.time} on ${data.availableDates.find((d) => d.date === selectedSlot.date)?.dayLabel ?? selectedSlot.date}`
|
|
1464
|
+
]
|
|
1465
|
+
}
|
|
1466
|
+
),
|
|
1467
|
+
/* @__PURE__ */ jsxs4(
|
|
1468
|
+
"div",
|
|
1469
|
+
{
|
|
1470
|
+
style: {
|
|
1471
|
+
display: "flex",
|
|
1472
|
+
alignItems: "center",
|
|
1473
|
+
justifyContent: "space-between",
|
|
1474
|
+
gap: "8px"
|
|
1475
|
+
},
|
|
1476
|
+
children: [
|
|
1477
|
+
/* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: "5px" }, children: [
|
|
1478
|
+
/* @__PURE__ */ jsx5(Globe, { size: 11, color: textMuted, "aria-hidden": "true" }),
|
|
1479
|
+
/* @__PURE__ */ jsx5("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: data.timezone })
|
|
1480
|
+
] }),
|
|
1481
|
+
/* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: "5px" }, children: [
|
|
1482
|
+
/* @__PURE__ */ jsx5(Clock, { size: 11, color: textMuted, "aria-hidden": "true" }),
|
|
1483
|
+
/* @__PURE__ */ jsxs4("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: [
|
|
1484
|
+
data.meetingDuration,
|
|
1485
|
+
" min"
|
|
1486
|
+
] })
|
|
1487
|
+
] })
|
|
1488
|
+
]
|
|
1489
|
+
}
|
|
1490
|
+
)
|
|
1491
|
+
] });
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/components/BookingConfirmationCard.tsx
|
|
1495
|
+
import { CircleCheck, Calendar as Calendar2, Clock as Clock2, MessageSquare, Video, CalendarPlus, Globe as Globe2 } from "lucide-react";
|
|
1496
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1497
|
+
function BookingConfirmationCard({ data, theme }) {
|
|
1498
|
+
const bgColor = theme?.background || "#00001a";
|
|
1499
|
+
const isLightTheme = (() => {
|
|
1500
|
+
if (!bgColor.startsWith("#")) return false;
|
|
1501
|
+
const hex = bgColor.replace("#", "");
|
|
1502
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1503
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1504
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1505
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1506
|
+
})();
|
|
1507
|
+
const primaryColor = theme?.primary || "#337eff";
|
|
1508
|
+
const successColor = theme?.statusPositive || "#22c55e";
|
|
1509
|
+
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1510
|
+
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
|
|
1511
|
+
const cardStyle = isLightTheme ? {
|
|
1512
|
+
backgroundColor: "rgba(0,0,0,0.03)",
|
|
1513
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.08)",
|
|
1514
|
+
borderRadius: "14px",
|
|
1515
|
+
padding: "16px",
|
|
1516
|
+
overflow: "hidden"
|
|
1517
|
+
} : {
|
|
1518
|
+
backgroundColor: "rgba(255,255,255,0.04)",
|
|
1519
|
+
boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",
|
|
1520
|
+
borderRadius: "14px",
|
|
1521
|
+
padding: "16px",
|
|
1522
|
+
overflow: "hidden"
|
|
1523
|
+
};
|
|
1524
|
+
const detailRowStyle = {
|
|
1525
|
+
display: "flex",
|
|
1526
|
+
alignItems: "flex-start",
|
|
1527
|
+
gap: "8px",
|
|
1528
|
+
marginBottom: "8px"
|
|
1529
|
+
};
|
|
1530
|
+
const dividerStyle = {
|
|
1531
|
+
height: "1px",
|
|
1532
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)",
|
|
1533
|
+
margin: "12px 0"
|
|
1534
|
+
};
|
|
1535
|
+
const actionButtonBase = {
|
|
1536
|
+
display: "flex",
|
|
1537
|
+
alignItems: "center",
|
|
1538
|
+
justifyContent: "center",
|
|
1539
|
+
gap: "6px",
|
|
1540
|
+
flex: 1,
|
|
1541
|
+
padding: "9px 12px",
|
|
1542
|
+
borderRadius: "9px",
|
|
1543
|
+
fontSize: "12px",
|
|
1544
|
+
fontWeight: "600",
|
|
1545
|
+
letterSpacing: "0.01em",
|
|
1546
|
+
cursor: "pointer",
|
|
1547
|
+
textDecoration: "none",
|
|
1548
|
+
border: "none",
|
|
1549
|
+
transition: "opacity 0.15s ease"
|
|
1550
|
+
};
|
|
1551
|
+
return /* @__PURE__ */ jsxs5("div", { style: cardStyle, children: [
|
|
1552
|
+
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" }, children: [
|
|
1553
|
+
/* @__PURE__ */ jsx6(CircleCheck, { size: 22, color: successColor, "aria-hidden": "true" }),
|
|
1554
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1555
|
+
/* @__PURE__ */ jsx6(
|
|
1556
|
+
"p",
|
|
1557
|
+
{
|
|
1558
|
+
style: {
|
|
1559
|
+
fontSize: "14px",
|
|
1560
|
+
fontWeight: "700",
|
|
1561
|
+
color: textColor,
|
|
1562
|
+
letterSpacing: "0.006em",
|
|
1563
|
+
lineHeight: 1.3
|
|
1564
|
+
},
|
|
1565
|
+
children: "Meeting Confirmed"
|
|
1566
|
+
}
|
|
1567
|
+
),
|
|
1568
|
+
/* @__PURE__ */ jsx6("p", { style: { fontSize: "11px", color: successColor, letterSpacing: "0.01em", marginTop: "1px" }, children: "You'll receive a calendar invite shortly" })
|
|
1569
|
+
] })
|
|
1570
|
+
] }),
|
|
1571
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1572
|
+
/* @__PURE__ */ jsxs5("div", { style: detailRowStyle, children: [
|
|
1573
|
+
/* @__PURE__ */ jsx6(Calendar2, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1574
|
+
/* @__PURE__ */ jsxs5("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: [
|
|
1575
|
+
data.meetingDate,
|
|
1576
|
+
" at ",
|
|
1577
|
+
data.meetingTime
|
|
1578
|
+
] })
|
|
1579
|
+
] }),
|
|
1580
|
+
/* @__PURE__ */ jsxs5("div", { style: detailRowStyle, children: [
|
|
1581
|
+
/* @__PURE__ */ jsx6(Clock2, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1582
|
+
/* @__PURE__ */ jsxs5("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: [
|
|
1583
|
+
data.meetingDuration,
|
|
1584
|
+
" minutes"
|
|
1585
|
+
] })
|
|
1586
|
+
] }),
|
|
1587
|
+
/* @__PURE__ */ jsxs5("div", { style: { ...detailRowStyle, marginBottom: "0" }, children: [
|
|
1588
|
+
/* @__PURE__ */ jsx6(MessageSquare, { size: 14, color: primaryColor, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1589
|
+
/* @__PURE__ */ jsx6("span", { style: { fontSize: "13px", color: textColor, lineHeight: 1.4 }, children: data.meetingPurpose })
|
|
1590
|
+
] })
|
|
1591
|
+
] }),
|
|
1592
|
+
data.timezone && /* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", gap: "5px", marginTop: "8px" }, children: [
|
|
1593
|
+
/* @__PURE__ */ jsx6(Globe2, { size: 11, color: textMuted, "aria-hidden": "true" }),
|
|
1594
|
+
/* @__PURE__ */ jsx6("span", { style: { fontSize: "11px", color: textMuted, letterSpacing: "0.01em" }, children: data.timezone })
|
|
1595
|
+
] }),
|
|
1596
|
+
(data.meetLink || data.calendarLink) && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1597
|
+
/* @__PURE__ */ jsx6("div", { style: dividerStyle }),
|
|
1598
|
+
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
1599
|
+
data.meetLink && /* @__PURE__ */ jsxs5(
|
|
1600
|
+
"a",
|
|
1601
|
+
{
|
|
1602
|
+
href: data.meetLink,
|
|
1603
|
+
target: "_blank",
|
|
1604
|
+
rel: "noopener noreferrer",
|
|
1605
|
+
style: {
|
|
1606
|
+
...actionButtonBase,
|
|
1607
|
+
background: `linear-gradient(135deg, ${primaryColor}, ${theme?.primaryStrong || "#005eff"})`,
|
|
1608
|
+
color: "#ffffff",
|
|
1609
|
+
boxShadow: `0 2px 8px -2px ${primaryColor}50`
|
|
1610
|
+
},
|
|
1611
|
+
children: [
|
|
1612
|
+
/* @__PURE__ */ jsx6(Video, { size: 13, "aria-hidden": "true" }),
|
|
1613
|
+
"Join Meet"
|
|
1614
|
+
]
|
|
1615
|
+
}
|
|
1616
|
+
),
|
|
1617
|
+
data.calendarLink && /* @__PURE__ */ jsxs5(
|
|
1618
|
+
"a",
|
|
1619
|
+
{
|
|
1620
|
+
href: data.calendarLink,
|
|
1621
|
+
target: "_blank",
|
|
1622
|
+
rel: "noopener noreferrer",
|
|
1623
|
+
style: {
|
|
1624
|
+
...actionButtonBase,
|
|
1625
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.05)" : "rgba(255,255,255,0.07)",
|
|
1626
|
+
color: textColor,
|
|
1627
|
+
boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.1)" : "inset 0 0 0 0.5px rgba(255,255,255,0.12)"
|
|
1628
|
+
},
|
|
1629
|
+
children: [
|
|
1630
|
+
/* @__PURE__ */ jsx6(CalendarPlus, { size: 13, "aria-hidden": "true" }),
|
|
1631
|
+
"Add to Calendar"
|
|
1632
|
+
]
|
|
1633
|
+
}
|
|
1634
|
+
)
|
|
1635
|
+
] })
|
|
1636
|
+
] })
|
|
1637
|
+
] });
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// src/components/BookingCancelledCard.tsx
|
|
1641
|
+
import { CircleX, Calendar as Calendar3, MessageSquare as MessageSquare2 } from "lucide-react";
|
|
1642
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1643
|
+
function BookingCancelledCard({ data, theme }) {
|
|
1644
|
+
const bgColor = theme?.background || "#00001a";
|
|
1645
|
+
const isLightTheme = (() => {
|
|
1646
|
+
if (!bgColor.startsWith("#")) return false;
|
|
1647
|
+
const hex = bgColor.replace("#", "");
|
|
1648
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1649
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1650
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1651
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1652
|
+
})();
|
|
1653
|
+
const negativeColor = theme?.statusNegative || "#ef4444";
|
|
1654
|
+
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1655
|
+
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)");
|
|
1656
|
+
const cardStyle = isLightTheme ? {
|
|
1657
|
+
backgroundColor: "rgba(239,68,68,0.04)",
|
|
1658
|
+
boxShadow: "inset 0 0 0 1px rgba(239,68,68,0.12)",
|
|
1659
|
+
borderRadius: "14px",
|
|
1660
|
+
padding: "16px"
|
|
1661
|
+
} : {
|
|
1662
|
+
backgroundColor: "rgba(239,68,68,0.06)",
|
|
1663
|
+
boxShadow: "inset 0 0 0 0.5px rgba(239,68,68,0.18)",
|
|
1664
|
+
borderRadius: "14px",
|
|
1665
|
+
padding: "16px"
|
|
1666
|
+
};
|
|
1667
|
+
const strikethroughTextStyle = {
|
|
1668
|
+
fontSize: "13px",
|
|
1669
|
+
color: textMuted,
|
|
1670
|
+
lineHeight: 1.4,
|
|
1671
|
+
textDecoration: "line-through",
|
|
1672
|
+
textDecorationColor: isLightTheme ? "rgba(0,0,0,0.25)" : "rgba(255,255,255,0.2)"
|
|
1673
|
+
};
|
|
1674
|
+
const detailRowStyle = {
|
|
1675
|
+
display: "flex",
|
|
1676
|
+
alignItems: "flex-start",
|
|
1677
|
+
gap: "8px",
|
|
1678
|
+
marginBottom: "8px"
|
|
1679
|
+
};
|
|
1680
|
+
const cancelledByLabel = data.cancelledBy === "visitor" ? "Cancelled by you" : "Cancelled by support";
|
|
1681
|
+
return /* @__PURE__ */ jsxs6("div", { style: cardStyle, children: [
|
|
1682
|
+
/* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" }, children: [
|
|
1683
|
+
/* @__PURE__ */ jsx7(CircleX, { size: 22, color: negativeColor, "aria-hidden": "true" }),
|
|
1684
|
+
/* @__PURE__ */ jsxs6("div", { children: [
|
|
1685
|
+
/* @__PURE__ */ jsx7(
|
|
1686
|
+
"p",
|
|
1687
|
+
{
|
|
1688
|
+
style: {
|
|
1689
|
+
fontSize: "14px",
|
|
1690
|
+
fontWeight: "700",
|
|
1691
|
+
color: textColor,
|
|
1692
|
+
letterSpacing: "0.006em",
|
|
1693
|
+
lineHeight: 1.3
|
|
1694
|
+
},
|
|
1695
|
+
children: "Meeting Cancelled"
|
|
1696
|
+
}
|
|
1697
|
+
),
|
|
1698
|
+
/* @__PURE__ */ jsx7(
|
|
1699
|
+
"p",
|
|
1700
|
+
{
|
|
1701
|
+
style: {
|
|
1702
|
+
fontSize: "11px",
|
|
1703
|
+
color: negativeColor,
|
|
1704
|
+
letterSpacing: "0.01em",
|
|
1705
|
+
marginTop: "1px",
|
|
1706
|
+
opacity: 0.8
|
|
1707
|
+
},
|
|
1708
|
+
children: cancelledByLabel
|
|
1709
|
+
}
|
|
1710
|
+
)
|
|
1711
|
+
] })
|
|
1712
|
+
] }),
|
|
1713
|
+
/* @__PURE__ */ jsxs6("div", { children: [
|
|
1714
|
+
/* @__PURE__ */ jsxs6("div", { style: detailRowStyle, children: [
|
|
1715
|
+
/* @__PURE__ */ jsx7(Calendar3, { size: 14, color: textMuted, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1716
|
+
/* @__PURE__ */ jsxs6("span", { style: strikethroughTextStyle, children: [
|
|
1717
|
+
data.meetingDate,
|
|
1718
|
+
" at ",
|
|
1719
|
+
data.meetingTime
|
|
1720
|
+
] })
|
|
1721
|
+
] }),
|
|
1722
|
+
/* @__PURE__ */ jsxs6("div", { style: { ...detailRowStyle, marginBottom: "0" }, children: [
|
|
1723
|
+
/* @__PURE__ */ jsx7(MessageSquare2, { size: 14, color: textMuted, "aria-hidden": "true", style: { marginTop: "1px", flexShrink: 0 } }),
|
|
1724
|
+
/* @__PURE__ */ jsx7("span", { style: strikethroughTextStyle, children: data.meetingPurpose })
|
|
1725
|
+
] })
|
|
1726
|
+
] })
|
|
1727
|
+
] });
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1212
1730
|
// src/components/MessageItem.tsx
|
|
1213
|
-
import { jsx as
|
|
1731
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1214
1732
|
function MessageItem({
|
|
1215
1733
|
message,
|
|
1216
1734
|
currentUser,
|
|
1217
1735
|
showAvatar = true,
|
|
1218
1736
|
showTimestamp = true,
|
|
1219
|
-
theme
|
|
1737
|
+
theme,
|
|
1738
|
+
onBookingSlotSelected
|
|
1220
1739
|
}) {
|
|
1221
1740
|
const isOwnMessage = message.senderType === currentUser.type;
|
|
1222
1741
|
const isSystemMessage = message.senderType === "system";
|
|
@@ -1235,8 +1754,63 @@ function MessageItem({
|
|
|
1235
1754
|
const primaryStrong = theme?.primaryStrong || "#005eff";
|
|
1236
1755
|
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1237
1756
|
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.35)");
|
|
1757
|
+
if (message.messageType === "booking_slots" || message.messageType === "booking_confirmation" || message.messageType === "booking_cancelled") {
|
|
1758
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex gap-2.5 mb-3 flex-row", children: [
|
|
1759
|
+
showAvatar && /* @__PURE__ */ jsx8("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ jsx8(XcelsiorAvatar, { size: 28 }) }),
|
|
1760
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col max-w-[85%] items-start", children: [
|
|
1761
|
+
/* @__PURE__ */ jsx8(
|
|
1762
|
+
"span",
|
|
1763
|
+
{
|
|
1764
|
+
className: "mb-1 px-1 font-medium",
|
|
1765
|
+
style: {
|
|
1766
|
+
color: isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)",
|
|
1767
|
+
fontSize: "11px",
|
|
1768
|
+
letterSpacing: "0.019em"
|
|
1769
|
+
},
|
|
1770
|
+
children: "AI Assistant"
|
|
1771
|
+
}
|
|
1772
|
+
),
|
|
1773
|
+
message.messageType === "booking_slots" && /* @__PURE__ */ jsx8(
|
|
1774
|
+
BookingSlotPicker,
|
|
1775
|
+
{
|
|
1776
|
+
data: message.metadata?.bookingData,
|
|
1777
|
+
theme,
|
|
1778
|
+
onSlotSelected: (date, time) => {
|
|
1779
|
+
onBookingSlotSelected?.(date, time, message.id);
|
|
1780
|
+
},
|
|
1781
|
+
disabled: !!message.metadata?.slotSelected
|
|
1782
|
+
}
|
|
1783
|
+
),
|
|
1784
|
+
message.messageType === "booking_confirmation" && /* @__PURE__ */ jsx8(
|
|
1785
|
+
BookingConfirmationCard,
|
|
1786
|
+
{
|
|
1787
|
+
data: message.metadata?.bookingConfirmation,
|
|
1788
|
+
theme
|
|
1789
|
+
}
|
|
1790
|
+
),
|
|
1791
|
+
message.messageType === "booking_cancelled" && /* @__PURE__ */ jsx8(
|
|
1792
|
+
BookingCancelledCard,
|
|
1793
|
+
{
|
|
1794
|
+
data: message.metadata?.bookingCancelled,
|
|
1795
|
+
theme
|
|
1796
|
+
}
|
|
1797
|
+
),
|
|
1798
|
+
showTimestamp && /* @__PURE__ */ jsx8("div", { className: "flex items-center gap-1.5 mt-1 px-1", children: /* @__PURE__ */ jsx8(
|
|
1799
|
+
"span",
|
|
1800
|
+
{
|
|
1801
|
+
style: {
|
|
1802
|
+
fontSize: "11px",
|
|
1803
|
+
letterSpacing: "0.019em",
|
|
1804
|
+
color: textMuted
|
|
1805
|
+
},
|
|
1806
|
+
children: formatDistanceToNow(new Date(message.createdAt), { addSuffix: true })
|
|
1807
|
+
}
|
|
1808
|
+
) })
|
|
1809
|
+
] })
|
|
1810
|
+
] });
|
|
1811
|
+
}
|
|
1238
1812
|
if (isSystemMessage) {
|
|
1239
|
-
return /* @__PURE__ */
|
|
1813
|
+
return /* @__PURE__ */ jsx8("div", { className: "flex justify-center my-3", children: /* @__PURE__ */ jsx8(
|
|
1240
1814
|
"div",
|
|
1241
1815
|
{
|
|
1242
1816
|
className: "px-4 py-1.5 rounded-full",
|
|
@@ -1244,7 +1818,7 @@ function MessageItem({
|
|
|
1244
1818
|
backgroundColor: isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.03)",
|
|
1245
1819
|
boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.06)" : "inset 0 0 0 0.5px rgba(255,255,255,0.06)"
|
|
1246
1820
|
},
|
|
1247
|
-
children: /* @__PURE__ */
|
|
1821
|
+
children: /* @__PURE__ */ jsx8(
|
|
1248
1822
|
"p",
|
|
1249
1823
|
{
|
|
1250
1824
|
style: {
|
|
@@ -1283,12 +1857,12 @@ function MessageItem({
|
|
|
1283
1857
|
borderRadius: "18px 18px 18px 4px",
|
|
1284
1858
|
boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.06), inset 0 1px 0 0 rgba(255,255,255,0.08)"
|
|
1285
1859
|
};
|
|
1286
|
-
return /* @__PURE__ */
|
|
1860
|
+
return /* @__PURE__ */ jsxs7(
|
|
1287
1861
|
"div",
|
|
1288
1862
|
{
|
|
1289
1863
|
className: `flex gap-2.5 mb-3 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
|
|
1290
1864
|
children: [
|
|
1291
|
-
showAvatar && !isOwnMessage && /* @__PURE__ */
|
|
1865
|
+
showAvatar && !isOwnMessage && /* @__PURE__ */ jsx8("div", { className: "flex-shrink-0 mt-auto mb-5", children: isBotMessage || isAIMessage ? /* @__PURE__ */ jsx8(XcelsiorAvatar, { size: 28 }) : /* @__PURE__ */ jsx8(
|
|
1292
1866
|
"div",
|
|
1293
1867
|
{
|
|
1294
1868
|
className: "h-7 w-7 rounded-full flex items-center justify-center",
|
|
@@ -1296,34 +1870,24 @@ function MessageItem({
|
|
|
1296
1870
|
background: isLightTheme ? `linear-gradient(135deg, ${primaryColor}30, rgba(0,0,0,0.04))` : `linear-gradient(135deg, ${primaryColor}60, rgba(255,255,255,0.06))`,
|
|
1297
1871
|
boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.08)" : "inset 0 0 0 0.5px rgba(255,255,255,0.1)"
|
|
1298
1872
|
},
|
|
1299
|
-
children: /* @__PURE__ */
|
|
1300
|
-
|
|
1873
|
+
children: /* @__PURE__ */ jsx8(
|
|
1874
|
+
Headphones,
|
|
1301
1875
|
{
|
|
1302
|
-
|
|
1303
|
-
height: "14",
|
|
1304
|
-
viewBox: "0 0 24 24",
|
|
1305
|
-
fill: "none",
|
|
1876
|
+
size: 14,
|
|
1306
1877
|
stroke: isLightTheme ? primaryColor : "white",
|
|
1307
|
-
strokeWidth:
|
|
1308
|
-
|
|
1309
|
-
strokeLinejoin: "round",
|
|
1310
|
-
"aria-hidden": "true",
|
|
1311
|
-
children: [
|
|
1312
|
-
/* @__PURE__ */ jsx5("title", { children: "Agent" }),
|
|
1313
|
-
/* @__PURE__ */ jsx5("path", { d: "M3 18v-6a9 9 0 0 1 18 0v6" }),
|
|
1314
|
-
/* @__PURE__ */ jsx5("path", { d: "M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" })
|
|
1315
|
-
]
|
|
1878
|
+
strokeWidth: 2,
|
|
1879
|
+
"aria-hidden": "true"
|
|
1316
1880
|
}
|
|
1317
1881
|
)
|
|
1318
1882
|
}
|
|
1319
1883
|
) }),
|
|
1320
|
-
showAvatar && isOwnMessage && /* @__PURE__ */
|
|
1321
|
-
/* @__PURE__ */
|
|
1884
|
+
showAvatar && isOwnMessage && /* @__PURE__ */ jsx8("div", { className: "w-7 flex-shrink-0" }),
|
|
1885
|
+
/* @__PURE__ */ jsxs7(
|
|
1322
1886
|
"div",
|
|
1323
1887
|
{
|
|
1324
1888
|
className: `flex flex-col max-w-[75%] ${isOwnMessage ? "items-end" : "items-start"}`,
|
|
1325
1889
|
children: [
|
|
1326
|
-
senderLabel && /* @__PURE__ */
|
|
1890
|
+
senderLabel && /* @__PURE__ */ jsx8(
|
|
1327
1891
|
"span",
|
|
1328
1892
|
{
|
|
1329
1893
|
className: "mb-1 px-1 font-medium",
|
|
@@ -1335,7 +1899,7 @@ function MessageItem({
|
|
|
1335
1899
|
children: senderLabel
|
|
1336
1900
|
}
|
|
1337
1901
|
),
|
|
1338
|
-
/* @__PURE__ */
|
|
1902
|
+
/* @__PURE__ */ jsxs7(
|
|
1339
1903
|
"div",
|
|
1340
1904
|
{
|
|
1341
1905
|
className: "px-4 py-2.5",
|
|
@@ -1348,12 +1912,12 @@ function MessageItem({
|
|
|
1348
1912
|
children: [
|
|
1349
1913
|
message.messageType === "text" && (isOwnMessage ? (
|
|
1350
1914
|
// User messages: plain text — no markdown parsing
|
|
1351
|
-
/* @__PURE__ */
|
|
1915
|
+
/* @__PURE__ */ jsx8("span", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: message.content })
|
|
1352
1916
|
) : (
|
|
1353
1917
|
// Bot / agent messages: full markdown rendering
|
|
1354
|
-
/* @__PURE__ */
|
|
1918
|
+
/* @__PURE__ */ jsx8(MarkdownMessage, { content: message.content, theme })
|
|
1355
1919
|
)),
|
|
1356
|
-
message.messageType === "image" && /* @__PURE__ */
|
|
1920
|
+
message.messageType === "image" && /* @__PURE__ */ jsx8("div", { children: /* @__PURE__ */ jsx8(
|
|
1357
1921
|
"img",
|
|
1358
1922
|
{
|
|
1359
1923
|
src: message.content,
|
|
@@ -1362,26 +1926,9 @@ function MessageItem({
|
|
|
1362
1926
|
loading: "lazy"
|
|
1363
1927
|
}
|
|
1364
1928
|
) }),
|
|
1365
|
-
message.messageType === "file" && /* @__PURE__ */
|
|
1366
|
-
/* @__PURE__ */
|
|
1367
|
-
|
|
1368
|
-
{
|
|
1369
|
-
width: "18",
|
|
1370
|
-
height: "18",
|
|
1371
|
-
viewBox: "0 0 24 24",
|
|
1372
|
-
fill: "none",
|
|
1373
|
-
stroke: "currentColor",
|
|
1374
|
-
strokeWidth: "1.75",
|
|
1375
|
-
strokeLinecap: "round",
|
|
1376
|
-
strokeLinejoin: "round",
|
|
1377
|
-
"aria-hidden": "true",
|
|
1378
|
-
children: [
|
|
1379
|
-
/* @__PURE__ */ jsx5("title", { children: "File" }),
|
|
1380
|
-
/* @__PURE__ */ jsx5("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" })
|
|
1381
|
-
]
|
|
1382
|
-
}
|
|
1383
|
-
),
|
|
1384
|
-
/* @__PURE__ */ jsx5(
|
|
1929
|
+
message.messageType === "file" && /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
|
|
1930
|
+
/* @__PURE__ */ jsx8(Paperclip, { size: 18, strokeWidth: 1.75, "aria-hidden": "true" }),
|
|
1931
|
+
/* @__PURE__ */ jsx8(
|
|
1385
1932
|
"a",
|
|
1386
1933
|
{
|
|
1387
1934
|
href: message.content,
|
|
@@ -1399,12 +1946,12 @@ function MessageItem({
|
|
|
1399
1946
|
]
|
|
1400
1947
|
}
|
|
1401
1948
|
),
|
|
1402
|
-
showTimestamp && /* @__PURE__ */
|
|
1949
|
+
showTimestamp && /* @__PURE__ */ jsxs7(
|
|
1403
1950
|
"div",
|
|
1404
1951
|
{
|
|
1405
1952
|
className: `flex items-center gap-1.5 mt-1 px-1 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
|
|
1406
1953
|
children: [
|
|
1407
|
-
/* @__PURE__ */
|
|
1954
|
+
/* @__PURE__ */ jsx8(
|
|
1408
1955
|
"span",
|
|
1409
1956
|
{
|
|
1410
1957
|
style: {
|
|
@@ -1417,63 +1964,10 @@ function MessageItem({
|
|
|
1417
1964
|
})
|
|
1418
1965
|
}
|
|
1419
1966
|
),
|
|
1420
|
-
isOwnMessage && message.status && /* @__PURE__ */
|
|
1421
|
-
message.status === "sent" && /* @__PURE__ */
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
width: "14",
|
|
1425
|
-
height: "14",
|
|
1426
|
-
viewBox: "0 0 24 24",
|
|
1427
|
-
fill: "none",
|
|
1428
|
-
stroke: "currentColor",
|
|
1429
|
-
strokeWidth: "2.5",
|
|
1430
|
-
strokeLinecap: "round",
|
|
1431
|
-
strokeLinejoin: "round",
|
|
1432
|
-
"aria-hidden": "true",
|
|
1433
|
-
children: [
|
|
1434
|
-
/* @__PURE__ */ jsx5("title", { children: "Sent" }),
|
|
1435
|
-
/* @__PURE__ */ jsx5("polyline", { points: "20 6 9 17 4 12" })
|
|
1436
|
-
]
|
|
1437
|
-
}
|
|
1438
|
-
),
|
|
1439
|
-
message.status === "delivered" && /* @__PURE__ */ jsxs4(
|
|
1440
|
-
"svg",
|
|
1441
|
-
{
|
|
1442
|
-
width: "14",
|
|
1443
|
-
height: "14",
|
|
1444
|
-
viewBox: "0 0 24 24",
|
|
1445
|
-
fill: "none",
|
|
1446
|
-
stroke: "currentColor",
|
|
1447
|
-
strokeWidth: "2.5",
|
|
1448
|
-
strokeLinecap: "round",
|
|
1449
|
-
strokeLinejoin: "round",
|
|
1450
|
-
"aria-hidden": "true",
|
|
1451
|
-
children: [
|
|
1452
|
-
/* @__PURE__ */ jsx5("title", { children: "Delivered" }),
|
|
1453
|
-
/* @__PURE__ */ jsx5("polyline", { points: "18 6 7 17 2 12" }),
|
|
1454
|
-
/* @__PURE__ */ jsx5("polyline", { points: "22 6 11 17" })
|
|
1455
|
-
]
|
|
1456
|
-
}
|
|
1457
|
-
),
|
|
1458
|
-
message.status === "read" && /* @__PURE__ */ jsxs4(
|
|
1459
|
-
"svg",
|
|
1460
|
-
{
|
|
1461
|
-
width: "14",
|
|
1462
|
-
height: "14",
|
|
1463
|
-
viewBox: "0 0 24 24",
|
|
1464
|
-
fill: "none",
|
|
1465
|
-
stroke: primaryColor,
|
|
1466
|
-
strokeWidth: "2.5",
|
|
1467
|
-
strokeLinecap: "round",
|
|
1468
|
-
strokeLinejoin: "round",
|
|
1469
|
-
"aria-hidden": "true",
|
|
1470
|
-
children: [
|
|
1471
|
-
/* @__PURE__ */ jsx5("title", { children: "Read" }),
|
|
1472
|
-
/* @__PURE__ */ jsx5("polyline", { points: "18 6 7 17 2 12" }),
|
|
1473
|
-
/* @__PURE__ */ jsx5("polyline", { points: "22 6 11 17" })
|
|
1474
|
-
]
|
|
1475
|
-
}
|
|
1476
|
-
)
|
|
1967
|
+
isOwnMessage && message.status && /* @__PURE__ */ jsxs7("span", { style: { color: textMuted }, children: [
|
|
1968
|
+
message.status === "sent" && /* @__PURE__ */ jsx8(Check, { size: 14, strokeWidth: 2.5, "aria-hidden": "true" }),
|
|
1969
|
+
message.status === "delivered" && /* @__PURE__ */ jsx8(CheckCheck, { size: 14, strokeWidth: 2.5, "aria-hidden": "true" }),
|
|
1970
|
+
message.status === "read" && /* @__PURE__ */ jsx8(CheckCheck, { size: 14, strokeWidth: 2.5, stroke: primaryColor, "aria-hidden": "true" })
|
|
1477
1971
|
] })
|
|
1478
1972
|
]
|
|
1479
1973
|
}
|
|
@@ -1487,8 +1981,8 @@ function MessageItem({
|
|
|
1487
1981
|
}
|
|
1488
1982
|
|
|
1489
1983
|
// src/components/ThinkingIndicator.tsx
|
|
1490
|
-
import { useEffect as useEffect5, useRef as
|
|
1491
|
-
import { jsx as
|
|
1984
|
+
import { useEffect as useEffect5, useRef as useRef4, useState as useState7 } from "react";
|
|
1985
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1492
1986
|
var PHRASE_POOLS = {
|
|
1493
1987
|
// ── Greetings & small talk ──
|
|
1494
1988
|
greeting: [
|
|
@@ -1671,10 +2165,10 @@ function ThinkingIndicator({
|
|
|
1671
2165
|
const isLightTheme = computeIsLightTheme2(theme?.background);
|
|
1672
2166
|
const primaryColor = theme?.primary || "#337eff";
|
|
1673
2167
|
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
|
|
1674
|
-
const [phraseIndex, setPhraseIndex] =
|
|
1675
|
-
const [displayText, setDisplayText] =
|
|
1676
|
-
const [isDeleting, setIsDeleting] =
|
|
1677
|
-
const phrasesRef =
|
|
2168
|
+
const [phraseIndex, setPhraseIndex] = useState7(0);
|
|
2169
|
+
const [displayText, setDisplayText] = useState7("");
|
|
2170
|
+
const [isDeleting, setIsDeleting] = useState7(false);
|
|
2171
|
+
const phrasesRef = useRef4(getShuffledPhrases(detectContext(lastUserMessage)));
|
|
1678
2172
|
useEffect5(() => {
|
|
1679
2173
|
phrasesRef.current = getShuffledPhrases(detectContext(lastUserMessage));
|
|
1680
2174
|
setPhraseIndex(0);
|
|
@@ -1707,7 +2201,7 @@ function ThinkingIndicator({
|
|
|
1707
2201
|
}
|
|
1708
2202
|
return () => clearTimeout(timeout);
|
|
1709
2203
|
}, [displayText, isDeleting, phraseIndex]);
|
|
1710
|
-
return /* @__PURE__ */
|
|
2204
|
+
return /* @__PURE__ */ jsxs8(
|
|
1711
2205
|
"div",
|
|
1712
2206
|
{
|
|
1713
2207
|
className: "flex gap-2.5 mb-3",
|
|
@@ -1715,8 +2209,8 @@ function ThinkingIndicator({
|
|
|
1715
2209
|
"aria-live": "polite",
|
|
1716
2210
|
"aria-label": "Xcelsior is thinking",
|
|
1717
2211
|
children: [
|
|
1718
|
-
showAvatar && /* @__PURE__ */
|
|
1719
|
-
/* @__PURE__ */
|
|
2212
|
+
showAvatar && /* @__PURE__ */ jsx9("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ jsx9(XcelsiorAvatar, { size: 28 }) }),
|
|
2213
|
+
/* @__PURE__ */ jsx9("div", { className: "flex flex-col items-start", children: /* @__PURE__ */ jsx9(
|
|
1720
2214
|
"div",
|
|
1721
2215
|
{
|
|
1722
2216
|
style: {
|
|
@@ -1726,8 +2220,8 @@ function ThinkingIndicator({
|
|
|
1726
2220
|
padding: "12px 16px",
|
|
1727
2221
|
minWidth: 160
|
|
1728
2222
|
},
|
|
1729
|
-
children: /* @__PURE__ */
|
|
1730
|
-
/* @__PURE__ */
|
|
2223
|
+
children: /* @__PURE__ */ jsxs8("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
2224
|
+
/* @__PURE__ */ jsx9("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx9(
|
|
1731
2225
|
"span",
|
|
1732
2226
|
{
|
|
1733
2227
|
style: {
|
|
@@ -1741,7 +2235,7 @@ function ThinkingIndicator({
|
|
|
1741
2235
|
},
|
|
1742
2236
|
i
|
|
1743
2237
|
)) }),
|
|
1744
|
-
/* @__PURE__ */
|
|
2238
|
+
/* @__PURE__ */ jsxs8(
|
|
1745
2239
|
"span",
|
|
1746
2240
|
{
|
|
1747
2241
|
style: {
|
|
@@ -1752,7 +2246,7 @@ function ThinkingIndicator({
|
|
|
1752
2246
|
},
|
|
1753
2247
|
children: [
|
|
1754
2248
|
displayText,
|
|
1755
|
-
/* @__PURE__ */
|
|
2249
|
+
/* @__PURE__ */ jsx9(
|
|
1756
2250
|
"span",
|
|
1757
2251
|
{
|
|
1758
2252
|
style: {
|
|
@@ -1778,7 +2272,7 @@ function ThinkingIndicator({
|
|
|
1778
2272
|
}
|
|
1779
2273
|
|
|
1780
2274
|
// src/components/MessageList.tsx
|
|
1781
|
-
import { jsx as
|
|
2275
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1782
2276
|
function MessageList({
|
|
1783
2277
|
messages,
|
|
1784
2278
|
currentUser,
|
|
@@ -1791,15 +2285,16 @@ function MessageList({
|
|
|
1791
2285
|
isLoadingMore = false,
|
|
1792
2286
|
theme,
|
|
1793
2287
|
onQuickAction,
|
|
1794
|
-
isBotThinking = false
|
|
2288
|
+
isBotThinking = false,
|
|
2289
|
+
onBookingSlotSelected
|
|
1795
2290
|
}) {
|
|
1796
|
-
const messagesEndRef =
|
|
1797
|
-
const containerRef =
|
|
1798
|
-
const prevLengthRef =
|
|
1799
|
-
const loadMoreTriggerRef =
|
|
1800
|
-
const prevScrollHeightRef =
|
|
1801
|
-
const hasInitialScrolledRef =
|
|
1802
|
-
const isUserScrollingRef =
|
|
2291
|
+
const messagesEndRef = useRef5(null);
|
|
2292
|
+
const containerRef = useRef5(null);
|
|
2293
|
+
const prevLengthRef = useRef5(messages.length);
|
|
2294
|
+
const loadMoreTriggerRef = useRef5(null);
|
|
2295
|
+
const prevScrollHeightRef = useRef5(0);
|
|
2296
|
+
const hasInitialScrolledRef = useRef5(false);
|
|
2297
|
+
const isUserScrollingRef = useRef5(false);
|
|
1803
2298
|
const bgColor = theme?.background || "#00001a";
|
|
1804
2299
|
const isLightTheme = (() => {
|
|
1805
2300
|
if (!bgColor.startsWith("#")) return false;
|
|
@@ -1844,7 +2339,7 @@ function MessageList({
|
|
|
1844
2339
|
prevScrollHeightRef.current = 0;
|
|
1845
2340
|
}
|
|
1846
2341
|
}, [isLoadingMore]);
|
|
1847
|
-
const handleScroll =
|
|
2342
|
+
const handleScroll = useCallback5(() => {
|
|
1848
2343
|
if (!containerRef.current || !onLoadMore || !hasMore || isLoadingMore) return;
|
|
1849
2344
|
if (!isUserScrollingRef.current) return;
|
|
1850
2345
|
const { scrollTop } = containerRef.current;
|
|
@@ -1859,11 +2354,11 @@ function MessageList({
|
|
|
1859
2354
|
return () => container.removeEventListener("scroll", handleScroll);
|
|
1860
2355
|
}, [handleScroll]);
|
|
1861
2356
|
if (isLoading) {
|
|
1862
|
-
return /* @__PURE__ */
|
|
2357
|
+
return /* @__PURE__ */ jsx10("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx10(Spinner, { size: "lg" }) });
|
|
1863
2358
|
}
|
|
1864
2359
|
if (messages.length === 0) {
|
|
1865
|
-
return /* @__PURE__ */
|
|
1866
|
-
/* @__PURE__ */
|
|
2360
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col items-center justify-center h-full text-center", style: { padding: "40px 32px" }, children: [
|
|
2361
|
+
/* @__PURE__ */ jsx10(
|
|
1867
2362
|
"h3",
|
|
1868
2363
|
{
|
|
1869
2364
|
className: "font-semibold mb-2",
|
|
@@ -1875,7 +2370,7 @@ function MessageList({
|
|
|
1875
2370
|
children: "How can we help?"
|
|
1876
2371
|
}
|
|
1877
2372
|
),
|
|
1878
|
-
/* @__PURE__ */
|
|
2373
|
+
/* @__PURE__ */ jsx10(
|
|
1879
2374
|
"p",
|
|
1880
2375
|
{
|
|
1881
2376
|
className: "max-w-[240px]",
|
|
@@ -1889,11 +2384,11 @@ function MessageList({
|
|
|
1889
2384
|
children: "Ask us anything. We are here to help you get the most out of Xcelsior."
|
|
1890
2385
|
}
|
|
1891
2386
|
),
|
|
1892
|
-
/* @__PURE__ */
|
|
2387
|
+
/* @__PURE__ */ jsx10("div", { className: "flex flex-wrap justify-center gap-2", children: [
|
|
1893
2388
|
{ label: "Our services", message: "What services does Xcelsior offer?" },
|
|
1894
2389
|
{ label: "Get a quote", message: "I would like to get a quote for a project" },
|
|
1895
2390
|
{ label: "Support", message: "I need help with something" }
|
|
1896
|
-
].map((action) => /* @__PURE__ */
|
|
2391
|
+
].map((action) => /* @__PURE__ */ jsx10(
|
|
1897
2392
|
"button",
|
|
1898
2393
|
{
|
|
1899
2394
|
type: "button",
|
|
@@ -1925,14 +2420,14 @@ function MessageList({
|
|
|
1925
2420
|
)) })
|
|
1926
2421
|
] });
|
|
1927
2422
|
}
|
|
1928
|
-
return /* @__PURE__ */
|
|
2423
|
+
return /* @__PURE__ */ jsxs9(
|
|
1929
2424
|
"div",
|
|
1930
2425
|
{
|
|
1931
2426
|
ref: containerRef,
|
|
1932
2427
|
className: "flex-1 overflow-y-auto px-4 py-3",
|
|
1933
2428
|
style: { scrollBehavior: "smooth" },
|
|
1934
2429
|
children: [
|
|
1935
|
-
/* @__PURE__ */
|
|
2430
|
+
/* @__PURE__ */ jsx10("style", { children: `
|
|
1936
2431
|
@keyframes thinkingPulse {
|
|
1937
2432
|
0%, 60%, 100% { opacity: 0.3; transform: scale(0.8); }
|
|
1938
2433
|
30% { opacity: 1; transform: scale(1); }
|
|
@@ -1942,23 +2437,24 @@ function MessageList({
|
|
|
1942
2437
|
50% { opacity: 0; }
|
|
1943
2438
|
}
|
|
1944
2439
|
` }),
|
|
1945
|
-
isLoadingMore && /* @__PURE__ */
|
|
1946
|
-
/* @__PURE__ */
|
|
1947
|
-
messages.map((message) => /* @__PURE__ */
|
|
2440
|
+
isLoadingMore && /* @__PURE__ */ jsx10("div", { className: "flex justify-center py-3", children: /* @__PURE__ */ jsx10(Spinner, { size: "sm" }) }),
|
|
2441
|
+
/* @__PURE__ */ jsx10("div", { ref: loadMoreTriggerRef }),
|
|
2442
|
+
messages.map((message) => /* @__PURE__ */ jsx10(
|
|
1948
2443
|
MessageItem,
|
|
1949
2444
|
{
|
|
1950
2445
|
message,
|
|
1951
2446
|
currentUser,
|
|
1952
2447
|
showAvatar: true,
|
|
1953
2448
|
showTimestamp: true,
|
|
1954
|
-
theme
|
|
2449
|
+
theme,
|
|
2450
|
+
onBookingSlotSelected
|
|
1955
2451
|
},
|
|
1956
2452
|
message.id
|
|
1957
2453
|
)),
|
|
1958
|
-
isTyping && /* @__PURE__ */
|
|
1959
|
-
/* @__PURE__ */
|
|
1960
|
-
/* @__PURE__ */
|
|
1961
|
-
/* @__PURE__ */
|
|
2454
|
+
isTyping && /* @__PURE__ */ jsxs9("div", { className: "flex gap-2.5 mb-3", children: [
|
|
2455
|
+
/* @__PURE__ */ jsx10("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ jsx10(XcelsiorAvatar, { size: 28 }) }),
|
|
2456
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-col items-start", children: [
|
|
2457
|
+
/* @__PURE__ */ jsx10(
|
|
1962
2458
|
"div",
|
|
1963
2459
|
{
|
|
1964
2460
|
className: "px-4 py-3",
|
|
@@ -1967,7 +2463,7 @@ function MessageList({
|
|
|
1967
2463
|
borderRadius: "18px 18px 18px 4px",
|
|
1968
2464
|
boxShadow: isLightTheme ? "inset 0 0 0 1px rgba(0,0,0,0.06)" : "inset 0 0 0 0.5px rgba(255,255,255,0.06), inset 0 1px 0 0 rgba(255,255,255,0.08)"
|
|
1969
2465
|
},
|
|
1970
|
-
children: /* @__PURE__ */
|
|
2466
|
+
children: /* @__PURE__ */ jsx10("div", { className: "flex gap-1.5 items-center", style: { height: 16 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx10(
|
|
1971
2467
|
"span",
|
|
1972
2468
|
{
|
|
1973
2469
|
className: "rounded-full animate-bounce",
|
|
@@ -1983,7 +2479,7 @@ function MessageList({
|
|
|
1983
2479
|
)) })
|
|
1984
2480
|
}
|
|
1985
2481
|
),
|
|
1986
|
-
typingUser && /* @__PURE__ */
|
|
2482
|
+
typingUser && /* @__PURE__ */ jsxs9(
|
|
1987
2483
|
"span",
|
|
1988
2484
|
{
|
|
1989
2485
|
className: "mt-1 px-1",
|
|
@@ -2000,24 +2496,24 @@ function MessageList({
|
|
|
2000
2496
|
)
|
|
2001
2497
|
] })
|
|
2002
2498
|
] }),
|
|
2003
|
-
isBotThinking && !isTyping && /* @__PURE__ */
|
|
2499
|
+
isBotThinking && !isTyping && /* @__PURE__ */ jsx10(
|
|
2004
2500
|
ThinkingIndicator,
|
|
2005
2501
|
{
|
|
2006
2502
|
theme,
|
|
2007
2503
|
lastUserMessage: messages.filter((m) => m.senderType === "customer").pop()?.content
|
|
2008
2504
|
}
|
|
2009
2505
|
),
|
|
2010
|
-
/* @__PURE__ */
|
|
2506
|
+
/* @__PURE__ */ jsx10("div", { ref: messagesEndRef })
|
|
2011
2507
|
]
|
|
2012
2508
|
}
|
|
2013
2509
|
);
|
|
2014
2510
|
}
|
|
2015
2511
|
|
|
2016
2512
|
// src/components/ChatInput.tsx
|
|
2017
|
-
import { useEffect as useEffect7, useRef as
|
|
2513
|
+
import { useEffect as useEffect7, useRef as useRef6, useState as useState8 } from "react";
|
|
2018
2514
|
import { createPortal } from "react-dom";
|
|
2019
2515
|
import Picker from "@emoji-mart/react";
|
|
2020
|
-
import { Fragment as
|
|
2516
|
+
import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2021
2517
|
function isLightColor(color) {
|
|
2022
2518
|
let r = 0, g = 0, b = 0;
|
|
2023
2519
|
if (color.startsWith("#")) {
|
|
@@ -2035,18 +2531,18 @@ function ChatInput({
|
|
|
2035
2531
|
fileUpload,
|
|
2036
2532
|
disabled = false
|
|
2037
2533
|
}) {
|
|
2038
|
-
const [message, setMessage] =
|
|
2039
|
-
const [showEmojiPicker, setShowEmojiPicker] =
|
|
2040
|
-
const [emojiData, setEmojiData] =
|
|
2041
|
-
const [emojiPickerPosition, setEmojiPickerPosition] =
|
|
2042
|
-
const [isFocused, setIsFocused] =
|
|
2043
|
-
const textAreaRef =
|
|
2044
|
-
const emojiPickerRef =
|
|
2045
|
-
const emojiButtonRef =
|
|
2046
|
-
const fileInputRef =
|
|
2047
|
-
const typingTimeoutRef =
|
|
2048
|
-
const startTypingTimeoutRef =
|
|
2049
|
-
const isTypingRef =
|
|
2534
|
+
const [message, setMessage] = useState8("");
|
|
2535
|
+
const [showEmojiPicker, setShowEmojiPicker] = useState8(false);
|
|
2536
|
+
const [emojiData, setEmojiData] = useState8();
|
|
2537
|
+
const [emojiPickerPosition, setEmojiPickerPosition] = useState8(null);
|
|
2538
|
+
const [isFocused, setIsFocused] = useState8(false);
|
|
2539
|
+
const textAreaRef = useRef6(null);
|
|
2540
|
+
const emojiPickerRef = useRef6(null);
|
|
2541
|
+
const emojiButtonRef = useRef6(null);
|
|
2542
|
+
const fileInputRef = useRef6(null);
|
|
2543
|
+
const typingTimeoutRef = useRef6(null);
|
|
2544
|
+
const startTypingTimeoutRef = useRef6(null);
|
|
2545
|
+
const isTypingRef = useRef6(false);
|
|
2050
2546
|
const enableEmoji = config.enableEmoji ?? true;
|
|
2051
2547
|
const enableFileUpload = config.enableFileUpload ?? true;
|
|
2052
2548
|
const bgColor = config.theme?.background || "#00001a";
|
|
@@ -2195,7 +2691,7 @@ ${uploadedFile.markdown}
|
|
|
2195
2691
|
};
|
|
2196
2692
|
const canSend = message.trim().length > 0 && !disabled;
|
|
2197
2693
|
const placeholderColor = isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.3)";
|
|
2198
|
-
return /* @__PURE__ */
|
|
2694
|
+
return /* @__PURE__ */ jsxs10(
|
|
2199
2695
|
"div",
|
|
2200
2696
|
{
|
|
2201
2697
|
className: "px-4 py-3",
|
|
@@ -2203,7 +2699,7 @@ ${uploadedFile.markdown}
|
|
|
2203
2699
|
borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)"
|
|
2204
2700
|
},
|
|
2205
2701
|
children: [
|
|
2206
|
-
/* @__PURE__ */
|
|
2702
|
+
/* @__PURE__ */ jsxs10(
|
|
2207
2703
|
"div",
|
|
2208
2704
|
{
|
|
2209
2705
|
className: "relative flex items-center rounded-full transition-all duration-200",
|
|
@@ -2215,8 +2711,8 @@ ${uploadedFile.markdown}
|
|
|
2215
2711
|
backdropFilter: "blur(32px)"
|
|
2216
2712
|
},
|
|
2217
2713
|
children: [
|
|
2218
|
-
/* @__PURE__ */
|
|
2219
|
-
/* @__PURE__ */
|
|
2714
|
+
/* @__PURE__ */ jsx11("style", { children: `.xchat-input::placeholder { color: ${placeholderColor}; }` }),
|
|
2715
|
+
/* @__PURE__ */ jsx11(
|
|
2220
2716
|
"textarea",
|
|
2221
2717
|
{
|
|
2222
2718
|
ref: textAreaRef,
|
|
@@ -2245,8 +2741,8 @@ ${uploadedFile.markdown}
|
|
|
2245
2741
|
disabled
|
|
2246
2742
|
}
|
|
2247
2743
|
),
|
|
2248
|
-
/* @__PURE__ */
|
|
2249
|
-
enableEmoji && /* @__PURE__ */
|
|
2744
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1 px-2 shrink-0", children: [
|
|
2745
|
+
enableEmoji && /* @__PURE__ */ jsx11(
|
|
2250
2746
|
"button",
|
|
2251
2747
|
{
|
|
2252
2748
|
ref: emojiButtonRef,
|
|
@@ -2276,7 +2772,7 @@ ${uploadedFile.markdown}
|
|
|
2276
2772
|
},
|
|
2277
2773
|
disabled,
|
|
2278
2774
|
"aria-label": "Add emoji",
|
|
2279
|
-
children: /* @__PURE__ */
|
|
2775
|
+
children: /* @__PURE__ */ jsxs10(
|
|
2280
2776
|
"svg",
|
|
2281
2777
|
{
|
|
2282
2778
|
width: "18",
|
|
@@ -2289,18 +2785,18 @@ ${uploadedFile.markdown}
|
|
|
2289
2785
|
strokeLinejoin: "round",
|
|
2290
2786
|
"aria-hidden": "true",
|
|
2291
2787
|
children: [
|
|
2292
|
-
/* @__PURE__ */
|
|
2293
|
-
/* @__PURE__ */
|
|
2294
|
-
/* @__PURE__ */
|
|
2295
|
-
/* @__PURE__ */
|
|
2296
|
-
/* @__PURE__ */
|
|
2788
|
+
/* @__PURE__ */ jsx11("title", { children: "Emoji" }),
|
|
2789
|
+
/* @__PURE__ */ jsx11("circle", { cx: "12", cy: "12", r: "10" }),
|
|
2790
|
+
/* @__PURE__ */ jsx11("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
2791
|
+
/* @__PURE__ */ jsx11("line", { x1: "9", y1: "9", x2: "9.01", y2: "9" }),
|
|
2792
|
+
/* @__PURE__ */ jsx11("line", { x1: "15", y1: "9", x2: "15.01", y2: "9" })
|
|
2297
2793
|
]
|
|
2298
2794
|
}
|
|
2299
2795
|
)
|
|
2300
2796
|
}
|
|
2301
2797
|
),
|
|
2302
|
-
enableFileUpload && fileUpload.canUpload && /* @__PURE__ */
|
|
2303
|
-
/* @__PURE__ */
|
|
2798
|
+
enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ jsxs10(Fragment4, { children: [
|
|
2799
|
+
/* @__PURE__ */ jsx11(
|
|
2304
2800
|
"button",
|
|
2305
2801
|
{
|
|
2306
2802
|
type: "button",
|
|
@@ -2320,7 +2816,7 @@ ${uploadedFile.markdown}
|
|
|
2320
2816
|
},
|
|
2321
2817
|
disabled: disabled || fileUpload.isUploading,
|
|
2322
2818
|
"aria-label": "Attach file",
|
|
2323
|
-
children: fileUpload.isUploading ? /* @__PURE__ */
|
|
2819
|
+
children: fileUpload.isUploading ? /* @__PURE__ */ jsxs10(
|
|
2324
2820
|
"svg",
|
|
2325
2821
|
{
|
|
2326
2822
|
width: "18",
|
|
@@ -2332,11 +2828,11 @@ ${uploadedFile.markdown}
|
|
|
2332
2828
|
className: "animate-spin",
|
|
2333
2829
|
"aria-hidden": "true",
|
|
2334
2830
|
children: [
|
|
2335
|
-
/* @__PURE__ */
|
|
2336
|
-
/* @__PURE__ */
|
|
2831
|
+
/* @__PURE__ */ jsx11("title", { children: "Uploading" }),
|
|
2832
|
+
/* @__PURE__ */ jsx11("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
|
|
2337
2833
|
]
|
|
2338
2834
|
}
|
|
2339
|
-
) : /* @__PURE__ */
|
|
2835
|
+
) : /* @__PURE__ */ jsxs10(
|
|
2340
2836
|
"svg",
|
|
2341
2837
|
{
|
|
2342
2838
|
width: "18",
|
|
@@ -2349,14 +2845,14 @@ ${uploadedFile.markdown}
|
|
|
2349
2845
|
strokeLinejoin: "round",
|
|
2350
2846
|
"aria-hidden": "true",
|
|
2351
2847
|
children: [
|
|
2352
|
-
/* @__PURE__ */
|
|
2353
|
-
/* @__PURE__ */
|
|
2848
|
+
/* @__PURE__ */ jsx11("title", { children: "Attach file" }),
|
|
2849
|
+
/* @__PURE__ */ jsx11("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" })
|
|
2354
2850
|
]
|
|
2355
2851
|
}
|
|
2356
2852
|
)
|
|
2357
2853
|
}
|
|
2358
2854
|
),
|
|
2359
|
-
/* @__PURE__ */
|
|
2855
|
+
/* @__PURE__ */ jsx11(
|
|
2360
2856
|
"input",
|
|
2361
2857
|
{
|
|
2362
2858
|
ref: fileInputRef,
|
|
@@ -2367,7 +2863,7 @@ ${uploadedFile.markdown}
|
|
|
2367
2863
|
}
|
|
2368
2864
|
)
|
|
2369
2865
|
] }),
|
|
2370
|
-
/* @__PURE__ */
|
|
2866
|
+
/* @__PURE__ */ jsx11(
|
|
2371
2867
|
"button",
|
|
2372
2868
|
{
|
|
2373
2869
|
type: "button",
|
|
@@ -2382,7 +2878,7 @@ ${uploadedFile.markdown}
|
|
|
2382
2878
|
boxShadow: canSend ? `0 2px 8px -2px ${primaryColor}60` : "none"
|
|
2383
2879
|
},
|
|
2384
2880
|
"aria-label": "Send message",
|
|
2385
|
-
children: /* @__PURE__ */
|
|
2881
|
+
children: /* @__PURE__ */ jsxs10(
|
|
2386
2882
|
"svg",
|
|
2387
2883
|
{
|
|
2388
2884
|
width: "18",
|
|
@@ -2395,9 +2891,9 @@ ${uploadedFile.markdown}
|
|
|
2395
2891
|
strokeLinejoin: "round",
|
|
2396
2892
|
"aria-hidden": "true",
|
|
2397
2893
|
children: [
|
|
2398
|
-
/* @__PURE__ */
|
|
2399
|
-
/* @__PURE__ */
|
|
2400
|
-
/* @__PURE__ */
|
|
2894
|
+
/* @__PURE__ */ jsx11("title", { children: "Send" }),
|
|
2895
|
+
/* @__PURE__ */ jsx11("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
2896
|
+
/* @__PURE__ */ jsx11("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
2401
2897
|
]
|
|
2402
2898
|
}
|
|
2403
2899
|
)
|
|
@@ -2407,8 +2903,8 @@ ${uploadedFile.markdown}
|
|
|
2407
2903
|
]
|
|
2408
2904
|
}
|
|
2409
2905
|
),
|
|
2410
|
-
fileUpload.isUploading && /* @__PURE__ */
|
|
2411
|
-
/* @__PURE__ */
|
|
2906
|
+
fileUpload.isUploading && /* @__PURE__ */ jsxs10("div", { className: "mt-2 px-1", children: [
|
|
2907
|
+
/* @__PURE__ */ jsx11(
|
|
2412
2908
|
"div",
|
|
2413
2909
|
{
|
|
2414
2910
|
className: "w-full rounded-full overflow-hidden",
|
|
@@ -2416,7 +2912,7 @@ ${uploadedFile.markdown}
|
|
|
2416
2912
|
height: 3,
|
|
2417
2913
|
backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)"
|
|
2418
2914
|
},
|
|
2419
|
-
children: /* @__PURE__ */
|
|
2915
|
+
children: /* @__PURE__ */ jsx11(
|
|
2420
2916
|
"div",
|
|
2421
2917
|
{
|
|
2422
2918
|
className: "h-full rounded-full transition-all duration-300",
|
|
@@ -2428,7 +2924,7 @@ ${uploadedFile.markdown}
|
|
|
2428
2924
|
)
|
|
2429
2925
|
}
|
|
2430
2926
|
),
|
|
2431
|
-
/* @__PURE__ */
|
|
2927
|
+
/* @__PURE__ */ jsxs10(
|
|
2432
2928
|
"p",
|
|
2433
2929
|
{
|
|
2434
2930
|
className: "mt-1",
|
|
@@ -2446,7 +2942,7 @@ ${uploadedFile.markdown}
|
|
|
2446
2942
|
)
|
|
2447
2943
|
] }),
|
|
2448
2944
|
showEmojiPicker && emojiData && emojiPickerPosition && typeof document !== "undefined" && createPortal(
|
|
2449
|
-
/* @__PURE__ */
|
|
2945
|
+
/* @__PURE__ */ jsx11(
|
|
2450
2946
|
"div",
|
|
2451
2947
|
{
|
|
2452
2948
|
ref: emojiPickerRef,
|
|
@@ -2461,7 +2957,7 @@ ${uploadedFile.markdown}
|
|
|
2461
2957
|
"0 16px 48px -8px rgba(0,0,0,0.5)"
|
|
2462
2958
|
].join(", ")
|
|
2463
2959
|
},
|
|
2464
|
-
children: /* @__PURE__ */
|
|
2960
|
+
children: /* @__PURE__ */ jsx11(
|
|
2465
2961
|
Picker,
|
|
2466
2962
|
{
|
|
2467
2963
|
data: emojiData,
|
|
@@ -2487,7 +2983,7 @@ ${uploadedFile.markdown}
|
|
|
2487
2983
|
}
|
|
2488
2984
|
|
|
2489
2985
|
// src/components/ChatWidget.tsx
|
|
2490
|
-
import { jsx as
|
|
2986
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2491
2987
|
function getAnonymousUser() {
|
|
2492
2988
|
let anonId;
|
|
2493
2989
|
try {
|
|
@@ -2533,14 +3029,20 @@ function ChatWidget({
|
|
|
2533
3029
|
maxHeight: 900,
|
|
2534
3030
|
enabled: !isFullPage
|
|
2535
3031
|
});
|
|
2536
|
-
const
|
|
3032
|
+
const lastSentRef = useRef7(null);
|
|
3033
|
+
const handleSendMessage = useCallback6(
|
|
2537
3034
|
(content) => {
|
|
2538
3035
|
if (!websocket.isConnected) {
|
|
2539
3036
|
config.toast?.error("Not connected to chat server");
|
|
2540
3037
|
return;
|
|
2541
3038
|
}
|
|
3039
|
+
const now = Date.now();
|
|
3040
|
+
if (lastSentRef.current && lastSentRef.current.content === content && now - lastSentRef.current.time < 1e3) {
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3043
|
+
lastSentRef.current = { content, time: now };
|
|
2542
3044
|
const optimisticMessage = {
|
|
2543
|
-
id: `temp-${
|
|
3045
|
+
id: `temp-${now}`,
|
|
2544
3046
|
conversationId: config.conversationId || "",
|
|
2545
3047
|
senderId: effectiveUser.email,
|
|
2546
3048
|
senderType: effectiveUser.type,
|
|
@@ -2559,7 +3061,18 @@ function ChatWidget({
|
|
|
2559
3061
|
},
|
|
2560
3062
|
[websocket, config, addMessage, effectiveUser]
|
|
2561
3063
|
);
|
|
2562
|
-
const
|
|
3064
|
+
const handleBookingSlotSelected = useCallback6(
|
|
3065
|
+
(date, time, _messageId) => {
|
|
3066
|
+
if (!websocket.isConnected) return;
|
|
3067
|
+
websocket.sendMessage("bookSlot", {
|
|
3068
|
+
conversationId: config.conversationId,
|
|
3069
|
+
date,
|
|
3070
|
+
time
|
|
3071
|
+
});
|
|
3072
|
+
},
|
|
3073
|
+
[websocket, config]
|
|
3074
|
+
);
|
|
3075
|
+
const handleTyping = useCallback6(
|
|
2563
3076
|
(isTyping2) => {
|
|
2564
3077
|
if (!websocket.isConnected || config.enableTypingIndicator === false) return;
|
|
2565
3078
|
websocket.sendMessage("typing", {
|
|
@@ -2589,7 +3102,6 @@ function ChatWidget({
|
|
|
2589
3102
|
})();
|
|
2590
3103
|
const positionClass = resolvedPosition === "left" ? "left-4" : "right-4";
|
|
2591
3104
|
const containerStyle = isFullPage ? { backgroundColor: bgColor, color: textColor } : {
|
|
2592
|
-
position: "relative",
|
|
2593
3105
|
width,
|
|
2594
3106
|
height,
|
|
2595
3107
|
maxHeight: "calc(100vh - 100px)",
|
|
@@ -2621,14 +3133,14 @@ function ChatWidget({
|
|
|
2621
3133
|
outlineOffset: -1,
|
|
2622
3134
|
transition: "outline 150ms ease"
|
|
2623
3135
|
};
|
|
2624
|
-
return /* @__PURE__ */
|
|
3136
|
+
return /* @__PURE__ */ jsxs11(
|
|
2625
3137
|
"div",
|
|
2626
3138
|
{
|
|
2627
3139
|
className: isFullPage ? `flex flex-col h-full ${className}` : `fixed bottom-20 ${positionClass} z-50 flex flex-col overflow-hidden ${className}`,
|
|
2628
3140
|
style: { ...containerStyle, ...dotGridBg, ...edgeHintStyle },
|
|
2629
3141
|
...!isFullPage ? containerResizeProps : {},
|
|
2630
3142
|
children: [
|
|
2631
|
-
!isFullPage && /* @__PURE__ */
|
|
3143
|
+
!isFullPage && /* @__PURE__ */ jsx12(
|
|
2632
3144
|
ChatHeader,
|
|
2633
3145
|
{
|
|
2634
3146
|
agent: effectiveUser.type === "customer" ? {
|
|
@@ -2642,7 +3154,7 @@ function ChatWidget({
|
|
|
2642
3154
|
theme: config.theme
|
|
2643
3155
|
}
|
|
2644
3156
|
),
|
|
2645
|
-
!websocket.isConnected && /* @__PURE__ */
|
|
3157
|
+
!websocket.isConnected && /* @__PURE__ */ jsx12(
|
|
2646
3158
|
"div",
|
|
2647
3159
|
{
|
|
2648
3160
|
className: "px-4 py-2",
|
|
@@ -2650,15 +3162,15 @@ function ChatWidget({
|
|
|
2650
3162
|
backgroundColor: isLightTheme ? "rgba(255,169,56,0.08)" : "rgba(255,169,56,0.06)",
|
|
2651
3163
|
borderBottom: `1px solid rgba(255,169,56,${isLightTheme ? "0.15" : "0.12"})`
|
|
2652
3164
|
},
|
|
2653
|
-
children: /* @__PURE__ */
|
|
2654
|
-
/* @__PURE__ */
|
|
3165
|
+
children: /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
3166
|
+
/* @__PURE__ */ jsx12(
|
|
2655
3167
|
"div",
|
|
2656
3168
|
{
|
|
2657
3169
|
className: "w-1.5 h-1.5 rounded-full animate-pulse",
|
|
2658
3170
|
style: { backgroundColor: config.theme?.statusCaution || "#ffa938" }
|
|
2659
3171
|
}
|
|
2660
3172
|
),
|
|
2661
|
-
/* @__PURE__ */
|
|
3173
|
+
/* @__PURE__ */ jsx12(
|
|
2662
3174
|
"span",
|
|
2663
3175
|
{
|
|
2664
3176
|
style: {
|
|
@@ -2669,7 +3181,7 @@ function ChatWidget({
|
|
|
2669
3181
|
children: "Reconnecting..."
|
|
2670
3182
|
}
|
|
2671
3183
|
),
|
|
2672
|
-
/* @__PURE__ */
|
|
3184
|
+
/* @__PURE__ */ jsx12(
|
|
2673
3185
|
"button",
|
|
2674
3186
|
{
|
|
2675
3187
|
type: "button",
|
|
@@ -2693,8 +3205,8 @@ function ChatWidget({
|
|
|
2693
3205
|
] })
|
|
2694
3206
|
}
|
|
2695
3207
|
),
|
|
2696
|
-
isLoading ? /* @__PURE__ */
|
|
2697
|
-
/* @__PURE__ */
|
|
3208
|
+
isLoading ? /* @__PURE__ */ jsx12("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxs11("div", { className: "text-center", children: [
|
|
3209
|
+
/* @__PURE__ */ jsx12(
|
|
2698
3210
|
"div",
|
|
2699
3211
|
{
|
|
2700
3212
|
className: "w-7 h-7 border-2 border-t-transparent rounded-full animate-spin mx-auto mb-3",
|
|
@@ -2704,7 +3216,7 @@ function ChatWidget({
|
|
|
2704
3216
|
}
|
|
2705
3217
|
}
|
|
2706
3218
|
),
|
|
2707
|
-
/* @__PURE__ */
|
|
3219
|
+
/* @__PURE__ */ jsx12(
|
|
2708
3220
|
"p",
|
|
2709
3221
|
{
|
|
2710
3222
|
style: {
|
|
@@ -2715,7 +3227,7 @@ function ChatWidget({
|
|
|
2715
3227
|
children: "Loading messages..."
|
|
2716
3228
|
}
|
|
2717
3229
|
)
|
|
2718
|
-
] }) }) : /* @__PURE__ */
|
|
3230
|
+
] }) }) : /* @__PURE__ */ jsx12(
|
|
2719
3231
|
MessageList,
|
|
2720
3232
|
{
|
|
2721
3233
|
messages,
|
|
@@ -2728,10 +3240,11 @@ function ChatWidget({
|
|
|
2728
3240
|
isLoadingMore,
|
|
2729
3241
|
theme: config.theme,
|
|
2730
3242
|
onQuickAction: handleSendMessage,
|
|
2731
|
-
isBotThinking
|
|
3243
|
+
isBotThinking,
|
|
3244
|
+
onBookingSlotSelected: handleBookingSlotSelected
|
|
2732
3245
|
}
|
|
2733
3246
|
),
|
|
2734
|
-
/* @__PURE__ */
|
|
3247
|
+
/* @__PURE__ */ jsx12(
|
|
2735
3248
|
ChatInput,
|
|
2736
3249
|
{
|
|
2737
3250
|
onSend: handleSendMessage,
|
|
@@ -2741,7 +3254,7 @@ function ChatWidget({
|
|
|
2741
3254
|
disabled: !websocket.isConnected
|
|
2742
3255
|
}
|
|
2743
3256
|
),
|
|
2744
|
-
!isFullPage && /* @__PURE__ */
|
|
3257
|
+
!isFullPage && /* @__PURE__ */ jsx12(
|
|
2745
3258
|
"div",
|
|
2746
3259
|
{
|
|
2747
3260
|
className: "px-4 py-1.5 text-center",
|
|
@@ -2749,7 +3262,7 @@ function ChatWidget({
|
|
|
2749
3262
|
borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)",
|
|
2750
3263
|
backgroundColor: isLightTheme ? "rgba(0,0,0,0.03)" : "rgba(0,0,0,0.2)"
|
|
2751
3264
|
},
|
|
2752
|
-
children: /* @__PURE__ */
|
|
3265
|
+
children: /* @__PURE__ */ jsxs11(
|
|
2753
3266
|
"p",
|
|
2754
3267
|
{
|
|
2755
3268
|
style: {
|
|
@@ -2760,7 +3273,7 @@ function ChatWidget({
|
|
|
2760
3273
|
children: [
|
|
2761
3274
|
"Powered by",
|
|
2762
3275
|
" ",
|
|
2763
|
-
/* @__PURE__ */
|
|
3276
|
+
/* @__PURE__ */ jsx12("span", { style: {
|
|
2764
3277
|
fontWeight: 600,
|
|
2765
3278
|
color: isLightTheme ? "rgba(0,0,0,0.5)" : "rgba(247,247,248,0.45)"
|
|
2766
3279
|
}, children: "Xcelsior" })
|
|
@@ -2775,11 +3288,11 @@ function ChatWidget({
|
|
|
2775
3288
|
}
|
|
2776
3289
|
|
|
2777
3290
|
// src/components/Chat.tsx
|
|
2778
|
-
import { useCallback as
|
|
3291
|
+
import { useCallback as useCallback9, useEffect as useEffect10, useRef as useRef9, useState as useState12 } from "react";
|
|
2779
3292
|
|
|
2780
3293
|
// src/components/PreChatForm.tsx
|
|
2781
|
-
import { useState as
|
|
2782
|
-
import { jsx as
|
|
3294
|
+
import { useState as useState9 } from "react";
|
|
3295
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2783
3296
|
function PreChatForm({
|
|
2784
3297
|
onSubmit,
|
|
2785
3298
|
className = "",
|
|
@@ -2788,11 +3301,11 @@ function PreChatForm({
|
|
|
2788
3301
|
onMinimize,
|
|
2789
3302
|
onClose
|
|
2790
3303
|
}) {
|
|
2791
|
-
const [name, setName] =
|
|
2792
|
-
const [email, setEmail] =
|
|
2793
|
-
const [errors, setErrors] =
|
|
2794
|
-
const [isSubmitting, setIsSubmitting] =
|
|
2795
|
-
const [focusedField, setFocusedField] =
|
|
3304
|
+
const [name, setName] = useState9(initialName);
|
|
3305
|
+
const [email, setEmail] = useState9(initialEmail);
|
|
3306
|
+
const [errors, setErrors] = useState9({});
|
|
3307
|
+
const [isSubmitting, setIsSubmitting] = useState9(false);
|
|
3308
|
+
const [focusedField, setFocusedField] = useState9(null);
|
|
2796
3309
|
const validateForm = () => {
|
|
2797
3310
|
const newErrors = {};
|
|
2798
3311
|
if (!name.trim()) {
|
|
@@ -2840,7 +3353,7 @@ function PreChatForm({
|
|
|
2840
3353
|
transition: "box-shadow 0.2s ease",
|
|
2841
3354
|
caretColor: "#337eff"
|
|
2842
3355
|
});
|
|
2843
|
-
return /* @__PURE__ */
|
|
3356
|
+
return /* @__PURE__ */ jsxs12(
|
|
2844
3357
|
"div",
|
|
2845
3358
|
{
|
|
2846
3359
|
className: `fixed bottom-4 right-4 z-50 flex flex-col overflow-hidden ${className}`,
|
|
@@ -2860,7 +3373,7 @@ function PreChatForm({
|
|
|
2860
3373
|
backgroundSize: "24px 24px"
|
|
2861
3374
|
},
|
|
2862
3375
|
children: [
|
|
2863
|
-
/* @__PURE__ */
|
|
3376
|
+
/* @__PURE__ */ jsxs12(
|
|
2864
3377
|
"div",
|
|
2865
3378
|
{
|
|
2866
3379
|
className: "relative text-white overflow-hidden",
|
|
@@ -2868,7 +3381,7 @@ function PreChatForm({
|
|
|
2868
3381
|
background: "linear-gradient(135deg, #337eff 0%, #005eff 50%, #001a66 100%)"
|
|
2869
3382
|
},
|
|
2870
3383
|
children: [
|
|
2871
|
-
/* @__PURE__ */
|
|
3384
|
+
/* @__PURE__ */ jsx13(
|
|
2872
3385
|
"div",
|
|
2873
3386
|
{
|
|
2874
3387
|
className: "absolute inset-0 pointer-events-none",
|
|
@@ -2877,9 +3390,9 @@ function PreChatForm({
|
|
|
2877
3390
|
}
|
|
2878
3391
|
}
|
|
2879
3392
|
),
|
|
2880
|
-
/* @__PURE__ */
|
|
2881
|
-
/* @__PURE__ */
|
|
2882
|
-
/* @__PURE__ */
|
|
3393
|
+
/* @__PURE__ */ jsx13("div", { className: "relative px-5 py-4", children: /* @__PURE__ */ jsxs12("div", { className: "flex items-start justify-between", children: [
|
|
3394
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex-1", children: [
|
|
3395
|
+
/* @__PURE__ */ jsx13(
|
|
2883
3396
|
"h2",
|
|
2884
3397
|
{
|
|
2885
3398
|
className: "font-semibold",
|
|
@@ -2890,7 +3403,7 @@ function PreChatForm({
|
|
|
2890
3403
|
children: "Start a Conversation"
|
|
2891
3404
|
}
|
|
2892
3405
|
),
|
|
2893
|
-
/* @__PURE__ */
|
|
3406
|
+
/* @__PURE__ */ jsx13(
|
|
2894
3407
|
"p",
|
|
2895
3408
|
{
|
|
2896
3409
|
className: "mt-1",
|
|
@@ -2903,8 +3416,8 @@ function PreChatForm({
|
|
|
2903
3416
|
}
|
|
2904
3417
|
)
|
|
2905
3418
|
] }),
|
|
2906
|
-
/* @__PURE__ */
|
|
2907
|
-
/* @__PURE__ */
|
|
3419
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex gap-1 ml-2", children: [
|
|
3420
|
+
/* @__PURE__ */ jsx13(
|
|
2908
3421
|
"button",
|
|
2909
3422
|
{
|
|
2910
3423
|
type: "button",
|
|
@@ -2918,7 +3431,7 @@ function PreChatForm({
|
|
|
2918
3431
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
2919
3432
|
},
|
|
2920
3433
|
"aria-label": "Minimize chat",
|
|
2921
|
-
children: /* @__PURE__ */
|
|
3434
|
+
children: /* @__PURE__ */ jsxs12(
|
|
2922
3435
|
"svg",
|
|
2923
3436
|
{
|
|
2924
3437
|
width: "18",
|
|
@@ -2927,8 +3440,8 @@ function PreChatForm({
|
|
|
2927
3440
|
stroke: "currentColor",
|
|
2928
3441
|
viewBox: "0 0 24 24",
|
|
2929
3442
|
children: [
|
|
2930
|
-
/* @__PURE__ */
|
|
2931
|
-
/* @__PURE__ */
|
|
3443
|
+
/* @__PURE__ */ jsx13("title", { children: "Minimize" }),
|
|
3444
|
+
/* @__PURE__ */ jsx13(
|
|
2932
3445
|
"path",
|
|
2933
3446
|
{
|
|
2934
3447
|
strokeLinecap: "round",
|
|
@@ -2942,7 +3455,7 @@ function PreChatForm({
|
|
|
2942
3455
|
)
|
|
2943
3456
|
}
|
|
2944
3457
|
),
|
|
2945
|
-
/* @__PURE__ */
|
|
3458
|
+
/* @__PURE__ */ jsx13(
|
|
2946
3459
|
"button",
|
|
2947
3460
|
{
|
|
2948
3461
|
type: "button",
|
|
@@ -2956,7 +3469,7 @@ function PreChatForm({
|
|
|
2956
3469
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
2957
3470
|
},
|
|
2958
3471
|
"aria-label": "Close chat",
|
|
2959
|
-
children: /* @__PURE__ */
|
|
3472
|
+
children: /* @__PURE__ */ jsxs12(
|
|
2960
3473
|
"svg",
|
|
2961
3474
|
{
|
|
2962
3475
|
width: "18",
|
|
@@ -2965,8 +3478,8 @@ function PreChatForm({
|
|
|
2965
3478
|
stroke: "currentColor",
|
|
2966
3479
|
viewBox: "0 0 24 24",
|
|
2967
3480
|
children: [
|
|
2968
|
-
/* @__PURE__ */
|
|
2969
|
-
/* @__PURE__ */
|
|
3481
|
+
/* @__PURE__ */ jsx13("title", { children: "Close" }),
|
|
3482
|
+
/* @__PURE__ */ jsx13(
|
|
2970
3483
|
"path",
|
|
2971
3484
|
{
|
|
2972
3485
|
strokeLinecap: "round",
|
|
@@ -2982,7 +3495,7 @@ function PreChatForm({
|
|
|
2982
3495
|
)
|
|
2983
3496
|
] })
|
|
2984
3497
|
] }) }),
|
|
2985
|
-
/* @__PURE__ */
|
|
3498
|
+
/* @__PURE__ */ jsx13(
|
|
2986
3499
|
"div",
|
|
2987
3500
|
{
|
|
2988
3501
|
className: "h-px",
|
|
@@ -2994,9 +3507,9 @@ function PreChatForm({
|
|
|
2994
3507
|
]
|
|
2995
3508
|
}
|
|
2996
3509
|
),
|
|
2997
|
-
/* @__PURE__ */
|
|
2998
|
-
/* @__PURE__ */
|
|
2999
|
-
/* @__PURE__ */
|
|
3510
|
+
/* @__PURE__ */ jsxs12("form", { onSubmit: handleSubmit, className: "p-5 flex flex-col gap-4", children: [
|
|
3511
|
+
/* @__PURE__ */ jsxs12("div", { children: [
|
|
3512
|
+
/* @__PURE__ */ jsxs12(
|
|
3000
3513
|
"label",
|
|
3001
3514
|
{
|
|
3002
3515
|
htmlFor: "chat-name",
|
|
@@ -3008,11 +3521,11 @@ function PreChatForm({
|
|
|
3008
3521
|
},
|
|
3009
3522
|
children: [
|
|
3010
3523
|
"Name ",
|
|
3011
|
-
/* @__PURE__ */
|
|
3524
|
+
/* @__PURE__ */ jsx13("span", { style: { color: "#ff6363" }, children: "*" })
|
|
3012
3525
|
]
|
|
3013
3526
|
}
|
|
3014
3527
|
),
|
|
3015
|
-
/* @__PURE__ */
|
|
3528
|
+
/* @__PURE__ */ jsx13(
|
|
3016
3529
|
"input",
|
|
3017
3530
|
{
|
|
3018
3531
|
type: "text",
|
|
@@ -3032,7 +3545,7 @@ function PreChatForm({
|
|
|
3032
3545
|
autoComplete: "name"
|
|
3033
3546
|
}
|
|
3034
3547
|
),
|
|
3035
|
-
errors.name && /* @__PURE__ */
|
|
3548
|
+
errors.name && /* @__PURE__ */ jsx13(
|
|
3036
3549
|
"p",
|
|
3037
3550
|
{
|
|
3038
3551
|
className: "mt-1.5",
|
|
@@ -3046,8 +3559,8 @@ function PreChatForm({
|
|
|
3046
3559
|
}
|
|
3047
3560
|
)
|
|
3048
3561
|
] }),
|
|
3049
|
-
/* @__PURE__ */
|
|
3050
|
-
/* @__PURE__ */
|
|
3562
|
+
/* @__PURE__ */ jsxs12("div", { children: [
|
|
3563
|
+
/* @__PURE__ */ jsxs12(
|
|
3051
3564
|
"label",
|
|
3052
3565
|
{
|
|
3053
3566
|
htmlFor: "chat-email",
|
|
@@ -3059,11 +3572,11 @@ function PreChatForm({
|
|
|
3059
3572
|
},
|
|
3060
3573
|
children: [
|
|
3061
3574
|
"Email ",
|
|
3062
|
-
/* @__PURE__ */
|
|
3575
|
+
/* @__PURE__ */ jsx13("span", { style: { color: "#ff6363" }, children: "*" })
|
|
3063
3576
|
]
|
|
3064
3577
|
}
|
|
3065
3578
|
),
|
|
3066
|
-
/* @__PURE__ */
|
|
3579
|
+
/* @__PURE__ */ jsx13(
|
|
3067
3580
|
"input",
|
|
3068
3581
|
{
|
|
3069
3582
|
type: "email",
|
|
@@ -3083,7 +3596,7 @@ function PreChatForm({
|
|
|
3083
3596
|
autoComplete: "email"
|
|
3084
3597
|
}
|
|
3085
3598
|
),
|
|
3086
|
-
errors.email && /* @__PURE__ */
|
|
3599
|
+
errors.email && /* @__PURE__ */ jsx13(
|
|
3087
3600
|
"p",
|
|
3088
3601
|
{
|
|
3089
3602
|
className: "mt-1.5",
|
|
@@ -3097,7 +3610,7 @@ function PreChatForm({
|
|
|
3097
3610
|
}
|
|
3098
3611
|
)
|
|
3099
3612
|
] }),
|
|
3100
|
-
/* @__PURE__ */
|
|
3613
|
+
/* @__PURE__ */ jsx13(
|
|
3101
3614
|
"button",
|
|
3102
3615
|
{
|
|
3103
3616
|
type: "submit",
|
|
@@ -3119,8 +3632,8 @@ function PreChatForm({
|
|
|
3119
3632
|
onMouseLeave: (e) => {
|
|
3120
3633
|
e.currentTarget.style.boxShadow = "0 4px 16px -4px rgba(51,126,255,0.4)";
|
|
3121
3634
|
},
|
|
3122
|
-
children: isSubmitting ? /* @__PURE__ */
|
|
3123
|
-
/* @__PURE__ */
|
|
3635
|
+
children: isSubmitting ? /* @__PURE__ */ jsxs12("span", { className: "flex items-center justify-center gap-2", children: [
|
|
3636
|
+
/* @__PURE__ */ jsxs12(
|
|
3124
3637
|
"svg",
|
|
3125
3638
|
{
|
|
3126
3639
|
className: "animate-spin",
|
|
@@ -3132,8 +3645,8 @@ function PreChatForm({
|
|
|
3132
3645
|
strokeWidth: "2",
|
|
3133
3646
|
"aria-label": "Loading",
|
|
3134
3647
|
children: [
|
|
3135
|
-
/* @__PURE__ */
|
|
3136
|
-
/* @__PURE__ */
|
|
3648
|
+
/* @__PURE__ */ jsx13("title", { children: "Loading" }),
|
|
3649
|
+
/* @__PURE__ */ jsx13("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
|
|
3137
3650
|
]
|
|
3138
3651
|
}
|
|
3139
3652
|
),
|
|
@@ -3141,7 +3654,7 @@ function PreChatForm({
|
|
|
3141
3654
|
] }) : "Start Chat"
|
|
3142
3655
|
}
|
|
3143
3656
|
),
|
|
3144
|
-
/* @__PURE__ */
|
|
3657
|
+
/* @__PURE__ */ jsx13(
|
|
3145
3658
|
"p",
|
|
3146
3659
|
{
|
|
3147
3660
|
className: "text-center",
|
|
@@ -3154,7 +3667,7 @@ function PreChatForm({
|
|
|
3154
3667
|
}
|
|
3155
3668
|
)
|
|
3156
3669
|
] }),
|
|
3157
|
-
/* @__PURE__ */
|
|
3670
|
+
/* @__PURE__ */ jsx13(
|
|
3158
3671
|
"div",
|
|
3159
3672
|
{
|
|
3160
3673
|
className: "px-4 py-1.5 text-center",
|
|
@@ -3162,7 +3675,7 @@ function PreChatForm({
|
|
|
3162
3675
|
borderTop: "1px solid rgba(255,255,255,0.06)",
|
|
3163
3676
|
backgroundColor: "rgba(0,0,0,0.2)"
|
|
3164
3677
|
},
|
|
3165
|
-
children: /* @__PURE__ */
|
|
3678
|
+
children: /* @__PURE__ */ jsxs12(
|
|
3166
3679
|
"p",
|
|
3167
3680
|
{
|
|
3168
3681
|
style: {
|
|
@@ -3173,7 +3686,7 @@ function PreChatForm({
|
|
|
3173
3686
|
children: [
|
|
3174
3687
|
"Powered by",
|
|
3175
3688
|
" ",
|
|
3176
|
-
/* @__PURE__ */
|
|
3689
|
+
/* @__PURE__ */ jsx13("span", { style: { fontWeight: 600, color: "rgba(247,247,248,0.45)" }, children: "Xcelsior" })
|
|
3177
3690
|
]
|
|
3178
3691
|
}
|
|
3179
3692
|
)
|
|
@@ -3185,8 +3698,15 @@ function PreChatForm({
|
|
|
3185
3698
|
}
|
|
3186
3699
|
|
|
3187
3700
|
// src/hooks/useDraggablePosition.ts
|
|
3188
|
-
import { useState as
|
|
3701
|
+
import { useState as useState10, useCallback as useCallback7, useRef as useRef8, useEffect as useEffect9 } from "react";
|
|
3189
3702
|
var STORAGE_KEY2 = "xcelsior-chat-position";
|
|
3703
|
+
var DRAG_THRESHOLD = 5;
|
|
3704
|
+
var FAB_SIZE = 64;
|
|
3705
|
+
var MARGIN = 16;
|
|
3706
|
+
var BOTTOM = 20;
|
|
3707
|
+
var VELOCITY_THRESHOLD = 0.4;
|
|
3708
|
+
var SETTLE_DURATION = 400;
|
|
3709
|
+
var SETTLE_EASING = "cubic-bezier(0.2, 0.9, 0.3, 1.1)";
|
|
3190
3710
|
function getStoredPosition() {
|
|
3191
3711
|
try {
|
|
3192
3712
|
const stored = localStorage.getItem(STORAGE_KEY2);
|
|
@@ -3195,20 +3715,32 @@ function getStoredPosition() {
|
|
|
3195
3715
|
}
|
|
3196
3716
|
return "right";
|
|
3197
3717
|
}
|
|
3198
|
-
function storePosition(
|
|
3718
|
+
function storePosition(pos) {
|
|
3199
3719
|
try {
|
|
3200
|
-
localStorage.setItem(STORAGE_KEY2,
|
|
3720
|
+
localStorage.setItem(STORAGE_KEY2, pos);
|
|
3201
3721
|
} catch {
|
|
3202
3722
|
}
|
|
3203
3723
|
}
|
|
3724
|
+
function restingX(side) {
|
|
3725
|
+
return side === "left" ? MARGIN : window.innerWidth - MARGIN - FAB_SIZE;
|
|
3726
|
+
}
|
|
3727
|
+
function restingY() {
|
|
3728
|
+
return window.innerHeight - BOTTOM - FAB_SIZE;
|
|
3729
|
+
}
|
|
3204
3730
|
function useDraggablePosition(configPosition = "auto") {
|
|
3205
3731
|
const resolvedDefault = configPosition === "auto" ? getStoredPosition() : configPosition;
|
|
3206
|
-
const [position, setPosition] =
|
|
3207
|
-
const
|
|
3208
|
-
|
|
3209
|
-
const
|
|
3210
|
-
const
|
|
3211
|
-
const
|
|
3732
|
+
const [position, setPosition] = useState10(resolvedDefault);
|
|
3733
|
+
const positionRef = useRef8(position);
|
|
3734
|
+
positionRef.current = position;
|
|
3735
|
+
const [isDragging, setIsDragging] = useState10(false);
|
|
3736
|
+
const containerRef = useRef8(null);
|
|
3737
|
+
const draggingRef = useRef8(false);
|
|
3738
|
+
const didDragRef = useRef8(false);
|
|
3739
|
+
const startPointer = useRef8({ x: 0, y: 0 });
|
|
3740
|
+
const startRect = useRef8({ x: 0, y: 0 });
|
|
3741
|
+
const samples = useRef8([]);
|
|
3742
|
+
const hasShownHint = useRef8(false);
|
|
3743
|
+
const [showHint, setShowHint] = useState10(false);
|
|
3212
3744
|
useEffect9(() => {
|
|
3213
3745
|
try {
|
|
3214
3746
|
const hasVisited = localStorage.getItem("xcelsior-chat-hint-shown");
|
|
@@ -3224,53 +3756,115 @@ function useDraggablePosition(configPosition = "auto") {
|
|
|
3224
3756
|
} catch {
|
|
3225
3757
|
}
|
|
3226
3758
|
}, []);
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3759
|
+
useEffect9(() => {
|
|
3760
|
+
const handleMove = (e) => {
|
|
3761
|
+
if (!draggingRef.current) return;
|
|
3762
|
+
const el = containerRef.current;
|
|
3763
|
+
if (!el) return;
|
|
3764
|
+
const dx = e.clientX - startPointer.current.x;
|
|
3765
|
+
const dy = e.clientY - startPointer.current.y;
|
|
3766
|
+
if (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) {
|
|
3767
|
+
didDragRef.current = true;
|
|
3768
|
+
}
|
|
3769
|
+
el.style.transition = "none";
|
|
3770
|
+
el.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
3771
|
+
const now = performance.now();
|
|
3772
|
+
samples.current.push({ x: e.clientX, t: now });
|
|
3773
|
+
if (samples.current.length > 5) samples.current.shift();
|
|
3774
|
+
};
|
|
3775
|
+
const handleUp = (e) => {
|
|
3776
|
+
if (!draggingRef.current) return;
|
|
3777
|
+
draggingRef.current = false;
|
|
3778
|
+
const el = containerRef.current;
|
|
3779
|
+
if (!el) return;
|
|
3780
|
+
let velocityX = 0;
|
|
3781
|
+
const s = samples.current;
|
|
3782
|
+
if (s.length >= 2) {
|
|
3783
|
+
const first = s[0];
|
|
3784
|
+
const last = s[s.length - 1];
|
|
3785
|
+
const dt = last.t - first.t;
|
|
3786
|
+
if (dt > 0) velocityX = (last.x - first.x) / dt;
|
|
3787
|
+
}
|
|
3788
|
+
samples.current = [];
|
|
3789
|
+
const currentVisualX = startRect.current.x + (e.clientX - startPointer.current.x);
|
|
3790
|
+
const screenMid = window.innerWidth / 2;
|
|
3791
|
+
let targetSide;
|
|
3792
|
+
if (Math.abs(velocityX) > VELOCITY_THRESHOLD) {
|
|
3793
|
+
targetSide = velocityX < 0 ? "left" : "right";
|
|
3794
|
+
} else {
|
|
3795
|
+
targetSide = currentVisualX + FAB_SIZE / 2 < screenMid ? "left" : "right";
|
|
3796
|
+
}
|
|
3797
|
+
const oldBaseX = restingX(positionRef.current);
|
|
3798
|
+
const oldBaseY = restingY();
|
|
3799
|
+
const currentX = startRect.current.x + (e.clientX - startPointer.current.x);
|
|
3800
|
+
const currentY = startRect.current.y + (e.clientY - startPointer.current.y);
|
|
3801
|
+
const targetX = restingX(targetSide);
|
|
3802
|
+
const targetY = restingY();
|
|
3803
|
+
const fromTx = currentX - oldBaseX;
|
|
3804
|
+
const fromTy = currentY - oldBaseY;
|
|
3805
|
+
const toTx = targetX - oldBaseX;
|
|
3806
|
+
const toTy = targetY - oldBaseY;
|
|
3807
|
+
el.style.transition = "none";
|
|
3808
|
+
el.style.transform = `translate(${fromTx}px, ${fromTy}px)`;
|
|
3809
|
+
void el.offsetHeight;
|
|
3810
|
+
el.style.transition = `transform ${SETTLE_DURATION}ms ${SETTLE_EASING}`;
|
|
3811
|
+
el.style.transform = `translate(${toTx}px, ${toTy}px)`;
|
|
3812
|
+
const finish = () => {
|
|
3813
|
+
el.style.transition = "";
|
|
3814
|
+
el.style.transform = "";
|
|
3815
|
+
el.removeEventListener("transitionend", finish);
|
|
3816
|
+
setIsDragging(false);
|
|
3817
|
+
if (targetSide !== positionRef.current) {
|
|
3818
|
+
setPosition(targetSide);
|
|
3819
|
+
storePosition(targetSide);
|
|
3820
|
+
}
|
|
3821
|
+
};
|
|
3822
|
+
el.addEventListener("transitionend", finish, { once: true });
|
|
3823
|
+
setTimeout(finish, SETTLE_DURATION + 50);
|
|
3824
|
+
};
|
|
3825
|
+
document.addEventListener("pointermove", handleMove);
|
|
3826
|
+
document.addEventListener("pointerup", handleUp);
|
|
3827
|
+
return () => {
|
|
3828
|
+
document.removeEventListener("pointermove", handleMove);
|
|
3829
|
+
document.removeEventListener("pointerup", handleUp);
|
|
3830
|
+
};
|
|
3231
3831
|
}, []);
|
|
3232
|
-
const
|
|
3832
|
+
const handlePointerDown = useCallback7((e) => {
|
|
3833
|
+
e.preventDefault();
|
|
3834
|
+
const el = containerRef.current;
|
|
3835
|
+
if (!el) return;
|
|
3836
|
+
const rect = el.getBoundingClientRect();
|
|
3837
|
+
startPointer.current = { x: e.clientX, y: e.clientY };
|
|
3838
|
+
startRect.current = { x: rect.left, y: rect.top };
|
|
3839
|
+
draggingRef.current = true;
|
|
3840
|
+
didDragRef.current = false;
|
|
3841
|
+
samples.current = [];
|
|
3842
|
+
setIsDragging(true);
|
|
3233
3843
|
}, []);
|
|
3234
|
-
const
|
|
3235
|
-
(e) => {
|
|
3236
|
-
setIsDragging(false);
|
|
3237
|
-
e.target.releasePointerCapture(e.pointerId);
|
|
3238
|
-
const deltaX = e.clientX - dragStartX.current;
|
|
3239
|
-
if (Math.abs(deltaX) > 50) {
|
|
3240
|
-
const screenMid = window.innerWidth / 2;
|
|
3241
|
-
const newPosition = e.clientX < screenMid ? "left" : "right";
|
|
3242
|
-
if (newPosition !== position) {
|
|
3243
|
-
setPosition(newPosition);
|
|
3244
|
-
storePosition(newPosition);
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
},
|
|
3248
|
-
[position]
|
|
3249
|
-
);
|
|
3844
|
+
const shouldSuppressClick = useCallback7(() => didDragRef.current, []);
|
|
3250
3845
|
return {
|
|
3251
3846
|
position,
|
|
3252
3847
|
isDragging,
|
|
3253
3848
|
showHint,
|
|
3254
|
-
|
|
3849
|
+
containerRef,
|
|
3850
|
+
shouldSuppressClick,
|
|
3255
3851
|
handlers: {
|
|
3256
|
-
onPointerDown: handlePointerDown
|
|
3257
|
-
onPointerMove: handlePointerMove,
|
|
3258
|
-
onPointerUp: handlePointerUp
|
|
3852
|
+
onPointerDown: handlePointerDown
|
|
3259
3853
|
}
|
|
3260
3854
|
};
|
|
3261
3855
|
}
|
|
3262
3856
|
|
|
3263
3857
|
// src/hooks/useChatWidgetState.ts
|
|
3264
|
-
import { useCallback as
|
|
3858
|
+
import { useCallback as useCallback8, useState as useState11 } from "react";
|
|
3265
3859
|
function useChatWidgetState({
|
|
3266
3860
|
state: controlledState,
|
|
3267
3861
|
defaultState = "minimized",
|
|
3268
3862
|
onStateChange
|
|
3269
3863
|
}) {
|
|
3270
|
-
const [uncontrolledState, setUncontrolledState] =
|
|
3864
|
+
const [uncontrolledState, setUncontrolledState] = useState11(defaultState);
|
|
3271
3865
|
const isControlled = controlledState !== void 0 && controlledState !== "undefined";
|
|
3272
3866
|
const currentState = isControlled ? controlledState : uncontrolledState;
|
|
3273
|
-
const setState =
|
|
3867
|
+
const setState = useCallback8(
|
|
3274
3868
|
(newValue) => {
|
|
3275
3869
|
if (!isControlled) {
|
|
3276
3870
|
setUncontrolledState(newValue);
|
|
@@ -3287,7 +3881,7 @@ function useChatWidgetState({
|
|
|
3287
3881
|
}
|
|
3288
3882
|
|
|
3289
3883
|
// src/components/Chat.tsx
|
|
3290
|
-
import { jsx as
|
|
3884
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3291
3885
|
function generateSessionId() {
|
|
3292
3886
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
3293
3887
|
return crypto.randomUUID();
|
|
@@ -3303,62 +3897,39 @@ function Chat({
|
|
|
3303
3897
|
defaultState = "minimized",
|
|
3304
3898
|
onStateChange
|
|
3305
3899
|
}) {
|
|
3306
|
-
const [userInfo, setUserInfo] =
|
|
3307
|
-
const [conversationId, setConversationId] =
|
|
3308
|
-
const [isLoading, setIsLoading] =
|
|
3309
|
-
const [isAnimating, setIsAnimating] = useState11(false);
|
|
3310
|
-
const [showWidget, setShowWidget] = useState11(false);
|
|
3900
|
+
const [userInfo, setUserInfo] = useState12(null);
|
|
3901
|
+
const [conversationId, setConversationId] = useState12("");
|
|
3902
|
+
const [isLoading, setIsLoading] = useState12(true);
|
|
3311
3903
|
const identityMode = config.identityCollection || "progressive";
|
|
3312
|
-
const { position, isDragging, showHint, handlers } = useDraggablePosition(config.position);
|
|
3313
|
-
const
|
|
3904
|
+
const { position, isDragging, showHint, containerRef, shouldSuppressClick, handlers } = useDraggablePosition(config.position);
|
|
3905
|
+
const sessionInitializedRef = useRef9(false);
|
|
3906
|
+
const { currentState, setState } = useChatWidgetState({
|
|
3314
3907
|
state,
|
|
3315
3908
|
defaultState,
|
|
3316
3909
|
onStateChange
|
|
3317
3910
|
});
|
|
3318
|
-
const
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
setStateRaw(newState);
|
|
3324
|
-
requestAnimationFrame(() => {
|
|
3325
|
-
requestAnimationFrame(() => {
|
|
3326
|
-
setIsAnimating(false);
|
|
3327
|
-
});
|
|
3328
|
-
});
|
|
3329
|
-
} else if ((newState === "minimized" || newState === "closed") && currentState === "open") {
|
|
3330
|
-
setIsAnimating(true);
|
|
3331
|
-
setTimeout(() => {
|
|
3332
|
-
setShowWidget(false);
|
|
3333
|
-
setIsAnimating(false);
|
|
3334
|
-
setStateRaw(newState);
|
|
3335
|
-
}, 200);
|
|
3336
|
-
} else {
|
|
3337
|
-
setStateRaw(newState);
|
|
3338
|
-
}
|
|
3339
|
-
},
|
|
3340
|
-
[currentState, setStateRaw]
|
|
3341
|
-
);
|
|
3342
|
-
useEffect10(() => {
|
|
3343
|
-
if (currentState === "open") {
|
|
3344
|
-
setShowWidget(true);
|
|
3345
|
-
}
|
|
3346
|
-
}, [currentState]);
|
|
3911
|
+
const configConversationId = config.conversationId;
|
|
3912
|
+
const configUserEmail = config.currentUser?.email;
|
|
3913
|
+
const configUserName = config.currentUser?.name;
|
|
3914
|
+
const configUserAvatar = config.currentUser?.avatar;
|
|
3915
|
+
const configUserStatus = config.currentUser?.status;
|
|
3347
3916
|
useEffect10(() => {
|
|
3917
|
+
if (sessionInitializedRef.current) return;
|
|
3348
3918
|
const initializeSession = () => {
|
|
3349
3919
|
try {
|
|
3350
|
-
if (
|
|
3351
|
-
const convId2 =
|
|
3920
|
+
if (configUserEmail && configUserName) {
|
|
3921
|
+
const convId2 = configConversationId || generateSessionId();
|
|
3352
3922
|
const user = {
|
|
3353
|
-
name:
|
|
3354
|
-
email:
|
|
3355
|
-
avatar:
|
|
3923
|
+
name: configUserName,
|
|
3924
|
+
email: configUserEmail,
|
|
3925
|
+
avatar: configUserAvatar,
|
|
3356
3926
|
type: "customer",
|
|
3357
|
-
status:
|
|
3927
|
+
status: configUserStatus
|
|
3358
3928
|
};
|
|
3359
3929
|
setUserInfo(user);
|
|
3360
3930
|
setConversationId(convId2);
|
|
3361
3931
|
setIsLoading(false);
|
|
3932
|
+
sessionInitializedRef.current = true;
|
|
3362
3933
|
return;
|
|
3363
3934
|
}
|
|
3364
3935
|
const storedDataJson = localStorage.getItem(`${storageKeyPrefix}_user`);
|
|
@@ -3375,24 +3946,53 @@ function Chat({
|
|
|
3375
3946
|
setUserInfo(user);
|
|
3376
3947
|
setConversationId(storedData.conversationId);
|
|
3377
3948
|
setIsLoading(false);
|
|
3949
|
+
sessionInitializedRef.current = true;
|
|
3378
3950
|
return;
|
|
3379
3951
|
}
|
|
3380
3952
|
}
|
|
3381
|
-
const convId =
|
|
3953
|
+
const convId = configConversationId || generateSessionId();
|
|
3382
3954
|
setConversationId(convId);
|
|
3383
3955
|
if (identityMode === "progressive" || identityMode === "none") {
|
|
3384
|
-
|
|
3956
|
+
let anonId;
|
|
3957
|
+
try {
|
|
3958
|
+
const stored = localStorage.getItem("xcelsior-chat-anon-id");
|
|
3959
|
+
if (stored) {
|
|
3960
|
+
anonId = stored;
|
|
3961
|
+
} else {
|
|
3962
|
+
anonId = `anon-${crypto.randomUUID?.() || Math.random().toString(36).slice(2)}`;
|
|
3963
|
+
localStorage.setItem("xcelsior-chat-anon-id", anonId);
|
|
3964
|
+
}
|
|
3965
|
+
} catch {
|
|
3966
|
+
anonId = `anon-${Math.random().toString(36).slice(2)}`;
|
|
3967
|
+
}
|
|
3968
|
+
const anonEmail = `${anonId}@anonymous.xcelsior.co`;
|
|
3969
|
+
const anonUser = { name: "Visitor", email: anonEmail, type: "customer", status: "online" };
|
|
3970
|
+
setUserInfo(anonUser);
|
|
3971
|
+
try {
|
|
3972
|
+
localStorage.setItem(
|
|
3973
|
+
`${storageKeyPrefix}_user`,
|
|
3974
|
+
JSON.stringify({
|
|
3975
|
+
name: anonUser.name,
|
|
3976
|
+
email: anonUser.email,
|
|
3977
|
+
conversationId: convId,
|
|
3978
|
+
timestamp: Date.now()
|
|
3979
|
+
})
|
|
3980
|
+
);
|
|
3981
|
+
} catch {
|
|
3982
|
+
}
|
|
3385
3983
|
}
|
|
3984
|
+
sessionInitializedRef.current = true;
|
|
3386
3985
|
} catch (error) {
|
|
3387
3986
|
console.error("Error initializing chat session:", error);
|
|
3388
|
-
setConversationId(
|
|
3987
|
+
setConversationId(configConversationId || generateSessionId());
|
|
3988
|
+
sessionInitializedRef.current = true;
|
|
3389
3989
|
} finally {
|
|
3390
3990
|
setIsLoading(false);
|
|
3391
3991
|
}
|
|
3392
3992
|
};
|
|
3393
3993
|
initializeSession();
|
|
3394
|
-
}, [
|
|
3395
|
-
const handlePreChatSubmit =
|
|
3994
|
+
}, [configConversationId, configUserEmail, configUserName, configUserAvatar, configUserStatus, storageKeyPrefix, identityMode]);
|
|
3995
|
+
const handlePreChatSubmit = useCallback9(
|
|
3396
3996
|
(name, email) => {
|
|
3397
3997
|
const convId = conversationId || generateSessionId();
|
|
3398
3998
|
const user = { name, email, type: "customer", status: "online" };
|
|
@@ -3418,49 +4018,52 @@ function Chat({
|
|
|
3418
4018
|
const primaryColor = config.theme?.primary || "#337eff";
|
|
3419
4019
|
const primaryStrong = config.theme?.primaryStrong || "#005eff";
|
|
3420
4020
|
if (currentState === "minimized") {
|
|
3421
|
-
return /* @__PURE__ */
|
|
3422
|
-
"
|
|
4021
|
+
return /* @__PURE__ */ jsx14(
|
|
4022
|
+
"div",
|
|
3423
4023
|
{
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
4024
|
+
ref: containerRef,
|
|
4025
|
+
className: `fixed bottom-5 ${positionClass} z-50 ${className}`,
|
|
4026
|
+
children: /* @__PURE__ */ jsxs13(
|
|
4027
|
+
"button",
|
|
4028
|
+
{
|
|
4029
|
+
type: "button",
|
|
4030
|
+
onClick: () => {
|
|
4031
|
+
if (!shouldSuppressClick()) setState("open");
|
|
4032
|
+
},
|
|
4033
|
+
className: `group relative rounded-full text-white flex items-center justify-center touch-none select-none ${showHint ? "animate-bounce" : ""} ${isDragging ? "cursor-grabbing scale-110" : "cursor-grab hover:scale-[1.08] transition-transform duration-300"}`,
|
|
4034
|
+
style: {
|
|
4035
|
+
width: 64,
|
|
4036
|
+
height: 64,
|
|
4037
|
+
background: `linear-gradient(135deg, ${primaryColor}, ${primaryStrong})`,
|
|
4038
|
+
boxShadow: [
|
|
4039
|
+
`0 4px 24px 0 ${primaryColor}80`,
|
|
4040
|
+
`0 8px 32px -4px rgba(0,0,0,0.3)`,
|
|
4041
|
+
`inset 0 1px 0 0 rgba(255,255,255,0.2)`
|
|
4042
|
+
].join(", ")
|
|
4043
|
+
},
|
|
4044
|
+
"aria-label": "Open chat",
|
|
4045
|
+
...handlers,
|
|
4046
|
+
children: [
|
|
4047
|
+
/* @__PURE__ */ jsx14(ChatBubbleIcon, { size: 36, className: "pointer-events-none" }),
|
|
4048
|
+
/* @__PURE__ */ jsx14(
|
|
4049
|
+
"span",
|
|
4050
|
+
{
|
|
4051
|
+
className: "absolute inset-0 rounded-full animate-ping pointer-events-none",
|
|
4052
|
+
style: {
|
|
4053
|
+
backgroundColor: primaryColor,
|
|
4054
|
+
opacity: 0.15,
|
|
4055
|
+
animationDuration: "2.5s"
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
)
|
|
4059
|
+
]
|
|
4060
|
+
}
|
|
4061
|
+
)
|
|
3459
4062
|
}
|
|
3460
|
-
)
|
|
4063
|
+
);
|
|
3461
4064
|
}
|
|
3462
4065
|
if (identityMode === "form" && (!userInfo || !userInfo.email || !userInfo.name)) {
|
|
3463
|
-
return /* @__PURE__ */
|
|
4066
|
+
return /* @__PURE__ */ jsx14(
|
|
3464
4067
|
PreChatForm,
|
|
3465
4068
|
{
|
|
3466
4069
|
onSubmit: handlePreChatSubmit,
|
|
@@ -3477,16 +4080,7 @@ function Chat({
|
|
|
3477
4080
|
conversationId,
|
|
3478
4081
|
currentUser: userInfo || void 0
|
|
3479
4082
|
};
|
|
3480
|
-
|
|
3481
|
-
opacity: 1,
|
|
3482
|
-
transform: "translateY(0) scale(1)",
|
|
3483
|
-
transition: "opacity 0.25s ease-out, transform 0.25s ease-out"
|
|
3484
|
-
} : {
|
|
3485
|
-
opacity: 0,
|
|
3486
|
-
transform: "translateY(12px) scale(0.97)",
|
|
3487
|
-
transition: "opacity 0.2s ease-in, transform 0.2s ease-in"
|
|
3488
|
-
};
|
|
3489
|
-
return /* @__PURE__ */ jsx11("div", { style: widgetAnimationStyle, children: /* @__PURE__ */ jsx11(
|
|
4083
|
+
return /* @__PURE__ */ jsx14(
|
|
3490
4084
|
ChatWidget,
|
|
3491
4085
|
{
|
|
3492
4086
|
config: fullConfig,
|
|
@@ -3495,16 +4089,16 @@ function Chat({
|
|
|
3495
4089
|
onMinimize: () => setState("minimized"),
|
|
3496
4090
|
resolvedPosition: position
|
|
3497
4091
|
}
|
|
3498
|
-
)
|
|
4092
|
+
);
|
|
3499
4093
|
}
|
|
3500
4094
|
|
|
3501
4095
|
// src/components/TypingIndicator.tsx
|
|
3502
|
-
import { jsx as
|
|
4096
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3503
4097
|
function TypingIndicator({ isTyping, userName }) {
|
|
3504
4098
|
if (!isTyping) {
|
|
3505
4099
|
return null;
|
|
3506
4100
|
}
|
|
3507
|
-
return /* @__PURE__ */
|
|
4101
|
+
return /* @__PURE__ */ jsx15(
|
|
3508
4102
|
"div",
|
|
3509
4103
|
{
|
|
3510
4104
|
className: "px-4 py-2",
|
|
@@ -3512,8 +4106,8 @@ function TypingIndicator({ isTyping, userName }) {
|
|
|
3512
4106
|
borderTop: "1px solid rgba(255,255,255,0.06)",
|
|
3513
4107
|
backgroundColor: "rgba(0,0,0,0.15)"
|
|
3514
4108
|
},
|
|
3515
|
-
children: /* @__PURE__ */
|
|
3516
|
-
/* @__PURE__ */
|
|
4109
|
+
children: /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
|
|
4110
|
+
/* @__PURE__ */ jsx15("div", { className: "flex gap-1", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx15(
|
|
3517
4111
|
"span",
|
|
3518
4112
|
{
|
|
3519
4113
|
className: "rounded-full animate-bounce",
|
|
@@ -3527,7 +4121,7 @@ function TypingIndicator({ isTyping, userName }) {
|
|
|
3527
4121
|
},
|
|
3528
4122
|
i
|
|
3529
4123
|
)) }),
|
|
3530
|
-
/* @__PURE__ */
|
|
4124
|
+
/* @__PURE__ */ jsx15(
|
|
3531
4125
|
"span",
|
|
3532
4126
|
{
|
|
3533
4127
|
style: {
|
|
@@ -3543,6 +4137,9 @@ function TypingIndicator({ isTyping, userName }) {
|
|
|
3543
4137
|
);
|
|
3544
4138
|
}
|
|
3545
4139
|
export {
|
|
4140
|
+
BookingCancelledCard,
|
|
4141
|
+
BookingConfirmationCard,
|
|
4142
|
+
BookingSlotPicker,
|
|
3546
4143
|
Chat,
|
|
3547
4144
|
ChatHeader,
|
|
3548
4145
|
ChatInput,
|