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