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