@supernal/interface-nextjs 1.0.8 → 1.0.10

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