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