@xcelsior/ui-chat 1.0.8 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +69 -69
- package/dist/index.d.ts +69 -69
- package/dist/index.js +2458 -627
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2457 -628
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
- package/src/components/BrandIcons.stories.tsx +95 -0
- package/src/components/BrandIcons.tsx +84 -0
- package/src/components/Chat.stories.tsx +149 -16
- package/src/components/Chat.tsx +116 -96
- package/src/components/ChatHeader.tsx +124 -69
- package/src/components/ChatInput.tsx +253 -104
- package/src/components/ChatWidget.tsx +209 -63
- package/src/components/ConversationRating.stories.tsx +33 -0
- package/src/components/ConversationRating.tsx +156 -0
- package/src/components/MarkdownMessage.tsx +202 -0
- package/src/components/MessageItem.stories.tsx +253 -55
- package/src/components/MessageItem.tsx +223 -60
- package/src/components/MessageList.tsx +164 -35
- package/src/components/PreChatForm.tsx +236 -96
- package/src/components/ThinkingIndicator.tsx +370 -0
- package/src/components/TypingIndicator.tsx +27 -11
- package/src/hooks/useDraggablePosition.ts +91 -0
- package/src/hooks/useMessages.ts +12 -13
- package/src/hooks/useResizableWidget.ts +324 -0
- package/src/index.tsx +5 -0
- package/src/types.ts +51 -5
- package/src/utils/markdown-styles.ts +140 -0
- package/storybook-static/assets/BrandIcons-Cjy5INAp.js +4 -0
- package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +64 -0
- package/storybook-static/assets/Chat.stories-J_Yp51wU.js +803 -0
- package/storybook-static/assets/Color-YHDXOIA2-BMnd3YrF.js +1 -0
- package/storybook-static/assets/ConversationRating.stories-B5_QddHN.js +12 -0
- package/storybook-static/assets/DocsRenderer-CFRXHY34-i_W8iCu9.js +575 -0
- package/storybook-static/assets/MessageItem-DAaKZ9s9.js +14 -0
- package/storybook-static/assets/MessageItem.stories-Ckr1_scc.js +255 -0
- package/storybook-static/assets/ToastContext-Bty1K7ya.js +1 -0
- package/storybook-static/assets/chunk-XP5HYGXS-BpfKkqn7.js +1 -0
- package/storybook-static/assets/en-US-BukEqXxE.js +1 -0
- package/storybook-static/assets/entry-preview-docs-DHohToDm.js +46 -0
- package/storybook-static/assets/entry-preview-oDnntGcx.js +2 -0
- package/storybook-static/assets/iframe-CGBtu2Se.js +211 -0
- package/storybook-static/assets/index--qcDGAq6.js +1 -0
- package/storybook-static/assets/index-BLHw34Di.js +24 -0
- package/storybook-static/assets/index-B_4m48Mv.js +1 -0
- package/storybook-static/assets/index-DgH-xKnr.js +11 -0
- package/storybook-static/assets/index-DrFu-skq.js +6 -0
- package/storybook-static/assets/index-DrdPSA1J.js +240 -0
- package/storybook-static/assets/index-jvNEZhzf.js +1 -0
- package/storybook-static/assets/index-yBjzXJbu.js +9 -0
- package/storybook-static/assets/jsx-runtime-Cf8x2fCZ.js +9 -0
- package/storybook-static/assets/preview-B8lJiyuQ.js +34 -0
- package/storybook-static/assets/preview-BBWR9nbA.js +1 -0
- package/storybook-static/assets/preview-BRpahs9B.js +2 -0
- package/storybook-static/assets/preview-BWzBA1C2.js +396 -0
- package/storybook-static/assets/preview-CvbIS5ZJ.js +1 -0
- package/storybook-static/assets/preview-DD_OYowb.js +1 -0
- package/storybook-static/assets/preview-DGUiP6tS.js +7 -0
- package/storybook-static/assets/preview-DHQbi4pV.js +1 -0
- package/storybook-static/assets/preview-DUOvJmsz.js +1 -0
- package/storybook-static/assets/preview-DcGwT3kv.css +1 -0
- package/storybook-static/assets/preview-DwI0w3cI.js +1 -0
- package/storybook-static/assets/react-18-CALspjOX.js +1 -0
- package/storybook-static/assets/test-utils-BE0XkMtV.js +9 -0
- package/storybook-static/favicon.svg +1 -0
- package/storybook-static/iframe.html +666 -0
- package/storybook-static/index.html +177 -0
- package/storybook-static/index.json +1 -0
- package/storybook-static/nunito-sans-bold-italic.woff2 +0 -0
- package/storybook-static/nunito-sans-bold.woff2 +0 -0
- package/storybook-static/nunito-sans-italic.woff2 +0 -0
- package/storybook-static/nunito-sans-regular.woff2 +0 -0
- package/storybook-static/project.json +1 -0
- package/storybook-static/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
- package/storybook-static/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
- package/storybook-static/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
- package/storybook-static/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/interactions-10/manager-bundle.js +222 -0
- package/storybook-static/sb-addons/links-1/manager-bundle.js +3 -0
- package/storybook-static/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
- package/storybook-static/sb-common-assets/favicon.svg +1 -0
- package/storybook-static/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
- package/storybook-static/sb-common-assets/nunito-sans-bold.woff2 +0 -0
- package/storybook-static/sb-common-assets/nunito-sans-italic.woff2 +0 -0
- package/storybook-static/sb-common-assets/nunito-sans-regular.woff2 +0 -0
- package/storybook-static/sb-manager/globals-module-info.js +1052 -0
- package/storybook-static/sb-manager/globals-runtime.js +42127 -0
- package/storybook-static/sb-manager/globals.js +48 -0
- package/storybook-static/sb-manager/runtime.js +12048 -0
- package/.turbo/turbo-build.log +0 -22
- package/.turbo/turbo-lint.log +0 -5
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/ChatWidget.tsx
|
|
2
|
-
import { useCallback as
|
|
2
|
+
import { useCallback as useCallback5, useEffect as useEffect8 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/hooks/useWebSocket.ts
|
|
5
5
|
import { useEffect, useRef, useState, useCallback } from "react";
|
|
@@ -196,6 +196,7 @@ function useMessages(websocket, config) {
|
|
|
196
196
|
const [nextPageToken, setNextPageToken] = useState2(void 0);
|
|
197
197
|
const [hasMore, setHasMore] = useState2(true);
|
|
198
198
|
const [isLoadingMore, setIsLoadingMore] = useState2(false);
|
|
199
|
+
const [isBotThinking, setIsBotThinking] = useState2(false);
|
|
199
200
|
const { httpApiUrl, conversationId, headers, onError, toast } = config;
|
|
200
201
|
const headersWithApiKey = useMemo(
|
|
201
202
|
() => ({
|
|
@@ -244,6 +245,9 @@ function useMessages(websocket, config) {
|
|
|
244
245
|
}
|
|
245
246
|
return [...prev, newMessage];
|
|
246
247
|
});
|
|
248
|
+
if (newMessage.senderType === "bot" || newMessage.senderType === "system") {
|
|
249
|
+
setIsBotThinking(false);
|
|
250
|
+
}
|
|
247
251
|
onMessageReceived?.(newMessage);
|
|
248
252
|
}
|
|
249
253
|
}, [websocket.lastMessage, onMessageReceived, conversationId]);
|
|
@@ -254,6 +258,9 @@ function useMessages(websocket, config) {
|
|
|
254
258
|
}
|
|
255
259
|
return [...prev, message];
|
|
256
260
|
});
|
|
261
|
+
if (message.senderType === "customer") {
|
|
262
|
+
setIsBotThinking(true);
|
|
263
|
+
}
|
|
257
264
|
}, []);
|
|
258
265
|
const updateMessageStatus = useCallback2((messageId, status) => {
|
|
259
266
|
setMessages((prev) => prev.map((msg) => msg.id === messageId ? { ...msg, status } : msg));
|
|
@@ -305,7 +312,8 @@ function useMessages(websocket, config) {
|
|
|
305
312
|
error,
|
|
306
313
|
loadMore,
|
|
307
314
|
hasMore,
|
|
308
|
-
isLoadingMore
|
|
315
|
+
isLoadingMore,
|
|
316
|
+
isBotThinking
|
|
309
317
|
};
|
|
310
318
|
}
|
|
311
319
|
|
|
@@ -442,214 +450,1281 @@ function useTypingIndicator(websocket) {
|
|
|
442
450
|
};
|
|
443
451
|
}
|
|
444
452
|
|
|
445
|
-
// src/
|
|
453
|
+
// src/hooks/useResizableWidget.ts
|
|
454
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef2, useState as useState5 } from "react";
|
|
455
|
+
var STORAGE_KEY = "xcelsior-chat-size";
|
|
456
|
+
var EDGE_ZONE = 8;
|
|
457
|
+
var CURSOR_MAP = {
|
|
458
|
+
n: "ns-resize",
|
|
459
|
+
s: "ns-resize",
|
|
460
|
+
e: "ew-resize",
|
|
461
|
+
w: "ew-resize",
|
|
462
|
+
ne: "nesw-resize",
|
|
463
|
+
nw: "nwse-resize",
|
|
464
|
+
se: "nwse-resize",
|
|
465
|
+
sw: "nesw-resize"
|
|
466
|
+
};
|
|
467
|
+
function readStoredSize(key, fallbackWidth, fallbackHeight) {
|
|
468
|
+
try {
|
|
469
|
+
const stored = localStorage.getItem(key);
|
|
470
|
+
if (stored) {
|
|
471
|
+
const parsed = JSON.parse(stored);
|
|
472
|
+
if (typeof parsed.width === "number" && typeof parsed.height === "number") {
|
|
473
|
+
return { width: parsed.width, height: parsed.height };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
return { width: fallbackWidth, height: fallbackHeight };
|
|
479
|
+
}
|
|
480
|
+
function persistSize(key, width, height) {
|
|
481
|
+
try {
|
|
482
|
+
localStorage.setItem(key, JSON.stringify({ width, height }));
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function isMobile() {
|
|
487
|
+
return typeof window !== "undefined" && window.innerWidth < 768;
|
|
488
|
+
}
|
|
489
|
+
function detectEdge(e, rect) {
|
|
490
|
+
const { clientX: x, clientY: y } = e;
|
|
491
|
+
const nearTop = y - rect.top < EDGE_ZONE;
|
|
492
|
+
const nearBottom = rect.bottom - y < EDGE_ZONE;
|
|
493
|
+
const nearLeft = x - rect.left < EDGE_ZONE;
|
|
494
|
+
const nearRight = rect.right - x < EDGE_ZONE;
|
|
495
|
+
if (nearTop && nearLeft) return "nw";
|
|
496
|
+
if (nearTop && nearRight) return "ne";
|
|
497
|
+
if (nearBottom && nearLeft) return "sw";
|
|
498
|
+
if (nearBottom && nearRight) return "se";
|
|
499
|
+
if (nearTop) return "n";
|
|
500
|
+
if (nearBottom) return "s";
|
|
501
|
+
if (nearLeft) return "w";
|
|
502
|
+
if (nearRight) return "e";
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
function useResizableWidget({
|
|
506
|
+
initialWidth = 380,
|
|
507
|
+
initialHeight = 580,
|
|
508
|
+
minWidth = 320,
|
|
509
|
+
minHeight = 400,
|
|
510
|
+
maxWidth = 800,
|
|
511
|
+
maxHeight = 900,
|
|
512
|
+
storageKey = STORAGE_KEY,
|
|
513
|
+
enabled = true
|
|
514
|
+
} = {}) {
|
|
515
|
+
const [size, setSize] = useState5(() => {
|
|
516
|
+
if (typeof window === "undefined") return { width: initialWidth, height: initialHeight };
|
|
517
|
+
return readStoredSize(storageKey, initialWidth, initialHeight);
|
|
518
|
+
});
|
|
519
|
+
const [isResizing, setIsResizing] = useState5(false);
|
|
520
|
+
const [isNearEdge, setIsNearEdge] = useState5(false);
|
|
521
|
+
const [activeEdge, setActiveEdge] = useState5(null);
|
|
522
|
+
const sizeRef = useRef2(size);
|
|
523
|
+
sizeRef.current = size;
|
|
524
|
+
const dragRef = useRef2(null);
|
|
525
|
+
const containerRef = useRef2(null);
|
|
526
|
+
const clamp = useCallback3(
|
|
527
|
+
(w, h) => {
|
|
528
|
+
const mxW = Math.min(maxWidth, window.innerWidth - 24);
|
|
529
|
+
const mxH = Math.min(maxHeight, window.innerHeight - 100);
|
|
530
|
+
return {
|
|
531
|
+
width: Math.round(Math.max(minWidth, Math.min(mxW, w))),
|
|
532
|
+
height: Math.round(Math.max(minHeight, Math.min(mxH, h)))
|
|
533
|
+
};
|
|
534
|
+
},
|
|
535
|
+
[minWidth, minHeight, maxWidth, maxHeight]
|
|
536
|
+
);
|
|
537
|
+
const calcSize = useCallback3(
|
|
538
|
+
(dx, dy) => {
|
|
539
|
+
if (!dragRef.current) return sizeRef.current;
|
|
540
|
+
const { edge, startWidth, startHeight } = dragRef.current;
|
|
541
|
+
let w = startWidth;
|
|
542
|
+
let h = startHeight;
|
|
543
|
+
if (edge.includes("e")) w = startWidth + dx;
|
|
544
|
+
if (edge.includes("w")) w = startWidth - dx;
|
|
545
|
+
if (edge.includes("s")) h = startHeight + dy;
|
|
546
|
+
if (edge.includes("n")) h = startHeight - dy;
|
|
547
|
+
return clamp(w, h);
|
|
548
|
+
},
|
|
549
|
+
[clamp]
|
|
550
|
+
);
|
|
551
|
+
const handleDocMouseMove = useCallback3(
|
|
552
|
+
(e) => {
|
|
553
|
+
if (!dragRef.current) return;
|
|
554
|
+
const dx = e.clientX - dragRef.current.startX;
|
|
555
|
+
const dy = e.clientY - dragRef.current.startY;
|
|
556
|
+
setSize(calcSize(dx, dy));
|
|
557
|
+
},
|
|
558
|
+
[calcSize]
|
|
559
|
+
);
|
|
560
|
+
const handleDocMouseUp = useCallback3(
|
|
561
|
+
(e) => {
|
|
562
|
+
if (!dragRef.current) return;
|
|
563
|
+
const dx = e.clientX - dragRef.current.startX;
|
|
564
|
+
const dy = e.clientY - dragRef.current.startY;
|
|
565
|
+
const final = calcSize(dx, dy);
|
|
566
|
+
persistSize(storageKey, final.width, final.height);
|
|
567
|
+
dragRef.current = null;
|
|
568
|
+
setIsResizing(false);
|
|
569
|
+
setActiveEdge(null);
|
|
570
|
+
document.body.style.cursor = "";
|
|
571
|
+
document.removeEventListener("mousemove", handleDocMouseMove);
|
|
572
|
+
document.removeEventListener("mouseup", handleDocMouseUp);
|
|
573
|
+
},
|
|
574
|
+
[calcSize, storageKey, handleDocMouseMove]
|
|
575
|
+
);
|
|
576
|
+
const handleDocTouchMove = useCallback3(
|
|
577
|
+
(e) => {
|
|
578
|
+
if (!dragRef.current || e.touches.length === 0) return;
|
|
579
|
+
e.preventDefault();
|
|
580
|
+
const t = e.touches[0];
|
|
581
|
+
const dx = t.clientX - dragRef.current.startX;
|
|
582
|
+
const dy = t.clientY - dragRef.current.startY;
|
|
583
|
+
setSize(calcSize(dx, dy));
|
|
584
|
+
},
|
|
585
|
+
[calcSize]
|
|
586
|
+
);
|
|
587
|
+
const handleDocTouchEnd = useCallback3(
|
|
588
|
+
(e) => {
|
|
589
|
+
if (!dragRef.current) return;
|
|
590
|
+
const t = e.changedTouches[0];
|
|
591
|
+
if (t) {
|
|
592
|
+
const dx = t.clientX - dragRef.current.startX;
|
|
593
|
+
const dy = t.clientY - dragRef.current.startY;
|
|
594
|
+
const final = calcSize(dx, dy);
|
|
595
|
+
persistSize(storageKey, final.width, final.height);
|
|
596
|
+
}
|
|
597
|
+
dragRef.current = null;
|
|
598
|
+
setIsResizing(false);
|
|
599
|
+
setActiveEdge(null);
|
|
600
|
+
document.removeEventListener("touchmove", handleDocTouchMove);
|
|
601
|
+
document.removeEventListener("touchend", handleDocTouchEnd);
|
|
602
|
+
},
|
|
603
|
+
[calcSize, storageKey, handleDocTouchMove]
|
|
604
|
+
);
|
|
605
|
+
useEffect4(() => {
|
|
606
|
+
return () => {
|
|
607
|
+
document.removeEventListener("mousemove", handleDocMouseMove);
|
|
608
|
+
document.removeEventListener("mouseup", handleDocMouseUp);
|
|
609
|
+
document.removeEventListener("touchmove", handleDocTouchMove);
|
|
610
|
+
document.removeEventListener("touchend", handleDocTouchEnd);
|
|
611
|
+
document.body.style.cursor = "";
|
|
612
|
+
};
|
|
613
|
+
}, [handleDocMouseMove, handleDocMouseUp, handleDocTouchMove, handleDocTouchEnd]);
|
|
614
|
+
const onContainerMouseMove = useCallback3(
|
|
615
|
+
(e) => {
|
|
616
|
+
if (!enabled || isMobile() || isResizing) return;
|
|
617
|
+
const el = e.currentTarget;
|
|
618
|
+
containerRef.current = el;
|
|
619
|
+
const rect = el.getBoundingClientRect();
|
|
620
|
+
const edge = detectEdge(e, rect);
|
|
621
|
+
setIsNearEdge(!!edge);
|
|
622
|
+
setActiveEdge(edge);
|
|
623
|
+
el.style.cursor = edge ? CURSOR_MAP[edge] : "";
|
|
624
|
+
},
|
|
625
|
+
[enabled, isResizing]
|
|
626
|
+
);
|
|
627
|
+
const onContainerMouseDown = useCallback3(
|
|
628
|
+
(e) => {
|
|
629
|
+
if (!enabled || isMobile() || !activeEdge) return;
|
|
630
|
+
e.preventDefault();
|
|
631
|
+
e.stopPropagation();
|
|
632
|
+
dragRef.current = {
|
|
633
|
+
edge: activeEdge,
|
|
634
|
+
startX: e.clientX,
|
|
635
|
+
startY: e.clientY,
|
|
636
|
+
startWidth: sizeRef.current.width,
|
|
637
|
+
startHeight: sizeRef.current.height
|
|
638
|
+
};
|
|
639
|
+
setIsResizing(true);
|
|
640
|
+
document.body.style.cursor = CURSOR_MAP[activeEdge];
|
|
641
|
+
document.addEventListener("mousemove", handleDocMouseMove);
|
|
642
|
+
document.addEventListener("mouseup", handleDocMouseUp);
|
|
643
|
+
},
|
|
644
|
+
[enabled, activeEdge, handleDocMouseMove, handleDocMouseUp]
|
|
645
|
+
);
|
|
646
|
+
const onContainerMouseLeave = useCallback3(
|
|
647
|
+
(_e) => {
|
|
648
|
+
if (!isResizing) {
|
|
649
|
+
setIsNearEdge(false);
|
|
650
|
+
setActiveEdge(null);
|
|
651
|
+
if (containerRef.current) containerRef.current.style.cursor = "";
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
[isResizing]
|
|
655
|
+
);
|
|
656
|
+
const onContainerTouchStart = useCallback3(
|
|
657
|
+
(e) => {
|
|
658
|
+
if (!enabled || isMobile() || e.touches.length === 0) return;
|
|
659
|
+
const el = e.currentTarget;
|
|
660
|
+
const rect = el.getBoundingClientRect();
|
|
661
|
+
const t = e.touches[0];
|
|
662
|
+
const edge = detectEdge(t, rect);
|
|
663
|
+
if (!edge) return;
|
|
664
|
+
dragRef.current = {
|
|
665
|
+
edge,
|
|
666
|
+
startX: t.clientX,
|
|
667
|
+
startY: t.clientY,
|
|
668
|
+
startWidth: sizeRef.current.width,
|
|
669
|
+
startHeight: sizeRef.current.height
|
|
670
|
+
};
|
|
671
|
+
setIsResizing(true);
|
|
672
|
+
setActiveEdge(edge);
|
|
673
|
+
document.addEventListener("touchmove", handleDocTouchMove, { passive: false });
|
|
674
|
+
document.addEventListener("touchend", handleDocTouchEnd);
|
|
675
|
+
},
|
|
676
|
+
[enabled, handleDocTouchMove, handleDocTouchEnd]
|
|
677
|
+
);
|
|
678
|
+
return {
|
|
679
|
+
width: size.width,
|
|
680
|
+
height: size.height,
|
|
681
|
+
isResizing,
|
|
682
|
+
isNearEdge,
|
|
683
|
+
activeEdge,
|
|
684
|
+
containerResizeProps: {
|
|
685
|
+
onMouseMove: onContainerMouseMove,
|
|
686
|
+
onMouseDown: onContainerMouseDown,
|
|
687
|
+
onMouseLeave: onContainerMouseLeave,
|
|
688
|
+
onTouchStart: onContainerTouchStart
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/components/BrandIcons.tsx
|
|
446
694
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
447
|
-
function
|
|
448
|
-
return /* @__PURE__ */ jsxs(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
695
|
+
function XcelsiorSymbol({ size = 24, color = "white", className = "", style }) {
|
|
696
|
+
return /* @__PURE__ */ jsxs(
|
|
697
|
+
"svg",
|
|
698
|
+
{
|
|
699
|
+
width: size,
|
|
700
|
+
height: size,
|
|
701
|
+
viewBox: "0 0 32 32",
|
|
702
|
+
fill: "none",
|
|
703
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
704
|
+
className,
|
|
705
|
+
style,
|
|
706
|
+
"aria-hidden": "true",
|
|
707
|
+
children: [
|
|
708
|
+
/* @__PURE__ */ jsx(
|
|
709
|
+
"path",
|
|
453
710
|
{
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
className: "h-10 w-10 rounded-full object-cover"
|
|
711
|
+
d: "M20.582 15.027L31.849 0.036H24.808L17.039 10.303L20.582 15.027ZM24.808 31.837H31.849L20.582 16.846L17.039 21.57L24.808 31.837Z",
|
|
712
|
+
fill: color
|
|
457
713
|
}
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
714
|
+
),
|
|
715
|
+
/* @__PURE__ */ jsx(
|
|
716
|
+
"path",
|
|
717
|
+
{
|
|
718
|
+
d: "M14.313 15.027H18.402L7.135 0.036H0.185L9.406 12.392C10.587 13.983 12.359 15.027 14.313 15.027Z",
|
|
719
|
+
fill: color
|
|
720
|
+
}
|
|
721
|
+
),
|
|
722
|
+
/* @__PURE__ */ jsx(
|
|
723
|
+
"path",
|
|
724
|
+
{
|
|
725
|
+
d: "M0.185 31.837H7.135L18.402 16.846H14.313C12.359 16.846 10.588 17.891 9.406 19.481L0.185 31.837Z",
|
|
726
|
+
fill: color
|
|
727
|
+
}
|
|
728
|
+
)
|
|
729
|
+
]
|
|
730
|
+
}
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
function ChatBubbleIcon({ size = 28, color = "white", className = "", style }) {
|
|
734
|
+
return /* @__PURE__ */ jsx(
|
|
735
|
+
"svg",
|
|
736
|
+
{
|
|
737
|
+
width: size,
|
|
738
|
+
height: size,
|
|
739
|
+
viewBox: "0 0 24 24",
|
|
740
|
+
fill: "none",
|
|
741
|
+
stroke: color,
|
|
742
|
+
strokeWidth: 2,
|
|
743
|
+
strokeLinecap: "round",
|
|
744
|
+
strokeLinejoin: "round",
|
|
745
|
+
className,
|
|
746
|
+
style,
|
|
747
|
+
"aria-hidden": "true",
|
|
748
|
+
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" })
|
|
749
|
+
}
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
function XcelsiorAvatar({ size = 40, className = "" }) {
|
|
753
|
+
const iconSize = Math.round(size * 0.55);
|
|
754
|
+
return /* @__PURE__ */ jsx(
|
|
755
|
+
"div",
|
|
756
|
+
{
|
|
757
|
+
className: `flex items-center justify-center rounded-full ${className}`,
|
|
758
|
+
style: {
|
|
759
|
+
width: size,
|
|
760
|
+
height: size,
|
|
761
|
+
background: "linear-gradient(135deg, #337eff, #005eff)"
|
|
762
|
+
},
|
|
763
|
+
children: /* @__PURE__ */ jsx(XcelsiorSymbol, { size: iconSize, color: "white" })
|
|
764
|
+
}
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/components/ChatHeader.tsx
|
|
769
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
770
|
+
function ChatHeader({ agent, onClose, onMinimize, theme }) {
|
|
771
|
+
return /* @__PURE__ */ jsxs2(
|
|
772
|
+
"div",
|
|
773
|
+
{
|
|
774
|
+
className: "relative text-white overflow-hidden",
|
|
775
|
+
style: {
|
|
776
|
+
flexShrink: 0,
|
|
777
|
+
/* Layered blue smoke/glow effect for premium look */
|
|
778
|
+
background: [
|
|
779
|
+
"radial-gradient(ellipse at 30% 50%, rgba(0,50,255,0.4) 0%, transparent 60%)",
|
|
780
|
+
"radial-gradient(ellipse at 70% 30%, rgba(0,80,255,0.3) 0%, transparent 50%)",
|
|
781
|
+
"radial-gradient(ellipse at 50% 80%, rgba(0,30,200,0.3) 0%, transparent 60%)",
|
|
782
|
+
`linear-gradient(135deg, #0030cc 0%, #001a66 50%, #000d33 100%)`
|
|
783
|
+
].join(", ")
|
|
784
|
+
},
|
|
785
|
+
children: [
|
|
786
|
+
/* @__PURE__ */ jsx2(
|
|
787
|
+
"div",
|
|
788
|
+
{
|
|
789
|
+
className: "absolute inset-0 pointer-events-none",
|
|
790
|
+
style: {
|
|
791
|
+
background: "linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0) 50%)"
|
|
494
792
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
className: "w-5 h-5",
|
|
509
|
-
fill: "none",
|
|
510
|
-
viewBox: "0 0 24 24",
|
|
511
|
-
stroke: "currentColor",
|
|
512
|
-
"aria-hidden": "true",
|
|
513
|
-
children: [
|
|
514
|
-
/* @__PURE__ */ jsx("title", { children: "Close icon" }),
|
|
515
|
-
/* @__PURE__ */ jsx(
|
|
516
|
-
"path",
|
|
517
|
-
{
|
|
518
|
-
strokeLinecap: "round",
|
|
519
|
-
strokeLinejoin: "round",
|
|
520
|
-
strokeWidth: 2,
|
|
521
|
-
d: "M6 18L18 6M6 6l12 12"
|
|
793
|
+
}
|
|
794
|
+
),
|
|
795
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", style: { padding: "8px 16px" }, children: [
|
|
796
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3", children: [
|
|
797
|
+
/* @__PURE__ */ jsxs2("div", { className: "relative", children: [
|
|
798
|
+
agent?.avatar ? /* @__PURE__ */ jsx2(
|
|
799
|
+
"img",
|
|
800
|
+
{
|
|
801
|
+
src: agent.avatar,
|
|
802
|
+
alt: agent.name,
|
|
803
|
+
className: "h-10 w-10 rounded-full object-cover",
|
|
804
|
+
style: {
|
|
805
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.25)"
|
|
522
806
|
}
|
|
523
|
-
|
|
524
|
-
|
|
807
|
+
}
|
|
808
|
+
) : /* @__PURE__ */ jsx2("div", { style: { filter: "drop-shadow(0 2px 8px rgba(0,0,0,0.3))" }, children: /* @__PURE__ */ jsx2(XcelsiorSymbol, { size: 28, color: "white" }) }),
|
|
809
|
+
agent?.status === "online" && /* @__PURE__ */ jsx2(
|
|
810
|
+
"div",
|
|
811
|
+
{
|
|
812
|
+
className: "absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 rounded-full",
|
|
813
|
+
style: {
|
|
814
|
+
backgroundColor: theme?.statusPositive || "#1ed473",
|
|
815
|
+
border: "2.5px solid rgba(0,50,180,0.9)",
|
|
816
|
+
boxShadow: `0 0 8px ${theme?.statusPositive || "#1ed473"}80`
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
)
|
|
820
|
+
] }),
|
|
821
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
822
|
+
/* @__PURE__ */ jsx2(
|
|
823
|
+
"h3",
|
|
824
|
+
{
|
|
825
|
+
className: "font-semibold leading-tight",
|
|
826
|
+
style: {
|
|
827
|
+
fontSize: "15px",
|
|
828
|
+
letterSpacing: "-0.01em"
|
|
829
|
+
},
|
|
830
|
+
children: agent?.name || "Xcelsior Software"
|
|
831
|
+
}
|
|
832
|
+
),
|
|
833
|
+
/* @__PURE__ */ jsx2(
|
|
834
|
+
"p",
|
|
835
|
+
{
|
|
836
|
+
className: "mt-0.5 leading-tight",
|
|
837
|
+
style: {
|
|
838
|
+
fontSize: "12px",
|
|
839
|
+
letterSpacing: "0.015em",
|
|
840
|
+
color: "rgba(255,255,255,0.6)"
|
|
841
|
+
},
|
|
842
|
+
children: agent?.status === "online" ? /* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-1.5", children: [
|
|
843
|
+
/* @__PURE__ */ jsx2(
|
|
844
|
+
"span",
|
|
845
|
+
{
|
|
846
|
+
className: "inline-block w-1.5 h-1.5 rounded-full",
|
|
847
|
+
style: {
|
|
848
|
+
backgroundColor: theme?.statusPositive || "#1ed473",
|
|
849
|
+
boxShadow: `0 0 4px ${theme?.statusPositive || "#1ed473"}60`
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
),
|
|
853
|
+
"Online now"
|
|
854
|
+
] }) : agent?.status === "away" ? "Away" : "We typically reply within minutes"
|
|
855
|
+
}
|
|
856
|
+
)
|
|
857
|
+
] })
|
|
858
|
+
] }),
|
|
859
|
+
/* @__PURE__ */ jsx2("div", { className: "flex items-center gap-0.5", children: (onMinimize || onClose) && /* @__PURE__ */ jsx2(
|
|
860
|
+
"button",
|
|
861
|
+
{
|
|
862
|
+
type: "button",
|
|
863
|
+
onClick: onMinimize || onClose,
|
|
864
|
+
className: "p-2 rounded-lg transition-all duration-150",
|
|
865
|
+
style: { backgroundColor: "transparent" },
|
|
866
|
+
onMouseEnter: (e) => {
|
|
867
|
+
e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.1)";
|
|
868
|
+
},
|
|
869
|
+
onMouseLeave: (e) => {
|
|
870
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
871
|
+
},
|
|
872
|
+
"aria-label": "Minimize chat",
|
|
873
|
+
children: /* @__PURE__ */ jsxs2(
|
|
874
|
+
"svg",
|
|
875
|
+
{
|
|
876
|
+
width: "18",
|
|
877
|
+
height: "18",
|
|
878
|
+
fill: "none",
|
|
879
|
+
viewBox: "0 0 24 24",
|
|
880
|
+
stroke: "currentColor",
|
|
881
|
+
"aria-hidden": "true",
|
|
882
|
+
children: [
|
|
883
|
+
/* @__PURE__ */ jsx2("title", { children: "Minimize" }),
|
|
884
|
+
/* @__PURE__ */ jsx2(
|
|
885
|
+
"path",
|
|
886
|
+
{
|
|
887
|
+
strokeLinecap: "round",
|
|
888
|
+
strokeLinejoin: "round",
|
|
889
|
+
strokeWidth: 2,
|
|
890
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
891
|
+
}
|
|
892
|
+
)
|
|
893
|
+
]
|
|
894
|
+
}
|
|
895
|
+
)
|
|
525
896
|
}
|
|
526
|
-
)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
897
|
+
) })
|
|
898
|
+
] }),
|
|
899
|
+
/* @__PURE__ */ jsx2(
|
|
900
|
+
"div",
|
|
901
|
+
{
|
|
902
|
+
className: "h-px",
|
|
903
|
+
style: {
|
|
904
|
+
background: "linear-gradient(90deg, transparent 5%, rgba(255,255,255,0.12) 30%, rgba(255,255,255,0.12) 70%, transparent 95%)"
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
)
|
|
908
|
+
]
|
|
909
|
+
}
|
|
910
|
+
);
|
|
531
911
|
}
|
|
532
912
|
|
|
533
913
|
// src/components/MessageList.tsx
|
|
534
|
-
import {
|
|
914
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef4 } from "react";
|
|
535
915
|
import { Spinner } from "@xcelsior/design-system";
|
|
536
916
|
|
|
537
917
|
// src/components/MessageItem.tsx
|
|
538
918
|
import { formatDistanceToNow } from "date-fns";
|
|
919
|
+
|
|
920
|
+
// src/components/MarkdownMessage.tsx
|
|
539
921
|
import ReactMarkdown from "react-markdown";
|
|
540
|
-
|
|
922
|
+
|
|
923
|
+
// src/utils/markdown-styles.ts
|
|
924
|
+
function getMarkdownStyles(theme, isLightTheme) {
|
|
925
|
+
const primaryColor = theme?.primary || "#337eff";
|
|
926
|
+
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
927
|
+
const strongColor = isLightTheme ? "rgba(0,0,0,0.9)" : "rgba(255,255,255,0.95)";
|
|
928
|
+
const inlineCodeBg = isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.08)";
|
|
929
|
+
const codeBlockBg = isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(0,0,0,0.25)";
|
|
930
|
+
const codeBlockBorder = isLightTheme ? "1px solid rgba(0,0,0,0.08)" : "1px solid rgba(255,255,255,0.06)";
|
|
931
|
+
const blockquoteBorder = isLightTheme ? `3px solid ${primaryColor}60` : `3px solid ${primaryColor}80`;
|
|
932
|
+
const blockquoteBg = isLightTheme ? "rgba(0,0,0,0.02)" : "rgba(255,255,255,0.02)";
|
|
933
|
+
return {
|
|
934
|
+
paragraph: {
|
|
935
|
+
margin: "0 0 8px 0",
|
|
936
|
+
lineHeight: "1.6",
|
|
937
|
+
color: textColor
|
|
938
|
+
},
|
|
939
|
+
strong: {
|
|
940
|
+
fontWeight: 600,
|
|
941
|
+
color: strongColor
|
|
942
|
+
},
|
|
943
|
+
emphasis: {
|
|
944
|
+
fontStyle: "italic",
|
|
945
|
+
color: textColor
|
|
946
|
+
},
|
|
947
|
+
link: {
|
|
948
|
+
color: primaryColor,
|
|
949
|
+
textDecoration: "underline",
|
|
950
|
+
textUnderlineOffset: "2px",
|
|
951
|
+
cursor: "pointer"
|
|
952
|
+
},
|
|
953
|
+
list: {
|
|
954
|
+
paddingLeft: "20px",
|
|
955
|
+
margin: "8px 0",
|
|
956
|
+
color: textColor
|
|
957
|
+
},
|
|
958
|
+
listItem: {
|
|
959
|
+
margin: "4px 0",
|
|
960
|
+
lineHeight: "1.5",
|
|
961
|
+
color: textColor
|
|
962
|
+
},
|
|
963
|
+
code: {
|
|
964
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
965
|
+
fontSize: "0.875em",
|
|
966
|
+
backgroundColor: inlineCodeBg,
|
|
967
|
+
padding: "2px 6px",
|
|
968
|
+
borderRadius: "4px",
|
|
969
|
+
color: textColor
|
|
970
|
+
},
|
|
971
|
+
codeBlock: {
|
|
972
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
973
|
+
fontSize: "0.8125em",
|
|
974
|
+
backgroundColor: codeBlockBg,
|
|
975
|
+
border: codeBlockBorder,
|
|
976
|
+
padding: "12px",
|
|
977
|
+
borderRadius: "8px",
|
|
978
|
+
overflowX: "auto",
|
|
979
|
+
margin: "8px 0",
|
|
980
|
+
color: textColor,
|
|
981
|
+
lineHeight: "1.5",
|
|
982
|
+
display: "block",
|
|
983
|
+
whiteSpace: "pre"
|
|
984
|
+
},
|
|
985
|
+
heading: {
|
|
986
|
+
fontWeight: 600,
|
|
987
|
+
marginTop: "12px",
|
|
988
|
+
marginBottom: "6px",
|
|
989
|
+
color: strongColor,
|
|
990
|
+
lineHeight: "1.3"
|
|
991
|
+
},
|
|
992
|
+
blockquote: {
|
|
993
|
+
borderLeft: blockquoteBorder,
|
|
994
|
+
backgroundColor: blockquoteBg,
|
|
995
|
+
margin: "8px 0",
|
|
996
|
+
padding: "6px 12px",
|
|
997
|
+
borderRadius: "0 4px 4px 0",
|
|
998
|
+
color: textColor,
|
|
999
|
+
fontStyle: "italic"
|
|
1000
|
+
},
|
|
1001
|
+
hr: {
|
|
1002
|
+
border: "none",
|
|
1003
|
+
borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.1)" : "1px solid rgba(255,255,255,0.08)",
|
|
1004
|
+
margin: "12px 0"
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/components/MarkdownMessage.tsx
|
|
1010
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1011
|
+
function computeIsLightTheme(theme) {
|
|
1012
|
+
const bgColor = theme?.background || "#00001a";
|
|
1013
|
+
if (!bgColor.startsWith("#")) return false;
|
|
1014
|
+
const hex = bgColor.replace("#", "");
|
|
1015
|
+
if (hex.length < 6) return false;
|
|
1016
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1017
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1018
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1019
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1020
|
+
}
|
|
1021
|
+
function MarkdownMessage({ content, theme }) {
|
|
1022
|
+
const isLightTheme = computeIsLightTheme(theme);
|
|
1023
|
+
const styles = getMarkdownStyles(theme, isLightTheme);
|
|
1024
|
+
const wrapperStyle = {
|
|
1025
|
+
// Collapse bottom margin of the last child to avoid extra padding
|
|
1026
|
+
// inside the bubble.
|
|
1027
|
+
display: "block"
|
|
1028
|
+
};
|
|
1029
|
+
return /* @__PURE__ */ jsx3("div", { style: wrapperStyle, children: /* @__PURE__ */ jsx3(
|
|
1030
|
+
ReactMarkdown,
|
|
1031
|
+
{
|
|
1032
|
+
components: {
|
|
1033
|
+
// Paragraphs
|
|
1034
|
+
p: ({ children }) => /* @__PURE__ */ jsx3("p", { style: styles.paragraph, children }),
|
|
1035
|
+
// Bold
|
|
1036
|
+
strong: ({ children }) => /* @__PURE__ */ jsx3("strong", { style: styles.strong, children }),
|
|
1037
|
+
// Italic
|
|
1038
|
+
em: ({ children }) => /* @__PURE__ */ jsx3("em", { style: styles.emphasis, children }),
|
|
1039
|
+
// Links — open in new tab
|
|
1040
|
+
a: ({ href, children }) => /* @__PURE__ */ jsx3(
|
|
1041
|
+
"a",
|
|
1042
|
+
{
|
|
1043
|
+
href,
|
|
1044
|
+
target: "_blank",
|
|
1045
|
+
rel: "noopener noreferrer",
|
|
1046
|
+
style: styles.link,
|
|
1047
|
+
children
|
|
1048
|
+
}
|
|
1049
|
+
),
|
|
1050
|
+
// Unordered list
|
|
1051
|
+
ul: ({ children }) => /* @__PURE__ */ jsx3("ul", { style: { ...styles.list, listStyleType: "disc" }, children }),
|
|
1052
|
+
// Ordered list
|
|
1053
|
+
ol: ({ children }) => /* @__PURE__ */ jsx3(
|
|
1054
|
+
"ol",
|
|
1055
|
+
{
|
|
1056
|
+
style: { ...styles.list, listStyleType: "decimal" },
|
|
1057
|
+
children
|
|
1058
|
+
}
|
|
1059
|
+
),
|
|
1060
|
+
// List item
|
|
1061
|
+
li: ({ children }) => /* @__PURE__ */ jsx3("li", { style: styles.listItem, children }),
|
|
1062
|
+
// Inline code vs code block — react-markdown wraps fenced
|
|
1063
|
+
// code blocks as <pre><code className="language-*">. The
|
|
1064
|
+
// cleanest heuristic: if the className starts with
|
|
1065
|
+
// "language-" it is a fenced block; otherwise it's inline.
|
|
1066
|
+
code: (({ children, className }) => {
|
|
1067
|
+
const isBlock = Boolean(
|
|
1068
|
+
className?.startsWith("language-")
|
|
1069
|
+
);
|
|
1070
|
+
if (isBlock) {
|
|
1071
|
+
return /* @__PURE__ */ jsx3(
|
|
1072
|
+
"code",
|
|
1073
|
+
{
|
|
1074
|
+
className,
|
|
1075
|
+
style: styles.codeBlock,
|
|
1076
|
+
children
|
|
1077
|
+
}
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
return /* @__PURE__ */ jsx3("code", { style: styles.code, children });
|
|
1081
|
+
}),
|
|
1082
|
+
// Pre wrapper for code blocks
|
|
1083
|
+
pre: ({ children }) => /* @__PURE__ */ jsx3(
|
|
1084
|
+
"pre",
|
|
1085
|
+
{
|
|
1086
|
+
style: {
|
|
1087
|
+
margin: "8px 0",
|
|
1088
|
+
padding: 0,
|
|
1089
|
+
backgroundColor: "transparent",
|
|
1090
|
+
border: "none",
|
|
1091
|
+
overflow: "visible"
|
|
1092
|
+
},
|
|
1093
|
+
children
|
|
1094
|
+
}
|
|
1095
|
+
),
|
|
1096
|
+
// Headings — h1 through h6 with progressive size reduction
|
|
1097
|
+
h1: ({ children }) => /* @__PURE__ */ jsx3(
|
|
1098
|
+
"h1",
|
|
1099
|
+
{
|
|
1100
|
+
style: {
|
|
1101
|
+
...styles.heading,
|
|
1102
|
+
fontSize: "1.15em"
|
|
1103
|
+
},
|
|
1104
|
+
children
|
|
1105
|
+
}
|
|
1106
|
+
),
|
|
1107
|
+
h2: ({ children }) => /* @__PURE__ */ jsx3(
|
|
1108
|
+
"h2",
|
|
1109
|
+
{
|
|
1110
|
+
style: {
|
|
1111
|
+
...styles.heading,
|
|
1112
|
+
fontSize: "1.1em"
|
|
1113
|
+
},
|
|
1114
|
+
children
|
|
1115
|
+
}
|
|
1116
|
+
),
|
|
1117
|
+
h3: ({ children }) => /* @__PURE__ */ jsx3(
|
|
1118
|
+
"h3",
|
|
1119
|
+
{
|
|
1120
|
+
style: {
|
|
1121
|
+
...styles.heading,
|
|
1122
|
+
fontSize: "1.05em"
|
|
1123
|
+
},
|
|
1124
|
+
children
|
|
1125
|
+
}
|
|
1126
|
+
),
|
|
1127
|
+
h4: ({ children }) => /* @__PURE__ */ jsx3("h4", { style: { ...styles.heading, fontSize: "1em" }, children }),
|
|
1128
|
+
h5: ({ children }) => /* @__PURE__ */ jsx3(
|
|
1129
|
+
"h5",
|
|
1130
|
+
{
|
|
1131
|
+
style: {
|
|
1132
|
+
...styles.heading,
|
|
1133
|
+
fontSize: "0.95em"
|
|
1134
|
+
},
|
|
1135
|
+
children
|
|
1136
|
+
}
|
|
1137
|
+
),
|
|
1138
|
+
h6: ({ children }) => /* @__PURE__ */ jsx3(
|
|
1139
|
+
"h6",
|
|
1140
|
+
{
|
|
1141
|
+
style: {
|
|
1142
|
+
...styles.heading,
|
|
1143
|
+
fontSize: "0.9em"
|
|
1144
|
+
},
|
|
1145
|
+
children
|
|
1146
|
+
}
|
|
1147
|
+
),
|
|
1148
|
+
// Blockquote
|
|
1149
|
+
blockquote: ({ children }) => /* @__PURE__ */ jsx3("blockquote", { style: styles.blockquote, children }),
|
|
1150
|
+
// Horizontal rule
|
|
1151
|
+
hr: () => /* @__PURE__ */ jsx3("hr", { style: styles.hr })
|
|
1152
|
+
},
|
|
1153
|
+
children: content
|
|
1154
|
+
}
|
|
1155
|
+
) });
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// src/components/MessageItem.tsx
|
|
1159
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
541
1160
|
function MessageItem({
|
|
542
1161
|
message,
|
|
543
1162
|
currentUser,
|
|
544
1163
|
showAvatar = true,
|
|
545
|
-
showTimestamp = true
|
|
1164
|
+
showTimestamp = true,
|
|
1165
|
+
theme
|
|
546
1166
|
}) {
|
|
547
1167
|
const isOwnMessage = message.senderType === currentUser.type;
|
|
548
1168
|
const isSystemMessage = message.senderType === "system";
|
|
549
1169
|
const isAIMessage = message.metadata?.isAI === true;
|
|
1170
|
+
const isBotMessage = message.senderType === "bot";
|
|
1171
|
+
const bgColor = theme?.background || "#00001a";
|
|
1172
|
+
const isLightTheme = (() => {
|
|
1173
|
+
if (!bgColor.startsWith("#")) return false;
|
|
1174
|
+
const hex = bgColor.replace("#", "");
|
|
1175
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1176
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1177
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1178
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1179
|
+
})();
|
|
1180
|
+
const primaryColor = theme?.primary || "#337eff";
|
|
1181
|
+
const primaryStrong = theme?.primaryStrong || "#005eff";
|
|
1182
|
+
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1183
|
+
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.35)");
|
|
550
1184
|
if (isSystemMessage) {
|
|
551
|
-
return /* @__PURE__ */
|
|
1185
|
+
return /* @__PURE__ */ jsx4("div", { className: "flex justify-center my-3", children: /* @__PURE__ */ jsx4(
|
|
1186
|
+
"div",
|
|
1187
|
+
{
|
|
1188
|
+
className: "px-4 py-1.5 rounded-full",
|
|
1189
|
+
style: {
|
|
1190
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.03)",
|
|
1191
|
+
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)"
|
|
1192
|
+
},
|
|
1193
|
+
children: /* @__PURE__ */ jsx4(
|
|
1194
|
+
"p",
|
|
1195
|
+
{
|
|
1196
|
+
style: {
|
|
1197
|
+
fontSize: "11px",
|
|
1198
|
+
letterSpacing: "0.019em",
|
|
1199
|
+
color: textMuted
|
|
1200
|
+
},
|
|
1201
|
+
children: message.content
|
|
1202
|
+
}
|
|
1203
|
+
)
|
|
1204
|
+
}
|
|
1205
|
+
) });
|
|
552
1206
|
}
|
|
553
|
-
const
|
|
554
|
-
if (isAIMessage)
|
|
555
|
-
return "\u{1F916}";
|
|
556
|
-
}
|
|
1207
|
+
const getSenderLabel = () => {
|
|
1208
|
+
if (isBotMessage || isAIMessage) return "AI Assistant";
|
|
557
1209
|
if (message.senderType === "agent") {
|
|
558
|
-
return "
|
|
1210
|
+
return message.metadata?.agentName || "Support Agent";
|
|
559
1211
|
}
|
|
560
|
-
return
|
|
1212
|
+
return null;
|
|
561
1213
|
};
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
1214
|
+
const senderLabel = !isOwnMessage ? getSenderLabel() : null;
|
|
1215
|
+
const ownBubbleStyle = {
|
|
1216
|
+
background: `linear-gradient(135deg, ${primaryColor}, ${primaryStrong})`,
|
|
1217
|
+
color: "#ffffff",
|
|
1218
|
+
borderRadius: "18px 18px 4px 18px",
|
|
1219
|
+
boxShadow: `0 2px 12px -3px ${primaryColor}40`
|
|
1220
|
+
};
|
|
1221
|
+
const otherBubbleStyle = isLightTheme ? {
|
|
1222
|
+
backgroundColor: "rgba(0,0,0,0.04)",
|
|
1223
|
+
color: textColor,
|
|
1224
|
+
borderRadius: "18px 18px 18px 4px",
|
|
1225
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.06)"
|
|
1226
|
+
} : {
|
|
1227
|
+
backgroundColor: "rgba(255,255,255,0.04)",
|
|
1228
|
+
color: textColor,
|
|
1229
|
+
borderRadius: "18px 18px 18px 4px",
|
|
1230
|
+
boxShadow: "inset 0 0 0 0.5px rgba(255,255,255,0.06), inset 0 1px 0 0 rgba(255,255,255,0.08)"
|
|
1231
|
+
};
|
|
1232
|
+
return /* @__PURE__ */ jsxs3(
|
|
1233
|
+
"div",
|
|
1234
|
+
{
|
|
1235
|
+
className: `flex gap-2.5 mb-3 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
|
|
1236
|
+
children: [
|
|
1237
|
+
showAvatar && !isOwnMessage && /* @__PURE__ */ jsx4("div", { className: "flex-shrink-0 mt-auto mb-5", children: isBotMessage || isAIMessage ? /* @__PURE__ */ jsx4(XcelsiorAvatar, { size: 28 }) : /* @__PURE__ */ jsx4(
|
|
1238
|
+
"div",
|
|
1239
|
+
{
|
|
1240
|
+
className: "h-7 w-7 rounded-full flex items-center justify-center",
|
|
1241
|
+
style: {
|
|
1242
|
+
background: isLightTheme ? `linear-gradient(135deg, ${primaryColor}30, rgba(0,0,0,0.04))` : `linear-gradient(135deg, ${primaryColor}60, rgba(255,255,255,0.06))`,
|
|
1243
|
+
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)"
|
|
1244
|
+
},
|
|
1245
|
+
children: /* @__PURE__ */ jsxs3(
|
|
1246
|
+
"svg",
|
|
1247
|
+
{
|
|
1248
|
+
width: "14",
|
|
1249
|
+
height: "14",
|
|
1250
|
+
viewBox: "0 0 24 24",
|
|
1251
|
+
fill: "none",
|
|
1252
|
+
stroke: isLightTheme ? primaryColor : "white",
|
|
1253
|
+
strokeWidth: "2",
|
|
1254
|
+
strokeLinecap: "round",
|
|
1255
|
+
strokeLinejoin: "round",
|
|
1256
|
+
"aria-hidden": "true",
|
|
1257
|
+
children: [
|
|
1258
|
+
/* @__PURE__ */ jsx4("title", { children: "Agent" }),
|
|
1259
|
+
/* @__PURE__ */ jsx4("path", { d: "M3 18v-6a9 9 0 0 1 18 0v6" }),
|
|
1260
|
+
/* @__PURE__ */ jsx4("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" })
|
|
1261
|
+
]
|
|
1262
|
+
}
|
|
1263
|
+
)
|
|
1264
|
+
}
|
|
1265
|
+
) }),
|
|
1266
|
+
showAvatar && isOwnMessage && /* @__PURE__ */ jsx4("div", { className: "w-7 flex-shrink-0" }),
|
|
1267
|
+
/* @__PURE__ */ jsxs3(
|
|
1268
|
+
"div",
|
|
1269
|
+
{
|
|
1270
|
+
className: `flex flex-col max-w-[75%] ${isOwnMessage ? "items-end" : "items-start"}`,
|
|
1271
|
+
children: [
|
|
1272
|
+
senderLabel && /* @__PURE__ */ jsx4(
|
|
1273
|
+
"span",
|
|
1274
|
+
{
|
|
1275
|
+
className: "mb-1 px-1 font-medium",
|
|
1276
|
+
style: {
|
|
1277
|
+
color: isLightTheme ? "rgba(0,0,0,0.45)" : "rgba(247,247,248,0.4)",
|
|
1278
|
+
fontSize: "11px",
|
|
1279
|
+
letterSpacing: "0.019em"
|
|
1280
|
+
},
|
|
1281
|
+
children: senderLabel
|
|
1282
|
+
}
|
|
1283
|
+
),
|
|
1284
|
+
/* @__PURE__ */ jsxs3(
|
|
1285
|
+
"div",
|
|
1286
|
+
{
|
|
1287
|
+
className: "px-4 py-2.5",
|
|
1288
|
+
style: {
|
|
1289
|
+
...isOwnMessage ? ownBubbleStyle : otherBubbleStyle,
|
|
1290
|
+
fontSize: "14px",
|
|
1291
|
+
lineHeight: "1.5",
|
|
1292
|
+
letterSpacing: "0.006em"
|
|
1293
|
+
},
|
|
1294
|
+
children: [
|
|
1295
|
+
message.messageType === "text" && (isOwnMessage ? (
|
|
1296
|
+
// User messages: plain text — no markdown parsing
|
|
1297
|
+
/* @__PURE__ */ jsx4("span", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: message.content })
|
|
1298
|
+
) : (
|
|
1299
|
+
// Bot / agent messages: full markdown rendering
|
|
1300
|
+
/* @__PURE__ */ jsx4(MarkdownMessage, { content: message.content, theme })
|
|
1301
|
+
)),
|
|
1302
|
+
message.messageType === "image" && /* @__PURE__ */ jsx4("div", { children: /* @__PURE__ */ jsx4(
|
|
1303
|
+
"img",
|
|
1304
|
+
{
|
|
1305
|
+
src: message.content,
|
|
1306
|
+
alt: "Attachment",
|
|
1307
|
+
className: "max-w-full h-auto rounded-lg",
|
|
1308
|
+
loading: "lazy"
|
|
1309
|
+
}
|
|
1310
|
+
) }),
|
|
1311
|
+
message.messageType === "file" && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
1312
|
+
/* @__PURE__ */ jsxs3(
|
|
1313
|
+
"svg",
|
|
581
1314
|
{
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
1315
|
+
width: "18",
|
|
1316
|
+
height: "18",
|
|
1317
|
+
viewBox: "0 0 24 24",
|
|
1318
|
+
fill: "none",
|
|
1319
|
+
stroke: "currentColor",
|
|
1320
|
+
strokeWidth: "1.75",
|
|
1321
|
+
strokeLinecap: "round",
|
|
1322
|
+
strokeLinejoin: "round",
|
|
1323
|
+
"aria-hidden": "true",
|
|
1324
|
+
children: [
|
|
1325
|
+
/* @__PURE__ */ jsx4("title", { children: "File" }),
|
|
1326
|
+
/* @__PURE__ */ jsx4("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" })
|
|
1327
|
+
]
|
|
587
1328
|
}
|
|
588
1329
|
),
|
|
589
|
-
|
|
1330
|
+
/* @__PURE__ */ jsx4(
|
|
590
1331
|
"a",
|
|
591
1332
|
{
|
|
592
|
-
|
|
593
|
-
href,
|
|
1333
|
+
href: message.content,
|
|
594
1334
|
target: "_blank",
|
|
595
1335
|
rel: "noopener noreferrer",
|
|
596
|
-
|
|
597
|
-
|
|
1336
|
+
style: {
|
|
1337
|
+
color: isOwnMessage ? "rgba(255,255,255,0.85)" : primaryColor,
|
|
1338
|
+
textDecoration: "underline",
|
|
1339
|
+
textUnderlineOffset: "2px"
|
|
1340
|
+
},
|
|
1341
|
+
children: message.metadata?.fileName || "Download file"
|
|
598
1342
|
}
|
|
599
1343
|
)
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1344
|
+
] })
|
|
1345
|
+
]
|
|
1346
|
+
}
|
|
1347
|
+
),
|
|
1348
|
+
showTimestamp && /* @__PURE__ */ jsxs3(
|
|
1349
|
+
"div",
|
|
1350
|
+
{
|
|
1351
|
+
className: `flex items-center gap-1.5 mt-1 px-1 ${isOwnMessage ? "flex-row-reverse" : "flex-row"}`,
|
|
1352
|
+
children: [
|
|
1353
|
+
/* @__PURE__ */ jsx4(
|
|
1354
|
+
"span",
|
|
1355
|
+
{
|
|
1356
|
+
style: {
|
|
1357
|
+
fontSize: "11px",
|
|
1358
|
+
letterSpacing: "0.019em",
|
|
1359
|
+
color: textMuted
|
|
1360
|
+
},
|
|
1361
|
+
children: formatDistanceToNow(new Date(message.createdAt), {
|
|
1362
|
+
addSuffix: true
|
|
1363
|
+
})
|
|
1364
|
+
}
|
|
1365
|
+
),
|
|
1366
|
+
isOwnMessage && message.status && /* @__PURE__ */ jsxs3("span", { style: { color: textMuted }, children: [
|
|
1367
|
+
message.status === "sent" && /* @__PURE__ */ jsxs3(
|
|
1368
|
+
"svg",
|
|
1369
|
+
{
|
|
1370
|
+
width: "14",
|
|
1371
|
+
height: "14",
|
|
1372
|
+
viewBox: "0 0 24 24",
|
|
1373
|
+
fill: "none",
|
|
1374
|
+
stroke: "currentColor",
|
|
1375
|
+
strokeWidth: "2.5",
|
|
1376
|
+
strokeLinecap: "round",
|
|
1377
|
+
strokeLinejoin: "round",
|
|
1378
|
+
"aria-hidden": "true",
|
|
1379
|
+
children: [
|
|
1380
|
+
/* @__PURE__ */ jsx4("title", { children: "Sent" }),
|
|
1381
|
+
/* @__PURE__ */ jsx4("polyline", { points: "20 6 9 17 4 12" })
|
|
1382
|
+
]
|
|
1383
|
+
}
|
|
1384
|
+
),
|
|
1385
|
+
message.status === "delivered" && /* @__PURE__ */ jsxs3(
|
|
1386
|
+
"svg",
|
|
1387
|
+
{
|
|
1388
|
+
width: "14",
|
|
1389
|
+
height: "14",
|
|
1390
|
+
viewBox: "0 0 24 24",
|
|
1391
|
+
fill: "none",
|
|
1392
|
+
stroke: "currentColor",
|
|
1393
|
+
strokeWidth: "2.5",
|
|
1394
|
+
strokeLinecap: "round",
|
|
1395
|
+
strokeLinejoin: "round",
|
|
1396
|
+
"aria-hidden": "true",
|
|
1397
|
+
children: [
|
|
1398
|
+
/* @__PURE__ */ jsx4("title", { children: "Delivered" }),
|
|
1399
|
+
/* @__PURE__ */ jsx4("polyline", { points: "18 6 7 17 2 12" }),
|
|
1400
|
+
/* @__PURE__ */ jsx4("polyline", { points: "22 6 11 17" })
|
|
1401
|
+
]
|
|
1402
|
+
}
|
|
1403
|
+
),
|
|
1404
|
+
message.status === "read" && /* @__PURE__ */ jsxs3(
|
|
1405
|
+
"svg",
|
|
1406
|
+
{
|
|
1407
|
+
width: "14",
|
|
1408
|
+
height: "14",
|
|
1409
|
+
viewBox: "0 0 24 24",
|
|
1410
|
+
fill: "none",
|
|
1411
|
+
stroke: primaryColor,
|
|
1412
|
+
strokeWidth: "2.5",
|
|
1413
|
+
strokeLinecap: "round",
|
|
1414
|
+
strokeLinejoin: "round",
|
|
1415
|
+
"aria-hidden": "true",
|
|
1416
|
+
children: [
|
|
1417
|
+
/* @__PURE__ */ jsx4("title", { children: "Read" }),
|
|
1418
|
+
/* @__PURE__ */ jsx4("polyline", { points: "18 6 7 17 2 12" }),
|
|
1419
|
+
/* @__PURE__ */ jsx4("polyline", { points: "22 6 11 17" })
|
|
1420
|
+
]
|
|
1421
|
+
}
|
|
1422
|
+
)
|
|
1423
|
+
] })
|
|
1424
|
+
]
|
|
1425
|
+
}
|
|
1426
|
+
)
|
|
1427
|
+
]
|
|
1428
|
+
}
|
|
1429
|
+
)
|
|
1430
|
+
]
|
|
1431
|
+
}
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// src/components/ThinkingIndicator.tsx
|
|
1436
|
+
import { useEffect as useEffect5, useRef as useRef3, useState as useState6 } from "react";
|
|
1437
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1438
|
+
var PHRASE_POOLS = {
|
|
1439
|
+
// ── Greetings & small talk ──
|
|
1440
|
+
greeting: [
|
|
1441
|
+
"Hey there!",
|
|
1442
|
+
"Hello!",
|
|
1443
|
+
"Hi! One moment",
|
|
1444
|
+
"Welcome!",
|
|
1445
|
+
"Nice to meet you"
|
|
1446
|
+
],
|
|
1447
|
+
farewell: [
|
|
1448
|
+
"Wrapping up",
|
|
1449
|
+
"One last thing",
|
|
1450
|
+
"Almost done"
|
|
1451
|
+
],
|
|
1452
|
+
// ── Sales & pricing ──
|
|
1453
|
+
pricing: [
|
|
1454
|
+
"Crunching numbers",
|
|
1455
|
+
"Checking our plans",
|
|
1456
|
+
"Let me look into that",
|
|
1457
|
+
"Pulling up pricing",
|
|
1458
|
+
"Running the numbers",
|
|
1459
|
+
"Checking options"
|
|
1460
|
+
],
|
|
1461
|
+
quote: [
|
|
1462
|
+
"Putting this together",
|
|
1463
|
+
"Working on your quote",
|
|
1464
|
+
"Gathering the details",
|
|
1465
|
+
"Tailoring this for you"
|
|
1466
|
+
],
|
|
1467
|
+
// ── Scheduling & booking ──
|
|
1468
|
+
booking: [
|
|
1469
|
+
"Checking the calendar",
|
|
1470
|
+
"Finding good times",
|
|
1471
|
+
"Pulling up availability",
|
|
1472
|
+
"Let me check slots"
|
|
1473
|
+
],
|
|
1474
|
+
// ── Technical questions ──
|
|
1475
|
+
technical: [
|
|
1476
|
+
"Diving into the docs",
|
|
1477
|
+
"Interesting question",
|
|
1478
|
+
"Let me look that up",
|
|
1479
|
+
"Checking the specs",
|
|
1480
|
+
"Hmm, good one",
|
|
1481
|
+
"Researching this"
|
|
1482
|
+
],
|
|
1483
|
+
code: [
|
|
1484
|
+
"Reading the code",
|
|
1485
|
+
"Checking the repo",
|
|
1486
|
+
"Let me trace that",
|
|
1487
|
+
"Debugging in my head"
|
|
1488
|
+
],
|
|
1489
|
+
// ── Support & issues ──
|
|
1490
|
+
support: [
|
|
1491
|
+
"On it!",
|
|
1492
|
+
"Let me help",
|
|
1493
|
+
"Looking into this",
|
|
1494
|
+
"Checking for you",
|
|
1495
|
+
"I got you",
|
|
1496
|
+
"Investigating"
|
|
1497
|
+
],
|
|
1498
|
+
frustrated: [
|
|
1499
|
+
"I hear you",
|
|
1500
|
+
"Let me fix this",
|
|
1501
|
+
"Sorry about that",
|
|
1502
|
+
"Working on it now",
|
|
1503
|
+
"Bear with me"
|
|
1504
|
+
],
|
|
1505
|
+
// ── Services & capabilities ──
|
|
1506
|
+
services: [
|
|
1507
|
+
"Great question",
|
|
1508
|
+
"Let me explain",
|
|
1509
|
+
"Pulling up details",
|
|
1510
|
+
"Good to know you ask"
|
|
1511
|
+
],
|
|
1512
|
+
portfolio: [
|
|
1513
|
+
"Checking our work",
|
|
1514
|
+
"Pulling up examples",
|
|
1515
|
+
"Let me show you",
|
|
1516
|
+
"Finding case studies"
|
|
1517
|
+
],
|
|
1518
|
+
// ── About the company ──
|
|
1519
|
+
about: [
|
|
1520
|
+
"Glad you asked!",
|
|
1521
|
+
"Let me tell you",
|
|
1522
|
+
"Good question",
|
|
1523
|
+
"Here we go"
|
|
1524
|
+
],
|
|
1525
|
+
// ── Comparison & decisions ──
|
|
1526
|
+
comparison: [
|
|
1527
|
+
"Weighing the options",
|
|
1528
|
+
"Let me compare",
|
|
1529
|
+
"Thinking through this",
|
|
1530
|
+
"Good point"
|
|
1531
|
+
],
|
|
1532
|
+
// ── Follow-up & continuation ──
|
|
1533
|
+
followUp: [
|
|
1534
|
+
"Let me dig deeper",
|
|
1535
|
+
"More details coming",
|
|
1536
|
+
"Building on that",
|
|
1537
|
+
"Expanding on this"
|
|
1538
|
+
],
|
|
1539
|
+
// ── General / fallback ──
|
|
1540
|
+
general: [
|
|
1541
|
+
"Hmm, let me think",
|
|
1542
|
+
"One sec",
|
|
1543
|
+
"On it",
|
|
1544
|
+
"Working on it",
|
|
1545
|
+
"Almost there",
|
|
1546
|
+
"Bear with me",
|
|
1547
|
+
"Let me check",
|
|
1548
|
+
"Good question",
|
|
1549
|
+
"Hang tight",
|
|
1550
|
+
"Looking into it",
|
|
1551
|
+
"Let me see",
|
|
1552
|
+
"Thinking",
|
|
1553
|
+
"Just a moment",
|
|
1554
|
+
"Figuring this out"
|
|
1555
|
+
]
|
|
1556
|
+
};
|
|
1557
|
+
var CONTEXT_RULES = [
|
|
1558
|
+
// Frustration / urgency (check first — trumps topic)
|
|
1559
|
+
{ key: "frustrated", pattern: /frustrat|angry|annoyed|terrible|worst|useless|waste|stupid|wtf|seriously|ridiculous|unacceptable|disappointing/ },
|
|
1560
|
+
// Greetings (start of message)
|
|
1561
|
+
{ key: "greeting", pattern: /^(hi\b|hey\b|hello\b|good morning|good afternoon|good evening|g'day|howdy|yo\b|sup\b|what's up|greetings)/ },
|
|
1562
|
+
{ key: "farewell", pattern: /\b(thanks|thank you|bye|goodbye|cheers|that's all|that's it|no more|all good|perfect thanks)\b/ },
|
|
1563
|
+
// Booking & scheduling
|
|
1564
|
+
{ key: "booking", pattern: /\b(book|schedule|meeting|appointment|calendar|available time|free slot|set up a call|arrange|consultation time|when can)\b/ },
|
|
1565
|
+
// Pricing & quotes
|
|
1566
|
+
{ key: "quote", pattern: /\b(quote|proposal|estimate|project cost|custom price|tailor|bespoke)\b/ },
|
|
1567
|
+
{ key: "pricing", pattern: /\b(price|cost|how much|pricing|rate|fee|budget|afford|charge|per hour|hourly|package|plan|tier|subscription)\b/ },
|
|
1568
|
+
// Technical / code
|
|
1569
|
+
{ key: "code", pattern: /\b(code|github|repo|commit|deploy|ci\/cd|docker|aws|lambda|api endpoint|sdk|npm|yarn|pnpm|typescript|react|next\.?js)\b/ },
|
|
1570
|
+
{ key: "technical", pattern: /\b(api|integrate|integration|stack|server|database|backend|frontend|infrastructure|architecture|performance|scalab|migration|devops|cloud)\b/ },
|
|
1571
|
+
// Support & bugs
|
|
1572
|
+
{ key: "support", pattern: /\b(bug|error|issue|broken|not working|help|support|problem|fix|crash|down|fail|stuck|trouble|can't|cannot|doesn't work|won't)\b/ },
|
|
1573
|
+
// Services & portfolio
|
|
1574
|
+
{ key: "portfolio", pattern: /\b(portfolio|case stud|example|previous work|client|project you|showcase|demo|sample)\b/ },
|
|
1575
|
+
{ key: "services", pattern: /\b(service|what do you|what can you|offer|do you do|capabilit|specializ|expertise|solution|consult|develop|design|build|create)\b/ },
|
|
1576
|
+
// About company
|
|
1577
|
+
{ key: "about", pattern: /\b(who are|about you|your team|your company|where are you|location|office|founded|history|values|mission)\b/ },
|
|
1578
|
+
// Comparison / decision-making
|
|
1579
|
+
{ key: "comparison", pattern: /\b(compare|vs|versus|difference|better|which one|alternative|competitor|pros and cons|trade.?off|should i|recommend)\b/ },
|
|
1580
|
+
// Follow-up patterns (vague follow-ups to prior answers)
|
|
1581
|
+
{ key: "followUp", pattern: /\b(more detail|tell me more|elaborate|explain further|what about|and also|another question|one more|can you also|what else|go on)\b/ }
|
|
1582
|
+
];
|
|
1583
|
+
function detectContext(message) {
|
|
1584
|
+
if (!message) return "general";
|
|
1585
|
+
const lower = message.toLowerCase().trim();
|
|
1586
|
+
for (const rule of CONTEXT_RULES) {
|
|
1587
|
+
if (rule.pattern.test(lower)) return rule.key;
|
|
1588
|
+
}
|
|
1589
|
+
if (lower.split(/\s+/).length <= 3) return "general";
|
|
1590
|
+
if (lower.endsWith("?")) return "general";
|
|
1591
|
+
return "general";
|
|
1592
|
+
}
|
|
1593
|
+
function getShuffledPhrases(context) {
|
|
1594
|
+
const pool = PHRASE_POOLS[context] || PHRASE_POOLS.general;
|
|
1595
|
+
const contextPhrases = [...pool];
|
|
1596
|
+
const extras = PHRASE_POOLS.general.filter((p) => !contextPhrases.includes(p)).sort(() => Math.random() - 0.5).slice(0, 3);
|
|
1597
|
+
const combined = [...contextPhrases, ...extras];
|
|
1598
|
+
for (let i = combined.length - 1; i > 0; i--) {
|
|
1599
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1600
|
+
[combined[i], combined[j]] = [combined[j], combined[i]];
|
|
1601
|
+
}
|
|
1602
|
+
return combined;
|
|
1603
|
+
}
|
|
1604
|
+
function computeIsLightTheme2(bg) {
|
|
1605
|
+
if (!bg?.startsWith("#")) return false;
|
|
1606
|
+
const hex = bg.replace("#", "");
|
|
1607
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1608
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1609
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1610
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1611
|
+
}
|
|
1612
|
+
function ThinkingIndicator({
|
|
1613
|
+
lastUserMessage,
|
|
1614
|
+
theme,
|
|
1615
|
+
showAvatar = true
|
|
1616
|
+
}) {
|
|
1617
|
+
const isLightTheme = computeIsLightTheme2(theme?.background);
|
|
1618
|
+
const primaryColor = theme?.primary || "#337eff";
|
|
1619
|
+
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
|
|
1620
|
+
const [phraseIndex, setPhraseIndex] = useState6(0);
|
|
1621
|
+
const [displayText, setDisplayText] = useState6("");
|
|
1622
|
+
const [isDeleting, setIsDeleting] = useState6(false);
|
|
1623
|
+
const phrasesRef = useRef3(getShuffledPhrases(detectContext(lastUserMessage)));
|
|
1624
|
+
useEffect5(() => {
|
|
1625
|
+
phrasesRef.current = getShuffledPhrases(detectContext(lastUserMessage));
|
|
1626
|
+
setPhraseIndex(0);
|
|
1627
|
+
setDisplayText("");
|
|
1628
|
+
setIsDeleting(false);
|
|
1629
|
+
}, [lastUserMessage]);
|
|
1630
|
+
useEffect5(() => {
|
|
1631
|
+
const phrases = phrasesRef.current;
|
|
1632
|
+
const phrase = phrases[phraseIndex % phrases.length];
|
|
1633
|
+
let timeout;
|
|
1634
|
+
if (!isDeleting) {
|
|
1635
|
+
if (displayText.length < phrase.length) {
|
|
1636
|
+
timeout = setTimeout(
|
|
1637
|
+
() => setDisplayText(phrase.slice(0, displayText.length + 1)),
|
|
1638
|
+
30 + Math.random() * 30
|
|
1639
|
+
);
|
|
1640
|
+
} else {
|
|
1641
|
+
timeout = setTimeout(() => setIsDeleting(true), 1800);
|
|
646
1642
|
}
|
|
647
|
-
|
|
648
|
-
|
|
1643
|
+
} else {
|
|
1644
|
+
if (displayText.length > 0) {
|
|
1645
|
+
timeout = setTimeout(
|
|
1646
|
+
() => setDisplayText(displayText.slice(0, -1)),
|
|
1647
|
+
18
|
|
1648
|
+
);
|
|
1649
|
+
} else {
|
|
1650
|
+
setIsDeleting(false);
|
|
1651
|
+
setPhraseIndex((prev) => (prev + 1) % phrasesRef.current.length);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return () => clearTimeout(timeout);
|
|
1655
|
+
}, [displayText, isDeleting, phraseIndex]);
|
|
1656
|
+
return /* @__PURE__ */ jsxs4(
|
|
1657
|
+
"div",
|
|
1658
|
+
{
|
|
1659
|
+
className: "flex gap-2.5 mb-3",
|
|
1660
|
+
role: "status",
|
|
1661
|
+
"aria-live": "polite",
|
|
1662
|
+
"aria-label": "Xcelsior is thinking",
|
|
1663
|
+
children: [
|
|
1664
|
+
showAvatar && /* @__PURE__ */ jsx5("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ jsx5(XcelsiorAvatar, { size: 28 }) }),
|
|
1665
|
+
/* @__PURE__ */ jsx5("div", { className: "flex flex-col items-start", children: /* @__PURE__ */ jsx5(
|
|
1666
|
+
"div",
|
|
1667
|
+
{
|
|
1668
|
+
style: {
|
|
1669
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.04)",
|
|
1670
|
+
borderRadius: "18px 18px 18px 4px",
|
|
1671
|
+
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)",
|
|
1672
|
+
padding: "12px 16px",
|
|
1673
|
+
minWidth: 160
|
|
1674
|
+
},
|
|
1675
|
+
children: /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
1676
|
+
/* @__PURE__ */ jsx5("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx5(
|
|
1677
|
+
"span",
|
|
1678
|
+
{
|
|
1679
|
+
style: {
|
|
1680
|
+
width: 5,
|
|
1681
|
+
height: 5,
|
|
1682
|
+
borderRadius: "50%",
|
|
1683
|
+
backgroundColor: primaryColor,
|
|
1684
|
+
display: "inline-block",
|
|
1685
|
+
animation: `thinkingPulse 1.4s ease-in-out ${i * 0.2}s infinite`
|
|
1686
|
+
}
|
|
1687
|
+
},
|
|
1688
|
+
i
|
|
1689
|
+
)) }),
|
|
1690
|
+
/* @__PURE__ */ jsxs4(
|
|
1691
|
+
"span",
|
|
1692
|
+
{
|
|
1693
|
+
style: {
|
|
1694
|
+
fontSize: 12,
|
|
1695
|
+
color: textMuted,
|
|
1696
|
+
letterSpacing: "0.015em",
|
|
1697
|
+
fontStyle: "italic"
|
|
1698
|
+
},
|
|
1699
|
+
children: [
|
|
1700
|
+
displayText,
|
|
1701
|
+
/* @__PURE__ */ jsx5(
|
|
1702
|
+
"span",
|
|
1703
|
+
{
|
|
1704
|
+
style: {
|
|
1705
|
+
display: "inline-block",
|
|
1706
|
+
width: 1,
|
|
1707
|
+
height: 13,
|
|
1708
|
+
backgroundColor: textMuted,
|
|
1709
|
+
marginLeft: 1,
|
|
1710
|
+
animation: "cursorBlink 0.8s step-end infinite",
|
|
1711
|
+
verticalAlign: "text-bottom"
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
)
|
|
1715
|
+
]
|
|
1716
|
+
}
|
|
1717
|
+
)
|
|
1718
|
+
] })
|
|
1719
|
+
}
|
|
1720
|
+
) })
|
|
1721
|
+
]
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
649
1724
|
}
|
|
650
1725
|
|
|
651
1726
|
// src/components/MessageList.tsx
|
|
652
|
-
import { jsx as
|
|
1727
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
653
1728
|
function MessageList({
|
|
654
1729
|
messages,
|
|
655
1730
|
currentUser,
|
|
@@ -659,16 +1734,31 @@ function MessageList({
|
|
|
659
1734
|
autoScroll = true,
|
|
660
1735
|
onLoadMore,
|
|
661
1736
|
hasMore = false,
|
|
662
|
-
isLoadingMore = false
|
|
1737
|
+
isLoadingMore = false,
|
|
1738
|
+
theme,
|
|
1739
|
+
onQuickAction,
|
|
1740
|
+
isBotThinking = false
|
|
663
1741
|
}) {
|
|
664
|
-
const messagesEndRef =
|
|
665
|
-
const containerRef =
|
|
666
|
-
const prevLengthRef =
|
|
667
|
-
const loadMoreTriggerRef =
|
|
668
|
-
const prevScrollHeightRef =
|
|
669
|
-
const hasInitialScrolledRef =
|
|
670
|
-
const isUserScrollingRef =
|
|
671
|
-
|
|
1742
|
+
const messagesEndRef = useRef4(null);
|
|
1743
|
+
const containerRef = useRef4(null);
|
|
1744
|
+
const prevLengthRef = useRef4(messages.length);
|
|
1745
|
+
const loadMoreTriggerRef = useRef4(null);
|
|
1746
|
+
const prevScrollHeightRef = useRef4(0);
|
|
1747
|
+
const hasInitialScrolledRef = useRef4(false);
|
|
1748
|
+
const isUserScrollingRef = useRef4(false);
|
|
1749
|
+
const bgColor = theme?.background || "#00001a";
|
|
1750
|
+
const isLightTheme = (() => {
|
|
1751
|
+
if (!bgColor.startsWith("#")) return false;
|
|
1752
|
+
const hex = bgColor.replace("#", "");
|
|
1753
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1754
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1755
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1756
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1757
|
+
})();
|
|
1758
|
+
const primaryColor = theme?.primary || "#337eff";
|
|
1759
|
+
const textColor = theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
1760
|
+
const textMuted = theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
|
|
1761
|
+
useEffect6(() => {
|
|
672
1762
|
if (autoScroll && messagesEndRef.current) {
|
|
673
1763
|
if (messages.length > prevLengthRef.current && !isLoadingMore) {
|
|
674
1764
|
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
|
|
@@ -676,7 +1766,7 @@ function MessageList({
|
|
|
676
1766
|
prevLengthRef.current = messages.length;
|
|
677
1767
|
}
|
|
678
1768
|
}, [messages.length, autoScroll, isLoadingMore]);
|
|
679
|
-
|
|
1769
|
+
useEffect6(() => {
|
|
680
1770
|
if (messages.length > 0 && messagesEndRef.current && !isLoading && !hasInitialScrolledRef.current) {
|
|
681
1771
|
setTimeout(() => {
|
|
682
1772
|
messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
|
|
@@ -690,7 +1780,7 @@ function MessageList({
|
|
|
690
1780
|
hasInitialScrolledRef.current = true;
|
|
691
1781
|
}
|
|
692
1782
|
}, [isLoading, messages.length]);
|
|
693
|
-
|
|
1783
|
+
useEffect6(() => {
|
|
694
1784
|
if (isLoadingMore) {
|
|
695
1785
|
prevScrollHeightRef.current = containerRef.current?.scrollHeight || 0;
|
|
696
1786
|
} else if (prevScrollHeightRef.current > 0 && containerRef.current) {
|
|
@@ -700,7 +1790,7 @@ function MessageList({
|
|
|
700
1790
|
prevScrollHeightRef.current = 0;
|
|
701
1791
|
}
|
|
702
1792
|
}, [isLoadingMore]);
|
|
703
|
-
const handleScroll =
|
|
1793
|
+
const handleScroll = useCallback4(() => {
|
|
704
1794
|
if (!containerRef.current || !onLoadMore || !hasMore || isLoadingMore) return;
|
|
705
1795
|
if (!isUserScrollingRef.current) return;
|
|
706
1796
|
const { scrollTop } = containerRef.current;
|
|
@@ -708,79 +1798,182 @@ function MessageList({
|
|
|
708
1798
|
onLoadMore();
|
|
709
1799
|
}
|
|
710
1800
|
}, [onLoadMore, hasMore, isLoadingMore]);
|
|
711
|
-
|
|
1801
|
+
useEffect6(() => {
|
|
712
1802
|
const container = containerRef.current;
|
|
713
1803
|
if (!container) return;
|
|
714
1804
|
container.addEventListener("scroll", handleScroll);
|
|
715
1805
|
return () => container.removeEventListener("scroll", handleScroll);
|
|
716
1806
|
}, [handleScroll]);
|
|
717
1807
|
if (isLoading) {
|
|
718
|
-
return /* @__PURE__ */
|
|
1808
|
+
return /* @__PURE__ */ jsx6("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx6(Spinner, { size: "lg" }) });
|
|
719
1809
|
}
|
|
720
1810
|
if (messages.length === 0) {
|
|
721
|
-
return /* @__PURE__ */
|
|
722
|
-
/* @__PURE__ */
|
|
723
|
-
|
|
724
|
-
|
|
1811
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col items-center justify-center h-full text-center", style: { padding: "40px 32px" }, children: [
|
|
1812
|
+
/* @__PURE__ */ jsx6(
|
|
1813
|
+
"h3",
|
|
1814
|
+
{
|
|
1815
|
+
className: "font-semibold mb-2",
|
|
1816
|
+
style: {
|
|
1817
|
+
color: textColor,
|
|
1818
|
+
fontSize: "17px",
|
|
1819
|
+
letterSpacing: "-0.01em"
|
|
1820
|
+
},
|
|
1821
|
+
children: "How can we help?"
|
|
1822
|
+
}
|
|
1823
|
+
),
|
|
1824
|
+
/* @__PURE__ */ jsx6(
|
|
1825
|
+
"p",
|
|
1826
|
+
{
|
|
1827
|
+
className: "max-w-[240px]",
|
|
1828
|
+
style: {
|
|
1829
|
+
color: textMuted,
|
|
1830
|
+
fontSize: "13px",
|
|
1831
|
+
lineHeight: "1.5",
|
|
1832
|
+
letterSpacing: "0.015em",
|
|
1833
|
+
marginBottom: 20
|
|
1834
|
+
},
|
|
1835
|
+
children: "Ask us anything. We are here to help you get the most out of Xcelsior."
|
|
1836
|
+
}
|
|
1837
|
+
),
|
|
1838
|
+
/* @__PURE__ */ jsx6("div", { className: "flex flex-wrap justify-center gap-2", children: [
|
|
1839
|
+
{ label: "Our services", message: "What services does Xcelsior offer?" },
|
|
1840
|
+
{ label: "Get a quote", message: "I would like to get a quote for a project" },
|
|
1841
|
+
{ label: "Support", message: "I need help with something" }
|
|
1842
|
+
].map((action) => /* @__PURE__ */ jsx6(
|
|
1843
|
+
"button",
|
|
1844
|
+
{
|
|
1845
|
+
type: "button",
|
|
1846
|
+
onClick: () => onQuickAction?.(action.message),
|
|
1847
|
+
style: {
|
|
1848
|
+
padding: "6px 14px",
|
|
1849
|
+
borderRadius: "999px",
|
|
1850
|
+
cursor: "pointer",
|
|
1851
|
+
transition: "all 150ms ease",
|
|
1852
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.04)",
|
|
1853
|
+
border: isLightTheme ? "1px solid rgba(0,0,0,0.1)" : "1px solid rgba(255,255,255,0.1)",
|
|
1854
|
+
color: isLightTheme ? "rgba(0,0,0,0.6)" : "rgba(247,247,248,0.6)",
|
|
1855
|
+
fontSize: "12px",
|
|
1856
|
+
letterSpacing: "0.015em"
|
|
1857
|
+
},
|
|
1858
|
+
onMouseEnter: (e) => {
|
|
1859
|
+
e.currentTarget.style.backgroundColor = isLightTheme ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.08)";
|
|
1860
|
+
e.currentTarget.style.color = isLightTheme ? "rgba(0,0,0,0.8)" : "rgba(247,247,248,0.8)";
|
|
1861
|
+
e.currentTarget.style.borderColor = primaryColor;
|
|
1862
|
+
},
|
|
1863
|
+
onMouseLeave: (e) => {
|
|
1864
|
+
e.currentTarget.style.backgroundColor = isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.04)";
|
|
1865
|
+
e.currentTarget.style.color = isLightTheme ? "rgba(0,0,0,0.6)" : "rgba(247,247,248,0.6)";
|
|
1866
|
+
e.currentTarget.style.borderColor = isLightTheme ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)";
|
|
1867
|
+
},
|
|
1868
|
+
children: action.label
|
|
1869
|
+
},
|
|
1870
|
+
action.label
|
|
1871
|
+
)) })
|
|
725
1872
|
] });
|
|
726
1873
|
}
|
|
727
|
-
return /* @__PURE__ */
|
|
1874
|
+
return /* @__PURE__ */ jsxs5(
|
|
728
1875
|
"div",
|
|
729
1876
|
{
|
|
730
1877
|
ref: containerRef,
|
|
731
|
-
className: "flex-1 overflow-y-auto
|
|
1878
|
+
className: "flex-1 overflow-y-auto px-4 py-3",
|
|
732
1879
|
style: { scrollBehavior: "smooth" },
|
|
733
1880
|
children: [
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1881
|
+
/* @__PURE__ */ jsx6("style", { children: `
|
|
1882
|
+
@keyframes thinkingPulse {
|
|
1883
|
+
0%, 60%, 100% { opacity: 0.3; transform: scale(0.8); }
|
|
1884
|
+
30% { opacity: 1; transform: scale(1); }
|
|
1885
|
+
}
|
|
1886
|
+
@keyframes cursorBlink {
|
|
1887
|
+
0%, 100% { opacity: 1; }
|
|
1888
|
+
50% { opacity: 0; }
|
|
1889
|
+
}
|
|
1890
|
+
` }),
|
|
1891
|
+
isLoadingMore && /* @__PURE__ */ jsx6("div", { className: "flex justify-center py-3", children: /* @__PURE__ */ jsx6(Spinner, { size: "sm" }) }),
|
|
1892
|
+
/* @__PURE__ */ jsx6("div", { ref: loadMoreTriggerRef }),
|
|
1893
|
+
messages.map((message) => /* @__PURE__ */ jsx6(
|
|
737
1894
|
MessageItem,
|
|
738
1895
|
{
|
|
739
1896
|
message,
|
|
740
1897
|
currentUser,
|
|
741
1898
|
showAvatar: true,
|
|
742
|
-
showTimestamp: true
|
|
1899
|
+
showTimestamp: true,
|
|
1900
|
+
theme
|
|
743
1901
|
},
|
|
744
1902
|
message.id
|
|
745
1903
|
)),
|
|
746
|
-
isTyping && /* @__PURE__ */
|
|
747
|
-
/* @__PURE__ */
|
|
748
|
-
/* @__PURE__ */
|
|
749
|
-
/* @__PURE__ */
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
"
|
|
753
|
-
{
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1904
|
+
isTyping && /* @__PURE__ */ jsxs5("div", { className: "flex gap-2.5 mb-3", children: [
|
|
1905
|
+
/* @__PURE__ */ jsx6("div", { className: "flex-shrink-0 mt-auto mb-5", children: /* @__PURE__ */ jsx6(XcelsiorAvatar, { size: 28 }) }),
|
|
1906
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-col items-start", children: [
|
|
1907
|
+
/* @__PURE__ */ jsx6(
|
|
1908
|
+
"div",
|
|
1909
|
+
{
|
|
1910
|
+
className: "px-4 py-3",
|
|
1911
|
+
style: {
|
|
1912
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.04)",
|
|
1913
|
+
borderRadius: "18px 18px 18px 4px",
|
|
1914
|
+
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)"
|
|
1915
|
+
},
|
|
1916
|
+
children: /* @__PURE__ */ jsx6("div", { className: "flex gap-1.5 items-center", style: { height: 16 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx6(
|
|
1917
|
+
"span",
|
|
1918
|
+
{
|
|
1919
|
+
className: "rounded-full animate-bounce",
|
|
1920
|
+
style: {
|
|
1921
|
+
width: 5,
|
|
1922
|
+
height: 5,
|
|
1923
|
+
backgroundColor: `${primaryColor}80`,
|
|
1924
|
+
animationDelay: `${i * 0.15}s`,
|
|
1925
|
+
animationDuration: "0.8s"
|
|
1926
|
+
}
|
|
1927
|
+
},
|
|
1928
|
+
i
|
|
1929
|
+
)) })
|
|
1930
|
+
}
|
|
1931
|
+
),
|
|
1932
|
+
typingUser && /* @__PURE__ */ jsxs5(
|
|
1933
|
+
"span",
|
|
1934
|
+
{
|
|
1935
|
+
className: "mt-1 px-1",
|
|
1936
|
+
style: {
|
|
1937
|
+
color: textMuted,
|
|
1938
|
+
fontSize: "11px",
|
|
1939
|
+
letterSpacing: "0.019em"
|
|
1940
|
+
},
|
|
1941
|
+
children: [
|
|
1942
|
+
typingUser,
|
|
1943
|
+
" is typing..."
|
|
1944
|
+
]
|
|
1945
|
+
}
|
|
1946
|
+
)
|
|
770
1947
|
] })
|
|
771
1948
|
] }),
|
|
772
|
-
/* @__PURE__ */
|
|
1949
|
+
isBotThinking && !isTyping && /* @__PURE__ */ jsx6(
|
|
1950
|
+
ThinkingIndicator,
|
|
1951
|
+
{
|
|
1952
|
+
theme,
|
|
1953
|
+
lastUserMessage: messages.filter((m) => m.senderType === "customer").pop()?.content
|
|
1954
|
+
}
|
|
1955
|
+
),
|
|
1956
|
+
/* @__PURE__ */ jsx6("div", { ref: messagesEndRef })
|
|
773
1957
|
]
|
|
774
1958
|
}
|
|
775
1959
|
);
|
|
776
1960
|
}
|
|
777
1961
|
|
|
778
1962
|
// src/components/ChatInput.tsx
|
|
779
|
-
import { useEffect as
|
|
1963
|
+
import { useEffect as useEffect7, useRef as useRef5, useState as useState7 } from "react";
|
|
780
1964
|
import { createPortal } from "react-dom";
|
|
781
|
-
import { Button, TextArea } from "@xcelsior/design-system";
|
|
782
1965
|
import Picker from "@emoji-mart/react";
|
|
783
|
-
import { Fragment, jsx as
|
|
1966
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1967
|
+
function isLightColor(color) {
|
|
1968
|
+
let r = 0, g = 0, b = 0;
|
|
1969
|
+
if (color.startsWith("#")) {
|
|
1970
|
+
const hex = color.replace("#", "");
|
|
1971
|
+
r = parseInt(hex.substring(0, 2), 16);
|
|
1972
|
+
g = parseInt(hex.substring(2, 4), 16);
|
|
1973
|
+
b = parseInt(hex.substring(4, 6), 16);
|
|
1974
|
+
}
|
|
1975
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
1976
|
+
}
|
|
784
1977
|
function ChatInput({
|
|
785
1978
|
onSend,
|
|
786
1979
|
onTyping,
|
|
@@ -788,20 +1981,26 @@ function ChatInput({
|
|
|
788
1981
|
fileUpload,
|
|
789
1982
|
disabled = false
|
|
790
1983
|
}) {
|
|
791
|
-
const [message, setMessage] =
|
|
792
|
-
const [showEmojiPicker, setShowEmojiPicker] =
|
|
793
|
-
const [emojiData, setEmojiData] =
|
|
794
|
-
const [emojiPickerPosition, setEmojiPickerPosition] =
|
|
795
|
-
const
|
|
796
|
-
const
|
|
797
|
-
const
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
const
|
|
1984
|
+
const [message, setMessage] = useState7("");
|
|
1985
|
+
const [showEmojiPicker, setShowEmojiPicker] = useState7(false);
|
|
1986
|
+
const [emojiData, setEmojiData] = useState7();
|
|
1987
|
+
const [emojiPickerPosition, setEmojiPickerPosition] = useState7(null);
|
|
1988
|
+
const [isFocused, setIsFocused] = useState7(false);
|
|
1989
|
+
const textAreaRef = useRef5(null);
|
|
1990
|
+
const emojiPickerRef = useRef5(null);
|
|
1991
|
+
const emojiButtonRef = useRef5(null);
|
|
1992
|
+
const fileInputRef = useRef5(null);
|
|
1993
|
+
const typingTimeoutRef = useRef5(null);
|
|
1994
|
+
const startTypingTimeoutRef = useRef5(null);
|
|
1995
|
+
const isTypingRef = useRef5(false);
|
|
802
1996
|
const enableEmoji = config.enableEmoji ?? true;
|
|
803
1997
|
const enableFileUpload = config.enableFileUpload ?? true;
|
|
804
|
-
|
|
1998
|
+
const bgColor = config.theme?.background || "#00001a";
|
|
1999
|
+
const isLightTheme = isLightColor(bgColor);
|
|
2000
|
+
const textColor = config.theme?.text || (isLightTheme ? "#1a1a2e" : "#f7f7f8");
|
|
2001
|
+
const textMuted = config.theme?.textMuted || (isLightTheme ? "rgba(0,0,0,0.4)" : "rgba(247,247,248,0.45)");
|
|
2002
|
+
const primaryColor = config.theme?.primary || "#337eff";
|
|
2003
|
+
useEffect7(() => {
|
|
805
2004
|
if (!enableEmoji) return;
|
|
806
2005
|
(async () => {
|
|
807
2006
|
try {
|
|
@@ -812,7 +2011,7 @@ function ChatInput({
|
|
|
812
2011
|
}
|
|
813
2012
|
})();
|
|
814
2013
|
}, [enableEmoji]);
|
|
815
|
-
|
|
2014
|
+
useEffect7(() => {
|
|
816
2015
|
const handleClickOutside = (event) => {
|
|
817
2016
|
if (emojiPickerRef.current && !emojiPickerRef.current.contains(event.target)) {
|
|
818
2017
|
setShowEmojiPicker(false);
|
|
@@ -825,7 +2024,7 @@ function ChatInput({
|
|
|
825
2024
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
826
2025
|
};
|
|
827
2026
|
}, [showEmojiPicker]);
|
|
828
|
-
|
|
2027
|
+
useEffect7(() => {
|
|
829
2028
|
return () => {
|
|
830
2029
|
if (typingTimeoutRef.current) {
|
|
831
2030
|
clearTimeout(typingTimeoutRef.current);
|
|
@@ -835,7 +2034,7 @@ function ChatInput({
|
|
|
835
2034
|
}
|
|
836
2035
|
};
|
|
837
2036
|
}, []);
|
|
838
|
-
|
|
2037
|
+
useEffect7(() => {
|
|
839
2038
|
if (!showEmojiPicker) return;
|
|
840
2039
|
const updatePosition = () => {
|
|
841
2040
|
if (emojiButtonRef.current) {
|
|
@@ -940,167 +2139,347 @@ ${uploadedFile.markdown}
|
|
|
940
2139
|
}
|
|
941
2140
|
}
|
|
942
2141
|
};
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
disabled
|
|
956
|
-
}
|
|
957
|
-
),
|
|
958
|
-
/* @__PURE__ */ jsxs4("div", { className: "absolute right-12 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [
|
|
959
|
-
enableEmoji && /* @__PURE__ */ jsx4("div", { className: "relative", children: /* @__PURE__ */ jsx4(
|
|
960
|
-
"button",
|
|
2142
|
+
const canSend = message.trim().length > 0 && !disabled;
|
|
2143
|
+
const placeholderColor = isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.3)";
|
|
2144
|
+
return /* @__PURE__ */ jsxs6(
|
|
2145
|
+
"div",
|
|
2146
|
+
{
|
|
2147
|
+
className: "px-4 py-3",
|
|
2148
|
+
style: {
|
|
2149
|
+
borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)"
|
|
2150
|
+
},
|
|
2151
|
+
children: [
|
|
2152
|
+
/* @__PURE__ */ jsxs6(
|
|
2153
|
+
"div",
|
|
961
2154
|
{
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
left: rect.left - 290
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
setShowEmojiPicker((v) => !v);
|
|
2155
|
+
className: "relative flex items-center rounded-full transition-all duration-200",
|
|
2156
|
+
style: {
|
|
2157
|
+
backgroundColor: isLightTheme ? "rgba(112,115,124,0.08)" : "rgba(112,115,124,0.12)",
|
|
2158
|
+
outline: isFocused ? `2px solid ${primaryColor}` : "none",
|
|
2159
|
+
outlineOffset: "-1px",
|
|
2160
|
+
border: isLightTheme ? "1px solid rgba(0,0,0,0.1)" : "1px solid rgba(255,255,255,0.08)",
|
|
2161
|
+
backdropFilter: "blur(32px)"
|
|
973
2162
|
},
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
2163
|
+
children: [
|
|
2164
|
+
/* @__PURE__ */ jsx7("style", { children: `.xchat-input::placeholder { color: ${placeholderColor}; }` }),
|
|
2165
|
+
/* @__PURE__ */ jsx7(
|
|
2166
|
+
"textarea",
|
|
2167
|
+
{
|
|
2168
|
+
ref: textAreaRef,
|
|
2169
|
+
value: message,
|
|
2170
|
+
onChange: (e) => handleTyping(e.target.value),
|
|
2171
|
+
onKeyDown: handleKeyDown,
|
|
2172
|
+
onFocus: () => setIsFocused(true),
|
|
2173
|
+
onBlur: () => setIsFocused(false),
|
|
2174
|
+
placeholder: "Type a message...",
|
|
2175
|
+
rows: 1,
|
|
2176
|
+
className: "xchat-input flex-1 resize-none bg-transparent",
|
|
2177
|
+
style: {
|
|
2178
|
+
color: textColor,
|
|
2179
|
+
border: "none",
|
|
2180
|
+
outline: "none",
|
|
2181
|
+
boxShadow: "none",
|
|
2182
|
+
WebkitAppearance: "none",
|
|
2183
|
+
padding: "10px 0 10px 20px",
|
|
2184
|
+
minHeight: "42px",
|
|
2185
|
+
maxHeight: "120px",
|
|
2186
|
+
caretColor: primaryColor,
|
|
2187
|
+
fontSize: "14px",
|
|
2188
|
+
lineHeight: "20px",
|
|
2189
|
+
letterSpacing: "0.006em"
|
|
2190
|
+
},
|
|
2191
|
+
disabled
|
|
2192
|
+
}
|
|
2193
|
+
),
|
|
2194
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1 px-2 shrink-0", children: [
|
|
2195
|
+
enableEmoji && /* @__PURE__ */ jsx7(
|
|
2196
|
+
"button",
|
|
2197
|
+
{
|
|
2198
|
+
ref: emojiButtonRef,
|
|
2199
|
+
type: "button",
|
|
2200
|
+
onClick: () => {
|
|
2201
|
+
if (!showEmojiPicker && emojiButtonRef.current) {
|
|
2202
|
+
const rect = emojiButtonRef.current.getBoundingClientRect();
|
|
2203
|
+
setEmojiPickerPosition({
|
|
2204
|
+
top: rect.top - 450,
|
|
2205
|
+
left: rect.left - 290
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
setShowEmojiPicker((v) => !v);
|
|
2209
|
+
},
|
|
2210
|
+
className: "p-1.5 rounded-lg transition-all duration-150",
|
|
2211
|
+
style: {
|
|
2212
|
+
color: textMuted,
|
|
2213
|
+
backgroundColor: "transparent"
|
|
2214
|
+
},
|
|
2215
|
+
onMouseEnter: (e) => {
|
|
2216
|
+
e.currentTarget.style.backgroundColor = isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)";
|
|
2217
|
+
e.currentTarget.style.color = textColor;
|
|
2218
|
+
},
|
|
2219
|
+
onMouseLeave: (e) => {
|
|
2220
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
2221
|
+
e.currentTarget.style.color = textMuted;
|
|
2222
|
+
},
|
|
2223
|
+
disabled,
|
|
2224
|
+
"aria-label": "Add emoji",
|
|
2225
|
+
children: /* @__PURE__ */ jsxs6(
|
|
2226
|
+
"svg",
|
|
2227
|
+
{
|
|
2228
|
+
width: "18",
|
|
2229
|
+
height: "18",
|
|
2230
|
+
viewBox: "0 0 24 24",
|
|
2231
|
+
fill: "none",
|
|
2232
|
+
stroke: "currentColor",
|
|
2233
|
+
strokeWidth: "1.75",
|
|
2234
|
+
strokeLinecap: "round",
|
|
2235
|
+
strokeLinejoin: "round",
|
|
2236
|
+
"aria-hidden": "true",
|
|
2237
|
+
children: [
|
|
2238
|
+
/* @__PURE__ */ jsx7("title", { children: "Emoji" }),
|
|
2239
|
+
/* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "10" }),
|
|
2240
|
+
/* @__PURE__ */ jsx7("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
2241
|
+
/* @__PURE__ */ jsx7("line", { x1: "9", y1: "9", x2: "9.01", y2: "9" }),
|
|
2242
|
+
/* @__PURE__ */ jsx7("line", { x1: "15", y1: "9", x2: "15.01", y2: "9" })
|
|
2243
|
+
]
|
|
2244
|
+
}
|
|
2245
|
+
)
|
|
2246
|
+
}
|
|
2247
|
+
),
|
|
2248
|
+
enableFileUpload && fileUpload.canUpload && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
2249
|
+
/* @__PURE__ */ jsx7(
|
|
2250
|
+
"button",
|
|
2251
|
+
{
|
|
2252
|
+
type: "button",
|
|
2253
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2254
|
+
className: "p-1.5 rounded-lg transition-all duration-150",
|
|
2255
|
+
style: {
|
|
2256
|
+
color: textMuted,
|
|
2257
|
+
backgroundColor: "transparent"
|
|
2258
|
+
},
|
|
2259
|
+
onMouseEnter: (e) => {
|
|
2260
|
+
e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.06)";
|
|
2261
|
+
e.currentTarget.style.color = textColor;
|
|
2262
|
+
},
|
|
2263
|
+
onMouseLeave: (e) => {
|
|
2264
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
2265
|
+
e.currentTarget.style.color = textMuted;
|
|
2266
|
+
},
|
|
2267
|
+
disabled: disabled || fileUpload.isUploading,
|
|
2268
|
+
"aria-label": "Attach file",
|
|
2269
|
+
children: fileUpload.isUploading ? /* @__PURE__ */ jsxs6(
|
|
2270
|
+
"svg",
|
|
2271
|
+
{
|
|
2272
|
+
width: "18",
|
|
2273
|
+
height: "18",
|
|
2274
|
+
viewBox: "0 0 24 24",
|
|
2275
|
+
fill: "none",
|
|
2276
|
+
stroke: "currentColor",
|
|
2277
|
+
strokeWidth: "1.75",
|
|
2278
|
+
className: "animate-spin",
|
|
2279
|
+
"aria-hidden": "true",
|
|
2280
|
+
children: [
|
|
2281
|
+
/* @__PURE__ */ jsx7("title", { children: "Uploading" }),
|
|
2282
|
+
/* @__PURE__ */ jsx7("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
|
|
2283
|
+
]
|
|
2284
|
+
}
|
|
2285
|
+
) : /* @__PURE__ */ jsxs6(
|
|
2286
|
+
"svg",
|
|
2287
|
+
{
|
|
2288
|
+
width: "18",
|
|
2289
|
+
height: "18",
|
|
2290
|
+
viewBox: "0 0 24 24",
|
|
2291
|
+
fill: "none",
|
|
2292
|
+
stroke: "currentColor",
|
|
2293
|
+
strokeWidth: "1.75",
|
|
2294
|
+
strokeLinecap: "round",
|
|
2295
|
+
strokeLinejoin: "round",
|
|
2296
|
+
"aria-hidden": "true",
|
|
2297
|
+
children: [
|
|
2298
|
+
/* @__PURE__ */ jsx7("title", { children: "Attach file" }),
|
|
2299
|
+
/* @__PURE__ */ jsx7("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" })
|
|
2300
|
+
]
|
|
2301
|
+
}
|
|
2302
|
+
)
|
|
2303
|
+
}
|
|
2304
|
+
),
|
|
2305
|
+
/* @__PURE__ */ jsx7(
|
|
2306
|
+
"input",
|
|
2307
|
+
{
|
|
2308
|
+
ref: fileInputRef,
|
|
2309
|
+
type: "file",
|
|
2310
|
+
accept: "image/*,application/pdf,.doc,.docx",
|
|
2311
|
+
className: "hidden",
|
|
2312
|
+
onChange: handleFileSelect
|
|
2313
|
+
}
|
|
2314
|
+
)
|
|
2315
|
+
] }),
|
|
2316
|
+
/* @__PURE__ */ jsx7(
|
|
2317
|
+
"button",
|
|
2318
|
+
{
|
|
2319
|
+
type: "button",
|
|
2320
|
+
onClick: handleSend,
|
|
2321
|
+
disabled: !canSend,
|
|
2322
|
+
className: "p-1.5 rounded-lg transition-all duration-200",
|
|
2323
|
+
style: {
|
|
2324
|
+
background: canSend ? `linear-gradient(135deg, ${primaryColor}, ${config.theme?.primaryStrong || "#005eff"})` : "transparent",
|
|
2325
|
+
color: canSend ? "#ffffff" : textMuted,
|
|
2326
|
+
opacity: canSend ? 1 : 0.35,
|
|
2327
|
+
cursor: canSend ? "pointer" : "default",
|
|
2328
|
+
boxShadow: canSend ? `0 2px 8px -2px ${primaryColor}60` : "none"
|
|
2329
|
+
},
|
|
2330
|
+
"aria-label": "Send message",
|
|
2331
|
+
children: /* @__PURE__ */ jsxs6(
|
|
2332
|
+
"svg",
|
|
2333
|
+
{
|
|
2334
|
+
width: "18",
|
|
2335
|
+
height: "18",
|
|
2336
|
+
viewBox: "0 0 24 24",
|
|
2337
|
+
fill: "none",
|
|
2338
|
+
stroke: "currentColor",
|
|
2339
|
+
strokeWidth: "2",
|
|
2340
|
+
strokeLinecap: "round",
|
|
2341
|
+
strokeLinejoin: "round",
|
|
2342
|
+
"aria-hidden": "true",
|
|
2343
|
+
children: [
|
|
2344
|
+
/* @__PURE__ */ jsx7("title", { children: "Send" }),
|
|
2345
|
+
/* @__PURE__ */ jsx7("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
2346
|
+
/* @__PURE__ */ jsx7("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
2347
|
+
]
|
|
2348
|
+
}
|
|
2349
|
+
)
|
|
2350
|
+
}
|
|
2351
|
+
)
|
|
2352
|
+
] })
|
|
2353
|
+
]
|
|
978
2354
|
}
|
|
979
|
-
)
|
|
980
|
-
|
|
981
|
-
/* @__PURE__ */
|
|
982
|
-
"
|
|
2355
|
+
),
|
|
2356
|
+
fileUpload.isUploading && /* @__PURE__ */ jsxs6("div", { className: "mt-2 px-1", children: [
|
|
2357
|
+
/* @__PURE__ */ jsx7(
|
|
2358
|
+
"div",
|
|
983
2359
|
{
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
children:
|
|
2360
|
+
className: "w-full rounded-full overflow-hidden",
|
|
2361
|
+
style: {
|
|
2362
|
+
height: 3,
|
|
2363
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.06)"
|
|
2364
|
+
},
|
|
2365
|
+
children: /* @__PURE__ */ jsx7(
|
|
2366
|
+
"div",
|
|
2367
|
+
{
|
|
2368
|
+
className: "h-full rounded-full transition-all duration-300",
|
|
2369
|
+
style: {
|
|
2370
|
+
width: `${fileUpload.uploadProgress}%`,
|
|
2371
|
+
background: `linear-gradient(90deg, ${primaryColor}, ${config.theme?.primaryStrong || "#005eff"})`
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
)
|
|
990
2375
|
}
|
|
991
2376
|
),
|
|
992
|
-
/* @__PURE__ */
|
|
993
|
-
"
|
|
2377
|
+
/* @__PURE__ */ jsxs6(
|
|
2378
|
+
"p",
|
|
994
2379
|
{
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
)
|
|
1002
|
-
] })
|
|
1003
|
-
] }),
|
|
1004
|
-
/* @__PURE__ */ jsx4("div", { className: "absolute right-2 top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsx4(
|
|
1005
|
-
Button,
|
|
1006
|
-
{
|
|
1007
|
-
onClick: handleSend,
|
|
1008
|
-
disabled: !message.trim() || disabled,
|
|
1009
|
-
variant: "primary",
|
|
1010
|
-
size: "sm",
|
|
1011
|
-
className: "h-9 w-9 p-0 rounded-full flex items-center justify-center shadow-sm",
|
|
1012
|
-
children: /* @__PURE__ */ jsx4("span", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs4(
|
|
1013
|
-
"svg",
|
|
1014
|
-
{
|
|
1015
|
-
className: "w-4 h-4 rotate-90",
|
|
1016
|
-
fill: "none",
|
|
1017
|
-
viewBox: "0 0 24 24",
|
|
1018
|
-
stroke: "currentColor",
|
|
1019
|
-
"aria-hidden": "true",
|
|
2380
|
+
className: "mt-1",
|
|
2381
|
+
style: {
|
|
2382
|
+
fontSize: "11px",
|
|
2383
|
+
letterSpacing: "0.019em",
|
|
2384
|
+
color: textMuted
|
|
2385
|
+
},
|
|
1020
2386
|
children: [
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
]
|
|
1032
|
-
}
|
|
1033
|
-
) })
|
|
1034
|
-
}
|
|
1035
|
-
) })
|
|
1036
|
-
] }) }),
|
|
1037
|
-
fileUpload.isUploading && /* @__PURE__ */ jsxs4("div", { className: "mt-2", children: [
|
|
1038
|
-
/* @__PURE__ */ jsx4("div", { className: "w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5", children: /* @__PURE__ */ jsx4(
|
|
1039
|
-
"div",
|
|
1040
|
-
{
|
|
1041
|
-
className: "bg-blue-600 h-1.5 rounded-full transition-all duration-300",
|
|
1042
|
-
style: { width: `${fileUpload.uploadProgress}%` }
|
|
1043
|
-
}
|
|
1044
|
-
) }),
|
|
1045
|
-
/* @__PURE__ */ jsxs4("p", { className: "text-xs text-gray-600 dark:text-gray-400 mt-1", children: [
|
|
1046
|
-
"Uploading... ",
|
|
1047
|
-
fileUpload.uploadProgress,
|
|
1048
|
-
"%"
|
|
1049
|
-
] })
|
|
1050
|
-
] }),
|
|
1051
|
-
showEmojiPicker && emojiData && emojiPickerPosition && typeof document !== "undefined" && createPortal(
|
|
1052
|
-
/* @__PURE__ */ jsx4(
|
|
1053
|
-
"div",
|
|
1054
|
-
{
|
|
1055
|
-
ref: emojiPickerRef,
|
|
1056
|
-
className: "fixed rounded-lg border bg-white dark:bg-gray-800 dark:border-gray-700 shadow-xl",
|
|
1057
|
-
style: {
|
|
1058
|
-
top: `${emojiPickerPosition.top}px`,
|
|
1059
|
-
left: `${emojiPickerPosition.left}px`,
|
|
1060
|
-
zIndex: 9999
|
|
1061
|
-
},
|
|
1062
|
-
children: /* @__PURE__ */ jsx4(
|
|
1063
|
-
Picker,
|
|
2387
|
+
"Uploading... ",
|
|
2388
|
+
fileUpload.uploadProgress,
|
|
2389
|
+
"%"
|
|
2390
|
+
]
|
|
2391
|
+
}
|
|
2392
|
+
)
|
|
2393
|
+
] }),
|
|
2394
|
+
showEmojiPicker && emojiData && emojiPickerPosition && typeof document !== "undefined" && createPortal(
|
|
2395
|
+
/* @__PURE__ */ jsx7(
|
|
2396
|
+
"div",
|
|
1064
2397
|
{
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
2398
|
+
ref: emojiPickerRef,
|
|
2399
|
+
className: "fixed rounded-xl overflow-hidden",
|
|
2400
|
+
style: {
|
|
2401
|
+
top: `${emojiPickerPosition.top}px`,
|
|
2402
|
+
left: `${emojiPickerPosition.left}px`,
|
|
2403
|
+
zIndex: 9999,
|
|
2404
|
+
boxShadow: [
|
|
2405
|
+
"inset 0 0 0 0.5px rgba(255,255,255,0.06)",
|
|
2406
|
+
"inset 0 1px 0 0 rgba(255,255,255,0.1)",
|
|
2407
|
+
"0 16px 48px -8px rgba(0,0,0,0.5)"
|
|
2408
|
+
].join(", ")
|
|
1069
2409
|
},
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
2410
|
+
children: /* @__PURE__ */ jsx7(
|
|
2411
|
+
Picker,
|
|
2412
|
+
{
|
|
2413
|
+
data: emojiData,
|
|
2414
|
+
onEmojiSelect: (emoji) => {
|
|
2415
|
+
insertAtCursor(emoji.native || emoji.shortcodes || "");
|
|
2416
|
+
setShowEmojiPicker(false);
|
|
2417
|
+
},
|
|
2418
|
+
previewPosition: "none",
|
|
2419
|
+
skinTonePosition: "none",
|
|
2420
|
+
navPosition: "bottom",
|
|
2421
|
+
perLine: 8,
|
|
2422
|
+
searchPosition: "sticky",
|
|
2423
|
+
theme: "dark"
|
|
2424
|
+
}
|
|
2425
|
+
)
|
|
1076
2426
|
}
|
|
1077
|
-
)
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
2427
|
+
),
|
|
2428
|
+
document.body
|
|
2429
|
+
)
|
|
2430
|
+
]
|
|
2431
|
+
}
|
|
2432
|
+
);
|
|
1083
2433
|
}
|
|
1084
2434
|
|
|
1085
2435
|
// src/components/ChatWidget.tsx
|
|
1086
|
-
import { jsx as
|
|
2436
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2437
|
+
function getAnonymousUser() {
|
|
2438
|
+
let anonId;
|
|
2439
|
+
try {
|
|
2440
|
+
const stored = localStorage.getItem("xcelsior-chat-anon-id");
|
|
2441
|
+
if (stored) {
|
|
2442
|
+
anonId = stored;
|
|
2443
|
+
} else {
|
|
2444
|
+
anonId = `anon-${crypto.randomUUID?.() || Math.random().toString(36).slice(2)}`;
|
|
2445
|
+
localStorage.setItem("xcelsior-chat-anon-id", anonId);
|
|
2446
|
+
}
|
|
2447
|
+
} catch {
|
|
2448
|
+
anonId = `anon-${Math.random().toString(36).slice(2)}`;
|
|
2449
|
+
}
|
|
2450
|
+
return {
|
|
2451
|
+
name: "Visitor",
|
|
2452
|
+
email: `${anonId}@anonymous.xcelsior.co`,
|
|
2453
|
+
type: "customer",
|
|
2454
|
+
status: "online"
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
1087
2457
|
function ChatWidget({
|
|
1088
2458
|
config,
|
|
1089
2459
|
className = "",
|
|
1090
2460
|
variant = "popover",
|
|
1091
2461
|
externalWebSocket,
|
|
1092
2462
|
onMinimize,
|
|
1093
|
-
onClose
|
|
2463
|
+
onClose,
|
|
2464
|
+
resolvedPosition = "right"
|
|
1094
2465
|
}) {
|
|
1095
2466
|
const isFullPage = variant === "fullPage";
|
|
1096
|
-
const
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
);
|
|
2467
|
+
const effectiveUser = config.currentUser || getAnonymousUser();
|
|
2468
|
+
const effectiveConfig = { ...config, currentUser: effectiveUser };
|
|
2469
|
+
const websocket = useWebSocket(effectiveConfig, externalWebSocket);
|
|
2470
|
+
const { messages, addMessage, isLoading, loadMore, hasMore, isLoadingMore, isBotThinking } = useMessages(websocket, effectiveConfig);
|
|
1101
2471
|
const fileUpload = useFileUpload(config.apiKey, config.fileUpload);
|
|
1102
2472
|
const { isTyping, typingUsers } = useTypingIndicator(websocket);
|
|
1103
|
-
const
|
|
2473
|
+
const { width, height, isResizing, isNearEdge, containerResizeProps } = useResizableWidget({
|
|
2474
|
+
initialWidth: 380,
|
|
2475
|
+
initialHeight: 580,
|
|
2476
|
+
minWidth: 320,
|
|
2477
|
+
minHeight: 400,
|
|
2478
|
+
maxWidth: 800,
|
|
2479
|
+
maxHeight: 900,
|
|
2480
|
+
enabled: !isFullPage
|
|
2481
|
+
});
|
|
2482
|
+
const handleSendMessage = useCallback5(
|
|
1104
2483
|
(content) => {
|
|
1105
2484
|
if (!websocket.isConnected) {
|
|
1106
2485
|
config.toast?.error("Not connected to chat server");
|
|
@@ -1109,8 +2488,8 @@ function ChatWidget({
|
|
|
1109
2488
|
const optimisticMessage = {
|
|
1110
2489
|
id: `temp-${Date.now()}`,
|
|
1111
2490
|
conversationId: config.conversationId || "",
|
|
1112
|
-
senderId:
|
|
1113
|
-
senderType:
|
|
2491
|
+
senderId: effectiveUser.email,
|
|
2492
|
+
senderType: effectiveUser.type,
|
|
1114
2493
|
content,
|
|
1115
2494
|
messageType: "text",
|
|
1116
2495
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1124,13 +2503,11 @@ function ChatWidget({
|
|
|
1124
2503
|
});
|
|
1125
2504
|
config.onMessageSent?.(optimisticMessage);
|
|
1126
2505
|
},
|
|
1127
|
-
[websocket, config, addMessage]
|
|
2506
|
+
[websocket, config, addMessage, effectiveUser]
|
|
1128
2507
|
);
|
|
1129
|
-
const handleTyping =
|
|
2508
|
+
const handleTyping = useCallback5(
|
|
1130
2509
|
(isTyping2) => {
|
|
1131
|
-
if (!websocket.isConnected || config.enableTypingIndicator === false)
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
2510
|
+
if (!websocket.isConnected || config.enableTypingIndicator === false) return;
|
|
1134
2511
|
websocket.sendMessage("typing", {
|
|
1135
2512
|
conversationId: config.conversationId,
|
|
1136
2513
|
isTyping: isTyping2
|
|
@@ -1138,83 +2515,217 @@ function ChatWidget({
|
|
|
1138
2515
|
},
|
|
1139
2516
|
[websocket, config]
|
|
1140
2517
|
);
|
|
1141
|
-
|
|
2518
|
+
useEffect8(() => {
|
|
1142
2519
|
if (websocket.error) {
|
|
1143
2520
|
config.toast?.error(websocket.error.message || "An error occurred");
|
|
1144
2521
|
}
|
|
1145
2522
|
}, [websocket.error, config]);
|
|
1146
|
-
const
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
2523
|
+
const bgColor = config.theme?.background || "#00001a";
|
|
2524
|
+
const bgAlt = config.theme?.backgroundAlt || "#02164a";
|
|
2525
|
+
const textColor = config.theme?.text || "#f7f7f8";
|
|
2526
|
+
const primaryColor = config.theme?.primary || "#337eff";
|
|
2527
|
+
const textMuted = config.theme?.textMuted || "rgba(247,247,248,0.45)";
|
|
2528
|
+
const isLightTheme = (() => {
|
|
2529
|
+
if (!bgColor.startsWith("#")) return false;
|
|
2530
|
+
const hex = bgColor.replace("#", "");
|
|
2531
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
2532
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
2533
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
2534
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
|
|
2535
|
+
})();
|
|
2536
|
+
const positionClass = resolvedPosition === "left" ? "left-4" : "right-4";
|
|
2537
|
+
const containerStyle = isFullPage ? { backgroundColor: bgColor, color: textColor } : {
|
|
2538
|
+
position: "relative",
|
|
2539
|
+
width,
|
|
2540
|
+
height,
|
|
2541
|
+
maxHeight: "calc(100vh - 100px)",
|
|
2542
|
+
backgroundColor: bgColor,
|
|
2543
|
+
color: textColor,
|
|
2544
|
+
borderRadius: 16,
|
|
2545
|
+
/* Xcelsior card pattern: inset box-shadow borders instead of border-image */
|
|
2546
|
+
boxShadow: isLightTheme ? [
|
|
2547
|
+
"0 25px 60px -12px rgba(0,0,0,0.15)",
|
|
2548
|
+
"0 0 0 1px rgba(0,0,0,0.08)"
|
|
2549
|
+
].join(", ") : [
|
|
2550
|
+
"inset 0 0 0 0.5px rgba(255,255,255,0.06)",
|
|
2551
|
+
"inset 0 1px 0 0 rgba(255,255,255,0.12)",
|
|
2552
|
+
"0 25px 60px -12px rgba(0,0,0,0.6)",
|
|
2553
|
+
"0 0 40px -8px rgba(51,126,255,0.15)"
|
|
2554
|
+
].join(", "),
|
|
2555
|
+
userSelect: isResizing ? "none" : void 0
|
|
1151
2556
|
};
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
2557
|
+
const dotGridBg = !isFullPage ? {
|
|
2558
|
+
backgroundImage: isLightTheme ? `radial-gradient(circle, rgba(0,0,0,0.04) 1px, transparent 1px)` : `radial-gradient(circle, rgba(255,255,255,0.03) 1px, transparent 1px)`,
|
|
2559
|
+
backgroundSize: "24px 24px"
|
|
2560
|
+
} : {};
|
|
2561
|
+
const edgeHintStyle = !isFullPage && isNearEdge ? {
|
|
2562
|
+
outline: isLightTheme ? "2px solid rgba(0,94,255,0.25)" : "2px solid rgba(51,126,255,0.35)",
|
|
2563
|
+
outlineOffset: -1,
|
|
2564
|
+
transition: "outline 150ms ease"
|
|
2565
|
+
} : {
|
|
2566
|
+
outline: "2px solid transparent",
|
|
2567
|
+
outlineOffset: -1,
|
|
2568
|
+
transition: "outline 150ms ease"
|
|
2569
|
+
};
|
|
2570
|
+
return /* @__PURE__ */ jsxs7(
|
|
2571
|
+
"div",
|
|
2572
|
+
{
|
|
2573
|
+
className: isFullPage ? `flex flex-col h-full ${className}` : `fixed bottom-20 ${positionClass} z-50 flex flex-col overflow-hidden ${className}`,
|
|
2574
|
+
style: { ...containerStyle, ...dotGridBg, ...edgeHintStyle },
|
|
2575
|
+
...!isFullPage ? containerResizeProps : {},
|
|
2576
|
+
children: [
|
|
2577
|
+
!isFullPage && /* @__PURE__ */ jsx8(
|
|
2578
|
+
ChatHeader,
|
|
2579
|
+
{
|
|
2580
|
+
agent: effectiveUser.type === "customer" ? {
|
|
2581
|
+
email: "contact@xcelsior.co",
|
|
2582
|
+
name: "Xcelsior Software",
|
|
2583
|
+
type: "agent",
|
|
2584
|
+
status: websocket.isConnected ? "online" : "offline"
|
|
2585
|
+
} : void 0,
|
|
2586
|
+
onMinimize,
|
|
2587
|
+
onClose,
|
|
2588
|
+
theme: config.theme
|
|
2589
|
+
}
|
|
2590
|
+
),
|
|
2591
|
+
!websocket.isConnected && /* @__PURE__ */ jsx8(
|
|
2592
|
+
"div",
|
|
2593
|
+
{
|
|
2594
|
+
className: "px-4 py-2",
|
|
2595
|
+
style: {
|
|
2596
|
+
backgroundColor: isLightTheme ? "rgba(255,169,56,0.08)" : "rgba(255,169,56,0.06)",
|
|
2597
|
+
borderBottom: `1px solid rgba(255,169,56,${isLightTheme ? "0.15" : "0.12"})`
|
|
2598
|
+
},
|
|
2599
|
+
children: /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
|
|
2600
|
+
/* @__PURE__ */ jsx8(
|
|
2601
|
+
"div",
|
|
2602
|
+
{
|
|
2603
|
+
className: "w-1.5 h-1.5 rounded-full animate-pulse",
|
|
2604
|
+
style: { backgroundColor: config.theme?.statusCaution || "#ffa938" }
|
|
2605
|
+
}
|
|
2606
|
+
),
|
|
2607
|
+
/* @__PURE__ */ jsx8(
|
|
2608
|
+
"span",
|
|
2609
|
+
{
|
|
2610
|
+
style: {
|
|
2611
|
+
fontSize: "12px",
|
|
2612
|
+
letterSpacing: "0.015em",
|
|
2613
|
+
color: config.theme?.statusCaution || "#ffa938"
|
|
2614
|
+
},
|
|
2615
|
+
children: "Reconnecting..."
|
|
2616
|
+
}
|
|
2617
|
+
),
|
|
2618
|
+
/* @__PURE__ */ jsx8(
|
|
2619
|
+
"button",
|
|
2620
|
+
{
|
|
2621
|
+
type: "button",
|
|
2622
|
+
onClick: websocket.reconnect,
|
|
2623
|
+
className: "ml-auto transition-opacity",
|
|
2624
|
+
style: {
|
|
2625
|
+
fontSize: "12px",
|
|
2626
|
+
letterSpacing: "0.015em",
|
|
2627
|
+
color: config.theme?.statusCaution || "#ffa938",
|
|
2628
|
+
opacity: 0.7
|
|
2629
|
+
},
|
|
2630
|
+
onMouseEnter: (e) => {
|
|
2631
|
+
e.currentTarget.style.opacity = "1";
|
|
2632
|
+
},
|
|
2633
|
+
onMouseLeave: (e) => {
|
|
2634
|
+
e.currentTarget.style.opacity = "0.7";
|
|
2635
|
+
},
|
|
2636
|
+
children: "Retry"
|
|
2637
|
+
}
|
|
2638
|
+
)
|
|
2639
|
+
] })
|
|
2640
|
+
}
|
|
2641
|
+
),
|
|
2642
|
+
isLoading ? /* @__PURE__ */ jsx8("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxs7("div", { className: "text-center", children: [
|
|
2643
|
+
/* @__PURE__ */ jsx8(
|
|
2644
|
+
"div",
|
|
2645
|
+
{
|
|
2646
|
+
className: "w-7 h-7 border-2 border-t-transparent rounded-full animate-spin mx-auto mb-3",
|
|
2647
|
+
style: {
|
|
2648
|
+
borderColor: primaryColor,
|
|
2649
|
+
borderTopColor: "transparent"
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
),
|
|
2653
|
+
/* @__PURE__ */ jsx8(
|
|
2654
|
+
"p",
|
|
2655
|
+
{
|
|
2656
|
+
style: {
|
|
2657
|
+
fontSize: "12px",
|
|
2658
|
+
letterSpacing: "0.015em",
|
|
2659
|
+
color: textMuted
|
|
2660
|
+
},
|
|
2661
|
+
children: "Loading messages..."
|
|
2662
|
+
}
|
|
2663
|
+
)
|
|
2664
|
+
] }) }) : /* @__PURE__ */ jsx8(
|
|
2665
|
+
MessageList,
|
|
2666
|
+
{
|
|
2667
|
+
messages,
|
|
2668
|
+
currentUser: effectiveUser,
|
|
2669
|
+
isTyping,
|
|
2670
|
+
typingUser: typingUsers[0],
|
|
2671
|
+
autoScroll: true,
|
|
2672
|
+
onLoadMore: loadMore,
|
|
2673
|
+
hasMore,
|
|
2674
|
+
isLoadingMore,
|
|
2675
|
+
theme: config.theme,
|
|
2676
|
+
onQuickAction: handleSendMessage,
|
|
2677
|
+
isBotThinking
|
|
2678
|
+
}
|
|
2679
|
+
),
|
|
2680
|
+
/* @__PURE__ */ jsx8(
|
|
2681
|
+
ChatInput,
|
|
2682
|
+
{
|
|
2683
|
+
onSend: handleSendMessage,
|
|
2684
|
+
onTyping: handleTyping,
|
|
2685
|
+
config: effectiveConfig,
|
|
2686
|
+
fileUpload,
|
|
2687
|
+
disabled: !websocket.isConnected
|
|
2688
|
+
}
|
|
2689
|
+
),
|
|
2690
|
+
!isFullPage && /* @__PURE__ */ jsx8(
|
|
2691
|
+
"div",
|
|
2692
|
+
{
|
|
2693
|
+
className: "px-4 py-1.5 text-center",
|
|
2694
|
+
style: {
|
|
2695
|
+
borderTop: isLightTheme ? "1px solid rgba(0,0,0,0.06)" : "1px solid rgba(255,255,255,0.06)",
|
|
2696
|
+
backgroundColor: isLightTheme ? "rgba(0,0,0,0.03)" : "rgba(0,0,0,0.2)"
|
|
2697
|
+
},
|
|
2698
|
+
children: /* @__PURE__ */ jsxs7(
|
|
2699
|
+
"p",
|
|
2700
|
+
{
|
|
2701
|
+
style: {
|
|
2702
|
+
fontSize: "10px",
|
|
2703
|
+
letterSpacing: "0.025em",
|
|
2704
|
+
color: isLightTheme ? "rgba(0,0,0,0.35)" : "rgba(247,247,248,0.28)"
|
|
2705
|
+
},
|
|
2706
|
+
children: [
|
|
2707
|
+
"Powered by",
|
|
2708
|
+
" ",
|
|
2709
|
+
/* @__PURE__ */ jsx8("span", { style: {
|
|
2710
|
+
fontWeight: 600,
|
|
2711
|
+
color: isLightTheme ? "rgba(0,0,0,0.5)" : "rgba(247,247,248,0.45)"
|
|
2712
|
+
}, children: "Xcelsior" })
|
|
2713
|
+
]
|
|
2714
|
+
}
|
|
2715
|
+
)
|
|
2716
|
+
}
|
|
2717
|
+
)
|
|
2718
|
+
]
|
|
2719
|
+
}
|
|
2720
|
+
);
|
|
1210
2721
|
}
|
|
1211
2722
|
|
|
1212
2723
|
// src/components/Chat.tsx
|
|
1213
|
-
import { useCallback as
|
|
2724
|
+
import { useCallback as useCallback8, useEffect as useEffect10, useState as useState11 } from "react";
|
|
1214
2725
|
|
|
1215
2726
|
// src/components/PreChatForm.tsx
|
|
1216
|
-
import { useState as
|
|
1217
|
-
import { jsx as
|
|
2727
|
+
import { useState as useState8 } from "react";
|
|
2728
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1218
2729
|
function PreChatForm({
|
|
1219
2730
|
onSubmit,
|
|
1220
2731
|
className = "",
|
|
@@ -1223,10 +2734,11 @@ function PreChatForm({
|
|
|
1223
2734
|
onMinimize,
|
|
1224
2735
|
onClose
|
|
1225
2736
|
}) {
|
|
1226
|
-
const [name, setName] =
|
|
1227
|
-
const [email, setEmail] =
|
|
1228
|
-
const [errors, setErrors] =
|
|
1229
|
-
const [isSubmitting, setIsSubmitting] =
|
|
2737
|
+
const [name, setName] = useState8(initialName);
|
|
2738
|
+
const [email, setEmail] = useState8(initialEmail);
|
|
2739
|
+
const [errors, setErrors] = useState8({});
|
|
2740
|
+
const [isSubmitting, setIsSubmitting] = useState8(false);
|
|
2741
|
+
const [focusedField, setFocusedField] = useState8(null);
|
|
1230
2742
|
const validateForm = () => {
|
|
1231
2743
|
const newErrors = {};
|
|
1232
2744
|
if (!name.trim()) {
|
|
@@ -1259,97 +2771,194 @@ function PreChatForm({
|
|
|
1259
2771
|
setIsSubmitting(false);
|
|
1260
2772
|
}
|
|
1261
2773
|
};
|
|
1262
|
-
|
|
2774
|
+
const inputStyle = (fieldName, hasError) => ({
|
|
2775
|
+
width: "100%",
|
|
2776
|
+
padding: "10px 16px",
|
|
2777
|
+
fontSize: "14px",
|
|
2778
|
+
lineHeight: "20px",
|
|
2779
|
+
letterSpacing: "0.006em",
|
|
2780
|
+
color: "#f7f7f8",
|
|
2781
|
+
backgroundColor: "rgba(255,255,255,0.04)",
|
|
2782
|
+
border: "none",
|
|
2783
|
+
borderRadius: "12px",
|
|
2784
|
+
outline: "none",
|
|
2785
|
+
boxShadow: hasError ? "inset 0 0 0 1px rgba(255,99,99,0.5)" : focusedField === fieldName ? "inset 0 0 0 1px rgba(51,126,255,0.4), 0 0 0 3px rgba(51,126,255,0.15)" : "inset 0 0 0 0.5px rgba(255,255,255,0.08)",
|
|
2786
|
+
transition: "box-shadow 0.2s ease",
|
|
2787
|
+
caretColor: "#337eff"
|
|
2788
|
+
});
|
|
2789
|
+
return /* @__PURE__ */ jsxs8(
|
|
1263
2790
|
"div",
|
|
1264
2791
|
{
|
|
1265
|
-
className: `fixed bottom-4 right-4 z-50 flex flex-col
|
|
2792
|
+
className: `fixed bottom-4 right-4 z-50 flex flex-col overflow-hidden ${className}`,
|
|
1266
2793
|
style: {
|
|
1267
|
-
width:
|
|
1268
|
-
maxHeight: "calc(100vh - 2rem)"
|
|
2794
|
+
width: 380,
|
|
2795
|
+
maxHeight: "calc(100vh - 2rem)",
|
|
2796
|
+
backgroundColor: "#00001a",
|
|
2797
|
+
borderRadius: 16,
|
|
2798
|
+
boxShadow: [
|
|
2799
|
+
"inset 0 0 0 0.5px rgba(255,255,255,0.06)",
|
|
2800
|
+
"inset 0 1px 0 0 rgba(255,255,255,0.12)",
|
|
2801
|
+
"0 25px 60px -12px rgba(0,0,0,0.6)",
|
|
2802
|
+
"0 0 40px -8px rgba(51,126,255,0.15)"
|
|
2803
|
+
].join(", "),
|
|
2804
|
+
/* Dot grid texture */
|
|
2805
|
+
backgroundImage: "radial-gradient(circle, rgba(255,255,255,0.03) 1px, transparent 1px)",
|
|
2806
|
+
backgroundSize: "24px 24px"
|
|
1269
2807
|
},
|
|
1270
2808
|
children: [
|
|
1271
|
-
/* @__PURE__ */
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
2809
|
+
/* @__PURE__ */ jsxs8(
|
|
2810
|
+
"div",
|
|
2811
|
+
{
|
|
2812
|
+
className: "relative text-white overflow-hidden",
|
|
2813
|
+
style: {
|
|
2814
|
+
background: "linear-gradient(135deg, #337eff 0%, #005eff 50%, #001a66 100%)"
|
|
2815
|
+
},
|
|
2816
|
+
children: [
|
|
2817
|
+
/* @__PURE__ */ jsx9(
|
|
2818
|
+
"div",
|
|
2819
|
+
{
|
|
2820
|
+
className: "absolute inset-0 pointer-events-none",
|
|
2821
|
+
style: {
|
|
2822
|
+
background: "linear-gradient(180deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 40%)"
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
),
|
|
2826
|
+
/* @__PURE__ */ jsx9("div", { className: "relative px-5 py-4", children: /* @__PURE__ */ jsxs8("div", { className: "flex items-start justify-between", children: [
|
|
2827
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex-1", children: [
|
|
2828
|
+
/* @__PURE__ */ jsx9(
|
|
2829
|
+
"h2",
|
|
2830
|
+
{
|
|
2831
|
+
className: "font-semibold",
|
|
2832
|
+
style: {
|
|
2833
|
+
fontSize: "17px",
|
|
2834
|
+
letterSpacing: "-0.01em"
|
|
2835
|
+
},
|
|
2836
|
+
children: "Start a Conversation"
|
|
2837
|
+
}
|
|
2838
|
+
),
|
|
2839
|
+
/* @__PURE__ */ jsx9(
|
|
2840
|
+
"p",
|
|
2841
|
+
{
|
|
2842
|
+
className: "mt-1",
|
|
2843
|
+
style: {
|
|
2844
|
+
fontSize: "13px",
|
|
2845
|
+
letterSpacing: "0.015em",
|
|
2846
|
+
color: "rgba(255,255,255,0.6)"
|
|
2847
|
+
},
|
|
2848
|
+
children: "Please provide your details to continue"
|
|
2849
|
+
}
|
|
2850
|
+
)
|
|
2851
|
+
] }),
|
|
2852
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex gap-1 ml-2", children: [
|
|
2853
|
+
/* @__PURE__ */ jsx9(
|
|
2854
|
+
"button",
|
|
2855
|
+
{
|
|
2856
|
+
type: "button",
|
|
2857
|
+
onClick: onMinimize,
|
|
2858
|
+
className: "p-2 rounded-lg transition-all duration-150",
|
|
2859
|
+
style: { backgroundColor: "transparent" },
|
|
2860
|
+
onMouseEnter: (e) => {
|
|
2861
|
+
e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.1)";
|
|
2862
|
+
},
|
|
2863
|
+
onMouseLeave: (e) => {
|
|
2864
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
2865
|
+
},
|
|
2866
|
+
"aria-label": "Minimize chat",
|
|
2867
|
+
children: /* @__PURE__ */ jsxs8(
|
|
2868
|
+
"svg",
|
|
1295
2869
|
{
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
2870
|
+
width: "18",
|
|
2871
|
+
height: "18",
|
|
2872
|
+
fill: "none",
|
|
2873
|
+
stroke: "currentColor",
|
|
2874
|
+
viewBox: "0 0 24 24",
|
|
2875
|
+
children: [
|
|
2876
|
+
/* @__PURE__ */ jsx9("title", { children: "Minimize" }),
|
|
2877
|
+
/* @__PURE__ */ jsx9(
|
|
2878
|
+
"path",
|
|
2879
|
+
{
|
|
2880
|
+
strokeLinecap: "round",
|
|
2881
|
+
strokeLinejoin: "round",
|
|
2882
|
+
strokeWidth: 2,
|
|
2883
|
+
d: "M20 12H4"
|
|
2884
|
+
}
|
|
2885
|
+
)
|
|
2886
|
+
]
|
|
1300
2887
|
}
|
|
1301
2888
|
)
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
viewBox: "0 0 24 24",
|
|
1321
|
-
children: [
|
|
1322
|
-
/* @__PURE__ */ jsx6("title", { children: "Close" }),
|
|
1323
|
-
/* @__PURE__ */ jsx6(
|
|
1324
|
-
"path",
|
|
2889
|
+
}
|
|
2890
|
+
),
|
|
2891
|
+
/* @__PURE__ */ jsx9(
|
|
2892
|
+
"button",
|
|
2893
|
+
{
|
|
2894
|
+
type: "button",
|
|
2895
|
+
onClick: onClose,
|
|
2896
|
+
className: "p-2 rounded-lg transition-all duration-150",
|
|
2897
|
+
style: { backgroundColor: "transparent" },
|
|
2898
|
+
onMouseEnter: (e) => {
|
|
2899
|
+
e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.1)";
|
|
2900
|
+
},
|
|
2901
|
+
onMouseLeave: (e) => {
|
|
2902
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
2903
|
+
},
|
|
2904
|
+
"aria-label": "Close chat",
|
|
2905
|
+
children: /* @__PURE__ */ jsxs8(
|
|
2906
|
+
"svg",
|
|
1325
2907
|
{
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
2908
|
+
width: "18",
|
|
2909
|
+
height: "18",
|
|
2910
|
+
fill: "none",
|
|
2911
|
+
stroke: "currentColor",
|
|
2912
|
+
viewBox: "0 0 24 24",
|
|
2913
|
+
children: [
|
|
2914
|
+
/* @__PURE__ */ jsx9("title", { children: "Close" }),
|
|
2915
|
+
/* @__PURE__ */ jsx9(
|
|
2916
|
+
"path",
|
|
2917
|
+
{
|
|
2918
|
+
strokeLinecap: "round",
|
|
2919
|
+
strokeLinejoin: "round",
|
|
2920
|
+
strokeWidth: 2,
|
|
2921
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
2922
|
+
}
|
|
2923
|
+
)
|
|
2924
|
+
]
|
|
1330
2925
|
}
|
|
1331
2926
|
)
|
|
1332
|
-
|
|
2927
|
+
}
|
|
2928
|
+
)
|
|
2929
|
+
] })
|
|
2930
|
+
] }) }),
|
|
2931
|
+
/* @__PURE__ */ jsx9(
|
|
2932
|
+
"div",
|
|
2933
|
+
{
|
|
2934
|
+
className: "h-px",
|
|
2935
|
+
style: {
|
|
2936
|
+
background: "linear-gradient(90deg, transparent 5%, rgba(255,255,255,0.12) 30%, rgba(255,255,255,0.12) 70%, transparent 95%)"
|
|
1333
2937
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
/* @__PURE__ */
|
|
1340
|
-
/* @__PURE__ */
|
|
1341
|
-
/* @__PURE__ */
|
|
2938
|
+
}
|
|
2939
|
+
)
|
|
2940
|
+
]
|
|
2941
|
+
}
|
|
2942
|
+
),
|
|
2943
|
+
/* @__PURE__ */ jsxs8("form", { onSubmit: handleSubmit, className: "p-5 flex flex-col gap-4", children: [
|
|
2944
|
+
/* @__PURE__ */ jsxs8("div", { children: [
|
|
2945
|
+
/* @__PURE__ */ jsxs8(
|
|
1342
2946
|
"label",
|
|
1343
2947
|
{
|
|
1344
2948
|
htmlFor: "chat-name",
|
|
1345
|
-
className: "block mb-2
|
|
2949
|
+
className: "block mb-2 font-medium",
|
|
2950
|
+
style: {
|
|
2951
|
+
fontSize: "13px",
|
|
2952
|
+
letterSpacing: "0.015em",
|
|
2953
|
+
color: "rgba(247,247,248,0.72)"
|
|
2954
|
+
},
|
|
1346
2955
|
children: [
|
|
1347
2956
|
"Name ",
|
|
1348
|
-
/* @__PURE__ */
|
|
2957
|
+
/* @__PURE__ */ jsx9("span", { style: { color: "#ff6363" }, children: "*" })
|
|
1349
2958
|
]
|
|
1350
2959
|
}
|
|
1351
2960
|
),
|
|
1352
|
-
/* @__PURE__ */
|
|
2961
|
+
/* @__PURE__ */ jsx9(
|
|
1353
2962
|
"input",
|
|
1354
2963
|
{
|
|
1355
2964
|
type: "text",
|
|
@@ -1361,27 +2970,46 @@ function PreChatForm({
|
|
|
1361
2970
|
setErrors((prev) => ({ ...prev, name: void 0 }));
|
|
1362
2971
|
}
|
|
1363
2972
|
},
|
|
1364
|
-
|
|
2973
|
+
onFocus: () => setFocusedField("name"),
|
|
2974
|
+
onBlur: () => setFocusedField(null),
|
|
2975
|
+
style: inputStyle("name", !!errors.name),
|
|
1365
2976
|
placeholder: "John Doe",
|
|
1366
2977
|
disabled: isSubmitting,
|
|
1367
2978
|
autoComplete: "name"
|
|
1368
2979
|
}
|
|
1369
2980
|
),
|
|
1370
|
-
errors.name && /* @__PURE__ */
|
|
2981
|
+
errors.name && /* @__PURE__ */ jsx9(
|
|
2982
|
+
"p",
|
|
2983
|
+
{
|
|
2984
|
+
className: "mt-1.5",
|
|
2985
|
+
style: {
|
|
2986
|
+
fontSize: "12px",
|
|
2987
|
+
letterSpacing: "0.015em",
|
|
2988
|
+
color: "#ff6363"
|
|
2989
|
+
},
|
|
2990
|
+
role: "alert",
|
|
2991
|
+
children: errors.name
|
|
2992
|
+
}
|
|
2993
|
+
)
|
|
1371
2994
|
] }),
|
|
1372
|
-
/* @__PURE__ */
|
|
1373
|
-
/* @__PURE__ */
|
|
2995
|
+
/* @__PURE__ */ jsxs8("div", { children: [
|
|
2996
|
+
/* @__PURE__ */ jsxs8(
|
|
1374
2997
|
"label",
|
|
1375
2998
|
{
|
|
1376
2999
|
htmlFor: "chat-email",
|
|
1377
|
-
className: "block mb-2
|
|
3000
|
+
className: "block mb-2 font-medium",
|
|
3001
|
+
style: {
|
|
3002
|
+
fontSize: "13px",
|
|
3003
|
+
letterSpacing: "0.015em",
|
|
3004
|
+
color: "rgba(247,247,248,0.72)"
|
|
3005
|
+
},
|
|
1378
3006
|
children: [
|
|
1379
3007
|
"Email ",
|
|
1380
|
-
/* @__PURE__ */
|
|
3008
|
+
/* @__PURE__ */ jsx9("span", { style: { color: "#ff6363" }, children: "*" })
|
|
1381
3009
|
]
|
|
1382
3010
|
}
|
|
1383
3011
|
),
|
|
1384
|
-
/* @__PURE__ */
|
|
3012
|
+
/* @__PURE__ */ jsx9(
|
|
1385
3013
|
"input",
|
|
1386
3014
|
{
|
|
1387
3015
|
type: "email",
|
|
@@ -1393,50 +3021,65 @@ function PreChatForm({
|
|
|
1393
3021
|
setErrors((prev) => ({ ...prev, email: void 0 }));
|
|
1394
3022
|
}
|
|
1395
3023
|
},
|
|
1396
|
-
|
|
3024
|
+
onFocus: () => setFocusedField("email"),
|
|
3025
|
+
onBlur: () => setFocusedField(null),
|
|
3026
|
+
style: inputStyle("email", !!errors.email),
|
|
1397
3027
|
placeholder: "john@example.com",
|
|
1398
3028
|
disabled: isSubmitting,
|
|
1399
3029
|
autoComplete: "email"
|
|
1400
3030
|
}
|
|
1401
3031
|
),
|
|
1402
|
-
errors.email && /* @__PURE__ */
|
|
3032
|
+
errors.email && /* @__PURE__ */ jsx9(
|
|
3033
|
+
"p",
|
|
3034
|
+
{
|
|
3035
|
+
className: "mt-1.5",
|
|
3036
|
+
style: {
|
|
3037
|
+
fontSize: "12px",
|
|
3038
|
+
letterSpacing: "0.015em",
|
|
3039
|
+
color: "#ff6363"
|
|
3040
|
+
},
|
|
3041
|
+
role: "alert",
|
|
3042
|
+
children: errors.email
|
|
3043
|
+
}
|
|
3044
|
+
)
|
|
1403
3045
|
] }),
|
|
1404
|
-
/* @__PURE__ */
|
|
3046
|
+
/* @__PURE__ */ jsx9(
|
|
1405
3047
|
"button",
|
|
1406
3048
|
{
|
|
1407
3049
|
type: "submit",
|
|
1408
3050
|
disabled: isSubmitting,
|
|
1409
|
-
className: "w-full
|
|
1410
|
-
|
|
1411
|
-
|
|
3051
|
+
className: "w-full py-2.5 rounded-xl font-semibold text-white transition-all duration-200",
|
|
3052
|
+
style: {
|
|
3053
|
+
fontSize: "14px",
|
|
3054
|
+
letterSpacing: "0.006em",
|
|
3055
|
+
background: "linear-gradient(135deg, #337eff, #005eff)",
|
|
3056
|
+
boxShadow: "0 4px 16px -4px rgba(51,126,255,0.4)",
|
|
3057
|
+
opacity: isSubmitting ? 0.6 : 1,
|
|
3058
|
+
cursor: isSubmitting ? "not-allowed" : "pointer"
|
|
3059
|
+
},
|
|
3060
|
+
onMouseEnter: (e) => {
|
|
3061
|
+
if (!isSubmitting) {
|
|
3062
|
+
e.currentTarget.style.boxShadow = "0 6px 20px -4px rgba(51,126,255,0.5)";
|
|
3063
|
+
}
|
|
3064
|
+
},
|
|
3065
|
+
onMouseLeave: (e) => {
|
|
3066
|
+
e.currentTarget.style.boxShadow = "0 4px 16px -4px rgba(51,126,255,0.4)";
|
|
3067
|
+
},
|
|
3068
|
+
children: isSubmitting ? /* @__PURE__ */ jsxs8("span", { className: "flex items-center justify-center gap-2", children: [
|
|
3069
|
+
/* @__PURE__ */ jsxs8(
|
|
1412
3070
|
"svg",
|
|
1413
3071
|
{
|
|
1414
|
-
className: "animate-spin
|
|
1415
|
-
|
|
1416
|
-
|
|
3072
|
+
className: "animate-spin",
|
|
3073
|
+
width: "18",
|
|
3074
|
+
height: "18",
|
|
1417
3075
|
viewBox: "0 0 24 24",
|
|
3076
|
+
fill: "none",
|
|
3077
|
+
stroke: "currentColor",
|
|
3078
|
+
strokeWidth: "2",
|
|
1418
3079
|
"aria-label": "Loading",
|
|
1419
3080
|
children: [
|
|
1420
|
-
/* @__PURE__ */
|
|
1421
|
-
/* @__PURE__ */
|
|
1422
|
-
"circle",
|
|
1423
|
-
{
|
|
1424
|
-
className: "opacity-25",
|
|
1425
|
-
cx: "12",
|
|
1426
|
-
cy: "12",
|
|
1427
|
-
r: "10",
|
|
1428
|
-
stroke: "currentColor",
|
|
1429
|
-
strokeWidth: "4"
|
|
1430
|
-
}
|
|
1431
|
-
),
|
|
1432
|
-
/* @__PURE__ */ jsx6(
|
|
1433
|
-
"path",
|
|
1434
|
-
{
|
|
1435
|
-
className: "opacity-75",
|
|
1436
|
-
fill: "currentColor",
|
|
1437
|
-
d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
1438
|
-
}
|
|
1439
|
-
)
|
|
3081
|
+
/* @__PURE__ */ jsx9("title", { children: "Loading" }),
|
|
3082
|
+
/* @__PURE__ */ jsx9("path", { d: "M21 12a9 9 0 11-6.219-8.56" })
|
|
1440
3083
|
]
|
|
1441
3084
|
}
|
|
1442
3085
|
),
|
|
@@ -1444,28 +3087,136 @@ function PreChatForm({
|
|
|
1444
3087
|
] }) : "Start Chat"
|
|
1445
3088
|
}
|
|
1446
3089
|
),
|
|
1447
|
-
/* @__PURE__ */
|
|
3090
|
+
/* @__PURE__ */ jsx9(
|
|
3091
|
+
"p",
|
|
3092
|
+
{
|
|
3093
|
+
className: "text-center",
|
|
3094
|
+
style: {
|
|
3095
|
+
fontSize: "11px",
|
|
3096
|
+
letterSpacing: "0.019em",
|
|
3097
|
+
color: "rgba(247,247,248,0.28)"
|
|
3098
|
+
},
|
|
3099
|
+
children: "We respect your privacy. Your information will only be used to assist you."
|
|
3100
|
+
}
|
|
3101
|
+
)
|
|
1448
3102
|
] }),
|
|
1449
|
-
/* @__PURE__ */
|
|
1450
|
-
"
|
|
1451
|
-
|
|
1452
|
-
|
|
3103
|
+
/* @__PURE__ */ jsx9(
|
|
3104
|
+
"div",
|
|
3105
|
+
{
|
|
3106
|
+
className: "px-4 py-1.5 text-center",
|
|
3107
|
+
style: {
|
|
3108
|
+
borderTop: "1px solid rgba(255,255,255,0.06)",
|
|
3109
|
+
backgroundColor: "rgba(0,0,0,0.2)"
|
|
3110
|
+
},
|
|
3111
|
+
children: /* @__PURE__ */ jsxs8(
|
|
3112
|
+
"p",
|
|
3113
|
+
{
|
|
3114
|
+
style: {
|
|
3115
|
+
fontSize: "10px",
|
|
3116
|
+
letterSpacing: "0.025em",
|
|
3117
|
+
color: "rgba(247,247,248,0.28)"
|
|
3118
|
+
},
|
|
3119
|
+
children: [
|
|
3120
|
+
"Powered by",
|
|
3121
|
+
" ",
|
|
3122
|
+
/* @__PURE__ */ jsx9("span", { style: { fontWeight: 600, color: "rgba(247,247,248,0.45)" }, children: "Xcelsior" })
|
|
3123
|
+
]
|
|
3124
|
+
}
|
|
3125
|
+
)
|
|
3126
|
+
}
|
|
3127
|
+
)
|
|
1453
3128
|
]
|
|
1454
3129
|
}
|
|
1455
3130
|
);
|
|
1456
3131
|
}
|
|
1457
3132
|
|
|
3133
|
+
// src/hooks/useDraggablePosition.ts
|
|
3134
|
+
import { useState as useState9, useCallback as useCallback6, useRef as useRef6, useEffect as useEffect9 } from "react";
|
|
3135
|
+
var STORAGE_KEY2 = "xcelsior-chat-position";
|
|
3136
|
+
function getStoredPosition() {
|
|
3137
|
+
try {
|
|
3138
|
+
const stored = localStorage.getItem(STORAGE_KEY2);
|
|
3139
|
+
if (stored === "left" || stored === "right") return stored;
|
|
3140
|
+
} catch {
|
|
3141
|
+
}
|
|
3142
|
+
return "right";
|
|
3143
|
+
}
|
|
3144
|
+
function storePosition(position) {
|
|
3145
|
+
try {
|
|
3146
|
+
localStorage.setItem(STORAGE_KEY2, position);
|
|
3147
|
+
} catch {
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
function useDraggablePosition(configPosition = "auto") {
|
|
3151
|
+
const resolvedDefault = configPosition === "auto" ? getStoredPosition() : configPosition;
|
|
3152
|
+
const [position, setPosition] = useState9(resolvedDefault);
|
|
3153
|
+
const [isDragging, setIsDragging] = useState9(false);
|
|
3154
|
+
const dragStartX = useRef6(0);
|
|
3155
|
+
const fabRef = useRef6(null);
|
|
3156
|
+
const hasShownHint = useRef6(false);
|
|
3157
|
+
const [showHint, setShowHint] = useState9(false);
|
|
3158
|
+
useEffect9(() => {
|
|
3159
|
+
try {
|
|
3160
|
+
const hasVisited = localStorage.getItem("xcelsior-chat-hint-shown");
|
|
3161
|
+
if (!hasVisited && !hasShownHint.current) {
|
|
3162
|
+
hasShownHint.current = true;
|
|
3163
|
+
setShowHint(true);
|
|
3164
|
+
const timer = setTimeout(() => {
|
|
3165
|
+
setShowHint(false);
|
|
3166
|
+
localStorage.setItem("xcelsior-chat-hint-shown", "true");
|
|
3167
|
+
}, 2e3);
|
|
3168
|
+
return () => clearTimeout(timer);
|
|
3169
|
+
}
|
|
3170
|
+
} catch {
|
|
3171
|
+
}
|
|
3172
|
+
}, []);
|
|
3173
|
+
const handlePointerDown = useCallback6((e) => {
|
|
3174
|
+
dragStartX.current = e.clientX;
|
|
3175
|
+
setIsDragging(true);
|
|
3176
|
+
e.target.setPointerCapture(e.pointerId);
|
|
3177
|
+
}, []);
|
|
3178
|
+
const handlePointerMove = useCallback6((_e) => {
|
|
3179
|
+
}, []);
|
|
3180
|
+
const handlePointerUp = useCallback6(
|
|
3181
|
+
(e) => {
|
|
3182
|
+
setIsDragging(false);
|
|
3183
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
3184
|
+
const deltaX = e.clientX - dragStartX.current;
|
|
3185
|
+
if (Math.abs(deltaX) > 50) {
|
|
3186
|
+
const screenMid = window.innerWidth / 2;
|
|
3187
|
+
const newPosition = e.clientX < screenMid ? "left" : "right";
|
|
3188
|
+
if (newPosition !== position) {
|
|
3189
|
+
setPosition(newPosition);
|
|
3190
|
+
storePosition(newPosition);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
},
|
|
3194
|
+
[position]
|
|
3195
|
+
);
|
|
3196
|
+
return {
|
|
3197
|
+
position,
|
|
3198
|
+
isDragging,
|
|
3199
|
+
showHint,
|
|
3200
|
+
fabRef,
|
|
3201
|
+
handlers: {
|
|
3202
|
+
onPointerDown: handlePointerDown,
|
|
3203
|
+
onPointerMove: handlePointerMove,
|
|
3204
|
+
onPointerUp: handlePointerUp
|
|
3205
|
+
}
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
|
|
1458
3209
|
// src/hooks/useChatWidgetState.ts
|
|
1459
|
-
import { useCallback as
|
|
3210
|
+
import { useCallback as useCallback7, useState as useState10 } from "react";
|
|
1460
3211
|
function useChatWidgetState({
|
|
1461
3212
|
state: controlledState,
|
|
1462
3213
|
defaultState = "minimized",
|
|
1463
3214
|
onStateChange
|
|
1464
3215
|
}) {
|
|
1465
|
-
const [uncontrolledState, setUncontrolledState] =
|
|
3216
|
+
const [uncontrolledState, setUncontrolledState] = useState10(defaultState);
|
|
1466
3217
|
const isControlled = controlledState !== void 0 && controlledState !== "undefined";
|
|
1467
3218
|
const currentState = isControlled ? controlledState : uncontrolledState;
|
|
1468
|
-
const setState =
|
|
3219
|
+
const setState = useCallback7(
|
|
1469
3220
|
(newValue) => {
|
|
1470
3221
|
if (!isControlled) {
|
|
1471
3222
|
setUncontrolledState(newValue);
|
|
@@ -1482,7 +3233,7 @@ function useChatWidgetState({
|
|
|
1482
3233
|
}
|
|
1483
3234
|
|
|
1484
3235
|
// src/components/Chat.tsx
|
|
1485
|
-
import { jsx as
|
|
3236
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1486
3237
|
function generateSessionId() {
|
|
1487
3238
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1488
3239
|
return crypto.randomUUID();
|
|
@@ -1498,15 +3249,48 @@ function Chat({
|
|
|
1498
3249
|
defaultState = "minimized",
|
|
1499
3250
|
onStateChange
|
|
1500
3251
|
}) {
|
|
1501
|
-
const [userInfo, setUserInfo] =
|
|
1502
|
-
const [conversationId, setConversationId] =
|
|
1503
|
-
const [isLoading, setIsLoading] =
|
|
1504
|
-
const
|
|
3252
|
+
const [userInfo, setUserInfo] = useState11(null);
|
|
3253
|
+
const [conversationId, setConversationId] = useState11("");
|
|
3254
|
+
const [isLoading, setIsLoading] = useState11(true);
|
|
3255
|
+
const [isAnimating, setIsAnimating] = useState11(false);
|
|
3256
|
+
const [showWidget, setShowWidget] = useState11(false);
|
|
3257
|
+
const identityMode = config.identityCollection || "progressive";
|
|
3258
|
+
const { position, isDragging, showHint, handlers } = useDraggablePosition(config.position);
|
|
3259
|
+
const { currentState, setState: setStateRaw } = useChatWidgetState({
|
|
1505
3260
|
state,
|
|
1506
3261
|
defaultState,
|
|
1507
3262
|
onStateChange
|
|
1508
3263
|
});
|
|
1509
|
-
|
|
3264
|
+
const setState = useCallback8(
|
|
3265
|
+
(newState) => {
|
|
3266
|
+
if (newState === "open" && currentState === "minimized") {
|
|
3267
|
+
setShowWidget(true);
|
|
3268
|
+
setIsAnimating(true);
|
|
3269
|
+
setStateRaw(newState);
|
|
3270
|
+
requestAnimationFrame(() => {
|
|
3271
|
+
requestAnimationFrame(() => {
|
|
3272
|
+
setIsAnimating(false);
|
|
3273
|
+
});
|
|
3274
|
+
});
|
|
3275
|
+
} else if ((newState === "minimized" || newState === "closed") && currentState === "open") {
|
|
3276
|
+
setIsAnimating(true);
|
|
3277
|
+
setTimeout(() => {
|
|
3278
|
+
setShowWidget(false);
|
|
3279
|
+
setIsAnimating(false);
|
|
3280
|
+
setStateRaw(newState);
|
|
3281
|
+
}, 200);
|
|
3282
|
+
} else {
|
|
3283
|
+
setStateRaw(newState);
|
|
3284
|
+
}
|
|
3285
|
+
},
|
|
3286
|
+
[currentState, setStateRaw]
|
|
3287
|
+
);
|
|
3288
|
+
useEffect10(() => {
|
|
3289
|
+
if (currentState === "open") {
|
|
3290
|
+
setShowWidget(true);
|
|
3291
|
+
}
|
|
3292
|
+
}, [currentState]);
|
|
3293
|
+
useEffect10(() => {
|
|
1510
3294
|
const initializeSession = () => {
|
|
1511
3295
|
try {
|
|
1512
3296
|
if (config.currentUser?.email && config.currentUser?.name) {
|
|
@@ -1542,15 +3326,8 @@ function Chat({
|
|
|
1542
3326
|
}
|
|
1543
3327
|
const convId = config.conversationId || generateSessionId();
|
|
1544
3328
|
setConversationId(convId);
|
|
1545
|
-
if (
|
|
1546
|
-
|
|
1547
|
-
name: config.currentUser.name,
|
|
1548
|
-
email: config.currentUser.email,
|
|
1549
|
-
avatar: config.currentUser.avatar,
|
|
1550
|
-
type: "customer",
|
|
1551
|
-
status: "online"
|
|
1552
|
-
};
|
|
1553
|
-
setUserInfo(user);
|
|
3329
|
+
if (identityMode === "progressive" || identityMode === "none") {
|
|
3330
|
+
setUserInfo(null);
|
|
1554
3331
|
}
|
|
1555
3332
|
} catch (error) {
|
|
1556
3333
|
console.error("Error initializing chat session:", error);
|
|
@@ -1560,16 +3337,11 @@ function Chat({
|
|
|
1560
3337
|
}
|
|
1561
3338
|
};
|
|
1562
3339
|
initializeSession();
|
|
1563
|
-
}, [config, storageKeyPrefix]);
|
|
1564
|
-
const handlePreChatSubmit =
|
|
3340
|
+
}, [config, storageKeyPrefix, identityMode]);
|
|
3341
|
+
const handlePreChatSubmit = useCallback8(
|
|
1565
3342
|
(name, email) => {
|
|
1566
3343
|
const convId = conversationId || generateSessionId();
|
|
1567
|
-
const user = {
|
|
1568
|
-
name,
|
|
1569
|
-
email,
|
|
1570
|
-
type: "customer",
|
|
1571
|
-
status: "online"
|
|
1572
|
-
};
|
|
3344
|
+
const user = { name, email, type: "customer", status: "online" };
|
|
1573
3345
|
const storageData = {
|
|
1574
3346
|
name,
|
|
1575
3347
|
email,
|
|
@@ -1578,8 +3350,7 @@ function Chat({
|
|
|
1578
3350
|
};
|
|
1579
3351
|
try {
|
|
1580
3352
|
localStorage.setItem(`${storageKeyPrefix}_user`, JSON.stringify(storageData));
|
|
1581
|
-
} catch
|
|
1582
|
-
console.error("Error storing user data:", error);
|
|
3353
|
+
} catch {
|
|
1583
3354
|
}
|
|
1584
3355
|
setUserInfo(user);
|
|
1585
3356
|
setConversationId(convId);
|
|
@@ -1587,26 +3358,55 @@ function Chat({
|
|
|
1587
3358
|
},
|
|
1588
3359
|
[conversationId, storageKeyPrefix, onPreChatSubmit]
|
|
1589
3360
|
);
|
|
1590
|
-
if (isLoading)
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
}
|
|
3361
|
+
if (isLoading) return null;
|
|
3362
|
+
if (currentState === "closed") return null;
|
|
3363
|
+
const positionClass = position === "left" ? "left-4" : "right-4";
|
|
3364
|
+
const primaryColor = config.theme?.primary || "#337eff";
|
|
3365
|
+
const primaryStrong = config.theme?.primaryStrong || "#005eff";
|
|
1596
3366
|
if (currentState === "minimized") {
|
|
1597
|
-
return /* @__PURE__ */
|
|
3367
|
+
return /* @__PURE__ */ jsx10("div", { className: `fixed bottom-5 ${positionClass} z-50 ${className}`, children: /* @__PURE__ */ jsxs9(
|
|
1598
3368
|
"button",
|
|
1599
3369
|
{
|
|
1600
3370
|
type: "button",
|
|
1601
3371
|
onClick: () => setState("open"),
|
|
1602
|
-
className:
|
|
3372
|
+
className: `group relative rounded-full text-white transition-all duration-300 flex items-center justify-center touch-none select-none ${showHint ? "animate-bounce" : ""} ${isDragging ? "cursor-grabbing" : "cursor-grab"}`,
|
|
3373
|
+
onMouseEnter: (e) => {
|
|
3374
|
+
e.currentTarget.style.transform = "scale(1.08)";
|
|
3375
|
+
},
|
|
3376
|
+
onMouseLeave: (e) => {
|
|
3377
|
+
e.currentTarget.style.transform = isDragging ? "scale(1.1)" : "scale(1)";
|
|
3378
|
+
},
|
|
3379
|
+
style: {
|
|
3380
|
+
width: 64,
|
|
3381
|
+
height: 64,
|
|
3382
|
+
background: `linear-gradient(135deg, ${primaryColor}, ${primaryStrong})`,
|
|
3383
|
+
boxShadow: [
|
|
3384
|
+
`0 4px 24px 0 ${primaryColor}80`,
|
|
3385
|
+
`0 8px 32px -4px rgba(0,0,0,0.3)`,
|
|
3386
|
+
`inset 0 1px 0 0 rgba(255,255,255,0.2)`
|
|
3387
|
+
].join(", ")
|
|
3388
|
+
},
|
|
1603
3389
|
"aria-label": "Open chat",
|
|
1604
|
-
|
|
3390
|
+
...handlers,
|
|
3391
|
+
children: [
|
|
3392
|
+
/* @__PURE__ */ jsx10(ChatBubbleIcon, { size: 28, color: "white", className: "pointer-events-none" }),
|
|
3393
|
+
/* @__PURE__ */ jsx10(
|
|
3394
|
+
"span",
|
|
3395
|
+
{
|
|
3396
|
+
className: "absolute inset-0 rounded-full animate-ping pointer-events-none",
|
|
3397
|
+
style: {
|
|
3398
|
+
backgroundColor: primaryColor,
|
|
3399
|
+
opacity: 0.15,
|
|
3400
|
+
animationDuration: "2.5s"
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
)
|
|
3404
|
+
]
|
|
1605
3405
|
}
|
|
1606
3406
|
) });
|
|
1607
3407
|
}
|
|
1608
|
-
if (!userInfo || !userInfo.email || !userInfo.name) {
|
|
1609
|
-
return /* @__PURE__ */
|
|
3408
|
+
if (identityMode === "form" && (!userInfo || !userInfo.email || !userInfo.name)) {
|
|
3409
|
+
return /* @__PURE__ */ jsx10(
|
|
1610
3410
|
PreChatForm,
|
|
1611
3411
|
{
|
|
1612
3412
|
onSubmit: handlePreChatSubmit,
|
|
@@ -1621,54 +3421,83 @@ function Chat({
|
|
|
1621
3421
|
const fullConfig = {
|
|
1622
3422
|
...config,
|
|
1623
3423
|
conversationId,
|
|
1624
|
-
currentUser: userInfo
|
|
3424
|
+
currentUser: userInfo || void 0
|
|
3425
|
+
};
|
|
3426
|
+
const widgetAnimationStyle = showWidget && !isAnimating ? {
|
|
3427
|
+
opacity: 1,
|
|
3428
|
+
transform: "translateY(0) scale(1)",
|
|
3429
|
+
transition: "opacity 0.25s ease-out, transform 0.25s ease-out"
|
|
3430
|
+
} : {
|
|
3431
|
+
opacity: 0,
|
|
3432
|
+
transform: "translateY(12px) scale(0.97)",
|
|
3433
|
+
transition: "opacity 0.2s ease-in, transform 0.2s ease-in"
|
|
1625
3434
|
};
|
|
1626
|
-
return /* @__PURE__ */
|
|
3435
|
+
return /* @__PURE__ */ jsx10("div", { style: widgetAnimationStyle, children: /* @__PURE__ */ jsx10(
|
|
1627
3436
|
ChatWidget,
|
|
1628
3437
|
{
|
|
1629
3438
|
config: fullConfig,
|
|
1630
3439
|
className,
|
|
1631
3440
|
onClose: () => setState("closed"),
|
|
1632
|
-
onMinimize: () => setState("minimized")
|
|
3441
|
+
onMinimize: () => setState("minimized"),
|
|
3442
|
+
resolvedPosition: position
|
|
1633
3443
|
}
|
|
1634
|
-
);
|
|
3444
|
+
) });
|
|
1635
3445
|
}
|
|
1636
3446
|
|
|
1637
3447
|
// src/components/TypingIndicator.tsx
|
|
1638
|
-
import { jsx as
|
|
3448
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1639
3449
|
function TypingIndicator({ isTyping, userName }) {
|
|
1640
3450
|
if (!isTyping) {
|
|
1641
3451
|
return null;
|
|
1642
3452
|
}
|
|
1643
|
-
return /* @__PURE__ */
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
3453
|
+
return /* @__PURE__ */ jsx11(
|
|
3454
|
+
"div",
|
|
3455
|
+
{
|
|
3456
|
+
className: "px-4 py-2",
|
|
3457
|
+
style: {
|
|
3458
|
+
borderTop: "1px solid rgba(255,255,255,0.06)",
|
|
3459
|
+
backgroundColor: "rgba(0,0,0,0.15)"
|
|
3460
|
+
},
|
|
3461
|
+
children: /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
3462
|
+
/* @__PURE__ */ jsx11("div", { className: "flex gap-1", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx11(
|
|
3463
|
+
"span",
|
|
3464
|
+
{
|
|
3465
|
+
className: "rounded-full animate-bounce",
|
|
3466
|
+
style: {
|
|
3467
|
+
width: 5,
|
|
3468
|
+
height: 5,
|
|
3469
|
+
backgroundColor: "rgba(51,126,255,0.6)",
|
|
3470
|
+
animationDelay: `${i * 0.1}s`,
|
|
3471
|
+
animationDuration: "0.8s"
|
|
3472
|
+
}
|
|
3473
|
+
},
|
|
3474
|
+
i
|
|
3475
|
+
)) }),
|
|
3476
|
+
/* @__PURE__ */ jsx11(
|
|
3477
|
+
"span",
|
|
3478
|
+
{
|
|
3479
|
+
style: {
|
|
3480
|
+
fontSize: "12px",
|
|
3481
|
+
letterSpacing: "0.015em",
|
|
3482
|
+
color: "rgba(247,247,248,0.45)"
|
|
3483
|
+
},
|
|
3484
|
+
children: userName ? `${userName} is typing...` : "Someone is typing..."
|
|
3485
|
+
}
|
|
3486
|
+
)
|
|
3487
|
+
] })
|
|
3488
|
+
}
|
|
3489
|
+
);
|
|
1663
3490
|
}
|
|
1664
3491
|
export {
|
|
1665
3492
|
Chat,
|
|
1666
3493
|
ChatHeader,
|
|
1667
3494
|
ChatInput,
|
|
1668
3495
|
ChatWidget,
|
|
3496
|
+
MarkdownMessage,
|
|
1669
3497
|
MessageItem,
|
|
1670
3498
|
MessageList,
|
|
1671
3499
|
PreChatForm,
|
|
3500
|
+
ThinkingIndicator,
|
|
1672
3501
|
TypingIndicator,
|
|
1673
3502
|
fetchMessages,
|
|
1674
3503
|
useChatWidgetState,
|