@supernal/interface-nextjs 0.1.1 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -43,6 +43,9 @@ __export(index_exports, {
43
43
  });
44
44
  module.exports = __toCommonJS(index_exports);
45
45
 
46
+ // src/components/SupernalProvider.tsx
47
+ var import_react6 = require("react");
48
+
46
49
  // src/contexts/ChatInputContext.tsx
47
50
  var import_react = require("react");
48
51
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -343,78 +346,416 @@ function ChatProvider({
343
346
 
344
347
  // src/components/ChatBubble.tsx
345
348
  var import_react3 = require("react");
349
+
350
+ // ../names/Components.ts
351
+ var Components = {
352
+ // Chat components
353
+ ChatInput: "chat-message-input",
354
+ ChatSendButton: "chat-send-button",
355
+ ChatClearButton: "chat-clear-button",
356
+ ChatToggleButton: "chat-bubble-toggle",
357
+ ChatMessageList: "chat-message-list",
358
+ ChatTypingIndicator: "chat-typing-indicator",
359
+ // Demo widget components - buttons
360
+ OpenMenuButton: "open-main-menu",
361
+ CloseMenuButton: "close-main-menu",
362
+ // Demo widget components - checkboxes
363
+ FeatureToggle: "feature-toggle",
364
+ NotificationsToggle: "notification-toggle",
365
+ // Demo widget components - radios
366
+ PriorityHighRadio: "priority-high",
367
+ PriorityMediumRadio: "priority-medium",
368
+ PriorityLowRadio: "priority-low",
369
+ // Demo widget components - selects
370
+ StatusSelect: "status-dropdown",
371
+ ThemeSelect: "theme-toggle",
372
+ // Demo widget components - form
373
+ FormNameInput: "form-name",
374
+ DemoFormSubmitButton: "form-submit",
375
+ // Generic widget components
376
+ WidgetButton: "widget-button",
377
+ WidgetInput: "widget-input",
378
+ WidgetSelect: "widget-select",
379
+ WidgetCheckbox: "widget-checkbox",
380
+ WidgetRadio: "widget-radio",
381
+ WidgetTextarea: "widget-textarea",
382
+ // Tool command components
383
+ ToolCommandsList: "tool-commands-list",
384
+ ToolExecuteButton: "tool-execute-button",
385
+ ToolApprovalButton: "tool-approval-button",
386
+ ToolMetadataDisplay: "tool-metadata-display",
387
+ // Navigation components
388
+ NavMainMenu: "nav-main-menu",
389
+ NavHomeLink: "nav-home-link",
390
+ NavToolsLink: "nav-tools-link",
391
+ NavSettingsLink: "nav-settings-link",
392
+ NavBackButton: "nav-back-button",
393
+ // Form components
394
+ FormSubmitButton: "form-submit-button",
395
+ FormCancelButton: "form-cancel-button",
396
+ FormResetButton: "form-reset-button",
397
+ FormTextInput: "form-text-input",
398
+ FormEmailInput: "form-email-input",
399
+ FormPasswordInput: "form-password-input",
400
+ // Modal components
401
+ ModalCloseButton: "modal-close-button",
402
+ ModalConfirmButton: "modal-confirm-button",
403
+ ModalCancelButton: "modal-cancel-button",
404
+ ModalOverlay: "modal-overlay",
405
+ // Status/Feedback components
406
+ StatusSuccessMessage: "status-success-message",
407
+ StatusErrorMessage: "status-error-message",
408
+ StatusWarningMessage: "status-warning-message",
409
+ StatusLoadingSpinner: "status-loading-spinner",
410
+ StatusProgressBar: "status-progress-bar"
411
+ };
412
+
413
+ // src/components/ChatBubble.tsx
346
414
  var import_jsx_runtime3 = require("react/jsx-runtime");
347
415
  var ChatNames = {
348
- bubble: "chat-bubble",
349
- input: "chat-input",
350
- sendButton: "chat-send-button",
351
- clearButton: "chat-clear-button",
352
- messages: "chat-messages"
416
+ bubble: Components.ChatToggleButton,
417
+ input: Components.ChatInput,
418
+ sendButton: Components.ChatSendButton,
419
+ clearButton: Components.ChatClearButton
420
+ };
421
+ var DOCK_POSITIONS = {
422
+ "bottom-right": {
423
+ container: "bottom-4 right-4 sm:bottom-6 sm:right-6",
424
+ panel: "bottom-0 right-0"
425
+ },
426
+ "bottom-left": {
427
+ container: "bottom-4 left-4 sm:bottom-6 sm:left-6",
428
+ panel: "bottom-0 left-0"
429
+ },
430
+ "top-right": {
431
+ container: "top-4 right-4 sm:top-6 sm:right-6",
432
+ panel: "top-0 right-0"
433
+ },
434
+ "top-left": {
435
+ container: "top-4 left-4 sm:top-6 sm:left-6",
436
+ panel: "top-0 left-0"
437
+ },
438
+ "left-center": {
439
+ container: "left-4 top-1/2 -translate-y-1/2",
440
+ panel: "left-0 top-0"
441
+ },
442
+ "right-center": {
443
+ container: "right-4 top-1/2 -translate-y-1/2",
444
+ panel: "right-0 top-0"
445
+ },
446
+ "bottom-center": {
447
+ container: "bottom-4 left-1/2 -translate-x-1/2",
448
+ panel: "bottom-0 left-1/2 -translate-x-1/2"
449
+ }
450
+ };
451
+ var INLINE_STYLES = {
452
+ // Input field
453
+ input: (isDark) => ({
454
+ color: isDark ? "#ffffff" : "#111827"
455
+ // Fallback for placeholder handled via ::placeholder CSS or separate element
456
+ }),
457
+ // Message bubbles
458
+ messageUser: () => ({
459
+ background: "linear-gradient(to bottom right, rgb(37, 99, 235), rgb(147, 51, 234))",
460
+ color: "#ffffff"
461
+ }),
462
+ messageAI: (isDark) => ({
463
+ backgroundColor: isDark ? "rgba(31, 41, 55, 1)" : "rgba(255, 255, 255, 1)",
464
+ color: isDark ? "#ffffff" : "#111827"
465
+ }),
466
+ messageSystem: (isDark) => ({
467
+ backgroundColor: isDark ? "rgba(31, 41, 55, 0.95)" : "rgba(255, 255, 255, 0.95)",
468
+ color: isDark ? "#e5e7eb" : "#374151"
469
+ }),
470
+ // Welcome message
471
+ welcomeTitle: (isDark) => ({
472
+ color: isDark ? "#ffffff" : "#111827",
473
+ fontWeight: "bold"
474
+ }),
475
+ welcomeContent: (isDark) => ({
476
+ color: isDark ? "#ffffff" : "#374151"
477
+ }),
478
+ commandText: (isDark) => ({
479
+ color: isDark ? "#93c5fd" : "#1d4ed8"
480
+ }),
481
+ commandDesc: (isDark) => ({
482
+ color: isDark ? "#ffffff" : "#6b7280"
483
+ }),
484
+ // Info popup
485
+ infoText: (isDark) => ({
486
+ color: isDark ? "#ffffff" : "#374151"
487
+ }),
488
+ // Minimized message
489
+ minimizedMessage: (isDark) => ({
490
+ color: isDark ? "#ffffff" : "#374151"
491
+ }),
492
+ minimizedPrompt: (isDark) => ({
493
+ color: isDark ? "#ffffff" : "#9ca3af"
494
+ })
495
+ };
496
+ var THEME_CLASSES = {
497
+ // Message bubbles
498
+ message: {
499
+ user: "bg-gradient-to-br from-blue-600 to-purple-600 text-white ml-auto",
500
+ ai: "bg-white dark:bg-gray-800 text-gray-900 dark:text-white border border-gray-200 dark:border-gray-600",
501
+ system: "bg-white/70 dark:bg-gray-800/70 text-gray-700 dark:text-gray-200 border border-gray-200/40 dark:border-gray-600/40",
502
+ timestamp: {
503
+ user: "border-white/20 text-white/80",
504
+ other: "border-gray-300/30 dark:border-gray-500/30 text-gray-600 dark:text-gray-300"
505
+ }
506
+ },
507
+ // Header action buttons
508
+ button: {
509
+ theme: "p-2 text-yellow-600 dark:text-yellow-400 hover:text-yellow-700 dark:hover:text-yellow-300 transition-colors rounded-lg hover:bg-white/30",
510
+ home: "p-2 text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 transition-colors rounded-lg hover:bg-white/30",
511
+ dock: "p-2 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 transition-colors rounded-lg hover:bg-white/30",
512
+ info: "p-2 text-cyan-600 dark:text-cyan-400 hover:text-cyan-700 dark:hover:text-cyan-300 transition-colors rounded-lg hover:bg-white/30",
513
+ more: "p-2 text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100 transition-colors rounded-lg hover:bg-white/30",
514
+ minimize: "p-2 text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 transition-colors rounded-lg hover:bg-white/30",
515
+ clear: "p-2 text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 transition-colors rounded-lg hover:bg-white/30",
516
+ close: "p-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors rounded-lg hover:bg-white/30",
517
+ floatingClear: "p-1 text-gray-400 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
518
+ },
519
+ // Input field
520
+ input: {
521
+ field: "w-full pl-4 pr-12 py-3 text-sm text-gray-900 dark:text-white placeholder:text-gray-500 dark:placeholder:text-gray-300 rounded-3xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all shadow-sm",
522
+ sendButton: "absolute right-2 top-1/2 -translate-y-1/2 p-2 bg-blue-600 text-white rounded-full hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-all hover:scale-110"
523
+ },
524
+ // Welcome message
525
+ welcome: {
526
+ container: "bg-gradient-to-br from-blue-500/20 to-purple-500/20 backdrop-blur-sm p-4 rounded-2xl border border-blue-200/30 dark:border-blue-500/30 shadow-lg",
527
+ title: "font-bold text-gray-900 dark:text-white mb-2 text-sm",
528
+ content: "text-sm text-gray-700 dark:text-white mb-3 leading-relaxed",
529
+ commandsContainer: "bg-white/60 dark:bg-gray-800/60 backdrop-blur-sm p-3 rounded-xl border border-gray-200/30 dark:border-gray-600/30 shadow-sm",
530
+ commandsHeader: "text-xs font-medium text-gray-900 dark:text-white mb-2",
531
+ commandButton: "w-full text-left px-3 py-2 rounded-xl hover:bg-white/70 dark:hover:bg-gray-700/70 transition-all group border border-transparent hover:border-blue-200/50 dark:hover:border-blue-400/50 hover:shadow-md",
532
+ commandText: "text-sm font-medium text-blue-700 dark:text-blue-200 group-hover:text-blue-900 dark:group-hover:text-blue-100",
533
+ commandDesc: "text-xs text-gray-500 dark:text-gray-100 mt-0.5"
534
+ },
535
+ // Text colors for various elements
536
+ text: {
537
+ title: "font-bold text-gray-900 dark:text-white text-base truncate",
538
+ floatingTitle: "font-medium text-sm text-gray-900 dark:text-white",
539
+ minimizedMessage: "text-sm text-gray-700 dark:text-white line-clamp-2",
540
+ minimizedUser: "text-xs text-blue-600 dark:text-blue-200 line-clamp-1",
541
+ minimizedPrompt: "text-xs text-gray-400 dark:text-gray-100 text-center",
542
+ floatingTimestamp: "text-xs text-gray-400 dark:text-gray-100 mt-1 px-1 opacity-0 group-hover:opacity-100 transition-opacity",
543
+ infoPopup: "px-4 py-3 bg-blue-500/10 backdrop-blur-sm border-b border-blue-200/30 text-sm text-gray-700 dark:text-white"
544
+ },
545
+ // Backgrounds and containers
546
+ bg: {
547
+ header: "flex items-center justify-between p-4 border-b border-white/20",
548
+ headerGradient: "bg-gradient-to-r from-blue-500/20 to-purple-500/20",
549
+ headerLight: "bg-gradient-to-r from-blue-50 to-purple-50",
550
+ inputForm: "p-4",
551
+ inputFormLight: "bg-gray-50 dark:bg-gray-900",
552
+ bubble: "w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-xl hover:shadow-2xl transition-all duration-300 flex items-center justify-center relative hover:scale-110"
553
+ }
554
+ };
555
+ var DEFAULT_CONFIG = {
556
+ title: "Supernal Interface",
557
+ avatar: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: "/logo.svg", alt: "Supernal", className: "w-6 h-6" }),
558
+ description: "I'm a TOOL system AI can use to control this site",
559
+ placeholder: "Try: toggle notifications",
560
+ sendButtonLabel: "Send",
561
+ glassMode: true,
562
+ welcome: {
563
+ enabled: true,
564
+ title: "Welcome - I'm NOT an AI",
565
+ content: "I'm a tool system that AI assistants (like Claude, GPT) can use to navigate and control this site. This enables agentic UX \u2014 instead of clicking around, you tell an AI what you want, and it uses me to do it.",
566
+ suggestedCommands: [
567
+ { text: "open the docs", desc: "Navigate to documentation" },
568
+ { text: "show me the story system", desc: "View story system guide" },
569
+ { text: "go to examples", desc: "Browse code examples" }
570
+ ]
571
+ },
572
+ theme: {
573
+ primary: "blue",
574
+ secondary: "purple",
575
+ background: "white"
576
+ }
577
+ };
578
+ var InputField = ({
579
+ compact = false,
580
+ inputValue,
581
+ onInputChange,
582
+ onSubmit,
583
+ placeholder,
584
+ glassClasses,
585
+ theme,
586
+ inputRef,
587
+ sendButtonLabel
588
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("form", { onSubmit, className: compact ? "flex space-x-2" : THEME_CLASSES.bg.inputForm + " bg-transparent", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: compact ? "flex space-x-2 flex-1" : "relative", children: [
589
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
590
+ "input",
591
+ {
592
+ ref: compact ? void 0 : inputRef,
593
+ type: "text",
594
+ value: inputValue,
595
+ onChange: (e) => onInputChange(e.target.value),
596
+ placeholder,
597
+ className: compact ? `flex-1 px-3 py-2 text-xs border rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all ${glassClasses}` : `${THEME_CLASSES.input.field} ${glassClasses}`,
598
+ style: INLINE_STYLES.input(theme === "dark"),
599
+ "data-testid": Components.ChatInput
600
+ }
601
+ ),
602
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
603
+ "button",
604
+ {
605
+ type: "submit",
606
+ disabled: !inputValue.trim(),
607
+ className: compact ? "px-3 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl hover:from-blue-600 hover:to-blue-700 disabled:from-gray-300 disabled:to-gray-400 disabled:cursor-not-allowed transition-all text-xs font-medium shadow-md hover:shadow-lg" : THEME_CLASSES.input.sendButton,
608
+ "data-testid": Components.ChatSendButton,
609
+ title: sendButtonLabel,
610
+ children: compact ? "\u2192" : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", strokeWidth: "2.5", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 5l7 7m0 0l-7 7m7-7H3" }) })
611
+ }
612
+ )
613
+ ] }) });
614
+ var Avatar = ({ avatar, size = "normal" }) => {
615
+ if (!avatar) return null;
616
+ if (typeof avatar === "string") {
617
+ return size === "small" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-lg", children: avatar }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center shadow-md", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-white text-sm font-bold", children: avatar }) });
618
+ }
619
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: avatar });
353
620
  };
354
- var CHAT_EXPANDED_KEY = "supernal-chat-expanded";
355
621
  var ChatBubble = ({
356
- theme = "auto",
622
+ messages,
623
+ onSendMessage,
624
+ onClearChat,
357
625
  position = "bottom-right",
358
- welcomeMessage
626
+ variant = "full",
627
+ config: userConfig,
628
+ defaultExpanded = true,
629
+ storageKey = "chat-bubble-state"
359
630
  }) => {
360
- const { messages, sendMessage, clearMessages } = useChatContext();
361
- const [isExpanded, setIsExpanded] = (0, import_react3.useState)(false);
631
+ const config = { ...DEFAULT_CONFIG, ...userConfig };
632
+ const [isExpanded, setIsExpanded] = (0, import_react3.useState)(defaultExpanded);
633
+ const [isMinimized, setIsMinimized] = (0, import_react3.useState)(false);
362
634
  const [inputValue, setInputValue] = (0, import_react3.useState)("");
363
635
  const [lastReadMessageCount, setLastReadMessageCount] = (0, import_react3.useState)(0);
364
- const [showWelcome, setShowWelcome] = (0, import_react3.useState)(true);
365
- const [isClient, setIsClient] = (0, import_react3.useState)(false);
636
+ const [showWelcome, setShowWelcome] = (0, import_react3.useState)(
637
+ config.welcome?.enabled && messages.length === 0
638
+ );
639
+ const [showInfo, setShowInfo] = (0, import_react3.useState)(false);
640
+ const [isDragging, setIsDragging] = (0, import_react3.useState)(false);
641
+ const [isDocked, setIsDocked] = (0, import_react3.useState)(true);
642
+ const [panelPosition, setPanelPosition] = (0, import_react3.useState)({ x: 0, y: 0 });
643
+ const [theme, setTheme] = (0, import_react3.useState)("light");
644
+ const [showMoreMenu, setShowMoreMenu] = (0, import_react3.useState)(false);
645
+ const [, setTimestampTick] = (0, import_react3.useState)(0);
366
646
  const messagesEndRef = (0, import_react3.useRef)(null);
367
647
  const inputRef = (0, import_react3.useRef)(null);
648
+ const panelRef = (0, import_react3.useRef)(null);
649
+ const dragRef = (0, import_react3.useRef)(null);
650
+ const formatRelativeTime = (timestamp) => {
651
+ const now = /* @__PURE__ */ new Date();
652
+ const messageTime = new Date(timestamp);
653
+ const diffMs = now.getTime() - messageTime.getTime();
654
+ const diffSeconds = Math.floor(diffMs / 1e3);
655
+ const diffMinutes = Math.floor(diffSeconds / 60);
656
+ const diffHours = Math.floor(diffMinutes / 60);
657
+ const diffDays = Math.floor(diffHours / 24);
658
+ if (diffSeconds < 60) return "just now";
659
+ if (diffMinutes < 60) return `${diffMinutes} ${diffMinutes === 1 ? "minute" : "minutes"} ago`;
660
+ if (diffHours < 24) return `${diffHours} ${diffHours === 1 ? "hour" : "hours"} ago`;
661
+ if (diffDays < 7) return `${diffDays} ${diffDays === 1 ? "day" : "days"} ago`;
662
+ return messageTime.toLocaleDateString();
663
+ };
368
664
  (0, import_react3.useEffect)(() => {
369
- setIsClient(true);
370
- if (messages.length > 0) {
371
- setShowWelcome(false);
665
+ if (variant === "full") {
666
+ try {
667
+ const stored = localStorage.getItem(storageKey);
668
+ if (stored !== null) {
669
+ const state = JSON.parse(stored);
670
+ setIsExpanded(state.isExpanded ?? defaultExpanded);
671
+ setIsMinimized(state.isMinimized ?? false);
672
+ setIsDocked(state.isDocked ?? true);
673
+ setPanelPosition(state.panelPosition || { x: 0, y: 0 });
674
+ setTheme(state.theme || "light");
675
+ }
676
+ } catch {
677
+ }
372
678
  }
373
- }, [messages.length]);
679
+ }, [storageKey, variant, defaultExpanded]);
374
680
  (0, import_react3.useEffect)(() => {
375
- try {
376
- const stored = localStorage.getItem(CHAT_EXPANDED_KEY);
377
- if (stored !== null) {
378
- setIsExpanded(JSON.parse(stored));
379
- }
380
- } catch {
681
+ if (typeof window !== "undefined") {
682
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
683
+ setTheme(isDark ? "dark" : "light");
381
684
  }
382
685
  }, []);
686
+ (0, import_react3.useEffect)(() => {
687
+ const interval = setInterval(() => {
688
+ setTimestampTick((tick) => tick + 1);
689
+ }, 6e4);
690
+ return () => clearInterval(interval);
691
+ }, []);
692
+ (0, import_react3.useEffect)(() => {
693
+ if (variant === "full") {
694
+ try {
695
+ localStorage.setItem(
696
+ storageKey,
697
+ JSON.stringify({ isExpanded, isMinimized, isDocked, panelPosition, theme })
698
+ );
699
+ } catch (error) {
700
+ console.error("Failed to save chat state:", error);
701
+ }
702
+ }
703
+ }, [isExpanded, isMinimized, isDocked, panelPosition, theme, storageKey, variant]);
383
704
  const { registerInput } = useChatInput();
384
705
  (0, import_react3.useEffect)(() => {
385
706
  registerInput((text, submit = false) => {
386
707
  setInputValue(text);
387
- if (!isExpanded) {
708
+ if (!isExpanded && variant === "full") {
388
709
  setIsExpanded(true);
389
710
  }
390
711
  setTimeout(() => {
391
712
  inputRef.current?.focus();
392
713
  if (submit) {
393
- sendMessage(text);
714
+ onSendMessage(text);
394
715
  setInputValue("");
395
716
  }
396
717
  }, 100);
397
718
  });
398
- }, [registerInput, sendMessage, isExpanded]);
719
+ }, [registerInput, onSendMessage]);
399
720
  const unreadCount = Math.max(0, messages.length - lastReadMessageCount);
400
- const hasUnread = unreadCount > 0 && !isExpanded;
721
+ const hasUnread = unreadCount > 0 && !isExpanded && variant === "full";
401
722
  (0, import_react3.useEffect)(() => {
402
- if (isExpanded) {
723
+ if (isExpanded || variant === "floating") {
403
724
  messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
404
725
  setLastReadMessageCount(messages.length);
405
726
  if (messages.length > 0) {
406
727
  setShowWelcome(false);
407
728
  }
408
- inputRef.current?.focus();
729
+ if (variant === "full") {
730
+ inputRef.current?.focus();
731
+ }
409
732
  }
410
- }, [messages, isExpanded]);
733
+ }, [messages, isExpanded, variant]);
411
734
  (0, import_react3.useEffect)(() => {
412
- if (isExpanded) {
735
+ if (isExpanded && variant === "full") {
413
736
  inputRef.current?.focus();
414
737
  }
415
- }, [isExpanded]);
738
+ }, [isExpanded, variant]);
416
739
  (0, import_react3.useEffect)(() => {
417
740
  const handleKeyDown = (e) => {
741
+ if (variant !== "full") return;
742
+ if (e.key === "/" && !isExpanded) {
743
+ const target = e.target;
744
+ if (target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") {
745
+ e.preventDefault();
746
+ setIsExpanded(true);
747
+ setTimeout(() => inputRef.current?.focus(), 0);
748
+ }
749
+ }
750
+ if (e.key === "Escape") {
751
+ if (showMoreMenu) {
752
+ setShowMoreMenu(false);
753
+ } else if (showInfo) {
754
+ setShowInfo(false);
755
+ } else if (isExpanded) {
756
+ setIsExpanded(false);
757
+ }
758
+ }
418
759
  if ((e.metaKey || e.ctrlKey) && e.key === "/") {
419
760
  e.preventDefault();
420
761
  if (!isExpanded) {
@@ -425,151 +766,456 @@ var ChatBubble = ({
425
766
  };
426
767
  window.addEventListener("keydown", handleKeyDown);
427
768
  return () => window.removeEventListener("keydown", handleKeyDown);
428
- }, [isExpanded]);
769
+ }, [isExpanded, showInfo, showMoreMenu, variant]);
770
+ (0, import_react3.useEffect)(() => {
771
+ if (!showMoreMenu) return;
772
+ const handleClickOutside = (e) => {
773
+ const target = e.target;
774
+ if (!target.closest("[data-more-menu]")) {
775
+ setShowMoreMenu(false);
776
+ }
777
+ };
778
+ document.addEventListener("mousedown", handleClickOutside);
779
+ return () => document.removeEventListener("mousedown", handleClickOutside);
780
+ }, [showMoreMenu]);
781
+ const handlePanelMouseDown = (e) => {
782
+ if (variant !== "full" || !isExpanded) return;
783
+ const target = e.target;
784
+ if (!target.closest("[data-drag-handle]")) return;
785
+ if (target.closest("button") || target.closest("svg") || target.closest('[role="button"]')) {
786
+ return;
787
+ }
788
+ e.preventDefault();
789
+ setIsDragging(true);
790
+ const rect = panelRef.current?.getBoundingClientRect();
791
+ if (!rect) return;
792
+ const currentCenterX = rect.left + rect.width / 2;
793
+ const currentCenterY = rect.top + rect.height / 2;
794
+ const viewportCenterX = window.innerWidth / 2;
795
+ const viewportCenterY = window.innerHeight / 2;
796
+ const targetX = currentCenterX - viewportCenterX;
797
+ const targetY = currentCenterY - viewportCenterY;
798
+ setIsDocked(false);
799
+ setPanelPosition({ x: targetX, y: targetY });
800
+ dragRef.current = {
801
+ startX: e.clientX,
802
+ startY: e.clientY,
803
+ initialX: targetX,
804
+ initialY: targetY
805
+ };
806
+ };
807
+ (0, import_react3.useEffect)(() => {
808
+ if (!isDragging || !dragRef.current) return;
809
+ const handleMouseMove = (e) => {
810
+ if (!dragRef.current) return;
811
+ const deltaX = e.clientX - dragRef.current.startX;
812
+ const deltaY = e.clientY - dragRef.current.startY;
813
+ setPanelPosition({
814
+ x: dragRef.current.initialX + deltaX,
815
+ y: dragRef.current.initialY + deltaY
816
+ });
817
+ };
818
+ const handleMouseUp = () => {
819
+ setIsDragging(false);
820
+ dragRef.current = null;
821
+ if (panelRef.current) {
822
+ const rect = panelRef.current.getBoundingClientRect();
823
+ const threshold = 50;
824
+ const windowWidth = window.innerWidth;
825
+ const windowHeight = window.innerHeight;
826
+ if (rect.right > windowWidth - threshold || rect.left < threshold || rect.top < threshold || rect.bottom > windowHeight - threshold) {
827
+ setIsDocked(true);
828
+ setPanelPosition({ x: 0, y: 0 });
829
+ }
830
+ }
831
+ };
832
+ window.addEventListener("mousemove", handleMouseMove);
833
+ window.addEventListener("mouseup", handleMouseUp);
834
+ return () => {
835
+ window.removeEventListener("mousemove", handleMouseMove);
836
+ window.removeEventListener("mouseup", handleMouseUp);
837
+ };
838
+ }, [isDragging]);
429
839
  const handleSend = (e) => {
430
840
  e.preventDefault();
431
841
  if (!inputValue.trim()) return;
432
- sendMessage(inputValue.trim());
842
+ onSendMessage(inputValue.trim());
433
843
  setInputValue("");
434
- setTimeout(() => inputRef.current?.focus(), 0);
844
+ if (variant === "full") {
845
+ setTimeout(() => inputRef.current?.focus(), 0);
846
+ }
435
847
  };
436
848
  const handleToggle = () => {
437
- const newExpandedState = !isExpanded;
438
- setIsExpanded(newExpandedState);
439
- try {
440
- localStorage.setItem(CHAT_EXPANDED_KEY, JSON.stringify(newExpandedState));
441
- } catch {
849
+ setIsExpanded(!isExpanded);
850
+ };
851
+ const handleDock = () => {
852
+ setIsDocked(true);
853
+ setPanelPosition({ x: 0, y: 0 });
854
+ };
855
+ const handleHome = () => {
856
+ setIsDocked(true);
857
+ setPanelPosition({ x: 0, y: 0 });
858
+ setIsMinimized(false);
859
+ };
860
+ const handleClearChat = () => {
861
+ if (onClearChat) {
862
+ onClearChat();
863
+ setShowWelcome(true);
864
+ }
865
+ };
866
+ const handleToggleTheme = () => {
867
+ const newTheme = theme === "light" ? "dark" : "light";
868
+ setTheme(newTheme);
869
+ if (typeof window !== "undefined") {
870
+ document.documentElement.setAttribute("data-theme", newTheme);
442
871
  }
443
872
  };
444
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fixed bottom-4 right-4 sm:bottom-6 sm:right-6 z-[9999]", children: [
445
- isExpanded && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "absolute bottom-16 right-0 w-[calc(100vw-2rem)] sm:w-[500px] lg:w-[600px] h-[calc(100vh-10rem)] sm:h-[min(600px,calc(100vh-6rem))] lg:h-[min(700px,calc(100vh-6rem))] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col", children: [
446
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between p-3 sm:p-4 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-purple-50 rounded-t-lg", children: [
447
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2 sm:space-x-3", children: [
448
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", children: [
449
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-white text-base sm:text-lg", children: "\u{1F916}" }) }),
450
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute -bottom-1 -right-1 w-3 h-3 sm:w-4 sm:h-4 bg-green-500 rounded-full border-2 border-white" })
873
+ const dockClasses = DOCK_POSITIONS[position];
874
+ const primaryColor = config.theme?.primary || "blue";
875
+ const glassMode = config.glassMode ?? true;
876
+ const maxHeightVh = 80;
877
+ const dynamicHeight = `min(${maxHeightVh}vh, 700px)`;
878
+ const panelWidth = "min(650px, calc(100vw - 2rem))";
879
+ const glassClasses = glassMode ? "backdrop-blur-xl bg-white/70 dark:bg-gray-900/70 border border-white/20 dark:border-white/10" : "bg-white dark:bg-gray-900 border-gray-200";
880
+ const glassGradient = glassMode ? "bg-gradient-to-br from-white/90 via-white/70 to-white/50 dark:from-gray-900/80 dark:via-gray-900/70 dark:to-gray-900/60" : "bg-white dark:bg-gray-900";
881
+ if (variant === "floating") {
882
+ const recentMessage = messages[messages.length - 1];
883
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
884
+ "div",
885
+ {
886
+ className: `fixed z-50 ${isDragging ? "cursor-grabbing" : "cursor-grab"}`,
887
+ style: {
888
+ transform: `translate(${panelPosition.x}px, ${panelPosition.y}px)`,
889
+ ...!isDragging && { transition: "transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)" }
890
+ },
891
+ onMouseDown: handlePanelMouseDown,
892
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `${glassClasses} rounded-2xl shadow-2xl border p-3 max-w-xs`, children: [
893
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2", children: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { avatar: config.avatar, size: "small" }),
896
+ config.title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: THEME_CLASSES.text.floatingTitle, children: config.title })
897
+ ] }),
898
+ onClearChat && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
899
+ "button",
900
+ {
901
+ onClick: onClearChat,
902
+ className: THEME_CLASSES.button.floatingClear,
903
+ title: "Clear chat",
904
+ "data-testid": ChatNames.clearButton,
905
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
906
+ }
907
+ )
908
+ ] }),
909
+ recentMessage && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `mb-2 group flex items-center gap-2 ${recentMessage.type === "user" ? "flex-row-reverse" : "flex-row"}`, children: [
910
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
911
+ "div",
912
+ {
913
+ className: `text-xs px-3 py-2 rounded-xl transition-all ${recentMessage.type === "user" ? "bg-gradient-to-br from-blue-500 to-blue-600 text-white shadow-lg" : recentMessage.type === "ai" ? "bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-700 dark:to-gray-800 text-gray-900 dark:text-white shadow-md" : "bg-gradient-to-br from-yellow-100 to-yellow-200 text-yellow-900 shadow-md"}`,
914
+ children: recentMessage.text.length > 60 ? `${recentMessage.text.slice(0, 60)}...` : recentMessage.text
915
+ }
916
+ ),
917
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
918
+ "div",
919
+ {
920
+ className: `text-xs opacity-0 group-hover:opacity-70 transition-opacity whitespace-nowrap flex-shrink-0 ${recentMessage.type === "user" ? "text-gray-400 dark:text-gray-500 text-left" : "text-gray-600 dark:text-gray-400 text-right"}`,
921
+ title: typeof window !== "undefined" ? new Date(recentMessage.timestamp).toLocaleString() : "",
922
+ children: typeof window !== "undefined" ? formatRelativeTime(recentMessage.timestamp) : ""
923
+ }
924
+ )
451
925
  ] }),
452
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
453
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "font-bold text-gray-900 text-sm sm:text-base", children: "Supernal Intelligence Interface" }),
454
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-gray-600 hidden sm:block", children: "I'm a TOOL system AI can use to control this site" })
455
- ] })
456
- ] }),
457
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-1", children: [
458
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
459
- "button",
460
- {
461
- onClick: clearMessages,
462
- className: "p-1 text-gray-400 hover:text-gray-600 transition-colors",
463
- title: "Clear chat",
464
- "data-testid": ChatNames.clearButton,
465
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) })
466
- }
467
- ),
468
926
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
469
- "button",
927
+ InputField,
470
928
  {
471
- onClick: handleToggle,
472
- className: "p-1 text-gray-400 hover:text-gray-600 transition-colors",
473
- title: "Minimize chat",
474
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })
929
+ compact: true,
930
+ inputValue,
931
+ onInputChange: setInputValue,
932
+ onSubmit: handleSend,
933
+ placeholder: config.placeholder,
934
+ glassClasses,
935
+ theme,
936
+ sendButtonLabel: config.sendButtonLabel
475
937
  }
476
938
  )
477
939
  ] })
478
- ] }),
479
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 overflow-y-auto p-3 sm:p-4 space-y-3", "data-testid": ChatNames.messages, children: [
480
- showWelcome && messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "bg-gradient-to-br from-blue-50 to-purple-50 p-3 sm:p-4 rounded-lg border border-blue-200 mb-4", children: [
481
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className: "font-bold text-gray-900 mb-2 text-xs sm:text-sm", children: "\u{1F44B} Welcome! I'm NOT an AI" }),
482
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-xs text-gray-700 mb-3", children: [
483
- "I'm a ",
484
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "tool system" }),
485
- " that AI assistants (like Claude, GPT) can use to navigate and control this site. This enables ",
486
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "agentic UX" }),
487
- " \u2014 instead of clicking around, you tell an AI what you want, and it uses me to do it."
488
- ] }),
489
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "bg-white p-2 sm:p-3 rounded border border-gray-200 mb-3", children: [
490
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs font-medium text-gray-900 mb-2", children: "Try these commands:" }),
491
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "space-y-1", children: [
492
- { text: "open the docs", desc: "Navigate to documentation" },
493
- { text: "show me the story system", desc: "View story system guide" },
494
- { text: "go to examples", desc: "Browse code examples" }
495
- ].map((cmd) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
940
+ }
941
+ );
942
+ }
943
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `fixed ${dockClasses.container} z-50`, children: [
944
+ isExpanded && isMinimized && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
945
+ "div",
946
+ {
947
+ className: `absolute ${dockClasses.panel} ${glassClasses} rounded-3xl shadow-2xl border p-4 transition-all duration-300`,
948
+ style: { width: panelWidth, maxWidth: "400px" },
949
+ children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between mb-3", children: [
951
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2", children: [
952
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { avatar: config.avatar, size: "small" }),
953
+ config.title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: THEME_CLASSES.text.floatingTitle, children: config.title })
954
+ ] }),
955
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
496
956
  "button",
497
957
  {
498
- onClick: () => {
499
- setInputValue(cmd.text);
500
- setShowWelcome(false);
501
- setTimeout(() => inputRef.current?.focus(), 0);
502
- },
503
- className: "w-full text-left px-2 py-1.5 rounded hover:bg-blue-50 transition-colors group",
504
- children: [
505
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-xs font-mono text-blue-700 group-hover:text-blue-900", children: [
506
- '"',
507
- cmd.text,
508
- '"'
509
- ] }),
510
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-gray-500 hidden sm:block", children: cmd.desc })
511
- ]
512
- },
513
- cmd.text
514
- )) })
958
+ onClick: () => setIsMinimized(false),
959
+ className: "p-1 text-gray-400 hover:text-gray-600 dark:text-gray-300 dark:hover:text-gray-200 transition-colors",
960
+ title: "Expand chat",
961
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 15l7-7 7 7" }) })
962
+ }
963
+ )
515
964
  ] }),
516
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-gray-600 italic", children: "Type a command or click a suggestion above to start" })
517
- ] }),
518
- messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col", children: [
965
+ (() => {
966
+ const lastAiMessage = [...messages].reverse().find((m) => m.type === "ai");
967
+ return lastAiMessage ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `text-xs px-3 py-2 rounded-xl ${THEME_CLASSES.message.ai}`, style: INLINE_STYLES.messageAI(theme === "dark"), children: lastAiMessage.text.length > 100 ? `${lastAiMessage.text.slice(0, 100)}...` : lastAiMessage.text }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: THEME_CLASSES.text.minimizedMessage, style: INLINE_STYLES.minimizedMessage(theme === "dark"), children: "No AI responses yet" }) });
968
+ })(),
519
969
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
520
- "div",
970
+ InputField,
521
971
  {
522
- className: `inline-block px-3 py-2 rounded-lg max-w-[85%] sm:max-w-xs text-xs sm:text-sm ${message.type === "user" ? "bg-blue-600 text-white ml-auto" : message.type === "ai" ? "bg-gray-200 text-gray-800" : "bg-yellow-100 text-yellow-800 text-xs"}`,
523
- "data-testid": `chat-message-${message.type}`,
524
- children: message.text
972
+ compact: true,
973
+ inputValue,
974
+ onInputChange: setInputValue,
975
+ onSubmit: handleSend,
976
+ placeholder: config.placeholder,
977
+ glassClasses,
978
+ theme,
979
+ sendButtonLabel: config.sendButtonLabel
525
980
  }
526
- ),
527
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-gray-400 mt-1 px-1", children: new Date(message.timestamp).toLocaleTimeString() })
528
- ] }, message.id)),
529
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: messagesEndRef })
530
- ] }),
531
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleSend, className: "p-3 sm:p-4 border-t border-gray-200", children: [
532
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex space-x-2", children: [
533
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
534
- "input",
981
+ )
982
+ ]
983
+ }
984
+ ),
985
+ isExpanded && !isMinimized && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
986
+ "div",
987
+ {
988
+ ref: panelRef,
989
+ className: `${isDocked ? "absolute " + dockClasses.panel : "fixed"} ${glassGradient} rounded-3xl shadow-2xl border border-white/20 dark:border-white/10 backdrop-blur-xl flex flex-col overflow-hidden transition-all duration-300`,
990
+ style: {
991
+ width: panelWidth,
992
+ height: dynamicHeight,
993
+ ...!isDocked && {
994
+ left: "50%",
995
+ top: "50%",
996
+ transform: `translate(calc(-50% + ${panelPosition.x}px), calc(-50% + ${panelPosition.y}px))`
997
+ },
998
+ ...isDragging && { cursor: "grabbing" }
999
+ },
1000
+ children: [
1001
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1002
+ "div",
535
1003
  {
536
- ref: inputRef,
537
- type: "text",
538
- value: inputValue,
539
- onChange: (e) => setInputValue(e.target.value),
540
- placeholder: "Try: toggle notifications",
541
- className: "flex-1 px-3 py-2 text-xs sm:text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-800",
542
- "data-testid": ChatNames.input
1004
+ "data-drag-handle": true,
1005
+ className: `${THEME_CLASSES.bg.header} ${glassMode ? THEME_CLASSES.bg.headerGradient : THEME_CLASSES.bg.headerLight} cursor-move`,
1006
+ onMouseDown: handlePanelMouseDown,
1007
+ children: [
1008
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-3", children: [
1009
+ config.avatar && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative flex-shrink-0", children: [
1010
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { avatar: config.avatar }),
1011
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white" })
1012
+ ] }),
1013
+ config.title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: THEME_CLASSES.text.title, children: config.title }) })
1014
+ ] }),
1015
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-1 flex-shrink-0 relative", "data-more-menu": true, children: [
1016
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1017
+ "button",
1018
+ {
1019
+ onClick: () => setShowMoreMenu(!showMoreMenu),
1020
+ className: THEME_CLASSES.button.more,
1021
+ title: "More options",
1022
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" }) })
1023
+ }
1024
+ ),
1025
+ showMoreMenu && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "absolute right-0 top-10 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-600 p-2 z-[100] min-w-[160px]", "data-more-menu": true, children: [
1026
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1027
+ "button",
1028
+ {
1029
+ onClick: () => {
1030
+ handleToggleTheme();
1031
+ setShowMoreMenu(false);
1032
+ },
1033
+ className: "w-full flex items-center space-x-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",
1034
+ children: [
1035
+ theme === "light" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" }) }),
1036
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: theme === "light" ? "Dark mode" : "Light mode" })
1037
+ ]
1038
+ }
1039
+ ),
1040
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1041
+ "button",
1042
+ {
1043
+ onClick: () => {
1044
+ handleHome();
1045
+ setShowMoreMenu(false);
1046
+ },
1047
+ className: "w-full flex items-center space-x-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",
1048
+ children: [
1049
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" }) }),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Reset position" })
1051
+ ]
1052
+ }
1053
+ ),
1054
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1055
+ "button",
1056
+ {
1057
+ onClick: () => {
1058
+ setShowInfo(!showInfo);
1059
+ setShowMoreMenu(false);
1060
+ },
1061
+ className: "w-full flex items-center space-x-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",
1062
+ children: [
1063
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "How to use" })
1065
+ ]
1066
+ }
1067
+ ),
1068
+ onClearChat && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1069
+ "button",
1070
+ {
1071
+ onClick: () => {
1072
+ handleClearChat();
1073
+ setShowMoreMenu(false);
1074
+ },
1075
+ className: "w-full flex items-center space-x-2 px-3 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",
1076
+ children: [
1077
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Clear chat" })
1079
+ ]
1080
+ }
1081
+ )
1082
+ ] }),
1083
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1084
+ "button",
1085
+ {
1086
+ onClick: () => setIsMinimized(true),
1087
+ className: THEME_CLASSES.button.minimize,
1088
+ title: "Minimize",
1089
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 12H4" }) })
1090
+ }
1091
+ ),
1092
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1093
+ "button",
1094
+ {
1095
+ onClick: handleToggle,
1096
+ className: THEME_CLASSES.button.close,
1097
+ title: "Close",
1098
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
1099
+ }
1100
+ )
1101
+ ] })
1102
+ ]
543
1103
  }
544
1104
  ),
1105
+ showInfo && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: THEME_CLASSES.text.infoPopup, style: INLINE_STYLES.infoText(theme === "dark"), children: [
1106
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-bold mb-2", children: "How to Use" }),
1107
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2 text-xs", style: INLINE_STYLES.infoText(theme === "dark"), children: [
1108
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1109
+ "\u2022 ",
1110
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Theme:" }),
1111
+ " Toggle between light and dark modes"
1112
+ ] }),
1113
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1114
+ "\u2022 ",
1115
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Home:" }),
1116
+ " Reset chat position to default"
1117
+ ] }),
1118
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1119
+ "\u2022 ",
1120
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Minimize:" }),
1121
+ " Compact view with last message"
1122
+ ] }),
1123
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1124
+ "\u2022 ",
1125
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Clear:" }),
1126
+ " Delete all messages and start fresh"
1127
+ ] }),
1128
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1129
+ "\u2022 ",
1130
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Drag:" }),
1131
+ " Click and drag header to reposition"
1132
+ ] }),
1133
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1134
+ "\u2022 ",
1135
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Keyboard:" }),
1136
+ ' Press "/" to open, Esc to close'
1137
+ ] })
1138
+ ] }),
1139
+ config.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-3 pt-3 border-t border-gray-300/30 dark:border-gray-600/30", children: config.description })
1140
+ ] }),
1141
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 overflow-y-auto p-4 space-y-2", children: [
1142
+ showWelcome && messages.length === 0 && config.welcome?.enabled && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: THEME_CLASSES.welcome.container, children: [
1143
+ config.welcome.title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className: THEME_CLASSES.welcome.title, style: INLINE_STYLES.welcomeTitle(theme === "dark"), children: config.welcome.title }),
1144
+ config.welcome.content && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: THEME_CLASSES.welcome.content, style: INLINE_STYLES.welcomeContent(theme === "dark"), children: config.welcome.content }),
1145
+ config.welcome.suggestedCommands && config.welcome.suggestedCommands.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: THEME_CLASSES.welcome.commandsContainer, children: [
1146
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: THEME_CLASSES.welcome.commandsHeader, children: "Try these commands:" }),
1147
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "space-y-1", children: config.welcome.suggestedCommands.map((cmd, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1148
+ "button",
1149
+ {
1150
+ onClick: () => {
1151
+ setInputValue(cmd.text);
1152
+ setShowWelcome(false);
1153
+ setTimeout(() => inputRef.current?.focus(), 0);
1154
+ },
1155
+ className: THEME_CLASSES.welcome.commandButton,
1156
+ children: [
1157
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: THEME_CLASSES.welcome.commandText, style: INLINE_STYLES.commandText(theme === "dark"), children: [
1158
+ '"',
1159
+ cmd.text,
1160
+ '"'
1161
+ ] }),
1162
+ cmd.desc && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: THEME_CLASSES.welcome.commandDesc, style: INLINE_STYLES.commandDesc(theme === "dark"), children: cmd.desc })
1163
+ ]
1164
+ },
1165
+ idx
1166
+ )) })
1167
+ ] })
1168
+ ] }),
1169
+ messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `group flex items-center gap-2 mb-2 ${message.type === "user" ? "flex-row-reverse" : "flex-row"}`, children: [
1170
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1171
+ "div",
1172
+ {
1173
+ className: `inline-block px-4 py-2.5 rounded-2xl max-w-[80%] text-sm shadow-sm transition-all ${message.type === "user" ? THEME_CLASSES.message.user : message.type === "ai" ? THEME_CLASSES.message.ai : THEME_CLASSES.message.system}`,
1174
+ style: message.type === "user" ? INLINE_STYLES.messageUser() : message.type === "ai" ? INLINE_STYLES.messageAI(theme === "dark") : INLINE_STYLES.messageSystem(theme === "dark"),
1175
+ "data-testid": `chat-message-${message.type}`,
1176
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "break-words leading-relaxed", children: message.text })
1177
+ }
1178
+ ),
1179
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1180
+ "div",
1181
+ {
1182
+ className: `text-xs opacity-0 group-hover:opacity-70 transition-opacity whitespace-nowrap flex-shrink-0 ${message.type === "user" ? "text-gray-400 dark:text-gray-500 text-left" : "text-gray-600 dark:text-gray-400 text-right"}`,
1183
+ title: typeof window !== "undefined" ? new Date(message.timestamp).toLocaleString() : "",
1184
+ children: typeof window !== "undefined" ? formatRelativeTime(message.timestamp) : ""
1185
+ }
1186
+ )
1187
+ ] }, message.id)),
1188
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: messagesEndRef })
1189
+ ] }),
545
1190
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
546
- "button",
1191
+ InputField,
547
1192
  {
548
- type: "submit",
549
- disabled: !inputValue.trim(),
550
- className: "px-3 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors text-xs sm:text-sm font-medium",
551
- "data-testid": ChatNames.sendButton,
552
- children: "Execute"
1193
+ inputValue,
1194
+ onInputChange: setInputValue,
1195
+ onSubmit: handleSend,
1196
+ placeholder: config.placeholder,
1197
+ glassClasses,
1198
+ theme,
1199
+ inputRef,
1200
+ sendButtonLabel: config.sendButtonLabel
553
1201
  }
554
1202
  )
555
- ] }),
556
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-2 text-xs text-gray-500 hidden sm:block", children: [
557
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Demo:" }),
558
- " Commands execute @Tool methods and update widgets above"
559
- ] })
560
- ] })
561
- ] }),
562
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1203
+ ]
1204
+ }
1205
+ ),
1206
+ !isExpanded && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
563
1207
  "button",
564
1208
  {
565
1209
  onClick: handleToggle,
566
- className: "absolute bottom-0 right-0 w-12 h-12 sm:w-14 sm:h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-lg transition-all duration-200 flex items-center justify-center",
1210
+ className: THEME_CLASSES.bg.bubble,
567
1211
  "data-testid": ChatNames.bubble,
568
- title: isExpanded ? "Minimize chat" : "Open AI chat",
569
- children: isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-5 h-5 sm:w-6 sm:h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-5 h-5 sm:w-6 sm:h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" }) })
1212
+ title: "Open chat",
1213
+ children: [
1214
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: "/logo.svg", alt: "Supernal", className: "w-8 h-8" }),
1215
+ hasUnread && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute -top-1 -right-1 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center animate-pulse shadow-lg", "data-testid": "unread-indicator", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-white font-bold", children: unreadCount > 9 ? "9+" : unreadCount }) })
1216
+ ]
570
1217
  }
571
- ),
572
- hasUnread && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute -top-1 -right-1 w-4 h-4 sm:w-5 sm:h-5 bg-red-500 rounded-full flex items-center justify-center animate-pulse", "data-testid": "unread-indicator", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-white font-bold", children: unreadCount > 9 ? "9+" : unreadCount }) })
1218
+ )
573
1219
  ] }) });
574
1220
  };
575
1221
 
@@ -763,7 +1409,26 @@ function inferContextFromPath(path, customRoutes) {
763
1409
  }
764
1410
 
765
1411
  // src/components/SupernalProvider.tsx
1412
+ var import_browser3 = require("@supernal/interface/browser");
766
1413
  var import_jsx_runtime6 = require("react/jsx-runtime");
1414
+ function ChatBubbleConnector({
1415
+ theme,
1416
+ position,
1417
+ welcomeMessage
1418
+ }) {
1419
+ const { messages, sendMessage, clearMessages } = useChatContext();
1420
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1421
+ ChatBubble,
1422
+ {
1423
+ messages,
1424
+ onSendMessage: sendMessage,
1425
+ onClearChat: clearMessages,
1426
+ position,
1427
+ variant: "full",
1428
+ defaultExpanded: true
1429
+ }
1430
+ );
1431
+ }
767
1432
  function SupernalProvider({
768
1433
  children,
769
1434
  theme = "auto",
@@ -777,10 +1442,44 @@ function SupernalProvider({
777
1442
  onToolExecute
778
1443
  }) {
779
1444
  const shouldRenderChatBubble = !disabled;
1445
+ console.log("[SupernalProvider] disabled:", disabled, "type:", typeof disabled);
1446
+ console.log("[SupernalProvider] shouldRenderChatBubble:", shouldRenderChatBubble);
1447
+ (0, import_react6.useEffect)(() => {
1448
+ if (typeof window === "undefined") return;
1449
+ const collector = import_browser3.ExposureCollector.getInstance();
1450
+ const registeredToolIds = /* @__PURE__ */ new Set();
1451
+ const registerTools = () => {
1452
+ const allTools = import_browser3.ToolRegistry.getAllTools();
1453
+ allTools.forEach((tool) => {
1454
+ if (tool.elementId && !registeredToolIds.has(tool.toolId)) {
1455
+ const element = document.querySelector(`[data-testid="${tool.elementId}"]`);
1456
+ if (element) {
1457
+ collector.registerTool(tool.toolId, element, {
1458
+ name: tool.name,
1459
+ description: tool.description
1460
+ });
1461
+ registeredToolIds.add(tool.toolId);
1462
+ }
1463
+ }
1464
+ });
1465
+ };
1466
+ registerTools();
1467
+ const observer = new MutationObserver(() => {
1468
+ registerTools();
1469
+ });
1470
+ observer.observe(document.body, {
1471
+ childList: true,
1472
+ subtree: true
1473
+ });
1474
+ return () => {
1475
+ observer.disconnect();
1476
+ collector.destroy();
1477
+ };
1478
+ }, []);
780
1479
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChatInputProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(ChatProvider, { mode, apiKey, onToolExecute, children: [
781
1480
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AutoNavigationContext, { routes, onNavigate, children }),
782
1481
  shouldRenderChatBubble ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
783
- ChatBubble,
1482
+ ChatBubbleConnector,
784
1483
  {
785
1484
  theme,
786
1485
  position,