@supernal/interface-nextjs 1.0.21 → 1.0.22

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
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  "use strict";
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/index.ts
@@ -23,11 +33,19 @@ var index_exports = {};
23
33
  __export(index_exports, {
24
34
  AutoNavigationContext: () => AutoNavigationContext,
25
35
  ChatBubble: () => ChatBubble,
36
+ ChatBubbleSettingsModal: () => ChatBubbleSettingsModal,
37
+ ChatBubbleVariant: () => ChatBubbleVariant,
26
38
  ChatInputProvider: () => ChatInputProvider,
27
39
  ChatProvider: () => ChatProvider,
40
+ CodeBlock: () => CodeBlock,
41
+ Components: () => Components,
28
42
  DemoAIInterface: () => DemoAIInterface,
29
43
  FuzzyMatcher: () => FuzzyMatcher,
44
+ MermaidDiagram: () => MermaidDiagram,
45
+ MessageRenderer: () => MessageRenderer,
30
46
  NavigationContextProvider: () => NavigationContextProvider,
47
+ PageLayout: () => PageLayout,
48
+ SubtitleOverlay: () => SubtitleOverlay,
31
49
  SupernalProvider: () => SupernalProvider,
32
50
  ToolManager: () => ToolManager,
33
51
  findBestMatch: () => findBestMatch,
@@ -44,7 +62,7 @@ __export(index_exports, {
44
62
  module.exports = __toCommonJS(index_exports);
45
63
 
46
64
  // src/components/SupernalProvider.tsx
47
- var import_react6 = require("react");
65
+ var import_react12 = require("react");
48
66
 
49
67
  // src/contexts/ChatInputContext.tsx
50
68
  var import_react = require("react");
@@ -241,13 +259,13 @@ function getInitialMessages() {
241
259
  },
242
260
  {
243
261
  id: "3",
244
- text: '\u{1F3AE} Try these commands:\n\u2022 "open menu" or "close menu"\n\u2022 "toggle notifications"\n\u2022 "set priority high"',
262
+ text: '\u{1F3AE} **Try these commands:**\n\n- "open menu" or "close menu"\n- "toggle notifications"\n- "set priority high"',
245
263
  type: "system",
246
264
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
247
265
  },
248
266
  {
249
267
  id: "4",
250
- text: '\u{1F5FA}\uFE0F Navigate pages:\n\u2022 "architecture" or "dashboard"\n\u2022 "demo" or "home"\n\u2022 "docs" or "examples"',
268
+ text: '\u{1F5FA}\uFE0F **Navigate pages:**\n\n- "architecture" or "dashboard"\n- "demo" or "home"\n- "docs" or "examples"',
251
269
  type: "system",
252
270
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
253
271
  },
@@ -344,74 +362,40 @@ function ChatProvider({
344
362
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatContext.Provider, { value: { messages, sendMessage, clearMessages, isLoading }, children });
345
363
  }
346
364
 
347
- // src/components/ChatBubble.tsx
348
- var import_react3 = require("react");
365
+ // src/components/ChatBubble/constants.ts
366
+ var import_react3 = __toESM(require("react"));
349
367
 
350
- // ../names/Components.ts
368
+ // src/names/Components.ts
351
369
  var Components = {
352
- // Chat components
370
+ // Chat component testids
371
+ ChatToggleButton: "chat-toggle-button",
353
372
  ChatInput: "chat-message-input",
354
373
  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"
374
+ ChatClearButton: "chat-clear-button"
375
+ };
376
+ var ChatBubbleVariant = {
377
+ full: "full",
378
+ // Full-screen chat panel
379
+ floating: "floating",
380
+ // Floating draggable widget
381
+ drawer: "drawer",
382
+ // Mobile bottom drawer
383
+ subtitle: "subtitle"
384
+ // Minimalist voice-first overlay (premium)
385
+ };
386
+ var PageLayout = {
387
+ default: "default",
388
+ // Standard page layout
389
+ landing: "landing",
390
+ // Landing page with hero
391
+ docs: "docs",
392
+ // Documentation layout
393
+ blog: "blog"
394
+ // Blog post layout
411
395
  };
412
396
 
413
- // src/components/ChatBubble.tsx
414
- var import_jsx_runtime3 = require("react/jsx-runtime");
397
+ // src/components/ChatBubble/constants.ts
398
+ var DEFAULT_LOGO = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcz4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibG9nb0dyYWRpZW50IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3R5bGU9InN0b3AtY29sb3I6IzAwNjZmZjtzdG9wLW9wYWNpdHk6MSIgLz4KICAgICAgPHN0b3Agb2Zmc2V0PSI1MCUiIHN0eWxlPSJzdG9wLWNvbG9yOiM0ZDk0ZmY7c3RvcC1vcGFjaXR5OjEiIC8+CiAgICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3R5bGU9InN0b3AtY29sb3I6IzAwY2NmZjtzdG9wLW9wYWNpdHk6MSIgLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgPC9kZWZzPgoKICA8IS0tIENpcmNsZSBiYWNrZ3JvdW5kIHdpdGggZ3JhZGllbnQgLS0+CiAgPGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMTgiIGZpbGw9InVybCgjbG9nb0dyYWRpZW50KSIgb3BhY2l0eT0iMC4xNSIgLz4KCiAgPCEtLSBUZXh0OiAnQC8nIC0gZWFzeSB0byBjaGFuZ2UgdG8gJ34rJyBvciBhbnkgb3RoZXIgY2hhcmFjdGVycyAtLT4KICA8dGV4dCB4PSIyMCIgeT0iMjciCiAgICAgICAgZm9udC1mYW1pbHk9InN5c3RlbS11aSwgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAnU2Vnb2UgVUknLCBzYW5zLXNlcmlmIgogICAgICAgIGZvbnQtc2l6ZT0iMjAiCiAgICAgICAgZm9udC13ZWlnaHQ9IjcwMCIKICAgICAgICB0ZXh0LWFuY2hvcj0ibWlkZGxlIgogICAgICAgIGZpbGw9InVybCgjbG9nb0dyYWRpZW50KSI+QC88L3RleHQ+Cjwvc3ZnPg==";
415
399
  var ChatNames = {
416
400
  bubble: Components.ChatToggleButton,
417
401
  input: Components.ChatInput,
@@ -552,9 +536,44 @@ var THEME_CLASSES = {
552
536
  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
537
  }
554
538
  };
539
+ var GLASS_RESPONSE_BUBBLE = {
540
+ light: {
541
+ background: "rgba(255, 255, 255, 0.7)",
542
+ border: "1px solid rgba(0, 0, 0, 0.08)",
543
+ backdropFilter: "blur(12px) saturate(180%)",
544
+ WebkitBackdropFilter: "blur(12px) saturate(180%)",
545
+ boxShadow: "0 2px 12px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.9)"
546
+ },
547
+ dark: {
548
+ background: "rgba(55, 65, 81, 0.6)",
549
+ border: "1px solid rgba(255, 255, 255, 0.1)",
550
+ backdropFilter: "blur(12px) saturate(180%)",
551
+ WebkitBackdropFilter: "blur(12px) saturate(180%)",
552
+ boxShadow: "0 2px 12px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.05)"
553
+ }
554
+ };
555
+ var GLASS_INVERTED = {
556
+ darkOnLight: {
557
+ background: "rgba(0, 0, 0, 0.6)",
558
+ border: "1px solid rgba(255, 255, 255, 0.15)",
559
+ backdropFilter: "blur(16px) saturate(150%)",
560
+ WebkitBackdropFilter: "blur(16px) saturate(150%)",
561
+ color: "rgba(255, 255, 255, 0.95)",
562
+ boxShadow: "0 4px 24px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1)"
563
+ },
564
+ lightOnDark: {
565
+ background: "rgba(255, 255, 255, 0.7)",
566
+ border: "1px solid rgba(0, 0, 0, 0.1)",
567
+ backdropFilter: "blur(16px) saturate(180%)",
568
+ WebkitBackdropFilter: "blur(16px) saturate(180%)",
569
+ color: "rgba(0, 0, 0, 0.9)",
570
+ boxShadow: "0 4px 24px rgba(0, 0, 0, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.9)"
571
+ }
572
+ };
555
573
  var DEFAULT_CONFIG = {
556
574
  title: "Supernal Interface",
557
- avatar: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: "/logo.svg", alt: "Supernal", className: "w-6 h-6" }),
575
+ logo: DEFAULT_LOGO,
576
+ avatar: import_react3.default.createElement("img", { src: DEFAULT_LOGO, alt: "Supernal", className: "w-6 h-6" }),
558
577
  description: "I'm a TOOL system AI can use to control this site",
559
578
  placeholder: "Try: toggle notifications",
560
579
  sendButtonLabel: "Send",
@@ -575,6 +594,9 @@ var DEFAULT_CONFIG = {
575
594
  background: "white"
576
595
  }
577
596
  };
597
+
598
+ // src/components/ChatBubble/InputField.tsx
599
+ var import_jsx_runtime3 = require("react/jsx-runtime");
578
600
  var InputField = ({
579
601
  compact = false,
580
602
  inputValue,
@@ -584,7 +606,11 @@ var InputField = ({
584
606
  glassClasses,
585
607
  theme,
586
608
  inputRef,
587
- sendButtonLabel
609
+ sendButtonLabel,
610
+ voiceEnabled = false,
611
+ isListening = false,
612
+ onMicClick,
613
+ modKey = "Ctrl"
588
614
  }) => /* @__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
615
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
590
616
  "input",
@@ -594,325 +620,2324 @@ var InputField = ({
594
620
  value: inputValue,
595
621
  onChange: (e) => onInputChange(e.target.value),
596
622
  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}`,
623
+ 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}` : `w-full pl-4 ${inputValue.trim() ? "pr-12" : "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 ${glassClasses}`,
598
624
  style: INLINE_STYLES.input(theme === "dark"),
599
- "data-testid": Components.ChatInput
625
+ "data-testid": ChatNames.input
600
626
  }
601
627
  ),
602
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
628
+ voiceEnabled && onMicClick && !compact && (!inputValue.trim() || isListening) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
629
+ "button",
630
+ {
631
+ type: "button",
632
+ onClick: onMicClick,
633
+ className: `absolute right-2 top-1/2 -translate-y-1/2 p-2 rounded-full transition-all ${isListening ? "bg-red-500 text-white animate-pulse" : "text-gray-500 hover:text-blue-600 hover:bg-gray-100 dark:hover:bg-gray-700"}`,
634
+ title: isListening ? "Stop recording (ESC)" : `Voice input (click or press ${modKey}+/)`,
635
+ "data-testid": "voice-input-button",
636
+ children: isListening ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" }) })
637
+ }
638
+ ),
639
+ inputValue.trim() && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
603
640
  "button",
604
641
  {
605
642
  type: "submit",
606
- disabled: !inputValue.trim(),
607
643
  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,
644
+ "data-testid": ChatNames.sendButton,
609
645
  title: sendButtonLabel,
610
646
  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
647
  }
612
648
  )
613
649
  ] }) });
650
+
651
+ // src/components/ChatBubble/Avatar.tsx
652
+ var import_jsx_runtime4 = require("react/jsx-runtime");
614
653
  var Avatar = ({ avatar, size = "normal" }) => {
615
654
  if (!avatar) return null;
616
655
  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 }) });
656
+ return size === "small" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-lg", children: avatar }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center shadow-md", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-white text-sm font-bold", children: avatar }) });
618
657
  }
619
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: avatar });
658
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: avatar });
620
659
  };
621
- var ChatBubble = ({
622
- messages,
623
- onSendMessage,
624
- onClearChat,
625
- position = "bottom-right",
626
- variant = "full",
627
- config: userConfig,
628
- defaultExpanded = true,
629
- storageKey = "chat-bubble-state"
630
- }) => {
631
- const config = { ...DEFAULT_CONFIG, ...userConfig };
632
- const [isExpanded, setIsExpanded] = (0, import_react3.useState)(defaultExpanded);
633
- const [isMinimized, setIsMinimized] = (0, import_react3.useState)(false);
634
- const [inputValue, setInputValue] = (0, import_react3.useState)("");
635
- const [lastReadMessageCount, setLastReadMessageCount] = (0, import_react3.useState)(0);
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);
646
- const messagesEndRef = (0, import_react3.useRef)(null);
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();
660
+
661
+ // src/components/ChatBubble/ChatBubble.tsx
662
+ var import_react9 = __toESM(require("react"));
663
+
664
+ // src/components/MessageRenderer.tsx
665
+ var import_react_markdown = __toESM(require("react-markdown"));
666
+ var import_remark_gfm = __toESM(require("remark-gfm"));
667
+ var import_remark_math = __toESM(require("remark-math"));
668
+ var import_remark_directive = __toESM(require("remark-directive"));
669
+ var import_rehype_katex = __toESM(require("rehype-katex"));
670
+
671
+ // src/components/CodeBlock.tsx
672
+ var import_react4 = __toESM(require("react"));
673
+ var import_react_syntax_highlighter = require("react-syntax-highlighter");
674
+ var import_prism = require("react-syntax-highlighter/dist/cjs/styles/prism");
675
+ var import_jsx_runtime5 = require("react/jsx-runtime");
676
+ function CodeBlock({ children, className, inline, theme = "dark" }) {
677
+ const [copied, setCopied] = (0, import_react4.useState)(false);
678
+ const match = /language-(\w+)/.exec(className || "");
679
+ const language = match ? match[1] : "text";
680
+ const handleCopy = async () => {
681
+ await navigator.clipboard.writeText(children);
682
+ setCopied(true);
683
+ setTimeout(() => setCopied(false), 2e3);
663
684
  };
664
- (0, import_react3.useEffect)(() => {
665
- if (variant === "full") {
685
+ if (inline) {
686
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { className: `${className || ""} px-1.5 py-0.5 rounded text-sm font-mono ${theme === "dark" ? "bg-gray-800 text-gray-200" : "bg-gray-100 text-gray-800"}`, children });
687
+ }
688
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative group my-4", children: [
689
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
690
+ "button",
691
+ {
692
+ onClick: handleCopy,
693
+ className: `absolute top-2 right-2 px-3 py-1.5 text-xs font-medium rounded transition-all ${theme === "dark" ? "bg-gray-700 hover:bg-gray-600 text-gray-200" : "bg-gray-200 hover:bg-gray-300 text-gray-700"} ${copied ? "opacity-100" : "opacity-0 group-hover:opacity-100"}`,
694
+ "aria-label": "Copy code",
695
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex items-center gap-1", children: [
696
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
697
+ "Copied!"
698
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex items-center gap-1", children: [
699
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
700
+ "Copy"
701
+ ] })
702
+ }
703
+ ),
704
+ import_react4.default.createElement(import_react_syntax_highlighter.Prism, {
705
+ language,
706
+ style: theme === "dark" ? import_prism.vscDarkPlus : import_prism.vs,
707
+ customStyle: {
708
+ margin: 0,
709
+ borderRadius: "0.5rem",
710
+ fontSize: "0.875rem",
711
+ padding: "1rem"
712
+ },
713
+ showLineNumbers: true,
714
+ wrapLines: true,
715
+ children
716
+ })
717
+ ] });
718
+ }
719
+
720
+ // src/components/MermaidDiagram.tsx
721
+ var import_react5 = require("react");
722
+ var import_jsx_runtime6 = require("react/jsx-runtime");
723
+ function MermaidDiagram({ chart, theme = "dark" }) {
724
+ const containerRef = (0, import_react5.useRef)(null);
725
+ const [error, setError] = (0, import_react5.useState)(null);
726
+ const [isLoading, setIsLoading] = (0, import_react5.useState)(true);
727
+ (0, import_react5.useEffect)(() => {
728
+ let mounted = true;
729
+ const renderDiagram = async () => {
730
+ if (!containerRef.current) return;
666
731
  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");
732
+ setIsLoading(true);
733
+ setError(null);
734
+ const mermaid = (await import("mermaid")).default;
735
+ mermaid.initialize({
736
+ startOnLoad: false,
737
+ theme: theme === "dark" ? "dark" : "default",
738
+ securityLevel: "loose",
739
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif"
740
+ });
741
+ const id = `mermaid-${Math.random().toString(36).substr(2, 9)}`;
742
+ const { svg } = await mermaid.render(id, chart);
743
+ if (mounted && containerRef.current) {
744
+ containerRef.current.innerHTML = svg;
745
+ setIsLoading(false);
746
+ }
747
+ } catch (err) {
748
+ if (mounted) {
749
+ console.error("Mermaid rendering error:", err);
750
+ setError(err instanceof Error ? err.message : "Failed to render diagram");
751
+ setIsLoading(false);
675
752
  }
676
- } catch {
677
753
  }
678
- }
679
- }, [storageKey, variant, defaultExpanded]);
680
- (0, import_react3.useEffect)(() => {
681
- if (typeof window !== "undefined") {
682
- const isDark = document.documentElement.getAttribute("data-theme") === "dark";
683
- setTheme(isDark ? "dark" : "light");
684
- }
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);
754
+ };
755
+ renderDiagram();
756
+ return () => {
757
+ mounted = false;
758
+ };
759
+ }, [chart, theme]);
760
+ if (error) {
761
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `my-4 p-4 rounded-lg border ${theme === "dark" ? "bg-red-900/20 border-red-500 text-red-200" : "bg-red-50 border-red-300 text-red-800"}`, children: [
762
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "font-semibold mb-1", children: "Mermaid Diagram Error" }),
763
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm opacity-90", children: error }),
764
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("details", { className: "mt-2 text-xs opacity-75", children: [
765
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("summary", { className: "cursor-pointer", children: "View diagram source" }),
766
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { className: "mt-2 whitespace-pre-wrap", children: chart })
767
+ ] })
768
+ ] });
769
+ }
770
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "my-4 flex justify-center", children: [
771
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: `py-8 text-sm ${theme === "dark" ? "text-gray-400" : "text-gray-600"}`, children: "Rendering diagram..." }),
772
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
773
+ "div",
774
+ {
775
+ ref: containerRef,
776
+ className: `mermaid-diagram ${isLoading ? "hidden" : ""} ${theme === "dark" ? "mermaid-dark" : "mermaid-light"}`
701
777
  }
702
- }
703
- }, [isExpanded, isMinimized, isDocked, panelPosition, theme, storageKey, variant]);
704
- const { registerInput } = useChatInput();
705
- (0, import_react3.useEffect)(() => {
706
- registerInput((text, submit = false) => {
707
- setInputValue(text);
708
- if (!isExpanded && variant === "full") {
709
- setIsExpanded(true);
778
+ )
779
+ ] });
780
+ }
781
+
782
+ // src/components/MessageRenderer.tsx
783
+ var import_jsx_runtime7 = require("react/jsx-runtime");
784
+ function MessageRenderer({ content, theme = "dark" }) {
785
+ const components = {
786
+ // Code blocks with syntax highlighting
787
+ code(props) {
788
+ const { node, className, children, ...rest } = props;
789
+ const value = String(children).replace(/\n$/, "");
790
+ const match = /language-(\w+)/.exec(className || "");
791
+ const language = match ? match[1] : "";
792
+ const inline = !className;
793
+ if (language === "mermaid") {
794
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MermaidDiagram, { chart: value, theme });
710
795
  }
711
- setTimeout(() => {
712
- inputRef.current?.focus();
713
- if (submit) {
714
- onSendMessage(text);
715
- setInputValue("");
796
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
797
+ CodeBlock,
798
+ {
799
+ className,
800
+ inline,
801
+ theme,
802
+ children: value
716
803
  }
717
- }, 100);
718
- });
719
- }, [registerInput, onSendMessage]);
720
- const unreadCount = Math.max(0, messages.length - lastReadMessageCount);
721
- const hasUnread = unreadCount > 0 && !isExpanded && variant === "full";
722
- (0, import_react3.useEffect)(() => {
723
- if (isExpanded || variant === "floating") {
724
- messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
725
- setLastReadMessageCount(messages.length);
726
- if (messages.length > 0) {
727
- setShowWelcome(false);
804
+ );
805
+ },
806
+ // Headings with better styling
807
+ h1: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { className: `text-2xl font-bold mt-6 mb-3 ${theme === "dark" ? "text-gray-100" : "text-gray-900"}`, children }),
808
+ h2: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { className: `text-xl font-bold mt-5 mb-2.5 ${theme === "dark" ? "text-gray-100" : "text-gray-900"}`, children }),
809
+ h3: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: `text-lg font-semibold mt-4 mb-2 ${theme === "dark" ? "text-gray-200" : "text-gray-800"}`, children }),
810
+ // Paragraphs
811
+ p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: `mb-3 leading-relaxed ${theme === "dark" ? "text-gray-300" : "text-gray-700"}`, children }),
812
+ // Links
813
+ a: ({ href, children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
814
+ "a",
815
+ {
816
+ href,
817
+ target: "_blank",
818
+ rel: "noopener noreferrer",
819
+ className: `underline transition-colors ${theme === "dark" ? "text-blue-400 hover:text-blue-300" : "text-blue-600 hover:text-blue-700"}`,
820
+ children
728
821
  }
729
- if (variant === "full") {
730
- inputRef.current?.focus();
822
+ ),
823
+ // Lists
824
+ ul: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: `list-disc list-inside mb-3 space-y-1 ${theme === "dark" ? "text-gray-300" : "text-gray-700"}`, children }),
825
+ ol: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ol", { className: `list-decimal list-inside mb-3 space-y-1 ${theme === "dark" ? "text-gray-300" : "text-gray-700"}`, children }),
826
+ // Blockquotes
827
+ blockquote: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("blockquote", { className: `border-l-4 pl-4 my-3 italic ${theme === "dark" ? "border-blue-500 bg-blue-900/20 py-2 pr-2" : "border-blue-400 bg-blue-50 py-2 pr-2"}`, children }),
828
+ // Tables
829
+ table: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "overflow-x-auto my-4", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("table", { className: `min-w-full border-collapse ${theme === "dark" ? "border-gray-700" : "border-gray-300"}`, children }) }),
830
+ thead: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { className: theme === "dark" ? "bg-gray-800" : "bg-gray-100", children }),
831
+ th: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { className: `px-4 py-2 text-left font-semibold border ${theme === "dark" ? "border-gray-700 text-gray-200" : "border-gray-300 text-gray-800"}`, children }),
832
+ td: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { className: `px-4 py-2 border ${theme === "dark" ? "border-gray-700 text-gray-300" : "border-gray-300 text-gray-700"}`, children }),
833
+ // Horizontal rule
834
+ hr: () => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("hr", { className: `my-4 border-t ${theme === "dark" ? "border-gray-700" : "border-gray-300"}` }),
835
+ // Admonitions support (using containerDirective from remark-directive)
836
+ div: ({ node, className, children, ...props }) => {
837
+ if (className?.includes("admonition")) {
838
+ const type = className.split("-")[1];
839
+ const styles = {
840
+ tip: {
841
+ border: theme === "dark" ? "border-green-500" : "border-green-600",
842
+ bg: theme === "dark" ? "bg-green-900/20" : "bg-green-50",
843
+ icon: "\u{1F4A1}"
844
+ },
845
+ warning: {
846
+ border: theme === "dark" ? "border-yellow-500" : "border-yellow-600",
847
+ bg: theme === "dark" ? "bg-yellow-900/20" : "bg-yellow-50",
848
+ icon: "\u26A0\uFE0F"
849
+ },
850
+ danger: {
851
+ border: theme === "dark" ? "border-red-500" : "border-red-600",
852
+ bg: theme === "dark" ? "bg-red-900/20" : "bg-red-50",
853
+ icon: "\u{1F6A8}"
854
+ },
855
+ info: {
856
+ border: theme === "dark" ? "border-blue-500" : "border-blue-600",
857
+ bg: theme === "dark" ? "bg-blue-900/20" : "bg-blue-50",
858
+ icon: "\u2139\uFE0F"
859
+ }
860
+ };
861
+ const style = styles[type] || styles.info;
862
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: `my-4 p-4 border-l-4 rounded-r-lg ${style.border} ${style.bg}`, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-start gap-2", children: [
863
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-lg flex-shrink-0", children: style.icon }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex-1", children })
865
+ ] }) });
731
866
  }
867
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className, ...props, children });
732
868
  }
733
- }, [messages, isExpanded, variant]);
734
- (0, import_react3.useEffect)(() => {
735
- if (isExpanded && variant === "full") {
736
- inputRef.current?.focus();
869
+ };
870
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "markdown-content", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
871
+ import_react_markdown.default,
872
+ {
873
+ remarkPlugins: [import_remark_gfm.default, import_remark_math.default, import_remark_directive.default],
874
+ rehypePlugins: [import_rehype_katex.default],
875
+ components,
876
+ children: content
737
877
  }
738
- }, [isExpanded, variant]);
739
- (0, import_react3.useEffect)(() => {
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);
878
+ ) });
879
+ }
880
+
881
+ // src/hooks/useTTS.ts
882
+ var import_react6 = require("react");
883
+ var Platform = {
884
+ isNative: typeof window !== "undefined" && "Capacitor" in window,
885
+ isWeb: typeof window !== "undefined" && !("Capacitor" in window)
886
+ };
887
+ function isNativeTTSSupported() {
888
+ if (Platform.isNative) {
889
+ return true;
890
+ }
891
+ if (Platform.isWeb && "speechSynthesis" in window) {
892
+ return true;
893
+ }
894
+ return false;
895
+ }
896
+ function useTTS() {
897
+ const [isPlaying, setIsPlaying] = (0, import_react6.useState)(false);
898
+ const [error, setError] = (0, import_react6.useState)(null);
899
+ const [currentText, setCurrentText] = (0, import_react6.useState)(null);
900
+ const audioRef = (0, import_react6.useRef)(null);
901
+ const utteranceRef = (0, import_react6.useRef)(null);
902
+ const isNativeSupported = isNativeTTSSupported();
903
+ const speakNative = (0, import_react6.useCallback)(
904
+ async (text, options = {}) => {
905
+ if (Platform.isNative) {
906
+ try {
907
+ const { TextToSpeech } = await import("@capacitor/text-to-speech");
908
+ await TextToSpeech.speak({
909
+ text,
910
+ lang: "en-US",
911
+ rate: options.speed || 1
912
+ });
913
+ } catch (err) {
914
+ throw new Error("Capacitor TTS failed: " + err.message);
748
915
  }
916
+ return;
749
917
  }
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
- }
918
+ if ("speechSynthesis" in window) {
919
+ return new Promise((resolve, reject) => {
920
+ const utterance = new SpeechSynthesisUtterance(text);
921
+ utterance.rate = options.speed || 1;
922
+ if (options.voice) {
923
+ const voices = window.speechSynthesis.getVoices();
924
+ const voice = voices.find(
925
+ (v) => v.name.toLowerCase().includes(options.voice.toLowerCase())
926
+ );
927
+ if (voice) utterance.voice = voice;
928
+ }
929
+ utterance.onend = () => {
930
+ utteranceRef.current = null;
931
+ resolve();
932
+ };
933
+ utterance.onerror = (e) => {
934
+ utteranceRef.current = null;
935
+ reject(new Error("SpeechSynthesis error: " + e.error));
936
+ };
937
+ utteranceRef.current = utterance;
938
+ window.speechSynthesis.speak(utterance);
939
+ });
758
940
  }
759
- if ((e.metaKey || e.ctrlKey) && e.key === "/") {
760
- e.preventDefault();
761
- if (!isExpanded) {
762
- setIsExpanded(true);
941
+ throw new Error("Native TTS not supported on this platform");
942
+ },
943
+ []
944
+ );
945
+ const speakAPI = (0, import_react6.useCallback)(
946
+ async (text, options = {}) => {
947
+ const apiUrl = process.env.NEXT_PUBLIC_TTS_API_URL || "https://tts.supernal.ai";
948
+ const apiKey = process.env.NEXT_PUBLIC_TTS_API_KEY || "";
949
+ if (!apiKey && !apiUrl.includes("localhost")) {
950
+ throw new Error("TTS_API_KEY not configured for premium voices");
951
+ }
952
+ const response = await fetch(`${apiUrl}/api/v1/generate`, {
953
+ method: "POST",
954
+ headers: {
955
+ "Content-Type": "application/json",
956
+ ...apiKey && { "X-API-Key": apiKey }
957
+ },
958
+ body: JSON.stringify({
959
+ text,
960
+ options: {
961
+ provider: "openai",
962
+ voice: options.voice || "alloy",
963
+ speed: 1
964
+ // Always generate at 1.0x, adjust playback client-side
965
+ }
966
+ })
967
+ });
968
+ if (!response.ok) {
969
+ throw new Error(`TTS API failed: ${response.status} ${response.statusText}`);
970
+ }
971
+ const contentType = response.headers.get("content-type") || "";
972
+ let audioUrl;
973
+ if (contentType.includes("audio")) {
974
+ const blob = await response.blob();
975
+ audioUrl = URL.createObjectURL(blob);
976
+ } else {
977
+ const data = await response.json();
978
+ if (data.audio) {
979
+ const blob = base64ToBlob(data.audio, "audio/mpeg");
980
+ audioUrl = URL.createObjectURL(blob);
981
+ } else if (data.audioUrl) {
982
+ audioUrl = data.audioUrl;
983
+ } else {
984
+ throw new Error("Invalid TTS API response format");
763
985
  }
764
- inputRef.current?.focus();
765
986
  }
766
- };
767
- window.addEventListener("keydown", handleKeyDown);
768
- return () => window.removeEventListener("keydown", handleKeyDown);
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);
987
+ const audio = new Audio(audioUrl);
988
+ audio.playbackRate = options.speed || 1;
989
+ return new Promise((resolve, reject) => {
990
+ audio.onended = () => {
991
+ URL.revokeObjectURL(audioUrl);
992
+ audioRef.current = null;
993
+ resolve();
994
+ };
995
+ audio.onerror = () => {
996
+ URL.revokeObjectURL(audioUrl);
997
+ audioRef.current = null;
998
+ reject(new Error("Audio playback failed"));
999
+ };
1000
+ audioRef.current = audio;
1001
+ audio.play().catch(reject);
1002
+ });
1003
+ },
1004
+ []
1005
+ );
1006
+ const speak = (0, import_react6.useCallback)(
1007
+ async (options) => {
1008
+ const {
1009
+ text,
1010
+ voice,
1011
+ speed = 1,
1012
+ preferNative = true,
1013
+ usePremium = false,
1014
+ onComplete,
1015
+ onError
1016
+ } = options;
1017
+ stop();
1018
+ setIsPlaying(true);
1019
+ setError(null);
1020
+ setCurrentText(text);
1021
+ try {
1022
+ if (preferNative && !usePremium && isNativeSupported) {
1023
+ await speakNative(text, { voice, speed });
1024
+ } else {
1025
+ await speakAPI(text, { voice, speed });
1026
+ }
1027
+ onComplete?.();
1028
+ } catch (err) {
1029
+ const errorMsg = err instanceof Error ? err.message : "Unknown TTS error";
1030
+ setError(errorMsg);
1031
+ if (usePremium && isNativeSupported) {
1032
+ try {
1033
+ console.warn("Premium TTS failed, falling back to native");
1034
+ await speakNative(text, { voice, speed });
1035
+ onComplete?.();
1036
+ setError(null);
1037
+ } catch (fallbackErr) {
1038
+ onError?.(fallbackErr);
1039
+ }
1040
+ } else {
1041
+ onError?.(err);
1042
+ }
1043
+ } finally {
1044
+ setIsPlaying(false);
1045
+ setCurrentText(null);
776
1046
  }
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;
1047
+ },
1048
+ [speakNative, speakAPI, isNativeSupported]
1049
+ );
1050
+ const stop = (0, import_react6.useCallback)(() => {
1051
+ if ("speechSynthesis" in window && window.speechSynthesis.speaking) {
1052
+ window.speechSynthesis.cancel();
1053
+ utteranceRef.current = null;
787
1054
  }
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
- };
1055
+ if (audioRef.current) {
1056
+ audioRef.current.pause();
1057
+ audioRef.current.currentTime = 0;
1058
+ audioRef.current = null;
1059
+ }
1060
+ setIsPlaying(false);
1061
+ setCurrentText(null);
1062
+ }, []);
1063
+ return {
1064
+ speak,
1065
+ stop,
1066
+ isPlaying,
1067
+ error,
1068
+ isNativeSupported,
1069
+ currentText
806
1070
  };
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 });
1071
+ }
1072
+ function base64ToBlob(base64, mimeType) {
1073
+ const byteCharacters = atob(base64);
1074
+ const byteNumbers = new Array(byteCharacters.length);
1075
+ for (let i = 0; i < byteCharacters.length; i++) {
1076
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
1077
+ }
1078
+ const byteArray = new Uint8Array(byteNumbers);
1079
+ return new Blob([byteArray], { type: mimeType });
1080
+ }
1081
+
1082
+ // src/hooks/useSTT.ts
1083
+ var import_react7 = require("react");
1084
+ var Platform2 = {
1085
+ isNative: typeof window !== "undefined" && "Capacitor" in window,
1086
+ isWeb: typeof window !== "undefined" && !("Capacitor" in window)
1087
+ };
1088
+ function useSTT() {
1089
+ const [isListening, setIsListening] = (0, import_react7.useState)(false);
1090
+ const [transcript, setTranscript] = (0, import_react7.useState)("");
1091
+ const [interimTranscript, setInterimTranscript] = (0, import_react7.useState)("");
1092
+ const [error, setError] = (0, import_react7.useState)(null);
1093
+ const recognitionRef = (0, import_react7.useRef)(null);
1094
+ const isSupported = (0, import_react7.useMemo)(() => {
1095
+ if (Platform2.isNative) {
1096
+ return true;
1097
+ }
1098
+ if (Platform2.isWeb) {
1099
+ return "SpeechRecognition" in window || "webkitSpeechRecognition" in window;
1100
+ }
1101
+ return false;
1102
+ }, []);
1103
+ (0, import_react7.useEffect)(() => {
1104
+ if (!isSupported || Platform2.isNative) return;
1105
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1106
+ if (!SpeechRecognition) return;
1107
+ const recognition = new SpeechRecognition();
1108
+ recognition.continuous = false;
1109
+ recognition.interimResults = true;
1110
+ recognition.lang = "en-US";
1111
+ recognition.maxAlternatives = 1;
1112
+ recognition.onresult = (event) => {
1113
+ let interim = "";
1114
+ let final = "";
1115
+ for (let i = 0; i < event.results.length; i++) {
1116
+ const result = event.results[i];
1117
+ if (result.isFinal) {
1118
+ final += result[0].transcript;
1119
+ } else {
1120
+ interim += result[0].transcript;
829
1121
  }
830
1122
  }
1123
+ if (final) setTranscript(final);
1124
+ setInterimTranscript(interim);
831
1125
  };
832
- window.addEventListener("mousemove", handleMouseMove);
833
- window.addEventListener("mouseup", handleMouseUp);
1126
+ recognition.onerror = (event) => {
1127
+ console.error("Speech recognition error:", event.error);
1128
+ setError(event.error);
1129
+ setIsListening(false);
1130
+ };
1131
+ recognition.onend = () => {
1132
+ setIsListening(false);
1133
+ };
1134
+ recognitionRef.current = recognition;
834
1135
  return () => {
835
- window.removeEventListener("mousemove", handleMouseMove);
836
- window.removeEventListener("mouseup", handleMouseUp);
1136
+ if (recognition) {
1137
+ recognition.stop();
1138
+ }
837
1139
  };
838
- }, [isDragging]);
839
- const handleSend = (e) => {
840
- e.preventDefault();
841
- if (!inputValue.trim()) return;
842
- onSendMessage(inputValue.trim());
843
- setInputValue("");
844
- if (variant === "full") {
845
- setTimeout(() => inputRef.current?.focus(), 0);
1140
+ }, [isSupported]);
1141
+ const startListening = (0, import_react7.useCallback)(async () => {
1142
+ setError(null);
1143
+ setTranscript("");
1144
+ setInterimTranscript("");
1145
+ setIsListening(true);
1146
+ try {
1147
+ if (Platform2.isNative) {
1148
+ const { SpeechRecognition: CapSpeech } = await import("@capacitor-community/speech-recognition");
1149
+ await CapSpeech.requestPermissions();
1150
+ const { matches } = await CapSpeech.start({
1151
+ language: "en-US",
1152
+ maxResults: 1,
1153
+ popup: false
1154
+ });
1155
+ setTranscript(matches[0] || "");
1156
+ setIsListening(false);
1157
+ return;
1158
+ }
1159
+ if (recognitionRef.current) {
1160
+ recognitionRef.current.start();
1161
+ } else {
1162
+ throw new Error("Speech recognition not initialized");
1163
+ }
1164
+ } catch (err) {
1165
+ console.error("Failed to start speech recognition:", err);
1166
+ setError(err instanceof Error ? err.message : "Failed to start microphone");
1167
+ setIsListening(false);
846
1168
  }
847
- };
848
- const handleToggle = () => {
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);
1169
+ }, []);
1170
+ const stopListening = (0, import_react7.useCallback)(() => {
1171
+ if (Platform2.isNative) {
1172
+ setIsListening(false);
1173
+ return;
864
1174
  }
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);
1175
+ if (recognitionRef.current) {
1176
+ recognitionRef.current.stop();
871
1177
  }
872
- };
873
- const dockClasses = DOCK_POSITIONS[position];
1178
+ }, []);
1179
+ const resetTranscript = (0, import_react7.useCallback)(() => {
1180
+ setTranscript("");
1181
+ setInterimTranscript("");
1182
+ setError(null);
1183
+ }, []);
1184
+ return {
1185
+ startListening,
1186
+ stopListening,
1187
+ resetTranscript,
1188
+ isListening,
1189
+ transcript,
1190
+ interimTranscript,
1191
+ error,
1192
+ isSupported
1193
+ };
1194
+ }
1195
+
1196
+ // src/components/TTSButton.tsx
1197
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1198
+ function TTSButton({
1199
+ text,
1200
+ usePremiumVoices = false,
1201
+ speed = 1,
1202
+ size = "small",
1203
+ theme = "light"
1204
+ }) {
1205
+ const { speak, stop, isPlaying, error, isNativeSupported } = useTTS();
1206
+ const handleClick = async (e) => {
1207
+ e.stopPropagation();
1208
+ if (isPlaying) {
1209
+ stop();
1210
+ } else {
1211
+ await speak({
1212
+ text,
1213
+ speed,
1214
+ usePremium: usePremiumVoices,
1215
+ preferNative: !usePremiumVoices
1216
+ });
1217
+ }
1218
+ };
1219
+ if (!isNativeSupported && !usePremiumVoices) {
1220
+ return null;
1221
+ }
1222
+ const isDark = theme === "dark";
1223
+ const iconSize = size === "small" ? "w-3 h-3" : "w-4 h-4";
1224
+ const buttonPadding = size === "small" ? "p-1" : "p-2";
1225
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1226
+ "button",
1227
+ {
1228
+ onClick: handleClick,
1229
+ className: `${buttonPadding} rounded-lg transition-all ${isPlaying ? "bg-blue-500 text-white animate-pulse" : error ? "bg-red-500 text-white" : isDark ? "text-gray-400 hover:text-blue-400 hover:bg-gray-700/50" : "text-gray-500 hover:text-blue-600 hover:bg-gray-100/50"}`,
1230
+ title: isPlaying ? "Stop speaking" : "Read aloud",
1231
+ "data-testid": "tts-button",
1232
+ "aria-label": isPlaying ? "Stop speaking" : "Read aloud",
1233
+ children: isPlaying ? (
1234
+ // Stop icon (pulsing square)
1235
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { className: iconSize, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) })
1236
+ ) : error ? (
1237
+ // Error icon
1238
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { className: iconSize, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) })
1239
+ ) : (
1240
+ // Speaker icon
1241
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { className: iconSize, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15.414a2 2 0 002.828 0l1.768-1.768a2 2 0 00 0-2.828l-1.768-1.768a2 2 0 00-2.828 0V15.414z" }) })
1242
+ )
1243
+ }
1244
+ );
1245
+ }
1246
+
1247
+ // src/components/SubtitleOverlay.tsx
1248
+ var import_react8 = require("react");
1249
+
1250
+ // src/components/TTSPlaylistMenu.tsx
1251
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1252
+ var TTSPlaylistMenu = ({
1253
+ isOpen,
1254
+ onClose,
1255
+ widgets,
1256
+ theme,
1257
+ onWidgetSelect
1258
+ }) => {
1259
+ if (!isOpen || widgets.length === 0) return null;
1260
+ const handleWidgetClick = (widget) => {
1261
+ onWidgetSelect(widget);
1262
+ };
1263
+ const glassStyles = theme === "dark" ? {
1264
+ background: "rgba(31, 41, 55, 0.9)",
1265
+ border: "1px solid rgba(255, 255, 255, 0.12)",
1266
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)"
1267
+ } : {
1268
+ background: "rgba(255, 255, 255, 0.9)",
1269
+ border: "1px solid rgba(0, 0, 0, 0.1)",
1270
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)"
1271
+ };
1272
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1273
+ "div",
1274
+ {
1275
+ className: "fixed z-50",
1276
+ style: {
1277
+ bottom: "calc(env(safe-area-inset-bottom, 0px) + 80px)",
1278
+ left: "16px",
1279
+ maxHeight: "50vh",
1280
+ width: "min(90vw, 400px)"
1281
+ },
1282
+ "data-testid": "tts-playlist-menu",
1283
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1284
+ "div",
1285
+ {
1286
+ style: {
1287
+ ...glassStyles,
1288
+ backdropFilter: "blur(20px) saturate(180%)",
1289
+ WebkitBackdropFilter: "blur(20px) saturate(180%)",
1290
+ borderRadius: "16px"
1291
+ },
1292
+ children: [
1293
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1294
+ "div",
1295
+ {
1296
+ className: `p-4 border-b flex items-center justify-between ${theme === "dark" ? "border-white/10" : "border-black/10"}`,
1297
+ children: [
1298
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1299
+ "h3",
1300
+ {
1301
+ className: `font-semibold text-sm ${theme === "dark" ? "text-white" : "text-gray-900"}`,
1302
+ children: "Readable Sections"
1303
+ }
1304
+ ),
1305
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1306
+ "button",
1307
+ {
1308
+ onClick: onClose,
1309
+ className: `p-1.5 rounded-lg transition-all ${theme === "dark" ? "text-gray-400 hover:text-white hover:bg-white/10" : "text-gray-600 hover:text-gray-900 hover:bg-black/5"}`,
1310
+ title: "Close",
1311
+ "data-testid": "close-playlist-menu",
1312
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-lg font-bold", "aria-hidden": "true", children: "\xD7" })
1313
+ }
1314
+ )
1315
+ ]
1316
+ }
1317
+ ),
1318
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "overflow-y-auto max-h-80 p-2", children: widgets.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1319
+ "div",
1320
+ {
1321
+ className: `p-6 text-center text-sm ${theme === "dark" ? "text-gray-400" : "text-gray-600"}`,
1322
+ children: "No readable sections found on this page"
1323
+ }
1324
+ ) : widgets.map((widget) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1325
+ "button",
1326
+ {
1327
+ onClick: () => handleWidgetClick(widget),
1328
+ className: `w-full text-left p-3 rounded-lg transition-all ${theme === "dark" ? "hover:bg-white/10" : "hover:bg-black/5"}`,
1329
+ style: {
1330
+ backdropFilter: "blur(8px)",
1331
+ WebkitBackdropFilter: "blur(8px)"
1332
+ },
1333
+ "data-testid": "playlist-widget-item",
1334
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1335
+ "div",
1336
+ {
1337
+ className: `font-medium text-sm flex items-center ${theme === "dark" ? "text-white" : "text-gray-900"}`,
1338
+ children: [
1339
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mr-2", "aria-hidden": "true", children: "\u{1F50A}" }),
1340
+ widget.label || `TTS Widget ${widget.id}`
1341
+ ]
1342
+ }
1343
+ )
1344
+ },
1345
+ widget.id
1346
+ )) })
1347
+ ]
1348
+ }
1349
+ )
1350
+ }
1351
+ );
1352
+ };
1353
+
1354
+ // src/utils/pageContentParser.ts
1355
+ var GENERIC_SUGGESTIONS = [
1356
+ { text: "Try: navigate to...", type: "generic" },
1357
+ { text: "Ask: what can I do here?", type: "generic" },
1358
+ { text: "Show me around", type: "generic" },
1359
+ { text: "What's on this page?", type: "generic" }
1360
+ ];
1361
+ function extractPageSpecificSuggestions() {
1362
+ if (typeof window === "undefined") return [];
1363
+ const headings = document.querySelectorAll("h1, h2, h3");
1364
+ const suggestions = [];
1365
+ Array.from(headings).slice(0, 3).forEach((heading) => {
1366
+ const text = heading.textContent?.trim();
1367
+ if (text && text.length > 0 && text.length < 50) {
1368
+ suggestions.push({
1369
+ text: `Ask about: ${text}`,
1370
+ type: "page-specific"
1371
+ });
1372
+ }
1373
+ });
1374
+ return suggestions;
1375
+ }
1376
+ function extractPageSuggestions(maxSuggestions = 7, genericRatio = 0.4) {
1377
+ const pageSpecific = extractPageSpecificSuggestions();
1378
+ const numGeneric = Math.ceil(maxSuggestions * genericRatio);
1379
+ const numPageSpecific = maxSuggestions - numGeneric;
1380
+ const suggestions = [
1381
+ ...GENERIC_SUGGESTIONS.slice(0, numGeneric),
1382
+ ...pageSpecific.slice(0, numPageSpecific)
1383
+ ];
1384
+ return suggestions;
1385
+ }
1386
+
1387
+ // src/utils/backgroundDetection.ts
1388
+ function calculateLuminance(rgbColor) {
1389
+ const rgb = rgbColor.match(/\d+/g)?.map(Number);
1390
+ if (!rgb || rgb.length < 3) {
1391
+ console.warn("Invalid RGB color format, defaulting to 0.5 luminance");
1392
+ return 0.5;
1393
+ }
1394
+ const r = rgb[0] / 255;
1395
+ const g = rgb[1] / 255;
1396
+ const b = rgb[2] / 255;
1397
+ return 0.299 * r + 0.587 * g + 0.114 * b;
1398
+ }
1399
+ function sampleBackgroundColor(x, y) {
1400
+ if (typeof window === "undefined") return null;
1401
+ const element = document.elementFromPoint(x, y);
1402
+ if (!element) return null;
1403
+ return getComputedStyle(element).backgroundColor;
1404
+ }
1405
+ function detectBackgroundContext() {
1406
+ if (typeof window === "undefined") {
1407
+ return {
1408
+ pageTheme: "light",
1409
+ glassTheme: "dark",
1410
+ luminance: 0.5
1411
+ };
1412
+ }
1413
+ const explicitTheme = document.documentElement.getAttribute("data-theme");
1414
+ const overlayY = window.innerHeight - 100;
1415
+ const overlayX = window.innerWidth / 2;
1416
+ const bgColor = sampleBackgroundColor(overlayX, overlayY);
1417
+ const luminance = bgColor ? calculateLuminance(bgColor) : 0.5;
1418
+ const sampledTheme = luminance > 0.5 ? "light" : "dark";
1419
+ const pageTheme = explicitTheme === "dark" || explicitTheme === "light" ? explicitTheme : sampledTheme;
1420
+ const glassTheme = sampledTheme === "light" ? "dark" : "light";
1421
+ return {
1422
+ pageTheme,
1423
+ glassTheme,
1424
+ luminance
1425
+ };
1426
+ }
1427
+
1428
+ // src/utils/ttsDetection.ts
1429
+ function detectTTSWidgets() {
1430
+ if (typeof window === "undefined") return false;
1431
+ const widgetWrappers = document.querySelectorAll(".supernal-tts-widget[data-text]");
1432
+ console.log("[TTS Detection] Found widget wrappers:", widgetWrappers.length);
1433
+ if (widgetWrappers.length > 0) {
1434
+ console.log("[TTS Detection] Wrapper visible?", widgetWrappers[0].offsetParent !== null);
1435
+ console.log("[TTS Detection] Has play button?", !!widgetWrappers[0].querySelector(".supernal-tts-play"));
1436
+ }
1437
+ return widgetWrappers.length > 0;
1438
+ }
1439
+ function extractWidgetLabel(element, fallbackIndex) {
1440
+ const ariaLabel = element.getAttribute("aria-label");
1441
+ if (ariaLabel && ariaLabel.trim()) return ariaLabel.trim();
1442
+ const dataLabel = element.getAttribute("data-label");
1443
+ if (dataLabel && dataLabel.trim()) return dataLabel.trim();
1444
+ const parent = element.closest("section, article, div, p");
1445
+ if (parent) {
1446
+ const heading = parent.querySelector("h1, h2, h3, h4, h5, h6");
1447
+ if (heading?.textContent?.trim()) {
1448
+ return heading.textContent.trim();
1449
+ }
1450
+ let sibling = parent.previousElementSibling;
1451
+ while (sibling) {
1452
+ if (sibling.matches("h1, h2, h3, h4, h5, h6")) {
1453
+ return sibling.textContent?.trim() || `Readable Section ${fallbackIndex}`;
1454
+ }
1455
+ sibling = sibling.previousElementSibling;
1456
+ }
1457
+ }
1458
+ const parentSection = element.closest("section, article");
1459
+ if (parentSection) {
1460
+ const sectionHeading = parentSection.querySelector("h1, h2, h3, h4, h5, h6");
1461
+ if (sectionHeading?.textContent?.trim()) {
1462
+ return sectionHeading.textContent.trim();
1463
+ }
1464
+ }
1465
+ if (parent?.textContent) {
1466
+ const text = parent.textContent.trim().slice(0, 50);
1467
+ if (text.length > 10) return text;
1468
+ }
1469
+ return `Readable Section ${fallbackIndex}`;
1470
+ }
1471
+ function extractTTSWidgets() {
1472
+ if (typeof window === "undefined") return [];
1473
+ const widgets = [];
1474
+ const ttsWidgets = document.querySelectorAll(".supernal-tts-widget[data-text]");
1475
+ ttsWidgets.forEach((wrapper, idx) => {
1476
+ const element = wrapper;
1477
+ if (element.offsetParent === null) {
1478
+ console.log("[TTS Extract] Skipping hidden widget:", wrapper);
1479
+ return;
1480
+ }
1481
+ const label = extractWidgetLabel(wrapper, widgets.length + 1);
1482
+ const playButton = element.querySelector(".supernal-tts-play");
1483
+ const widgetId = `tts-widget-${idx}`;
1484
+ if (!element.hasAttribute("data-testid")) {
1485
+ element.setAttribute("data-testid", widgetId);
1486
+ }
1487
+ if (playButton && !playButton.hasAttribute("data-testid")) {
1488
+ playButton.setAttribute("data-testid", `${widgetId}-play`);
1489
+ }
1490
+ console.log("[TTS Extract] Widget:", {
1491
+ id: widgetId,
1492
+ label,
1493
+ hasButton: !!playButton,
1494
+ wrapper: element,
1495
+ playButton,
1496
+ testId: element.getAttribute("data-testid")
1497
+ });
1498
+ widgets.push({
1499
+ id: widgetId,
1500
+ element: wrapper,
1501
+ // Always use wrapper as the element to scroll to
1502
+ label
1503
+ });
1504
+ });
1505
+ console.log("[TTS Extract] Total widgets found:", widgets.length);
1506
+ return widgets;
1507
+ }
1508
+
1509
+ // src/components/SubtitleOverlay.tsx
1510
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1511
+ var opacityStates = {
1512
+ mobile: {
1513
+ idle: 1,
1514
+ // Always full opacity
1515
+ listening: 1,
1516
+ typing: 1,
1517
+ speaking: 1
1518
+ },
1519
+ desktop: {
1520
+ idle: 0.4,
1521
+ // Increased from 0.1 for better visibility
1522
+ listening: 0.7,
1523
+ typing: 0.9,
1524
+ speaking: 0.5
1525
+ }
1526
+ };
1527
+ function detectMode(context) {
1528
+ if (context.isListening) return "voice";
1529
+ if (context.inputValue.trim().length > 0) return "text";
1530
+ if (context.lastInputMethod && context.timeSinceLastInput < 1e4) {
1531
+ return context.lastInputMethod;
1532
+ }
1533
+ return "voice";
1534
+ }
1535
+ var getOverlayOpacity = (state, isMobile) => {
1536
+ return isMobile ? opacityStates.mobile[state] : opacityStates.desktop[state];
1537
+ };
1538
+ var SubtitleOverlay = ({
1539
+ messages,
1540
+ inputValue,
1541
+ onInputChange,
1542
+ onSendMessage,
1543
+ voiceEnabled,
1544
+ isListening,
1545
+ onMicClick,
1546
+ theme,
1547
+ config,
1548
+ sttTranscript,
1549
+ resetTranscript
1550
+ }) => {
1551
+ const [overlayState, setOverlayState] = (0, import_react8.useState)("idle");
1552
+ const [opacity, setOpacity] = (0, import_react8.useState)(1);
1553
+ const [lastInputMethod, setLastInputMethod] = (0, import_react8.useState)(null);
1554
+ const [isMobile, setIsMobile] = (0, import_react8.useState)(false);
1555
+ const [messageOpacity, setMessageOpacity] = (0, import_react8.useState)(1);
1556
+ const [expansionState, setExpansionState] = (0, import_react8.useState)("collapsed");
1557
+ const [touchStartY, setTouchStartY] = (0, import_react8.useState)(null);
1558
+ const lastInputTimeRef = (0, import_react8.useRef)(Date.now());
1559
+ const autoFadeTimerRef = (0, import_react8.useRef)(null);
1560
+ const messageFadeTimerRef = (0, import_react8.useRef)(null);
1561
+ const inputRef = (0, import_react8.useRef)(null);
1562
+ const [suggestions, setSuggestions] = (0, import_react8.useState)([]);
1563
+ const [currentSuggestionIndex, setCurrentSuggestionIndex] = (0, import_react8.useState)(0);
1564
+ const [glassTheme, setGlassTheme] = (0, import_react8.useState)("auto");
1565
+ const [detectedGlassTheme, setDetectedGlassTheme] = (0, import_react8.useState)("light");
1566
+ const [hasTTSWidgets, setHasTTSWidgets] = (0, import_react8.useState)(false);
1567
+ const [showPlaylist, setShowPlaylist] = (0, import_react8.useState)(false);
1568
+ const [ttsWidgets, setTTSWidgets] = (0, import_react8.useState)([]);
1569
+ const [touchStartYInput, setTouchStartYInput] = (0, import_react8.useState)(null);
1570
+ (0, import_react8.useEffect)(() => {
1571
+ const checkMobile = () => {
1572
+ const mobile = window.innerWidth < 768;
1573
+ setIsMobile(mobile);
1574
+ if (!mobile && expansionState === "collapsed") {
1575
+ setExpansionState("expanded");
1576
+ }
1577
+ };
1578
+ checkMobile();
1579
+ window.addEventListener("resize", checkMobile);
1580
+ return () => window.removeEventListener("resize", checkMobile);
1581
+ }, []);
1582
+ (0, import_react8.useEffect)(() => {
1583
+ if (autoFadeTimerRef.current) {
1584
+ clearTimeout(autoFadeTimerRef.current);
1585
+ autoFadeTimerRef.current = null;
1586
+ }
1587
+ if (!isMobile && overlayState === "idle") {
1588
+ autoFadeTimerRef.current = setTimeout(() => {
1589
+ setOpacity(0.4);
1590
+ }, 5e3);
1591
+ }
1592
+ return () => {
1593
+ if (autoFadeTimerRef.current) {
1594
+ clearTimeout(autoFadeTimerRef.current);
1595
+ }
1596
+ };
1597
+ }, [overlayState, isMobile]);
1598
+ (0, import_react8.useEffect)(() => {
1599
+ if (messageFadeTimerRef.current) {
1600
+ clearTimeout(messageFadeTimerRef.current);
1601
+ messageFadeTimerRef.current = null;
1602
+ }
1603
+ setMessageOpacity(1);
1604
+ messageFadeTimerRef.current = setTimeout(() => {
1605
+ setMessageOpacity(0);
1606
+ }, 8e3);
1607
+ return () => {
1608
+ if (messageFadeTimerRef.current) {
1609
+ clearTimeout(messageFadeTimerRef.current);
1610
+ }
1611
+ };
1612
+ }, [messages.filter((m) => m.type === "ai").slice(-1)[0]?.text]);
1613
+ (0, import_react8.useEffect)(() => {
1614
+ if (sttTranscript && voiceEnabled && resetTranscript) {
1615
+ onSendMessage(sttTranscript);
1616
+ resetTranscript();
1617
+ setExpansionState("expanded");
1618
+ setLastInputMethod("voice");
1619
+ lastInputTimeRef.current = Date.now();
1620
+ }
1621
+ }, [sttTranscript, voiceEnabled, resetTranscript, onSendMessage]);
1622
+ (0, import_react8.useEffect)(() => {
1623
+ const pageSuggestions = extractPageSuggestions();
1624
+ setSuggestions(pageSuggestions);
1625
+ }, []);
1626
+ (0, import_react8.useEffect)(() => {
1627
+ const detectWidgets = () => {
1628
+ const detected = detectTTSWidgets();
1629
+ setHasTTSWidgets(detected);
1630
+ if (detected) {
1631
+ const widgets = extractTTSWidgets();
1632
+ setTTSWidgets(widgets);
1633
+ } else {
1634
+ setTTSWidgets([]);
1635
+ }
1636
+ };
1637
+ detectWidgets();
1638
+ let timeoutId;
1639
+ const observer = new MutationObserver(() => {
1640
+ clearTimeout(timeoutId);
1641
+ timeoutId = setTimeout(detectWidgets, 500);
1642
+ });
1643
+ observer.observe(document.body, {
1644
+ childList: true,
1645
+ subtree: true
1646
+ });
1647
+ return () => {
1648
+ observer.disconnect();
1649
+ clearTimeout(timeoutId);
1650
+ };
1651
+ }, []);
1652
+ (0, import_react8.useEffect)(() => {
1653
+ if (glassTheme !== "auto") return;
1654
+ const { glassTheme: detected } = detectBackgroundContext();
1655
+ setDetectedGlassTheme(detected);
1656
+ let lastDetection = Date.now();
1657
+ const handleScroll = () => {
1658
+ const now = Date.now();
1659
+ if (now - lastDetection > 500) {
1660
+ const { glassTheme: newDetected } = detectBackgroundContext();
1661
+ setDetectedGlassTheme(newDetected);
1662
+ lastDetection = now;
1663
+ }
1664
+ };
1665
+ window.addEventListener("scroll", handleScroll);
1666
+ return () => window.removeEventListener("scroll", handleScroll);
1667
+ }, [glassTheme]);
1668
+ (0, import_react8.useEffect)(() => {
1669
+ const handleGlobalKeyDown = (e) => {
1670
+ if (e.key === "/" && expansionState === "collapsed" && !(e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement)) {
1671
+ e.preventDefault();
1672
+ setExpansionState("expanded");
1673
+ }
1674
+ if ((e.ctrlKey || e.metaKey) && e.key === "k") {
1675
+ e.preventDefault();
1676
+ if (expansionState === "collapsed") {
1677
+ setExpansionState("expanded");
1678
+ } else {
1679
+ inputRef.current?.focus();
1680
+ }
1681
+ }
1682
+ };
1683
+ window.addEventListener("keydown", handleGlobalKeyDown);
1684
+ return () => window.removeEventListener("keydown", handleGlobalKeyDown);
1685
+ }, [expansionState]);
1686
+ (0, import_react8.useEffect)(() => {
1687
+ if (expansionState === "expanded" && inputRef.current) {
1688
+ setTimeout(() => {
1689
+ inputRef.current?.focus();
1690
+ }, 100);
1691
+ }
1692
+ }, [expansionState]);
1693
+ (0, import_react8.useEffect)(() => {
1694
+ const timeSinceLastInput = Date.now() - lastInputTimeRef.current;
1695
+ const context = {
1696
+ isListening,
1697
+ inputValue,
1698
+ lastInputMethod,
1699
+ timeSinceLastInput
1700
+ };
1701
+ const detectedMode = detectMode(context);
1702
+ if (isListening) {
1703
+ setOverlayState("listening");
1704
+ setOpacity(getOverlayOpacity("listening", isMobile));
1705
+ } else if (inputValue.trim()) {
1706
+ setOverlayState("typing");
1707
+ setOpacity(getOverlayOpacity("typing", isMobile));
1708
+ } else {
1709
+ setOverlayState("idle");
1710
+ if (isMobile) {
1711
+ setOpacity(1);
1712
+ }
1713
+ }
1714
+ }, [isListening, inputValue, lastInputMethod, isMobile]);
1715
+ const handleInputChange = (value) => {
1716
+ onInputChange(value);
1717
+ setLastInputMethod("text");
1718
+ lastInputTimeRef.current = Date.now();
1719
+ };
1720
+ const handleIconClick = () => {
1721
+ if (expansionState === "collapsed") {
1722
+ setExpansionState("expanded");
1723
+ if (!isListening && voiceEnabled) {
1724
+ onMicClick();
1725
+ setLastInputMethod("voice");
1726
+ lastInputTimeRef.current = Date.now();
1727
+ }
1728
+ } else {
1729
+ onMicClick();
1730
+ setLastInputMethod("voice");
1731
+ lastInputTimeRef.current = Date.now();
1732
+ }
1733
+ };
1734
+ const handleTouchStart = (e) => {
1735
+ setTouchStartY(e.touches[0].clientY);
1736
+ };
1737
+ const handleTouchMove = (e) => {
1738
+ if (touchStartY === null) return;
1739
+ const touchY = e.touches[0].clientY;
1740
+ const deltaY = touchY - touchStartY;
1741
+ if (deltaY > 50 && expansionState === "expanded") {
1742
+ setExpansionState("collapsed");
1743
+ setTouchStartY(null);
1744
+ }
1745
+ };
1746
+ const handleTouchEnd = () => {
1747
+ setTouchStartY(null);
1748
+ };
1749
+ const handleSend = () => {
1750
+ if (inputValue.trim()) {
1751
+ onSendMessage(inputValue);
1752
+ lastInputTimeRef.current = Date.now();
1753
+ }
1754
+ };
1755
+ const handleKeyDown = (e) => {
1756
+ if (e.key === "Enter" && inputValue.trim()) {
1757
+ e.preventDefault();
1758
+ handleSend();
1759
+ }
1760
+ if (e.key === "Escape" && isListening) {
1761
+ onMicClick();
1762
+ }
1763
+ };
1764
+ const handleSuggestionTap = () => {
1765
+ if (suggestions.length > 0 && !inputValue.trim()) {
1766
+ onInputChange(suggestions[currentSuggestionIndex].text);
1767
+ }
1768
+ };
1769
+ const handleInputTouchStart = (e) => {
1770
+ setTouchStartYInput(e.touches[0].clientY);
1771
+ };
1772
+ const handleInputTouchMove = (e) => {
1773
+ if (touchStartYInput === null || inputValue.trim()) return;
1774
+ const touchY = e.touches[0].clientY;
1775
+ const deltaY = touchY - touchStartYInput;
1776
+ if (deltaY < -30) {
1777
+ setCurrentSuggestionIndex((prev) => (prev + 1) % suggestions.length);
1778
+ setTouchStartYInput(null);
1779
+ } else if (deltaY > 30) {
1780
+ setCurrentSuggestionIndex((prev) => (prev - 1 + suggestions.length) % suggestions.length);
1781
+ setTouchStartYInput(null);
1782
+ }
1783
+ };
1784
+ const handleInputTouchEnd = () => {
1785
+ setTouchStartYInput(null);
1786
+ };
1787
+ const handleWidgetSelect = (widget) => {
1788
+ console.log("[SubtitleOverlay] Selecting widget:", widget);
1789
+ const wrapper = widget.element.closest(".supernal-tts-widget") || widget.element;
1790
+ const playButton = wrapper.querySelector(".supernal-tts-play");
1791
+ const scrollTarget = playButton || wrapper;
1792
+ console.log("[SubtitleOverlay] Scrolling to:", scrollTarget);
1793
+ scrollTarget.scrollIntoView({
1794
+ behavior: "smooth",
1795
+ block: "start",
1796
+ inline: "nearest"
1797
+ });
1798
+ setShowPlaylist(false);
1799
+ setTimeout(() => {
1800
+ if (playButton) {
1801
+ console.log("[SubtitleOverlay] Clicking Supernal TTS play button:", playButton);
1802
+ playButton.click();
1803
+ } else {
1804
+ console.warn("[SubtitleOverlay] No play button found in widget. Widget may not be initialized yet.");
1805
+ console.warn("[SubtitleOverlay] Widget element:", wrapper);
1806
+ console.warn("[SubtitleOverlay] Widget HTML:", wrapper.innerHTML.substring(0, 300));
1807
+ }
1808
+ }, 800);
1809
+ };
1810
+ const handleTTSPlaylistClick = () => {
1811
+ if (ttsWidgets.length === 1) {
1812
+ handleWidgetSelect(ttsWidgets[0]);
1813
+ } else {
1814
+ setShowPlaylist(!showPlaylist);
1815
+ }
1816
+ };
1817
+ const effectiveGlassTheme = glassTheme === "auto" ? detectedGlassTheme : glassTheme;
1818
+ const glassStyles = effectiveGlassTheme === "dark" ? GLASS_INVERTED.darkOnLight : GLASS_INVERTED.lightOnDark;
1819
+ const lastAiMessage = messages.filter((m) => m.type === "ai").slice(-1)[0];
1820
+ const getIcon = () => {
1821
+ if (expansionState === "collapsed") return "@/";
1822
+ if (isListening) return "~/";
1823
+ return "</";
1824
+ };
1825
+ const getIconTitle = () => {
1826
+ if (expansionState === "collapsed") return "Tap to open chat";
1827
+ if (isListening) return "Tap to stop recording";
1828
+ return "Tap to start voice input";
1829
+ };
1830
+ if (expansionState === "collapsed") {
1831
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1832
+ hasTTSWidgets && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1833
+ "button",
1834
+ {
1835
+ type: "button",
1836
+ onClick: handleTTSPlaylistClick,
1837
+ className: `fixed p-3 rounded-full transition-all ${theme === "dark" ? "text-gray-400 hover:text-purple-400" : "text-gray-600 hover:text-purple-600"}`,
1838
+ style: {
1839
+ bottom: isMobile ? "calc(env(safe-area-inset-bottom, 0px) + 16px)" : "16px",
1840
+ left: "16px",
1841
+ zIndex: 55,
1842
+ background: theme === "dark" ? "rgba(55, 65, 81, 0.7)" : "rgba(255, 255, 255, 0.7)",
1843
+ backdropFilter: "blur(12px) saturate(180%)",
1844
+ WebkitBackdropFilter: "blur(12px) saturate(180%)",
1845
+ border: theme === "dark" ? "1px solid rgba(255, 255, 255, 0.12)" : "1px solid rgba(0, 0, 0, 0.1)",
1846
+ boxShadow: theme === "dark" ? "0 4px 16px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1)" : "0 4px 16px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.9)"
1847
+ },
1848
+ title: ttsWidgets.length === 1 ? "Go to readable section" : "View readable sections",
1849
+ "data-testid": "tts-playlist-button",
1850
+ "aria-label": ttsWidgets.length === 1 ? "Go to readable section" : "View readable sections",
1851
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xl font-bold select-none", "aria-hidden": "true", children: "~+" })
1852
+ }
1853
+ ),
1854
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1855
+ TTSPlaylistMenu,
1856
+ {
1857
+ isOpen: showPlaylist,
1858
+ onClose: () => setShowPlaylist(false),
1859
+ widgets: ttsWidgets,
1860
+ theme,
1861
+ onWidgetSelect: handleWidgetSelect
1862
+ }
1863
+ ),
1864
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1865
+ "div",
1866
+ {
1867
+ className: "fixed",
1868
+ style: {
1869
+ bottom: isMobile ? "calc(env(safe-area-inset-bottom, 0px) + 16px)" : "16px",
1870
+ right: "16px",
1871
+ zIndex: 55
1872
+ },
1873
+ "data-testid": "subtitle-overlay-collapsed",
1874
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1875
+ "button",
1876
+ {
1877
+ type: "button",
1878
+ onClick: handleIconClick,
1879
+ className: `p-3 rounded-full transition-all ${theme === "dark" ? "text-gray-400 hover:text-blue-400" : "text-gray-600 hover:text-blue-600"}`,
1880
+ style: {
1881
+ background: theme === "dark" ? "rgba(55, 65, 81, 0.7)" : "rgba(255, 255, 255, 0.7)",
1882
+ backdropFilter: "blur(12px) saturate(180%)",
1883
+ WebkitBackdropFilter: "blur(12px) saturate(180%)",
1884
+ border: theme === "dark" ? "1px solid rgba(255, 255, 255, 0.12)" : "1px solid rgba(0, 0, 0, 0.1)",
1885
+ boxShadow: theme === "dark" ? "0 4px 16px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1)" : "0 4px 16px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.9)"
1886
+ },
1887
+ title: getIconTitle(),
1888
+ "data-testid": "voice-input-button",
1889
+ "aria-label": getIconTitle(),
1890
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xl font-bold select-none", "aria-hidden": "true", children: getIcon() })
1891
+ }
1892
+ )
1893
+ }
1894
+ )
1895
+ ] });
1896
+ }
1897
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1898
+ "div",
1899
+ {
1900
+ className: `fixed bottom-0 left-0 right-0 transition-all duration-300 ease-in-out`,
1901
+ style: {
1902
+ opacity,
1903
+ bottom: isMobile ? "env(safe-area-inset-bottom, 0px)" : "0px",
1904
+ zIndex: 55,
1905
+ maxWidth: isMobile ? "100vw" : "650px",
1906
+ marginLeft: isMobile ? "0" : "auto",
1907
+ marginRight: isMobile ? "0" : "auto",
1908
+ padding: isMobile ? "12px" : "16px"
1909
+ },
1910
+ onTouchStart: handleTouchStart,
1911
+ onTouchMove: handleTouchMove,
1912
+ onTouchEnd: handleTouchEnd,
1913
+ "data-testid": "subtitle-overlay",
1914
+ role: "complementary",
1915
+ "aria-label": "Chat overlay",
1916
+ children: [
1917
+ lastAiMessage && messageOpacity > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1918
+ "div",
1919
+ {
1920
+ className: `mb-2 px-4 py-2 text-sm rounded-2xl transition-opacity duration-1000 ease-out animate-popup-in ${theme === "dark" ? "text-white" : "text-gray-900"}`,
1921
+ style: {
1922
+ opacity: messageOpacity,
1923
+ pointerEvents: messageOpacity === 0 ? "none" : "auto",
1924
+ ...theme === "dark" ? GLASS_RESPONSE_BUBBLE.dark : GLASS_RESPONSE_BUBBLE.light,
1925
+ animation: messageOpacity === 0 ? "popupFadeOut 0.5s forwards" : void 0
1926
+ },
1927
+ role: "status",
1928
+ "aria-live": "polite",
1929
+ children: [
1930
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-medium opacity-70", children: "AI:" }),
1931
+ " ",
1932
+ lastAiMessage.text
1933
+ ]
1934
+ }
1935
+ ),
1936
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center space-x-2", children: [
1937
+ hasTTSWidgets && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1938
+ "button",
1939
+ {
1940
+ type: "button",
1941
+ onClick: handleTTSPlaylistClick,
1942
+ className: `p-2 rounded-full transition-all flex-shrink-0 ${theme === "dark" ? "text-gray-400 hover:text-purple-400" : "text-gray-600 hover:text-purple-600"}`,
1943
+ style: {
1944
+ background: theme === "dark" ? "rgba(55, 65, 81, 0.5)" : "rgba(243, 244, 246, 0.5)",
1945
+ backdropFilter: "blur(8px)",
1946
+ WebkitBackdropFilter: "blur(8px)",
1947
+ border: theme === "dark" ? "1px solid rgba(255, 255, 255, 0.1)" : "1px solid rgba(0, 0, 0, 0.08)"
1948
+ },
1949
+ title: ttsWidgets.length === 1 ? "Go to readable section" : "View readable sections",
1950
+ "data-testid": "tts-playlist-button-expanded",
1951
+ "aria-label": ttsWidgets.length === 1 ? "Go to readable section" : "View readable sections",
1952
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-lg font-bold select-none", "aria-hidden": "true", children: "~+" })
1953
+ }
1954
+ ),
1955
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1956
+ "div",
1957
+ {
1958
+ className: "flex-1 flex items-center space-x-2 px-4 py-2 rounded-3xl",
1959
+ style: {
1960
+ ...glassStyles,
1961
+ border: effectiveGlassTheme === "dark" ? "1px solid rgba(255, 255, 255, 0.12)" : "1px solid rgba(0, 0, 0, 0.1)"
1962
+ },
1963
+ children: [
1964
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1965
+ "input",
1966
+ {
1967
+ ref: inputRef,
1968
+ type: "text",
1969
+ value: inputValue,
1970
+ onChange: (e) => handleInputChange(e.target.value),
1971
+ onKeyDown: handleKeyDown,
1972
+ onTouchStart: handleInputTouchStart,
1973
+ onTouchMove: handleInputTouchMove,
1974
+ onTouchEnd: handleInputTouchEnd,
1975
+ onClick: handleSuggestionTap,
1976
+ placeholder: suggestions.length > 0 && !inputValue.trim() ? `${suggestions[currentSuggestionIndex].text} (tap to use, swipe \u2195 to change)` : config.placeholder || "Type or speak...",
1977
+ className: "flex-1 px-3 py-2 text-sm bg-transparent focus:outline-none placeholder:opacity-60",
1978
+ style: {
1979
+ color: effectiveGlassTheme === "dark" ? "rgba(255, 255, 255, 0.95)" : "rgba(0, 0, 0, 0.9)"
1980
+ },
1981
+ "data-testid": "chat-input",
1982
+ "aria-label": "Chat message input"
1983
+ }
1984
+ ),
1985
+ isListening && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex space-x-1 items-center", "aria-hidden": "true", children: [...Array(5)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1986
+ "div",
1987
+ {
1988
+ className: "w-0.5 bg-red-500 rounded-full animate-pulse",
1989
+ style: {
1990
+ height: `${8 + Math.random() * 8}px`,
1991
+ animationDelay: `${i * 0.1}s`
1992
+ }
1993
+ },
1994
+ i
1995
+ )) }),
1996
+ inputValue.trim() && !isListening && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1997
+ "button",
1998
+ {
1999
+ type: "button",
2000
+ onClick: handleSend,
2001
+ className: "p-2 rounded-full text-white transition-all flex-shrink-0",
2002
+ style: {
2003
+ background: "linear-gradient(135deg, rgba(59, 130, 246, 0.9), rgba(37, 99, 235, 0.95))",
2004
+ backdropFilter: "blur(8px)",
2005
+ WebkitBackdropFilter: "blur(8px)",
2006
+ boxShadow: "0 0 12px rgba(59, 130, 246, 0.3), 0 2px 6px rgba(0, 0, 0, 0.2)",
2007
+ border: "1px solid rgba(255, 255, 255, 0.2)"
2008
+ },
2009
+ title: "Send message",
2010
+ "data-testid": "send-button",
2011
+ "aria-label": "Send message",
2012
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-base select-none", "aria-hidden": "true", children: "\u2192" })
2013
+ }
2014
+ ),
2015
+ voiceEnabled && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2016
+ "button",
2017
+ {
2018
+ type: "button",
2019
+ onClick: handleIconClick,
2020
+ className: `p-2 rounded-full transition-all flex-shrink-0 ${isListening ? "text-white animate-pulse" : `${theme === "dark" ? "text-gray-400 hover:text-blue-400" : "text-gray-600 hover:text-blue-600"}`}`,
2021
+ style: isListening ? {
2022
+ background: "linear-gradient(135deg, rgba(239, 68, 68, 0.8), rgba(220, 38, 38, 0.9))",
2023
+ backdropFilter: "blur(8px)",
2024
+ WebkitBackdropFilter: "blur(8px)",
2025
+ boxShadow: "0 0 16px rgba(239, 68, 68, 0.5), 0 2px 6px rgba(0, 0, 0, 0.2)"
2026
+ } : {
2027
+ background: theme === "dark" ? "rgba(55, 65, 81, 0.5)" : "rgba(243, 244, 246, 0.5)",
2028
+ backdropFilter: "blur(8px)",
2029
+ WebkitBackdropFilter: "blur(8px)",
2030
+ border: theme === "dark" ? "1px solid rgba(255, 255, 255, 0.1)" : "1px solid rgba(0, 0, 0, 0.08)"
2031
+ },
2032
+ title: getIconTitle(),
2033
+ "data-testid": "voice-input-button",
2034
+ "aria-label": getIconTitle(),
2035
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-lg font-bold select-none", "aria-hidden": "true", children: getIcon() })
2036
+ }
2037
+ ),
2038
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2039
+ "button",
2040
+ {
2041
+ type: "button",
2042
+ onClick: () => setExpansionState("collapsed"),
2043
+ className: `p-2 rounded-full transition-all flex-shrink-0 ${theme === "dark" ? "text-gray-400 hover:text-red-400" : "text-gray-600 hover:text-red-600"}`,
2044
+ style: {
2045
+ background: theme === "dark" ? "rgba(55, 65, 81, 0.5)" : "rgba(243, 244, 246, 0.5)",
2046
+ backdropFilter: "blur(8px)",
2047
+ WebkitBackdropFilter: "blur(8px)",
2048
+ border: theme === "dark" ? "1px solid rgba(255, 255, 255, 0.1)" : "1px solid rgba(0, 0, 0, 0.08)"
2049
+ },
2050
+ title: "Collapse",
2051
+ "data-testid": "collapse-button",
2052
+ "aria-label": "Collapse overlay",
2053
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-lg font-bold select-none", "aria-hidden": "true", children: "\xD7" })
2054
+ }
2055
+ )
2056
+ ]
2057
+ }
2058
+ )
2059
+ ] })
2060
+ ]
2061
+ }
2062
+ );
2063
+ };
2064
+
2065
+ // src/components/ChatBubble/ChatBubble.tsx
2066
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2067
+ var ChatBubble = ({
2068
+ messages,
2069
+ onSendMessage,
2070
+ onClearChat,
2071
+ position = "bottom-right",
2072
+ variant = "full",
2073
+ config: userConfig,
2074
+ defaultExpanded = true,
2075
+ storageKey = "chat-bubble-state",
2076
+ displayMode: propDisplayMode = "auto",
2077
+ drawerSide: propDrawerSide = "right"
2078
+ }) => {
2079
+ const mergedConfig = { ...DEFAULT_CONFIG, ...userConfig };
2080
+ if (userConfig?.logo && !userConfig?.avatar) {
2081
+ mergedConfig.avatar = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { src: userConfig.logo, alt: "Supernal", className: "w-6 h-6" });
2082
+ }
2083
+ const config = mergedConfig;
2084
+ const [isExpanded, setIsExpanded] = (0, import_react9.useState)(defaultExpanded);
2085
+ const [isMinimized, setIsMinimized] = (0, import_react9.useState)(false);
2086
+ const [inputValue, setInputValue] = (0, import_react9.useState)("");
2087
+ const [lastReadMessageCount, setLastReadMessageCount] = (0, import_react9.useState)(0);
2088
+ const [showWelcome, setShowWelcome] = (0, import_react9.useState)(
2089
+ config.welcome?.enabled && messages.length === 0
2090
+ );
2091
+ const [isDragging, setIsDragging] = (0, import_react9.useState)(false);
2092
+ const [dragInitiated, setDragInitiated] = (0, import_react9.useState)(false);
2093
+ const [isDocked, setIsDocked] = (0, import_react9.useState)(true);
2094
+ const [dockPosition, setDockPosition] = (0, import_react9.useState)(position);
2095
+ const [panelPosition, setPanelPosition] = (0, import_react9.useState)({ x: 0, y: 0 });
2096
+ const [theme, setTheme] = (0, import_react9.useState)("light");
2097
+ const [showMoreMenu, setShowMoreMenu] = (0, import_react9.useState)(false);
2098
+ const [, setTimestampTick] = (0, import_react9.useState)(0);
2099
+ const [localGlassMode, setLocalGlassMode] = (0, import_react9.useState)(config.glassMode ?? true);
2100
+ const [glassOpacity, setGlassOpacity] = (0, import_react9.useState)("medium");
2101
+ const [notifications, setNotifications] = (0, import_react9.useState)(true);
2102
+ const [voiceEnabled, setVoiceEnabled] = (0, import_react9.useState)(true);
2103
+ const [usePremiumVoices, setUsePremiumVoices] = (0, import_react9.useState)(false);
2104
+ const [autoReadResponses, setAutoReadResponses] = (0, import_react9.useState)(false);
2105
+ const [ttsSpeed, setTtsSpeed] = (0, import_react9.useState)(1);
2106
+ const [sttAutoRecordTimeout, setSttAutoRecordTimeout] = (0, import_react9.useState)(5e3);
2107
+ const [sttAutoExecute, setSttAutoExecute] = (0, import_react9.useState)(true);
2108
+ const sttAutoRecordTimeoutRef = (0, import_react9.useRef)(null);
2109
+ const { speak: speakTTS, stop: stopTTS, isPlaying: isTTSPlaying } = useTTS();
2110
+ const { startListening, stopListening, transcript: sttTranscript, isListening, resetTranscript } = useSTT();
2111
+ const [displayMode, setDisplayMode] = (0, import_react9.useState)(propDisplayMode);
2112
+ const [drawerSide, setDrawerSide] = (0, import_react9.useState)(propDrawerSide);
2113
+ (0, import_react9.useEffect)(() => {
2114
+ setDisplayMode(propDisplayMode);
2115
+ }, [propDisplayMode]);
2116
+ const [drawerOpen, setDrawerOpen] = (0, import_react9.useState)(false);
2117
+ const [touchStart, setTouchStart] = (0, import_react9.useState)(null);
2118
+ const [swipeProgress, setSwipeProgress] = (0, import_react9.useState)(0);
2119
+ const [isMobile, setIsMobile] = (0, import_react9.useState)(false);
2120
+ const [isMac, setIsMac] = (0, import_react9.useState)(false);
2121
+ const [currentHintIndex, setCurrentHintIndex] = (0, import_react9.useState)(0);
2122
+ const [isHydrated, setIsHydrated] = (0, import_react9.useState)(false);
2123
+ (0, import_react9.useEffect)(() => {
2124
+ if (typeof window !== "undefined") {
2125
+ const platform = window.navigator.platform.toLowerCase();
2126
+ const isMacPlatform = platform.includes("mac");
2127
+ setIsMac(isMacPlatform);
2128
+ }
2129
+ }, []);
2130
+ const inputHints = (0, import_react9.useMemo)(() => {
2131
+ const modKey = isMac ? "Cmd" : "Ctrl";
2132
+ return [
2133
+ `Press ${modKey}+/ to start voice recording`,
2134
+ "Press ESC to close this chat",
2135
+ "Type your message or click the mic",
2136
+ sttAutoExecute ? `Voice commands execute automatically` : "Voice commands fill this input"
2137
+ ];
2138
+ }, [isMac, sttAutoExecute]);
2139
+ (0, import_react9.useEffect)(() => {
2140
+ setCurrentHintIndex((prev) => (prev + 1) % inputHints.length);
2141
+ }, [messages.length, inputHints.length]);
2142
+ const messagesEndRef = (0, import_react9.useRef)(null);
2143
+ const inputRef = (0, import_react9.useRef)(null);
2144
+ const panelRef = (0, import_react9.useRef)(null);
2145
+ const dragRef = (0, import_react9.useRef)(null);
2146
+ const rafRef = (0, import_react9.useRef)(null);
2147
+ const formatRelativeTime = (timestamp) => {
2148
+ const now = /* @__PURE__ */ new Date();
2149
+ const messageTime = new Date(timestamp);
2150
+ const diffMs = now.getTime() - messageTime.getTime();
2151
+ const diffSeconds = Math.floor(diffMs / 1e3);
2152
+ const diffMinutes = Math.floor(diffSeconds / 60);
2153
+ const diffHours = Math.floor(diffMinutes / 60);
2154
+ const diffDays = Math.floor(diffHours / 24);
2155
+ if (diffSeconds < 60) return "just now";
2156
+ if (diffMinutes < 60) return `${diffMinutes} ${diffMinutes === 1 ? "minute" : "minutes"} ago`;
2157
+ if (diffHours < 24) return `${diffHours} ${diffHours === 1 ? "hour" : "hours"} ago`;
2158
+ if (diffDays < 7) return `${diffDays} ${diffDays === 1 ? "day" : "days"} ago`;
2159
+ return messageTime.toLocaleDateString();
2160
+ };
2161
+ (0, import_react9.useEffect)(() => {
2162
+ if (variant === "full") {
2163
+ try {
2164
+ const stored = localStorage.getItem(storageKey);
2165
+ if (stored !== null) {
2166
+ const state = JSON.parse(stored);
2167
+ const windowWidth = typeof window !== "undefined" ? window.innerWidth : 1920;
2168
+ const windowHeight = typeof window !== "undefined" ? window.innerHeight : 1080;
2169
+ const pos = state.panelPosition || { x: 0, y: 0 };
2170
+ const isInvalidPosition = Math.abs(pos.x) > windowWidth * 2 || Math.abs(pos.y) > windowHeight * 2;
2171
+ if (isInvalidPosition) {
2172
+ console.warn("ChatBubble: Invalid stored position detected, resetting to docked");
2173
+ setIsExpanded(defaultExpanded);
2174
+ setIsMinimized(false);
2175
+ setIsDocked(true);
2176
+ setDockPosition(position);
2177
+ setPanelPosition({ x: 0, y: 0 });
2178
+ setTheme(state.theme || "light");
2179
+ if (state.localGlassMode !== void 0) {
2180
+ setLocalGlassMode(state.localGlassMode);
2181
+ }
2182
+ if (state.glassOpacity !== void 0) {
2183
+ setGlassOpacity(state.glassOpacity);
2184
+ }
2185
+ if (state.notifications !== void 0) {
2186
+ setNotifications(state.notifications);
2187
+ }
2188
+ } else {
2189
+ setIsExpanded(state.isExpanded ?? defaultExpanded);
2190
+ setIsMinimized(state.isMinimized ?? false);
2191
+ setIsDocked(state.isDocked ?? true);
2192
+ setDockPosition(state.dockPosition || position);
2193
+ setPanelPosition(pos);
2194
+ setTheme(state.theme || "light");
2195
+ if (state.localGlassMode !== void 0) {
2196
+ setLocalGlassMode(state.localGlassMode);
2197
+ }
2198
+ if (state.notifications !== void 0) {
2199
+ setNotifications(state.notifications);
2200
+ }
2201
+ if (state.displayMode !== void 0) {
2202
+ setDisplayMode(state.displayMode);
2203
+ }
2204
+ if (state.drawerSide !== void 0) {
2205
+ setDrawerSide(state.drawerSide);
2206
+ }
2207
+ if (state.drawerOpen !== void 0) {
2208
+ setDrawerOpen(state.drawerOpen);
2209
+ }
2210
+ if (state.glassOpacity !== void 0) {
2211
+ setGlassOpacity(state.glassOpacity);
2212
+ }
2213
+ if (state.voiceEnabled !== void 0) {
2214
+ setVoiceEnabled(state.voiceEnabled);
2215
+ }
2216
+ if (state.usePremiumVoices !== void 0) {
2217
+ setUsePremiumVoices(state.usePremiumVoices);
2218
+ }
2219
+ if (state.autoReadResponses !== void 0) {
2220
+ setAutoReadResponses(state.autoReadResponses);
2221
+ }
2222
+ if (state.ttsSpeed !== void 0) {
2223
+ setTtsSpeed(state.ttsSpeed);
2224
+ }
2225
+ if (state.sttAutoRecordTimeout !== void 0) {
2226
+ setSttAutoRecordTimeout(state.sttAutoRecordTimeout);
2227
+ }
2228
+ if (state.sttAutoExecute !== void 0) {
2229
+ setSttAutoExecute(state.sttAutoExecute);
2230
+ }
2231
+ }
2232
+ }
2233
+ } catch {
2234
+ }
2235
+ }
2236
+ setIsHydrated(true);
2237
+ }, [storageKey, variant, defaultExpanded, position]);
2238
+ (0, import_react9.useEffect)(() => {
2239
+ if (variant !== "full") {
2240
+ setIsHydrated(true);
2241
+ }
2242
+ }, [variant]);
2243
+ (0, import_react9.useEffect)(() => {
2244
+ if (!isExpanded || isDocked || !panelRef.current) return;
2245
+ const checkBounds = () => {
2246
+ const rect = panelRef.current?.getBoundingClientRect();
2247
+ if (!rect) return;
2248
+ const windowWidth = window.innerWidth;
2249
+ const windowHeight = window.innerHeight;
2250
+ const isOffScreen = rect.right < 0 || rect.left > windowWidth || rect.bottom < 0 || rect.top > windowHeight;
2251
+ if (isOffScreen) {
2252
+ console.warn("ChatBubble detected off-screen, resetting to docked position");
2253
+ setIsDocked(true);
2254
+ setPanelPosition({ x: 0, y: 0 });
2255
+ }
2256
+ };
2257
+ const timeoutId = setTimeout(checkBounds, 100);
2258
+ return () => clearTimeout(timeoutId);
2259
+ }, [isExpanded, isDocked]);
2260
+ (0, import_react9.useEffect)(() => {
2261
+ const handleKeyDown = (e) => {
2262
+ if (e.key === "Escape" && isExpanded && !isDocked) {
2263
+ console.log("ChatBubble reset via Escape key");
2264
+ setIsDocked(true);
2265
+ setDockPosition(position);
2266
+ setPanelPosition({ x: 0, y: 0 });
2267
+ }
2268
+ };
2269
+ window.addEventListener("keydown", handleKeyDown);
2270
+ return () => window.removeEventListener("keydown", handleKeyDown);
2271
+ }, [isExpanded, isDocked, position]);
2272
+ (0, import_react9.useEffect)(() => {
2273
+ if (typeof window !== "undefined") {
2274
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
2275
+ setTheme(isDark ? "dark" : "light");
2276
+ }
2277
+ }, []);
2278
+ (0, import_react9.useEffect)(() => {
2279
+ if (typeof window === "undefined") return;
2280
+ const mediaQuery = window.matchMedia("(max-width: 767px)");
2281
+ const handleChange = (e) => {
2282
+ setIsMobile(e.matches);
2283
+ };
2284
+ handleChange(mediaQuery);
2285
+ mediaQuery.addEventListener("change", handleChange);
2286
+ return () => mediaQuery.removeEventListener("change", handleChange);
2287
+ }, []);
2288
+ const currentVariant = import_react9.default.useMemo(() => {
2289
+ if (displayMode !== "auto") {
2290
+ return displayMode;
2291
+ }
2292
+ return isMobile ? "drawer" : variant;
2293
+ }, [displayMode, isMobile, variant]);
2294
+ (0, import_react9.useEffect)(() => {
2295
+ const interval = setInterval(() => {
2296
+ setTimestampTick((tick) => tick + 1);
2297
+ }, 6e4);
2298
+ return () => clearInterval(interval);
2299
+ }, []);
2300
+ (0, import_react9.useEffect)(() => {
2301
+ if (variant === "full" || variant === "drawer") {
2302
+ try {
2303
+ localStorage.setItem(
2304
+ storageKey,
2305
+ JSON.stringify({
2306
+ isExpanded,
2307
+ isMinimized,
2308
+ isDocked,
2309
+ dockPosition,
2310
+ panelPosition,
2311
+ theme,
2312
+ localGlassMode,
2313
+ notifications,
2314
+ displayMode,
2315
+ drawerSide,
2316
+ drawerOpen,
2317
+ glassOpacity,
2318
+ voiceEnabled,
2319
+ usePremiumVoices,
2320
+ autoReadResponses,
2321
+ ttsSpeed,
2322
+ sttAutoRecordTimeout,
2323
+ sttAutoExecute
2324
+ })
2325
+ );
2326
+ } catch (error) {
2327
+ console.error("Failed to save chat state:", error);
2328
+ }
2329
+ }
2330
+ }, [isExpanded, isMinimized, isDocked, dockPosition, panelPosition, theme, localGlassMode, notifications, displayMode, drawerSide, drawerOpen, glassOpacity, voiceEnabled, usePremiumVoices, autoReadResponses, ttsSpeed, sttAutoRecordTimeout, sttAutoExecute, storageKey, variant]);
2331
+ const { registerInput } = useChatInput();
2332
+ (0, import_react9.useEffect)(() => {
2333
+ registerInput((text, submit = false) => {
2334
+ setInputValue(text);
2335
+ if (!isExpanded && variant === "full") {
2336
+ setIsExpanded(true);
2337
+ }
2338
+ setTimeout(() => {
2339
+ inputRef.current?.focus();
2340
+ if (submit) {
2341
+ onSendMessage(text);
2342
+ setInputValue("");
2343
+ }
2344
+ }, 100);
2345
+ });
2346
+ }, [registerInput, onSendMessage]);
2347
+ const unreadCount = Math.max(0, messages.length - lastReadMessageCount);
2348
+ const hasUnread = unreadCount > 0 && !isExpanded && variant === "full";
2349
+ (0, import_react9.useEffect)(() => {
2350
+ if (isExpanded || variant === "floating") {
2351
+ messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
2352
+ setLastReadMessageCount(messages.length);
2353
+ if (messages.length > 0) {
2354
+ setShowWelcome(false);
2355
+ }
2356
+ if (variant === "full") {
2357
+ inputRef.current?.focus();
2358
+ }
2359
+ }
2360
+ }, [messages, isExpanded, variant]);
2361
+ (0, import_react9.useEffect)(() => {
2362
+ if (isExpanded && variant === "full") {
2363
+ inputRef.current?.focus();
2364
+ }
2365
+ }, [isExpanded, variant]);
2366
+ (0, import_react9.useEffect)(() => {
2367
+ if (!voiceEnabled || !autoReadResponses || messages.length === 0) return;
2368
+ const lastMessage = messages[messages.length - 1];
2369
+ if (lastMessage.type === "ai") {
2370
+ speakTTS({
2371
+ text: lastMessage.text,
2372
+ speed: ttsSpeed,
2373
+ usePremium: usePremiumVoices,
2374
+ preferNative: !usePremiumVoices
2375
+ });
2376
+ }
2377
+ }, [messages, voiceEnabled, autoReadResponses, ttsSpeed, usePremiumVoices, speakTTS]);
2378
+ (0, import_react9.useEffect)(() => {
2379
+ if (sttTranscript && voiceEnabled) {
2380
+ setInputValue(sttTranscript);
2381
+ resetTranscript();
2382
+ if (sttAutoExecute && sttAutoRecordTimeoutRef.current) {
2383
+ onSendMessage(sttTranscript);
2384
+ setInputValue("");
2385
+ }
2386
+ if (sttAutoRecordTimeoutRef.current) {
2387
+ clearTimeout(sttAutoRecordTimeoutRef.current);
2388
+ sttAutoRecordTimeoutRef.current = null;
2389
+ }
2390
+ }
2391
+ }, [sttTranscript, voiceEnabled, resetTranscript, sttAutoExecute, onSendMessage]);
2392
+ (0, import_react9.useEffect)(() => {
2393
+ const handleKeyDown = (e) => {
2394
+ if (variant !== "full") return;
2395
+ if ((e.metaKey || e.ctrlKey) && e.key === "/" && voiceEnabled) {
2396
+ e.preventDefault();
2397
+ const wasExpanded = isExpanded;
2398
+ if (!isExpanded) {
2399
+ setIsExpanded(true);
2400
+ }
2401
+ if (!isListening) {
2402
+ const startRecording = async () => {
2403
+ try {
2404
+ await startListening();
2405
+ sttAutoRecordTimeoutRef.current = setTimeout(() => {
2406
+ stopListening();
2407
+ sttAutoRecordTimeoutRef.current = null;
2408
+ }, sttAutoRecordTimeout);
2409
+ } catch (error) {
2410
+ console.error("[ChatBubble] Failed to start recording:", error);
2411
+ }
2412
+ };
2413
+ if (wasExpanded) {
2414
+ startRecording();
2415
+ } else {
2416
+ setTimeout(startRecording, 300);
2417
+ }
2418
+ }
2419
+ return;
2420
+ }
2421
+ if (e.key === "/" && !e.shiftKey && !e.ctrlKey && !e.metaKey && !isExpanded) {
2422
+ const target = e.target;
2423
+ if (target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") {
2424
+ e.preventDefault();
2425
+ setIsExpanded(true);
2426
+ setTimeout(() => inputRef.current?.focus(), 0);
2427
+ }
2428
+ }
2429
+ if (e.key === "Escape") {
2430
+ if (sttAutoRecordTimeoutRef.current) {
2431
+ clearTimeout(sttAutoRecordTimeoutRef.current);
2432
+ sttAutoRecordTimeoutRef.current = null;
2433
+ stopListening();
2434
+ }
2435
+ if (showMoreMenu) {
2436
+ setShowMoreMenu(false);
2437
+ } else if (isExpanded) {
2438
+ setIsExpanded(false);
2439
+ }
2440
+ }
2441
+ };
2442
+ window.addEventListener("keydown", handleKeyDown);
2443
+ return () => {
2444
+ window.removeEventListener("keydown", handleKeyDown);
2445
+ if (sttAutoRecordTimeoutRef.current) {
2446
+ clearTimeout(sttAutoRecordTimeoutRef.current);
2447
+ }
2448
+ };
2449
+ }, [isExpanded, showMoreMenu, variant, voiceEnabled, isListening, startListening, stopListening, sttAutoRecordTimeout]);
2450
+ (0, import_react9.useEffect)(() => {
2451
+ if (currentVariant !== "drawer") return;
2452
+ const handleKeyDown = (e) => {
2453
+ if (e.key === "Escape" && drawerOpen) {
2454
+ setDrawerOpen(false);
2455
+ }
2456
+ };
2457
+ window.addEventListener("keydown", handleKeyDown);
2458
+ return () => window.removeEventListener("keydown", handleKeyDown);
2459
+ }, [currentVariant, drawerOpen]);
2460
+ (0, import_react9.useEffect)(() => {
2461
+ if (!showMoreMenu) return;
2462
+ const handleClickOutside = (e) => {
2463
+ const target = e.target;
2464
+ if (!target.closest("[data-more-menu]")) {
2465
+ setShowMoreMenu(false);
2466
+ }
2467
+ };
2468
+ document.addEventListener("mousedown", handleClickOutside);
2469
+ return () => document.removeEventListener("mousedown", handleClickOutside);
2470
+ }, [showMoreMenu]);
2471
+ (0, import_react9.useEffect)(() => {
2472
+ if (typeof window === "undefined" || currentVariant !== "drawer") return;
2473
+ const EDGE_ZONE_PX = 20;
2474
+ const SWIPE_THRESHOLD = 0.4;
2475
+ const VELOCITY_THRESHOLD = 0.5;
2476
+ const handleTouchStart = (e) => {
2477
+ const touch = e.touches[0];
2478
+ const isRightEdge = touch.clientX > window.innerWidth - EDGE_ZONE_PX;
2479
+ const isLeftEdge = touch.clientX < EDGE_ZONE_PX;
2480
+ if (drawerSide === "right" && isRightEdge || drawerSide === "left" && isLeftEdge) {
2481
+ setTouchStart({
2482
+ x: touch.clientX,
2483
+ y: touch.clientY,
2484
+ time: Date.now()
2485
+ });
2486
+ }
2487
+ };
2488
+ const handleTouchMove = (e) => {
2489
+ if (!touchStart || drawerOpen) return;
2490
+ const touch = e.touches[0];
2491
+ const deltaX = touch.clientX - touchStart.x;
2492
+ const deltaY = Math.abs(touch.clientY - touchStart.y);
2493
+ if (deltaY > 10 && Math.abs(deltaX) < deltaY) {
2494
+ setTouchStart(null);
2495
+ return;
2496
+ }
2497
+ const screenWidth = window.innerWidth;
2498
+ const direction = drawerSide === "right" ? -1 : 1;
2499
+ const progress = Math.max(0, Math.min(100, deltaX * direction / screenWidth * 100));
2500
+ setSwipeProgress(progress);
2501
+ if (Math.abs(deltaX) > 10) {
2502
+ e.preventDefault();
2503
+ }
2504
+ };
2505
+ const handleTouchEnd = () => {
2506
+ if (!touchStart) return;
2507
+ const deltaTime = Date.now() - touchStart.time;
2508
+ const velocity = swipeProgress / deltaTime;
2509
+ if (swipeProgress > SWIPE_THRESHOLD * 100 || velocity > VELOCITY_THRESHOLD) {
2510
+ setDrawerOpen(true);
2511
+ }
2512
+ setTouchStart(null);
2513
+ setSwipeProgress(0);
2514
+ };
2515
+ window.addEventListener("touchstart", handleTouchStart, { passive: true });
2516
+ window.addEventListener("touchmove", handleTouchMove, { passive: false });
2517
+ window.addEventListener("touchend", handleTouchEnd);
2518
+ return () => {
2519
+ window.removeEventListener("touchstart", handleTouchStart);
2520
+ window.removeEventListener("touchmove", handleTouchMove);
2521
+ window.removeEventListener("touchend", handleTouchEnd);
2522
+ };
2523
+ }, [touchStart, swipeProgress, drawerOpen, drawerSide, currentVariant]);
2524
+ const handlePanelMouseDown = (e) => {
2525
+ if (variant !== "full" || !isExpanded) return;
2526
+ const target = e.target;
2527
+ if (!target.closest("[data-drag-handle]")) return;
2528
+ if (target.closest("button") || target.closest("svg") || target.closest('[role="button"]')) {
2529
+ return;
2530
+ }
2531
+ e.preventDefault();
2532
+ setDragInitiated(true);
2533
+ const rect = panelRef.current?.getBoundingClientRect();
2534
+ if (!rect) return;
2535
+ if (isDocked) {
2536
+ const currentCenterX = rect.left + rect.width / 2;
2537
+ const currentCenterY = rect.top + rect.height / 2;
2538
+ const viewportCenterX = window.innerWidth / 2;
2539
+ const viewportCenterY = window.innerHeight / 2;
2540
+ const targetX = currentCenterX - viewportCenterX;
2541
+ const targetY = currentCenterY - viewportCenterY;
2542
+ setPanelPosition({ x: targetX, y: targetY });
2543
+ dragRef.current = {
2544
+ startX: e.clientX,
2545
+ startY: e.clientY,
2546
+ initialX: targetX,
2547
+ initialY: targetY,
2548
+ thresholdMet: false
2549
+ };
2550
+ } else {
2551
+ dragRef.current = {
2552
+ startX: e.clientX,
2553
+ startY: e.clientY,
2554
+ initialX: panelPosition.x,
2555
+ initialY: panelPosition.y,
2556
+ thresholdMet: false
2557
+ };
2558
+ }
2559
+ };
2560
+ (0, import_react9.useEffect)(() => {
2561
+ if (!dragInitiated || !dragRef.current) return;
2562
+ const dragThresholdPx = 5;
2563
+ const handleMouseMove = (e) => {
2564
+ if (!dragRef.current) return;
2565
+ const deltaX = e.clientX - dragRef.current.startX;
2566
+ const deltaY = e.clientY - dragRef.current.startY;
2567
+ const distance = Math.sqrt(deltaX ** 2 + deltaY ** 2);
2568
+ if (!dragRef.current.thresholdMet && distance < dragThresholdPx) {
2569
+ return;
2570
+ }
2571
+ if (!dragRef.current.thresholdMet) {
2572
+ dragRef.current.thresholdMet = true;
2573
+ setIsDragging(true);
2574
+ setIsDocked(false);
2575
+ }
2576
+ if (rafRef.current) {
2577
+ cancelAnimationFrame(rafRef.current);
2578
+ }
2579
+ rafRef.current = requestAnimationFrame(() => {
2580
+ setPanelPosition({
2581
+ x: dragRef.current.initialX + deltaX,
2582
+ y: dragRef.current.initialY + deltaY
2583
+ });
2584
+ });
2585
+ };
2586
+ const handleMouseUp = () => {
2587
+ if (rafRef.current) {
2588
+ cancelAnimationFrame(rafRef.current);
2589
+ rafRef.current = null;
2590
+ }
2591
+ if (dragRef.current?.thresholdMet && panelRef.current) {
2592
+ const rect = panelRef.current.getBoundingClientRect();
2593
+ const threshold = 20;
2594
+ const windowWidth = window.innerWidth;
2595
+ const windowHeight = window.innerHeight;
2596
+ const distanceToRight = windowWidth - rect.right;
2597
+ const distanceToLeft = rect.left;
2598
+ const distanceToTop = rect.top;
2599
+ const distanceToBottom = windowHeight - rect.bottom;
2600
+ const minDistance = Math.min(distanceToRight, distanceToLeft, distanceToTop, distanceToBottom);
2601
+ if (minDistance < threshold) {
2602
+ let newDockPosition;
2603
+ if (minDistance === distanceToRight) {
2604
+ newDockPosition = rect.top < windowHeight / 3 ? "top-right" : rect.bottom > windowHeight * 2 / 3 ? "bottom-right" : "right-center";
2605
+ } else if (minDistance === distanceToLeft) {
2606
+ newDockPosition = rect.top < windowHeight / 3 ? "top-left" : rect.bottom > windowHeight * 2 / 3 ? "bottom-left" : "left-center";
2607
+ } else if (minDistance === distanceToTop) {
2608
+ newDockPosition = rect.left < windowWidth / 3 ? "top-left" : rect.right > windowWidth * 2 / 3 ? "top-right" : "top-right";
2609
+ } else {
2610
+ newDockPosition = rect.left < windowWidth / 3 ? "bottom-left" : rect.right > windowWidth * 2 / 3 ? "bottom-right" : "bottom-center";
2611
+ }
2612
+ setDockPosition(newDockPosition);
2613
+ setIsDocked(true);
2614
+ setPanelPosition({ x: 0, y: 0 });
2615
+ }
2616
+ }
2617
+ setIsDragging(false);
2618
+ setDragInitiated(false);
2619
+ dragRef.current = null;
2620
+ };
2621
+ window.addEventListener("mousemove", handleMouseMove);
2622
+ window.addEventListener("mouseup", handleMouseUp);
2623
+ return () => {
2624
+ window.removeEventListener("mousemove", handleMouseMove);
2625
+ window.removeEventListener("mouseup", handleMouseUp);
2626
+ if (rafRef.current) {
2627
+ cancelAnimationFrame(rafRef.current);
2628
+ }
2629
+ };
2630
+ }, [dragInitiated]);
2631
+ const handleSend = (e) => {
2632
+ e.preventDefault();
2633
+ if (!inputValue.trim()) return;
2634
+ onSendMessage(inputValue.trim());
2635
+ setInputValue("");
2636
+ if (variant === "full") {
2637
+ setTimeout(() => inputRef.current?.focus(), 0);
2638
+ }
2639
+ };
2640
+ const handleToggle = () => {
2641
+ setIsExpanded(!isExpanded);
2642
+ };
2643
+ const handleDock = () => {
2644
+ setDockPosition(position);
2645
+ setIsDocked(true);
2646
+ setPanelPosition({ x: 0, y: 0 });
2647
+ };
2648
+ const handleHome = () => {
2649
+ setDockPosition(position);
2650
+ setIsDocked(true);
2651
+ setPanelPosition({ x: 0, y: 0 });
2652
+ setIsMinimized(false);
2653
+ };
2654
+ const handleClearChat = () => {
2655
+ if (onClearChat) {
2656
+ onClearChat();
2657
+ setShowWelcome(true);
2658
+ }
2659
+ };
2660
+ const handleMicClick = () => {
2661
+ if (isListening) {
2662
+ stopListening();
2663
+ } else {
2664
+ startListening();
2665
+ }
2666
+ };
2667
+ const handleDrawerTouchStart = (e) => {
2668
+ if (!drawerOpen) return;
2669
+ const touch = e.touches[0];
2670
+ setTouchStart({
2671
+ x: touch.clientX,
2672
+ y: touch.clientY,
2673
+ time: Date.now()
2674
+ });
2675
+ };
2676
+ const handleDrawerTouchMove = (e) => {
2677
+ if (!touchStart || !drawerOpen) return;
2678
+ const touch = e.touches[0];
2679
+ const deltaX = touch.clientX - touchStart.x;
2680
+ const deltaY = Math.abs(touch.clientY - touchStart.y);
2681
+ if (deltaY > 10 && Math.abs(deltaX) < deltaY) {
2682
+ return;
2683
+ }
2684
+ const screenWidth = window.innerWidth;
2685
+ const direction = drawerSide === "right" ? 1 : -1;
2686
+ const progress = Math.max(0, Math.min(100, deltaX * direction / screenWidth * 100));
2687
+ setSwipeProgress(progress);
2688
+ };
2689
+ const handleDrawerTouchEnd = () => {
2690
+ if (!touchStart) return;
2691
+ const SWIPE_THRESHOLD = 0.4;
2692
+ if (swipeProgress > SWIPE_THRESHOLD * 100) {
2693
+ setDrawerOpen(false);
2694
+ }
2695
+ setTouchStart(null);
2696
+ setSwipeProgress(0);
2697
+ };
2698
+ const dockClasses = DOCK_POSITIONS[dockPosition];
874
2699
  const primaryColor = config.theme?.primary || "blue";
875
- const glassMode = config.glassMode ?? true;
2700
+ const glassMode = localGlassMode;
2701
+ const glassClasses = glassMode ? glassOpacity === "low" ? "backdrop-blur-xl bg-white/90 dark:bg-gray-900/90 border border-white/20 dark:border-white/10" : glassOpacity === "high" ? "backdrop-blur-xl bg-white/50 dark:bg-gray-900/50 border border-white/20 dark:border-white/10" : "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";
2702
+ const glassGradient = glassMode ? glassOpacity === "low" ? "bg-gradient-to-br from-white/95 via-white/85 to-white/70 dark:from-gray-900/90 dark:via-gray-900/80 dark:to-gray-900/70" : glassOpacity === "high" ? "bg-gradient-to-br from-white/70 via-white/50 to-white/30 dark:from-gray-900/60 dark:via-gray-900/50 dark:to-gray-900/40" : "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";
2703
+ const getFloatingPositionStyle = () => {
2704
+ if (dockPosition.includes("top") && dockPosition.includes("left")) {
2705
+ return {
2706
+ top: 0,
2707
+ left: 0,
2708
+ transform: `translate(${panelPosition.x}px, ${panelPosition.y}px)`
2709
+ };
2710
+ } else if (dockPosition.includes("top") && dockPosition.includes("right")) {
2711
+ return {
2712
+ top: 0,
2713
+ right: 0,
2714
+ transform: `translate(${-panelPosition.x}px, ${panelPosition.y}px)`
2715
+ };
2716
+ } else if (dockPosition.includes("bottom") && dockPosition.includes("left")) {
2717
+ return {
2718
+ bottom: 0,
2719
+ left: 0,
2720
+ transform: `translate(${panelPosition.x}px, ${-panelPosition.y}px)`
2721
+ };
2722
+ } else if (dockPosition.includes("bottom") && dockPosition.includes("right")) {
2723
+ return {
2724
+ bottom: 0,
2725
+ right: 0,
2726
+ transform: `translate(${-panelPosition.x}px, ${-panelPosition.y}px)`
2727
+ };
2728
+ } else if (dockPosition.includes("left-center")) {
2729
+ return {
2730
+ left: 0,
2731
+ top: "50%",
2732
+ transform: `translate(${panelPosition.x}px, calc(-50% + ${panelPosition.y}px))`
2733
+ };
2734
+ } else if (dockPosition.includes("right-center")) {
2735
+ return {
2736
+ right: 0,
2737
+ top: "50%",
2738
+ transform: `translate(${-panelPosition.x}px, calc(-50% + ${panelPosition.y}px))`
2739
+ };
2740
+ } else if (dockPosition.includes("bottom-center")) {
2741
+ return {
2742
+ bottom: 0,
2743
+ left: "50%",
2744
+ transform: `translate(calc(-50% + ${panelPosition.x}px), ${-panelPosition.y}px)`
2745
+ };
2746
+ } else {
2747
+ return {
2748
+ left: "50%",
2749
+ top: "50%",
2750
+ transform: `translate(calc(-50% + ${panelPosition.x}px), calc(-50% + ${panelPosition.y}px))`
2751
+ };
2752
+ }
2753
+ };
876
2754
  const maxHeightVh = 80;
877
2755
  const dynamicHeight = `min(${maxHeightVh}vh, 700px)`;
878
2756
  const panelWidth = "min(650px, calc(100vw - 2rem))";
2757
+ const getDrawerTransform = () => {
2758
+ if (touchStart && swipeProgress > 0) {
2759
+ return drawerSide === "right" ? `translateX(${100 - swipeProgress}%)` : `translateX(${-100 + swipeProgress}%)`;
2760
+ }
2761
+ return drawerOpen ? "translateX(0%)" : drawerSide === "right" ? "translateX(100%)" : "translateX(-100%)";
2762
+ };
2763
+ if (!isHydrated) {
2764
+ return null;
2765
+ }
2766
+ if (currentVariant === "subtitle") {
2767
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2768
+ SubtitleOverlay,
2769
+ {
2770
+ messages,
2771
+ inputValue,
2772
+ onInputChange: setInputValue,
2773
+ onSendMessage: () => {
2774
+ const syntheticEvent = {
2775
+ preventDefault: () => {
2776
+ },
2777
+ stopPropagation: () => {
2778
+ }
2779
+ };
2780
+ handleSend(syntheticEvent);
2781
+ },
2782
+ voiceEnabled,
2783
+ isListening,
2784
+ onMicClick: handleMicClick,
2785
+ theme,
2786
+ config,
2787
+ sttTranscript,
2788
+ resetTranscript
2789
+ }
2790
+ );
2791
+ }
2792
+ if (currentVariant === "drawer") {
2793
+ const drawerWidth = "100vw";
2794
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2795
+ (drawerOpen || swipeProgress > 0) && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2796
+ "div",
2797
+ {
2798
+ className: "fixed inset-0 bg-black z-[99998] transition-opacity duration-300",
2799
+ style: {
2800
+ opacity: touchStart && swipeProgress > 0 ? swipeProgress / 100 * 0.5 : 0.5,
2801
+ zIndex: 999998
2802
+ // Force high z-index for overlay
2803
+ },
2804
+ onClick: () => setDrawerOpen(false)
2805
+ }
2806
+ ),
2807
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2808
+ "div",
2809
+ {
2810
+ className: `fixed ${drawerSide === "right" ? "right-0" : "left-0"} top-0 h-full z-[99999] flex flex-col ${glassClasses} shadow-2xl`,
2811
+ style: {
2812
+ width: drawerWidth,
2813
+ transform: getDrawerTransform(),
2814
+ transition: touchStart ? "none" : "transform 300ms cubic-bezier(0.4, 0, 0.2, 1)",
2815
+ willChange: "transform",
2816
+ zIndex: 999999
2817
+ // Force extremely high z-index
2818
+ },
2819
+ role: "dialog",
2820
+ "aria-modal": "true",
2821
+ "aria-label": "Chat drawer",
2822
+ onTouchStart: handleDrawerTouchStart,
2823
+ onTouchMove: handleDrawerTouchMove,
2824
+ onTouchEnd: handleDrawerTouchEnd,
2825
+ children: [
2826
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `${THEME_CLASSES.bg.header} ${glassMode ? THEME_CLASSES.bg.headerGradient : THEME_CLASSES.bg.headerLight}`, children: [
2827
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center space-x-3", children: [
2828
+ config.avatar && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative flex-shrink-0", children: [
2829
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Avatar, { avatar: config.avatar }),
2830
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white" })
2831
+ ] }),
2832
+ config.title && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: THEME_CLASSES.text.title, children: config.title }) })
2833
+ ] }),
2834
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { onClick: () => setDrawerOpen(false), className: THEME_CLASSES.button.close, title: "Close drawer", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
2835
+ ] }),
2836
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex-1 overflow-y-auto p-4 space-y-2", children: [
2837
+ showWelcome && messages.length === 0 && config.welcome?.enabled && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: THEME_CLASSES.welcome.container, children: [
2838
+ config.welcome.title && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h4", { className: THEME_CLASSES.welcome.title, style: INLINE_STYLES.welcomeTitle(theme === "dark"), children: config.welcome.title }),
2839
+ config.welcome.content && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: THEME_CLASSES.welcome.content, style: INLINE_STYLES.welcomeContent(theme === "dark"), children: config.welcome.content })
2840
+ ] }),
2841
+ messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `group flex items-center gap-1 mb-2 ${message.type === "user" ? "flex-row-reverse" : "flex-row"}`, children: [
2842
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2843
+ "div",
2844
+ {
2845
+ className: `inline-block px-4 py-2.5 rounded-2xl max-w-[95%] text-sm shadow-sm transition-all ${message.type === "user" ? THEME_CLASSES.message.user : message.type === "ai" ? THEME_CLASSES.message.ai : THEME_CLASSES.message.system}`,
2846
+ style: message.type === "user" ? INLINE_STYLES.messageUser() : message.type === "ai" ? INLINE_STYLES.messageAI(theme === "dark") : INLINE_STYLES.messageSystem(theme === "dark"),
2847
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageRenderer, { content: message.text, theme })
2848
+ }
2849
+ ),
2850
+ message.type === "ai" && voiceEnabled && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2851
+ TTSButton,
2852
+ {
2853
+ text: message.text,
2854
+ usePremiumVoices,
2855
+ speed: ttsSpeed,
2856
+ theme,
2857
+ size: "small"
2858
+ }
2859
+ ),
2860
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2861
+ "div",
2862
+ {
2863
+ 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"}`,
2864
+ title: typeof window !== "undefined" ? new Date(message.timestamp).toLocaleString() : "",
2865
+ children: typeof window !== "undefined" ? formatRelativeTime(message.timestamp) : ""
2866
+ }
2867
+ )
2868
+ ] }, message.id)),
2869
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ref: messagesEndRef })
2870
+ ] }),
2871
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2872
+ InputField,
2873
+ {
2874
+ inputValue,
2875
+ onInputChange: setInputValue,
2876
+ onSubmit: handleSend,
2877
+ placeholder: inputHints[currentHintIndex],
2878
+ glassClasses: "",
2879
+ theme,
2880
+ inputRef,
2881
+ sendButtonLabel: config.sendButtonLabel,
2882
+ voiceEnabled,
2883
+ isListening,
2884
+ onMicClick: handleMicClick,
2885
+ modKey: isMac ? "Cmd" : "Ctrl"
2886
+ }
2887
+ )
2888
+ ]
2889
+ }
2890
+ ),
2891
+ !drawerOpen && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2892
+ "div",
2893
+ {
2894
+ className: `fixed ${drawerSide === "right" ? "right-0" : "left-0"} bottom-20 opacity-30 hover:opacity-90 transition-opacity duration-300 z-[99999] cursor-pointer`,
2895
+ style: { zIndex: 999999 },
2896
+ onClick: () => setDrawerOpen(true),
2897
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: `${glassMode ? "backdrop-blur-md bg-white/50 dark:bg-gray-800/50" : "bg-white dark:bg-gray-800"} text-gray-700 dark:text-gray-200 px-3 py-3 ${drawerSide === "right" ? "rounded-l-xl" : "rounded-r-xl"} shadow-md hover:shadow-lg flex items-center justify-center transition-all`, children: voiceEnabled ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" }) }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.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" }) }) })
2898
+ }
2899
+ )
2900
+ ] });
2901
+ }
879
2902
  if (variant === "floating") {
880
2903
  const recentMessage = messages[messages.length - 1];
881
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2904
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
882
2905
  "div",
883
2906
  {
884
- className: `fixed z-50 ${isDragging ? "cursor-grabbing" : "cursor-grab"}`,
2907
+ className: `fixed z-[99999] ${isDragging ? "cursor-grabbing" : "cursor-grab"}`,
885
2908
  style: {
886
2909
  transform: `translate(${panelPosition.x}px, ${panelPosition.y}px)`,
2910
+ zIndex: 999999,
2911
+ // Force extremely high z-index
887
2912
  ...!isDragging && { transition: "transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)" }
888
2913
  },
889
2914
  onMouseDown: handlePanelMouseDown,
890
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: glassMode ? "backdrop-blur-xl bg-white/70 dark:bg-gray-900/70 border border-white/20 dark:border-white/10 rounded-2xl shadow-2xl p-3 max-w-xs" : "bg-white dark:bg-gray-900 border-gray-200 rounded-2xl shadow-2xl border p-3 max-w-xs", children: [
891
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
892
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2", children: [
893
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { avatar: config.avatar, size: "small" }),
894
- config.title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: THEME_CLASSES.text.floatingTitle, children: config.title })
2915
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `${glassClasses} rounded-2xl shadow-2xl p-3 max-w-xs ${!glassMode && "border-gray-200 border"}`, children: [
2916
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
2917
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center space-x-2", children: [
2918
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Avatar, { avatar: config.avatar, size: "small" }),
2919
+ config.title && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: THEME_CLASSES.text.floatingTitle, children: config.title })
895
2920
  ] }),
896
- onClearChat && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2921
+ onClearChat && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
897
2922
  "button",
898
2923
  {
899
2924
  onClick: onClearChat,
900
2925
  className: THEME_CLASSES.button.floatingClear,
901
2926
  title: "Clear chat",
902
2927
  "data-testid": ChatNames.clearButton,
903
- 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" }) })
2928
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
904
2929
  }
905
2930
  )
906
2931
  ] }),
907
- 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: [
908
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2932
+ recentMessage && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `mb-2 group flex items-center gap-2 ${recentMessage.type === "user" ? "flex-row-reverse" : "flex-row"}`, children: [
2933
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
909
2934
  "div",
910
2935
  {
911
2936
  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"}`,
912
2937
  children: recentMessage.text.length > 60 ? `${recentMessage.text.slice(0, 60)}...` : recentMessage.text
913
2938
  }
914
2939
  ),
915
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2940
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
916
2941
  "div",
917
2942
  {
918
2943
  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,29 +2946,33 @@ var ChatBubble = ({
921
2946
  }
922
2947
  )
923
2948
  ] }),
924
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2949
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
925
2950
  InputField,
926
2951
  {
927
2952
  compact: true,
928
2953
  inputValue,
929
2954
  onInputChange: setInputValue,
930
2955
  onSubmit: handleSend,
931
- placeholder: config.placeholder,
2956
+ placeholder: inputHints[currentHintIndex],
932
2957
  glassClasses: "",
933
2958
  theme,
934
- sendButtonLabel: config.sendButtonLabel
2959
+ sendButtonLabel: config.sendButtonLabel,
2960
+ modKey: isMac ? "Cmd" : "Ctrl"
935
2961
  }
936
2962
  )
937
2963
  ] })
938
2964
  }
939
2965
  );
940
2966
  }
941
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2967
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_jsx_runtime11.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
942
2968
  "div",
943
2969
  {
944
- className: "fixed z-50",
2970
+ className: "fixed",
945
2971
  style: {
946
2972
  ...dockClasses.container,
2973
+ zIndex: 2147483647,
2974
+ // Maximum z-index value
2975
+ position: "fixed",
947
2976
  ...isExpanded ? {
948
2977
  width: panelWidth,
949
2978
  height: isMinimized ? "auto" : dynamicHeight
@@ -953,107 +2982,247 @@ var ChatBubble = ({
953
2982
  }
954
2983
  },
955
2984
  children: [
956
- isExpanded && isMinimized && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2985
+ isExpanded && isMinimized && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
957
2986
  "div",
958
2987
  {
959
- className: glassMode ? "absolute backdrop-blur-xl bg-white/70 dark:bg-gray-900/70 border border-white/20 dark:border-white/10 rounded-3xl shadow-2xl p-4 transition-all duration-300" : "absolute bg-white dark:bg-gray-900 border-gray-200 rounded-3xl shadow-2xl border p-4 transition-all duration-300",
2988
+ ref: panelRef,
2989
+ className: `${isDocked ? "absolute" : "fixed z-[99999]"} ${glassGradient} rounded-3xl shadow-2xl border border-white/20 dark:border-white/10 ${!isDragging && "backdrop-blur-xl"} flex flex-col overflow-hidden ${!isDragging && "transition-all duration-300"}`,
960
2990
  style: {
961
- ...dockClasses.panel,
962
2991
  width: panelWidth,
963
- maxWidth: "400px"
2992
+ maxWidth: "400px",
2993
+ zIndex: 999999,
2994
+ // Force extremely high z-index
2995
+ ...isDocked ? {
2996
+ ...dockClasses.panel,
2997
+ // Only clear transform if dock position doesn't use transform
2998
+ ...dockClasses.panel.transform ? {} : { transform: "none" }
2999
+ } : getFloatingPositionStyle(),
3000
+ ...isDragging && { cursor: "grabbing" }
964
3001
  },
965
3002
  children: [
966
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between mb-3", children: [
967
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2", children: [
968
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { avatar: config.avatar, size: "small" }),
969
- config.title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: THEME_CLASSES.text.floatingTitle, children: config.title })
970
- ] }),
971
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
972
- "button",
3003
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
3004
+ "div",
3005
+ {
3006
+ "data-drag-handle": true,
3007
+ className: `${THEME_CLASSES.bg.header} ${glassMode ? THEME_CLASSES.bg.headerGradient : THEME_CLASSES.bg.headerLight} cursor-move`,
3008
+ onMouseDown: handlePanelMouseDown,
3009
+ onClick: (e) => {
3010
+ if (!dragRef.current?.thresholdMet) {
3011
+ const target = e.target;
3012
+ if (target.closest("button") || target.closest('[role="button"]')) {
3013
+ return;
3014
+ }
3015
+ setIsMinimized(false);
3016
+ }
3017
+ },
3018
+ children: [
3019
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center space-x-3", children: [
3020
+ config.avatar && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative flex-shrink-0", children: [
3021
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Avatar, { avatar: config.avatar }),
3022
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white" })
3023
+ ] }),
3024
+ config.title && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: THEME_CLASSES.text.title, children: config.title }) })
3025
+ ] }),
3026
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center space-x-1 flex-shrink-0", children: [
3027
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3028
+ "button",
3029
+ {
3030
+ onClick: (e) => {
3031
+ e.stopPropagation();
3032
+ setIsMinimized(true);
3033
+ },
3034
+ className: THEME_CLASSES.button.minimize,
3035
+ title: "Minimize chat",
3036
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })
3037
+ }
3038
+ ),
3039
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3040
+ "button",
3041
+ {
3042
+ onClick: (e) => {
3043
+ e.stopPropagation();
3044
+ setIsExpanded(false);
3045
+ },
3046
+ className: THEME_CLASSES.button.close,
3047
+ title: "Close chat",
3048
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
3049
+ }
3050
+ )
3051
+ ] })
3052
+ ]
3053
+ }
3054
+ ),
3055
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "p-4", children: [
3056
+ (() => {
3057
+ const lastAiMessage = [...messages].reverse().find((m) => m.type === "ai");
3058
+ return lastAiMessage ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.jsx)("div", { className: "mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: THEME_CLASSES.text.minimizedMessage, style: INLINE_STYLES.minimizedMessage(theme === "dark"), children: "No AI responses yet" }) });
3059
+ })(),
3060
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3061
+ InputField,
973
3062
  {
974
- onClick: () => setIsMinimized(false),
975
- className: "p-1 text-gray-400 hover:text-gray-600 dark:text-gray-300 dark:hover:text-gray-200 transition-colors",
976
- title: "Expand chat",
977
- 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" }) })
3063
+ compact: true,
3064
+ inputValue,
3065
+ onInputChange: setInputValue,
3066
+ onSubmit: handleSend,
3067
+ placeholder: inputHints[currentHintIndex],
3068
+ glassClasses: "",
3069
+ theme,
3070
+ sendButtonLabel: config.sendButtonLabel,
3071
+ modKey: isMac ? "Cmd" : "Ctrl"
978
3072
  }
979
3073
  )
980
- ] }),
981
- (() => {
982
- const lastAiMessage = [...messages].reverse().find((m) => m.type === "ai");
983
- 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" }) });
984
- })(),
985
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
986
- InputField,
987
- {
988
- compact: true,
989
- inputValue,
990
- onInputChange: setInputValue,
991
- onSubmit: handleSend,
992
- placeholder: config.placeholder,
993
- glassClasses: "",
994
- theme,
995
- sendButtonLabel: config.sendButtonLabel
996
- }
997
- )
3074
+ ] })
998
3075
  ]
999
3076
  }
1000
3077
  ),
1001
- isExpanded && !isMinimized && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3078
+ isExpanded && !isMinimized && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1002
3079
  "div",
1003
3080
  {
1004
3081
  ref: panelRef,
1005
- className: `${isDocked ? "absolute" : "fixed"} ${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"} rounded-3xl shadow-2xl border border-white/20 dark:border-white/10 backdrop-blur-xl flex flex-col overflow-hidden transition-all duration-300`,
3082
+ className: `${isDocked ? "absolute" : "fixed z-[99999]"} ${glassGradient} rounded-3xl shadow-2xl border border-white/20 dark:border-white/10 ${!isDragging && "backdrop-blur-xl"} flex flex-col overflow-hidden ${!isDragging && "transition-all duration-300"}`,
1006
3083
  style: {
1007
3084
  width: panelWidth,
1008
3085
  height: dynamicHeight,
1009
- ...isDocked ? dockClasses.panel : {
1010
- left: "50%",
1011
- top: "50%",
1012
- transform: `translate(calc(-50% + ${panelPosition.x}px), calc(-50% + ${panelPosition.y}px))`
1013
- },
3086
+ zIndex: 999999,
3087
+ // Force extremely high z-index
3088
+ ...isDocked ? {
3089
+ ...dockClasses.panel,
3090
+ // Only clear transform if dock position doesn't use transform
3091
+ ...dockClasses.panel.transform ? {} : { transform: "none" }
3092
+ } : getFloatingPositionStyle(),
1014
3093
  ...isDragging && { cursor: "grabbing" }
1015
3094
  },
1016
3095
  children: [
1017
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3096
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1018
3097
  "div",
1019
3098
  {
1020
3099
  "data-drag-handle": true,
1021
3100
  className: `${THEME_CLASSES.bg.header} ${glassMode ? THEME_CLASSES.bg.headerGradient : THEME_CLASSES.bg.headerLight} cursor-move`,
1022
3101
  onMouseDown: handlePanelMouseDown,
1023
3102
  children: [
1024
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-3", children: [
1025
- config.avatar && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative flex-shrink-0", children: [
1026
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { avatar: config.avatar }),
1027
- /* @__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" })
3103
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center space-x-3", children: [
3104
+ config.avatar && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative flex-shrink-0", children: [
3105
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Avatar, { avatar: config.avatar }),
3106
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white" })
1028
3107
  ] }),
1029
- 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 }) })
3108
+ config.title && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: THEME_CLASSES.text.title, children: config.title }) })
1030
3109
  ] }),
1031
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-1 flex-shrink-0 relative", "data-more-menu": true, children: [
1032
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3110
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center space-x-1 flex-shrink-0 relative", "data-more-menu": true, children: [
3111
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3112
+ "a",
3113
+ {
3114
+ href: "https://www.interface.supernal.ai",
3115
+ target: "_blank",
3116
+ rel: "noopener noreferrer",
3117
+ className: "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",
3118
+ title: "Visit Supernal Interface Documentation",
3119
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" }) })
3120
+ }
3121
+ ),
3122
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1033
3123
  "button",
1034
3124
  {
1035
3125
  onClick: () => setShowMoreMenu(!showMoreMenu),
1036
3126
  className: THEME_CLASSES.button.more,
1037
3127
  title: "More options",
1038
- 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" }) })
3128
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.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" }) })
1039
3129
  }
1040
3130
  ),
1041
- 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: [
1042
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3131
+ showMoreMenu && /* @__PURE__ */ (0, import_jsx_runtime11.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 min-w-[220px] z-50", "data-more-menu": true, children: [
3132
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "px-3 py-2 border-b border-gray-200 dark:border-gray-600 mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "grid grid-cols-4 gap-1", children: [
3133
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3134
+ "button",
3135
+ {
3136
+ onClick: () => setLocalGlassMode(false),
3137
+ className: `flex items-center justify-center p-2 rounded transition-all ${!localGlassMode ? "bg-blue-600 text-white shadow-sm" : "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600"}`,
3138
+ title: "Glass Off",
3139
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("rect", { x: "8", y: "8", width: "8", height: "8", rx: "1" }) })
3140
+ }
3141
+ ),
3142
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3143
+ "button",
3144
+ {
3145
+ onClick: () => {
3146
+ setLocalGlassMode(true);
3147
+ setGlassOpacity("low");
3148
+ },
3149
+ className: `flex items-center justify-center p-2 rounded transition-all ${localGlassMode && glassOpacity === "low" ? "bg-blue-600 text-white shadow-sm" : "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600"}`,
3150
+ title: "Glass Low",
3151
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("rect", { x: "9", y: "9", width: "6", height: "6", rx: "1", strokeWidth: "2" }) })
3152
+ }
3153
+ ),
3154
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3155
+ "button",
3156
+ {
3157
+ onClick: () => {
3158
+ setLocalGlassMode(true);
3159
+ setGlassOpacity("medium");
3160
+ },
3161
+ className: `flex items-center justify-center p-2 rounded transition-all ${localGlassMode && glassOpacity === "medium" ? "bg-blue-600 text-white shadow-sm" : "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600"}`,
3162
+ title: "Glass Medium",
3163
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [
3164
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("rect", { x: "8", y: "8", width: "8", height: "8", rx: "1", strokeWidth: "2" }),
3165
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("rect", { x: "10", y: "10", width: "4", height: "4", rx: "0.5", strokeWidth: "1.5" })
3166
+ ] })
3167
+ }
3168
+ ),
3169
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3170
+ "button",
3171
+ {
3172
+ onClick: () => {
3173
+ setLocalGlassMode(true);
3174
+ setGlassOpacity("high");
3175
+ },
3176
+ className: `flex items-center justify-center p-2 rounded transition-all ${localGlassMode && glassOpacity === "high" ? "bg-blue-600 text-white shadow-sm" : "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600"}`,
3177
+ title: "Glass High",
3178
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [
3179
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("rect", { x: "7", y: "7", width: "10", height: "10", rx: "1", strokeWidth: "2" }),
3180
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("rect", { x: "9", y: "9", width: "6", height: "6", rx: "0.5", strokeWidth: "1.5" }),
3181
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("rect", { x: "11", y: "11", width: "2", height: "2", rx: "0.5", strokeWidth: "1" })
3182
+ ] })
3183
+ }
3184
+ )
3185
+ ] }) }),
3186
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1043
3187
  "button",
1044
3188
  {
1045
3189
  onClick: () => {
1046
- handleToggleTheme();
1047
- setShowMoreMenu(false);
3190
+ const newTheme = theme === "light" ? "dark" : "light";
3191
+ setTheme(newTheme);
3192
+ if (typeof window !== "undefined") {
3193
+ document.documentElement.setAttribute("data-theme", newTheme);
3194
+ }
3195
+ },
3196
+ 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",
3197
+ children: [
3198
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.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" }) }),
3199
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
3200
+ theme === "light" ? "Dark" : "Light",
3201
+ " Mode"
3202
+ ] })
3203
+ ]
3204
+ }
3205
+ ),
3206
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
3207
+ "button",
3208
+ {
3209
+ onClick: () => {
3210
+ setVoiceEnabled(!voiceEnabled);
3211
+ if (!voiceEnabled) {
3212
+ onSendMessage("Voice control enabled! Use the microphone button to speak, or click speaker icons to hear messages.");
3213
+ }
1048
3214
  },
1049
3215
  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",
1050
3216
  children: [
1051
- 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" }) }),
1052
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: theme === "light" ? "Dark mode" : "Light mode" })
3217
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" }) }),
3218
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
3219
+ voiceEnabled ? "Disable" : "Enable",
3220
+ " Voice"
3221
+ ] })
1053
3222
  ]
1054
3223
  }
1055
3224
  ),
1056
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3225
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1057
3226
  "button",
1058
3227
  {
1059
3228
  onClick: () => {
@@ -1062,26 +3231,33 @@ var ChatBubble = ({
1062
3231
  },
1063
3232
  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",
1064
3233
  children: [
1065
- /* @__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" }) }),
1066
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Reset position" })
3234
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.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" }) }),
3235
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Reset position" })
1067
3236
  ]
1068
3237
  }
1069
3238
  ),
1070
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3239
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1071
3240
  "button",
1072
3241
  {
1073
3242
  onClick: () => {
1074
- setShowInfo(!showInfo);
3243
+ const helpMessages = [
3244
+ '**How to Use This Chat**\n\n- **Theme**: Toggle between light and dark modes\n- **Glass Effect**: Adjust transparency (Off/Low/Medium/High)\n- **Reset Position**: Return chat to default corner\n- **Minimize**: Compact view showing last message\n- **Clear**: Delete all messages and start fresh\n- **Drag**: Click and drag header to reposition chat\n- **Keyboard**: Press "/" to focus input, Esc to reset position'
3245
+ ];
3246
+ helpMessages.forEach((text, index) => {
3247
+ setTimeout(() => {
3248
+ onSendMessage(text);
3249
+ }, index * 100);
3250
+ });
1075
3251
  setShowMoreMenu(false);
1076
3252
  },
1077
3253
  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",
1078
3254
  children: [
1079
- /* @__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" }) }),
1080
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "How to use" })
3255
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.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" }) }),
3256
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "How to use" })
1081
3257
  ]
1082
3258
  }
1083
3259
  ),
1084
- onClearChat && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3260
+ onClearChat && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1085
3261
  "button",
1086
3262
  {
1087
3263
  onClick: () => {
@@ -1090,77 +3266,41 @@ var ChatBubble = ({
1090
3266
  },
1091
3267
  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",
1092
3268
  children: [
1093
- /* @__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" }) }),
1094
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Clear chat" })
3269
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.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" }) }),
3270
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Clear chat" })
1095
3271
  ]
1096
3272
  }
1097
3273
  )
1098
3274
  ] }),
1099
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3275
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1100
3276
  "button",
1101
3277
  {
1102
3278
  onClick: () => setIsMinimized(true),
1103
3279
  className: THEME_CLASSES.button.minimize,
1104
3280
  title: "Minimize",
1105
- 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" }) })
3281
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 12H4" }) })
1106
3282
  }
1107
3283
  ),
1108
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3284
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1109
3285
  "button",
1110
3286
  {
1111
3287
  onClick: handleToggle,
1112
3288
  className: THEME_CLASSES.button.close,
1113
3289
  title: "Close",
1114
- 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" }) })
3290
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
1115
3291
  }
1116
3292
  )
1117
3293
  ] })
1118
3294
  ]
1119
3295
  }
1120
3296
  ),
1121
- showInfo && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: THEME_CLASSES.text.infoPopup, style: INLINE_STYLES.infoText(theme === "dark"), children: [
1122
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-bold mb-2", children: "How to Use" }),
1123
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2 text-xs", style: INLINE_STYLES.infoText(theme === "dark"), children: [
1124
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1125
- "\u2022 ",
1126
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Theme:" }),
1127
- " Toggle between light and dark modes"
1128
- ] }),
1129
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1130
- "\u2022 ",
1131
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Home:" }),
1132
- " Reset chat position to default"
1133
- ] }),
1134
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1135
- "\u2022 ",
1136
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Minimize:" }),
1137
- " Compact view with last message"
1138
- ] }),
1139
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1140
- "\u2022 ",
1141
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Clear:" }),
1142
- " Delete all messages and start fresh"
1143
- ] }),
1144
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1145
- "\u2022 ",
1146
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Drag:" }),
1147
- " Click and drag header to reposition"
1148
- ] }),
1149
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1150
- "\u2022 ",
1151
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Keyboard:" }),
1152
- ' Press "/" to open, Esc to close'
1153
- ] })
1154
- ] }),
1155
- 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 })
1156
- ] }),
1157
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 overflow-y-auto p-4 space-y-2", children: [
1158
- showWelcome && messages.length === 0 && config.welcome?.enabled && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: THEME_CLASSES.welcome.container, children: [
1159
- 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 }),
1160
- 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 }),
1161
- config.welcome.suggestedCommands && config.welcome.suggestedCommands.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: THEME_CLASSES.welcome.commandsContainer, children: [
1162
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: THEME_CLASSES.welcome.commandsHeader, children: "Try these commands:" }),
1163
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "space-y-1", children: config.welcome.suggestedCommands.map((cmd, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3297
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex-1 overflow-y-auto p-4 space-y-2", children: [
3298
+ showWelcome && messages.length === 0 && config.welcome?.enabled && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: THEME_CLASSES.welcome.container, children: [
3299
+ config.welcome.title && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h4", { className: THEME_CLASSES.welcome.title, style: INLINE_STYLES.welcomeTitle(theme === "dark"), children: config.welcome.title }),
3300
+ config.welcome.content && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: THEME_CLASSES.welcome.content, style: INLINE_STYLES.welcomeContent(theme === "dark"), children: config.welcome.content }),
3301
+ config.welcome.suggestedCommands && config.welcome.suggestedCommands.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: THEME_CLASSES.welcome.commandsContainer, children: [
3302
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: THEME_CLASSES.welcome.commandsHeader, children: "Try these commands:" }),
3303
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "space-y-1", children: config.welcome.suggestedCommands.map((cmd, idx) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1164
3304
  "button",
1165
3305
  {
1166
3306
  onClick: () => {
@@ -1170,29 +3310,39 @@ var ChatBubble = ({
1170
3310
  },
1171
3311
  className: THEME_CLASSES.welcome.commandButton,
1172
3312
  children: [
1173
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: THEME_CLASSES.welcome.commandText, style: INLINE_STYLES.commandText(theme === "dark"), children: [
3313
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: THEME_CLASSES.welcome.commandText, style: INLINE_STYLES.commandText(theme === "dark"), children: [
1174
3314
  '"',
1175
3315
  cmd.text,
1176
3316
  '"'
1177
3317
  ] }),
1178
- cmd.desc && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: THEME_CLASSES.welcome.commandDesc, style: INLINE_STYLES.commandDesc(theme === "dark"), children: cmd.desc })
3318
+ cmd.desc && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: THEME_CLASSES.welcome.commandDesc, style: INLINE_STYLES.commandDesc(theme === "dark"), children: cmd.desc })
1179
3319
  ]
1180
3320
  },
1181
3321
  idx
1182
3322
  )) })
1183
3323
  ] })
1184
3324
  ] }),
1185
- 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: [
1186
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3325
+ messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `group flex items-center gap-2 mb-2 ${message.type === "user" ? "flex-row-reverse" : "flex-row"}`, children: [
3326
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1187
3327
  "div",
1188
3328
  {
1189
3329
  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}`,
1190
3330
  style: message.type === "user" ? INLINE_STYLES.messageUser() : message.type === "ai" ? INLINE_STYLES.messageAI(theme === "dark") : INLINE_STYLES.messageSystem(theme === "dark"),
1191
3331
  "data-testid": `chat-message-${message.type}`,
1192
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "break-words leading-relaxed", children: message.text })
3332
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageRenderer, { content: message.text, theme })
1193
3333
  }
1194
3334
  ),
1195
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3335
+ message.type === "ai" && voiceEnabled && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3336
+ TTSButton,
3337
+ {
3338
+ text: message.text,
3339
+ usePremiumVoices,
3340
+ speed: ttsSpeed,
3341
+ theme,
3342
+ size: "small"
3343
+ }
3344
+ ),
3345
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1196
3346
  "div",
1197
3347
  {
1198
3348
  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"}`,
@@ -1201,34 +3351,38 @@ var ChatBubble = ({
1201
3351
  }
1202
3352
  )
1203
3353
  ] }, message.id)),
1204
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: messagesEndRef })
3354
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ref: messagesEndRef })
1205
3355
  ] }),
1206
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3356
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1207
3357
  InputField,
1208
3358
  {
1209
3359
  inputValue,
1210
3360
  onInputChange: setInputValue,
1211
3361
  onSubmit: handleSend,
1212
- placeholder: config.placeholder,
3362
+ placeholder: inputHints[currentHintIndex],
1213
3363
  glassClasses: "",
1214
3364
  theme,
1215
3365
  inputRef,
1216
- sendButtonLabel: config.sendButtonLabel
3366
+ sendButtonLabel: config.sendButtonLabel,
3367
+ voiceEnabled,
3368
+ isListening,
3369
+ onMicClick: handleMicClick,
3370
+ modKey: isMac ? "Cmd" : "Ctrl"
1217
3371
  }
1218
3372
  )
1219
3373
  ]
1220
3374
  }
1221
3375
  ),
1222
- !isExpanded && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3376
+ !isExpanded && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1223
3377
  "button",
1224
3378
  {
1225
3379
  onClick: handleToggle,
1226
3380
  className: THEME_CLASSES.bg.bubble,
1227
3381
  "data-testid": ChatNames.bubble,
1228
- title: "Open chat",
3382
+ title: `Open chat (press ${isMac ? "Cmd" : "Ctrl"}+/ for voice recording)`,
1229
3383
  children: [
1230
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: "/logo.svg", alt: "Supernal", className: "w-8 h-8" }),
1231
- 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 }) })
3384
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { src: config.logo, alt: "Supernal", className: "w-8 h-8" }),
3385
+ hasUnread && notifications && /* @__PURE__ */ (0, import_jsx_runtime11.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_runtime11.jsx)("span", { className: "text-xs text-white font-bold", children: unreadCount > 9 ? "9+" : unreadCount }) })
1232
3386
  ]
1233
3387
  }
1234
3388
  )
@@ -1238,31 +3392,31 @@ var ChatBubble = ({
1238
3392
  };
1239
3393
 
1240
3394
  // src/components/AutoNavigationContext.tsx
1241
- var import_react5 = require("react");
3395
+ var import_react11 = require("react");
1242
3396
 
1243
3397
  // src/hooks/useNavigationGraph.tsx
1244
- var import_react4 = require("react");
3398
+ var import_react10 = require("react");
1245
3399
  var import_browser2 = require("@supernal/interface/browser");
1246
- var import_jsx_runtime4 = require("react/jsx-runtime");
1247
- var NavigationContextContext = (0, import_react4.createContext)("global");
3400
+ var import_jsx_runtime12 = require("react/jsx-runtime");
3401
+ var NavigationContextContext = (0, import_react10.createContext)("global");
1248
3402
  function NavigationContextProvider({
1249
3403
  value,
1250
3404
  children
1251
3405
  }) {
1252
3406
  const graph = useNavigationGraph();
1253
- (0, import_react4.useEffect)(() => {
3407
+ (0, import_react10.useEffect)(() => {
1254
3408
  graph.setCurrentContext(value);
1255
3409
  }, [value, graph]);
1256
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NavigationContextContext.Provider, { value, children });
3410
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(NavigationContextContext.Provider, { value, children });
1257
3411
  }
1258
3412
  function useNavigationGraph() {
1259
3413
  return import_browser2.NavigationGraph.getInstance();
1260
3414
  }
1261
3415
  function useCurrentContext() {
1262
- const contextFromProvider = (0, import_react4.useContext)(NavigationContextContext);
3416
+ const contextFromProvider = (0, import_react10.useContext)(NavigationContextContext);
1263
3417
  const graph = useNavigationGraph();
1264
- const [graphContext, setGraphContext] = (0, import_react4.useState)("");
1265
- (0, import_react4.useEffect)(() => {
3418
+ const [graphContext, setGraphContext] = (0, import_react10.useState)("");
3419
+ (0, import_react10.useEffect)(() => {
1266
3420
  const interval = setInterval(() => {
1267
3421
  const current = graph.getCurrentContext?.() || "";
1268
3422
  if (current !== graphContext) {
@@ -1276,7 +3430,7 @@ function useCurrentContext() {
1276
3430
  function useRegisterTool(toolId, contextId, metadata) {
1277
3431
  const graph = useNavigationGraph();
1278
3432
  const currentContext = useCurrentContext();
1279
- (0, import_react4.useEffect)(() => {
3433
+ (0, import_react10.useEffect)(() => {
1280
3434
  let targetContext = contextId || currentContext;
1281
3435
  if (!contextId && metadata) {
1282
3436
  const detection = graph.detectToolContext?.(toolId, metadata);
@@ -1290,10 +3444,10 @@ function useRegisterTool(toolId, contextId, metadata) {
1290
3444
  function useNavigationPath(targetContextOrToolId, isToolId = false) {
1291
3445
  const graph = useNavigationGraph();
1292
3446
  const currentContext = useCurrentContext();
1293
- const [path, setPath] = (0, import_react4.useState)(null);
1294
- const [loading, setLoading] = (0, import_react4.useState)(true);
1295
- const [error, setError] = (0, import_react4.useState)();
1296
- (0, import_react4.useEffect)(() => {
3447
+ const [path, setPath] = (0, import_react10.useState)(null);
3448
+ const [loading, setLoading] = (0, import_react10.useState)(true);
3449
+ const [error, setError] = (0, import_react10.useState)();
3450
+ (0, import_react10.useEffect)(() => {
1297
3451
  try {
1298
3452
  let targetContext = targetContextOrToolId;
1299
3453
  if (isToolId) {
@@ -1321,9 +3475,9 @@ function useNavigationPath(targetContextOrToolId, isToolId = false) {
1321
3475
  function useNavigate() {
1322
3476
  const graph = useNavigationGraph();
1323
3477
  const currentContext = useCurrentContext();
1324
- const [navigating, setNavigating] = (0, import_react4.useState)(false);
1325
- const [error, setError] = (0, import_react4.useState)();
1326
- const navigateTo = (0, import_react4.useCallback)(async (targetContextOrToolId, isToolId = false, executeNavigation) => {
3478
+ const [navigating, setNavigating] = (0, import_react10.useState)(false);
3479
+ const [error, setError] = (0, import_react10.useState)();
3480
+ const navigateTo = (0, import_react10.useCallback)(async (targetContextOrToolId, isToolId = false, executeNavigation) => {
1327
3481
  setNavigating(true);
1328
3482
  setError(void 0);
1329
3483
  try {
@@ -1365,8 +3519,8 @@ function useNavigate() {
1365
3519
  }
1366
3520
  function useAllContexts() {
1367
3521
  const graph = useNavigationGraph();
1368
- const [contexts, setContexts] = (0, import_react4.useState)(graph.getAllContexts?.());
1369
- (0, import_react4.useEffect)(() => {
3522
+ const [contexts, setContexts] = (0, import_react10.useState)(graph.getAllContexts?.());
3523
+ (0, import_react10.useEffect)(() => {
1370
3524
  const interval = setInterval(() => {
1371
3525
  setContexts(graph.getAllContexts?.());
1372
3526
  }, 1e3);
@@ -1376,28 +3530,28 @@ function useAllContexts() {
1376
3530
  }
1377
3531
 
1378
3532
  // src/components/AutoNavigationContext.tsx
1379
- var import_jsx_runtime5 = require("react/jsx-runtime");
3533
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1380
3534
  function AutoNavigationContext({
1381
3535
  children,
1382
3536
  routes,
1383
3537
  onNavigate
1384
3538
  }) {
1385
- const [pathname, setPathname] = (0, import_react5.useState)("/");
1386
- (0, import_react5.useEffect)(() => {
3539
+ const [pathname, setPathname] = (0, import_react11.useState)("/");
3540
+ (0, import_react11.useEffect)(() => {
1387
3541
  if (typeof window !== "undefined") {
1388
3542
  setPathname(window.location.pathname);
1389
3543
  }
1390
3544
  }, []);
1391
3545
  const context = routes ? inferContextFromPath(pathname, routes) : null;
1392
- (0, import_react5.useEffect)(() => {
3546
+ (0, import_react11.useEffect)(() => {
1393
3547
  if (onNavigate && context) {
1394
3548
  onNavigate(context);
1395
3549
  }
1396
3550
  }, [context, onNavigate]);
1397
3551
  if (!context) {
1398
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children });
3552
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children });
1399
3553
  }
1400
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NavigationContextProvider, { value: context, children });
3554
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(NavigationContextProvider, { value: context, children });
1401
3555
  }
1402
3556
  function inferContextFromPath(path, customRoutes) {
1403
3557
  if (customRoutes) {
@@ -1428,22 +3582,35 @@ function inferContextFromPath(path, customRoutes) {
1428
3582
 
1429
3583
  // src/components/SupernalProvider.tsx
1430
3584
  var import_browser3 = require("@supernal/interface/browser");
1431
- var import_jsx_runtime6 = require("react/jsx-runtime");
3585
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1432
3586
  function ChatBubbleConnector({
1433
3587
  theme,
1434
3588
  position,
1435
- welcomeMessage
3589
+ welcomeMessage,
3590
+ glassMode,
3591
+ logo,
3592
+ variant,
3593
+ displayMode,
3594
+ drawerSide
1436
3595
  }) {
1437
3596
  const { messages, sendMessage, clearMessages } = useChatContext();
1438
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3597
+ console.log("[ChatBubbleConnector] Props received:", { variant, displayMode, position });
3598
+ const config = {
3599
+ glassMode,
3600
+ ...logo ? { logo } : {}
3601
+ };
3602
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1439
3603
  ChatBubble,
1440
3604
  {
1441
3605
  messages,
1442
3606
  onSendMessage: sendMessage,
1443
3607
  onClearChat: clearMessages,
1444
3608
  position,
1445
- variant: "full",
1446
- defaultExpanded: true
3609
+ variant: variant || "full",
3610
+ defaultExpanded: true,
3611
+ config,
3612
+ displayMode,
3613
+ drawerSide
1447
3614
  }
1448
3615
  );
1449
3616
  }
@@ -1456,13 +3623,20 @@ function SupernalProvider({
1456
3623
  welcomeMessage,
1457
3624
  routes,
1458
3625
  disabled = false,
3626
+ glassMode = true,
3627
+ logo,
3628
+ variant = "full",
3629
+ displayMode = "auto",
3630
+ drawerSide = "right",
1459
3631
  onNavigate,
1460
3632
  onToolExecute
1461
3633
  }) {
1462
3634
  const shouldRenderChatBubble = !disabled;
3635
+ const effectiveDisplayMode = variant !== "full" ? variant : displayMode;
1463
3636
  console.log("[SupernalProvider] disabled:", disabled, "type:", typeof disabled);
1464
3637
  console.log("[SupernalProvider] shouldRenderChatBubble:", shouldRenderChatBubble);
1465
- (0, import_react6.useEffect)(() => {
3638
+ console.log("[SupernalProvider] variant:", variant, "effectiveDisplayMode:", effectiveDisplayMode);
3639
+ (0, import_react12.useEffect)(() => {
1466
3640
  if (typeof window === "undefined") return;
1467
3641
  const collector = import_browser3.ExposureCollector.getInstance();
1468
3642
  const registeredToolIds = /* @__PURE__ */ new Set();
@@ -1494,19 +3668,426 @@ function SupernalProvider({
1494
3668
  collector.destroy();
1495
3669
  };
1496
3670
  }, []);
1497
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChatInputProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(ChatProvider, { mode, apiKey, onToolExecute, children: [
1498
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AutoNavigationContext, { routes, onNavigate, children }),
1499
- shouldRenderChatBubble ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3671
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChatInputProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(ChatProvider, { mode, apiKey, onToolExecute, children: [
3672
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AutoNavigationContext, { routes, onNavigate, children }),
3673
+ shouldRenderChatBubble ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1500
3674
  ChatBubbleConnector,
1501
3675
  {
1502
3676
  theme,
1503
3677
  position,
1504
- welcomeMessage
3678
+ welcomeMessage,
3679
+ glassMode,
3680
+ logo,
3681
+ variant,
3682
+ displayMode: effectiveDisplayMode,
3683
+ drawerSide
1505
3684
  }
1506
3685
  ) : null
1507
3686
  ] }) });
1508
3687
  }
1509
3688
 
3689
+ // src/components/ChatBubbleSettingsModal.tsx
3690
+ var import_react13 = __toESM(require("react"));
3691
+ var import_jsx_runtime15 = require("react/jsx-runtime");
3692
+ function ChatBubbleSettingsModal({
3693
+ isOpen,
3694
+ onClose,
3695
+ settings,
3696
+ onSettingsChange
3697
+ }) {
3698
+ const [localSettings, setLocalSettings] = import_react13.default.useState(settings);
3699
+ (0, import_react13.useEffect)(() => {
3700
+ if (isOpen) {
3701
+ setLocalSettings(settings);
3702
+ }
3703
+ }, [isOpen, settings]);
3704
+ (0, import_react13.useEffect)(() => {
3705
+ const handleEscape = (e) => {
3706
+ if (e.key === "Escape" && isOpen) {
3707
+ onClose();
3708
+ }
3709
+ };
3710
+ window.addEventListener("keydown", handleEscape);
3711
+ return () => window.removeEventListener("keydown", handleEscape);
3712
+ }, [isOpen, onClose]);
3713
+ if (!isOpen) {
3714
+ return null;
3715
+ }
3716
+ const handleSave = () => {
3717
+ onSettingsChange(localSettings);
3718
+ onClose();
3719
+ };
3720
+ const handleCancel = () => {
3721
+ setLocalSettings(settings);
3722
+ onClose();
3723
+ };
3724
+ const isDark = localSettings.theme === "dark";
3725
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
3726
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3727
+ "div",
3728
+ {
3729
+ className: "fixed inset-0 bg-black bg-opacity-50 z-[60] backdrop-blur-sm",
3730
+ onClick: handleCancel,
3731
+ "aria-hidden": "true"
3732
+ }
3733
+ ),
3734
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3735
+ "div",
3736
+ {
3737
+ className: "fixed inset-0 z-[70] flex items-center justify-center p-4",
3738
+ "data-testid": "chat-settings-modal",
3739
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3740
+ "div",
3741
+ {
3742
+ className: `${isDark ? "bg-gray-800 text-white" : "bg-white text-gray-900"} rounded-2xl shadow-2xl max-w-md w-full p-6 border ${isDark ? "border-gray-700" : "border-gray-200"}`,
3743
+ role: "dialog",
3744
+ "aria-modal": "true",
3745
+ "aria-labelledby": "settings-modal-title",
3746
+ onClick: (e) => e.stopPropagation(),
3747
+ children: [
3748
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between mb-6", children: [
3749
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3750
+ "h2",
3751
+ {
3752
+ id: "settings-modal-title",
3753
+ className: "text-2xl font-bold",
3754
+ children: "Chat Settings"
3755
+ }
3756
+ ),
3757
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3758
+ "button",
3759
+ {
3760
+ onClick: handleCancel,
3761
+ className: `${isDark ? "text-gray-400 hover:text-gray-200" : "text-gray-400 hover:text-gray-600"} transition-colors p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700`,
3762
+ "aria-label": "Close modal",
3763
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
3764
+ }
3765
+ )
3766
+ ] }),
3767
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "space-y-5 mb-6", children: [
3768
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3769
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3770
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "block text-base font-medium mb-1", children: "Theme" }),
3771
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Switch between light and dark modes" })
3772
+ ] }),
3773
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center space-x-2", children: [
3774
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3775
+ "button",
3776
+ {
3777
+ onClick: () => setLocalSettings({ ...localSettings, theme: "light" }),
3778
+ className: `p-2 rounded-lg transition-all ${localSettings.theme === "light" ? "bg-blue-600 text-white shadow-lg" : isDark ? "bg-gray-700 text-gray-300 hover:bg-gray-600" : "bg-gray-100 text-gray-600 hover:bg-gray-200"}`,
3779
+ title: "Light mode",
3780
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime15.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" }) })
3781
+ }
3782
+ ),
3783
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3784
+ "button",
3785
+ {
3786
+ onClick: () => setLocalSettings({ ...localSettings, theme: "dark" }),
3787
+ className: `p-2 rounded-lg transition-all ${localSettings.theme === "dark" ? "bg-blue-600 text-white shadow-lg" : isDark ? "bg-gray-700 text-gray-300 hover:bg-gray-600" : "bg-gray-100 text-gray-600 hover:bg-gray-200"}`,
3788
+ title: "Dark mode",
3789
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime15.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" }) })
3790
+ }
3791
+ )
3792
+ ] })
3793
+ ] }),
3794
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3795
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3796
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "block text-base font-medium mb-1", children: "Glass Mode" }),
3797
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Enable glassmorphism transparency effect" })
3798
+ ] }),
3799
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3800
+ "button",
3801
+ {
3802
+ onClick: () => setLocalSettings({ ...localSettings, glassMode: !localSettings.glassMode }),
3803
+ className: `relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${localSettings.glassMode ? "bg-blue-600" : isDark ? "bg-gray-700" : "bg-gray-300"}`,
3804
+ role: "switch",
3805
+ "aria-checked": localSettings.glassMode,
3806
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3807
+ "span",
3808
+ {
3809
+ className: `inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSettings.glassMode ? "translate-x-6" : "translate-x-1"}`
3810
+ }
3811
+ )
3812
+ }
3813
+ )
3814
+ ] }),
3815
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3816
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3817
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "block text-base font-medium mb-1", children: "Notifications" }),
3818
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Show unread message indicators" })
3819
+ ] }),
3820
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3821
+ "button",
3822
+ {
3823
+ onClick: () => setLocalSettings({ ...localSettings, notifications: !localSettings.notifications }),
3824
+ className: `relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${localSettings.notifications ? "bg-blue-600" : isDark ? "bg-gray-700" : "bg-gray-300"}`,
3825
+ role: "switch",
3826
+ "aria-checked": localSettings.notifications,
3827
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3828
+ "span",
3829
+ {
3830
+ className: `inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSettings.notifications ? "translate-x-6" : "translate-x-1"}`
3831
+ }
3832
+ )
3833
+ }
3834
+ )
3835
+ ] }),
3836
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `border-t ${isDark ? "border-gray-700" : "border-gray-200"} my-2` }),
3837
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3838
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3839
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "block text-base font-medium mb-1 flex items-center space-x-2", children: [
3840
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Subtitle Overlay" }),
3841
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200", children: "BETA" })
3842
+ ] }),
3843
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Minimalist voice-first overlay with @/ icon" })
3844
+ ] }),
3845
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3846
+ "button",
3847
+ {
3848
+ onClick: () => setLocalSettings({
3849
+ ...localSettings,
3850
+ subtitleOverlayEnabled: !localSettings.subtitleOverlayEnabled,
3851
+ // When enabling subtitle overlay, switch displayMode to subtitle
3852
+ ...!localSettings.subtitleOverlayEnabled && { displayMode: "subtitle" }
3853
+ }),
3854
+ className: `relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${localSettings.subtitleOverlayEnabled ? "bg-blue-600" : isDark ? "bg-gray-700" : "bg-gray-300"}`,
3855
+ role: "switch",
3856
+ "aria-checked": localSettings.subtitleOverlayEnabled,
3857
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3858
+ "span",
3859
+ {
3860
+ className: `inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSettings.subtitleOverlayEnabled ? "translate-x-6" : "translate-x-1"}`
3861
+ }
3862
+ )
3863
+ }
3864
+ )
3865
+ ] }),
3866
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3867
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3868
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "block text-base font-medium mb-1", children: "Display Mode" }),
3869
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Auto switches drawer on mobile, panel on desktop" })
3870
+ ] }),
3871
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3872
+ "select",
3873
+ {
3874
+ value: localSettings.displayMode || "auto",
3875
+ onChange: (e) => setLocalSettings({
3876
+ ...localSettings,
3877
+ displayMode: e.target.value
3878
+ }),
3879
+ className: `px-3 py-2 rounded-lg border ${isDark ? "bg-gray-700 border-gray-600 text-white" : "bg-white border-gray-300 text-gray-900"} focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all`,
3880
+ children: [
3881
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "auto", children: "Auto (Recommended)" }),
3882
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "drawer", children: "Always Drawer" }),
3883
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "full", children: "Always Panel" }),
3884
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "floating", children: "Always Floating" }),
3885
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "subtitle", children: "Subtitle Overlay (Beta)" })
3886
+ ]
3887
+ }
3888
+ )
3889
+ ] }),
3890
+ (localSettings.displayMode === "auto" || localSettings.displayMode === "drawer") && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3891
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3892
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "block text-base font-medium mb-1", children: "Drawer Side" }),
3893
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Which edge drawer slides from" })
3894
+ ] }),
3895
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center space-x-2", children: [
3896
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3897
+ "button",
3898
+ {
3899
+ onClick: () => setLocalSettings({ ...localSettings, drawerSide: "left" }),
3900
+ className: `px-4 py-2 rounded-lg transition-all ${localSettings.drawerSide === "left" ? "bg-blue-600 text-white shadow-lg" : isDark ? "bg-gray-700 text-gray-300 hover:bg-gray-600" : "bg-gray-100 text-gray-600 hover:bg-gray-200"}`,
3901
+ children: "Left"
3902
+ }
3903
+ ),
3904
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3905
+ "button",
3906
+ {
3907
+ onClick: () => setLocalSettings({ ...localSettings, drawerSide: "right" }),
3908
+ className: `px-4 py-2 rounded-lg transition-all ${localSettings.drawerSide === "right" ? "bg-blue-600 text-white shadow-lg" : isDark ? "bg-gray-700 text-gray-300 hover:bg-gray-600" : "bg-gray-100 text-gray-600 hover:bg-gray-200"}`,
3909
+ children: "Right"
3910
+ }
3911
+ )
3912
+ ] })
3913
+ ] }),
3914
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `border-t ${isDark ? "border-gray-700" : "border-gray-200"} my-2` }),
3915
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "mb-3", children: [
3916
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h3", { className: "text-lg font-semibold mb-1", children: "Voice Control" }),
3917
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-xs ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Enable voice input and audio feedback" })
3918
+ ] }),
3919
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3920
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3921
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "block text-base font-medium mb-1", children: "Voice Control" }),
3922
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Enable voice input and TTS responses" })
3923
+ ] }),
3924
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3925
+ "button",
3926
+ {
3927
+ onClick: () => setLocalSettings({ ...localSettings, voiceEnabled: !localSettings.voiceEnabled }),
3928
+ className: `relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${localSettings.voiceEnabled ? "bg-blue-600" : isDark ? "bg-gray-700" : "bg-gray-300"}`,
3929
+ role: "switch",
3930
+ "aria-checked": localSettings.voiceEnabled,
3931
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3932
+ "span",
3933
+ {
3934
+ className: `inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSettings.voiceEnabled ? "translate-x-6" : "translate-x-1"}`
3935
+ }
3936
+ )
3937
+ }
3938
+ )
3939
+ ] }),
3940
+ localSettings.voiceEnabled && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
3941
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between pl-4 border-l-2 border-blue-500/30", children: [
3942
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3943
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "block text-sm font-medium mb-1", children: "Auto-read AI Responses" }),
3944
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-xs ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Automatically read AI messages aloud" })
3945
+ ] }),
3946
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3947
+ "button",
3948
+ {
3949
+ onClick: () => setLocalSettings({ ...localSettings, autoReadResponses: !localSettings.autoReadResponses }),
3950
+ className: `relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${localSettings.autoReadResponses ? "bg-blue-600" : isDark ? "bg-gray-700" : "bg-gray-300"}`,
3951
+ role: "switch",
3952
+ "aria-checked": localSettings.autoReadResponses,
3953
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3954
+ "span",
3955
+ {
3956
+ className: `inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSettings.autoReadResponses ? "translate-x-6" : "translate-x-1"}`
3957
+ }
3958
+ )
3959
+ }
3960
+ )
3961
+ ] }),
3962
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between pl-4 border-l-2 border-blue-500/30", children: [
3963
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3964
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "block text-sm font-medium mb-1", children: "Premium Voices \u{1F48E}" }),
3965
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-xs ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Use high-quality OpenAI voices (requires network)" })
3966
+ ] }),
3967
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3968
+ "button",
3969
+ {
3970
+ onClick: () => setLocalSettings({ ...localSettings, usePremiumVoices: !localSettings.usePremiumVoices }),
3971
+ className: `relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${localSettings.usePremiumVoices ? "bg-blue-600" : isDark ? "bg-gray-700" : "bg-gray-300"}`,
3972
+ role: "switch",
3973
+ "aria-checked": localSettings.usePremiumVoices,
3974
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3975
+ "span",
3976
+ {
3977
+ className: `inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSettings.usePremiumVoices ? "translate-x-6" : "translate-x-1"}`
3978
+ }
3979
+ )
3980
+ }
3981
+ )
3982
+ ] }),
3983
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "pl-4 border-l-2 border-blue-500/30", children: [
3984
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "block text-sm font-medium mb-2", children: [
3985
+ "Voice Speed: ",
3986
+ localSettings.ttsSpeed.toFixed(1),
3987
+ "x"
3988
+ ] }),
3989
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3990
+ "input",
3991
+ {
3992
+ type: "range",
3993
+ min: "0.5",
3994
+ max: "2.0",
3995
+ step: "0.1",
3996
+ value: localSettings.ttsSpeed,
3997
+ onChange: (e) => setLocalSettings({ ...localSettings, ttsSpeed: parseFloat(e.target.value) }),
3998
+ className: "w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-600"
3999
+ }
4000
+ ),
4001
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex justify-between text-xs text-gray-400 mt-1", children: [
4002
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "0.5x (Slow)" }),
4003
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "1.0x (Normal)" }),
4004
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "2.0x (Fast)" })
4005
+ ] })
4006
+ ] }),
4007
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `border-t ${isDark ? "border-gray-700" : "border-gray-200"} my-2` }),
4008
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "mb-3", children: [
4009
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h4", { className: "text-sm font-semibold mb-1", children: "Voice Quick Record (Ctrl+/)" }),
4010
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-xs ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Press Ctrl+/ (or Cmd+/ on Mac) to auto-record and execute voice commands" })
4011
+ ] }),
4012
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between pl-4 border-l-2 border-purple-500/30", children: [
4013
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
4014
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "block text-sm font-medium mb-1", children: "Auto-Execute Commands" }),
4015
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-xs ${isDark ? "text-gray-400" : "text-gray-500"}`, children: "Automatically run recognized voice commands" })
4016
+ ] }),
4017
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4018
+ "button",
4019
+ {
4020
+ onClick: () => setLocalSettings({ ...localSettings, sttAutoExecute: !localSettings.sttAutoExecute }),
4021
+ className: `relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${localSettings.sttAutoExecute ? "bg-purple-600" : isDark ? "bg-gray-700" : "bg-gray-300"}`,
4022
+ role: "switch",
4023
+ "aria-checked": localSettings.sttAutoExecute,
4024
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4025
+ "span",
4026
+ {
4027
+ className: `inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSettings.sttAutoExecute ? "translate-x-6" : "translate-x-1"}`
4028
+ }
4029
+ )
4030
+ }
4031
+ )
4032
+ ] }),
4033
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "pl-4 border-l-2 border-purple-500/30", children: [
4034
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "block text-sm font-medium mb-2", children: [
4035
+ "Recording Timeout: ",
4036
+ (localSettings.sttAutoRecordTimeout / 1e3).toFixed(1),
4037
+ "s"
4038
+ ] }),
4039
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4040
+ "input",
4041
+ {
4042
+ type: "range",
4043
+ min: "2000",
4044
+ max: "15000",
4045
+ step: "1000",
4046
+ value: localSettings.sttAutoRecordTimeout,
4047
+ onChange: (e) => setLocalSettings({ ...localSettings, sttAutoRecordTimeout: parseInt(e.target.value) }),
4048
+ className: "w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-purple-600"
4049
+ }
4050
+ ),
4051
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex justify-between text-xs text-gray-400 mt-1", children: [
4052
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "2s" }),
4053
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "8s" }),
4054
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "15s" })
4055
+ ] })
4056
+ ] }),
4057
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `p-3 rounded-lg ${isDark ? "bg-purple-900/20 border border-purple-500/30" : "bg-purple-50 border border-purple-200"}`, children: [
4058
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-xs ${isDark ? "text-purple-300" : "text-purple-800"} mb-1 font-medium`, children: "\u26A1 Quick Tip: Press Ctrl+/ anywhere (even while typing!)" }),
4059
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-xs ${isDark ? "text-purple-400" : "text-purple-700"}`, children: localSettings.sttAutoExecute ? "Recording auto-stops and executes your command" : "Recording auto-stops and fills the input (press Enter to send)" })
4060
+ ] }),
4061
+ !localSettings.usePremiumVoices && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `p-3 rounded-lg ${isDark ? "bg-green-900/20 border border-green-500/30" : "bg-green-50 border border-green-200"}`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-xs ${isDark ? "text-green-300" : "text-green-800"}`, children: "\u{1F49A} Using free device voices (works offline, zero cost)" }) }),
4062
+ localSettings.usePremiumVoices && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `p-3 rounded-lg ${isDark ? "bg-purple-900/20 border border-purple-500/30" : "bg-purple-50 border border-purple-200"}`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `text-xs ${isDark ? "text-purple-300" : "text-purple-800"}`, children: "\u{1F48E} Using premium OpenAI voices (requires internet connection)" }) })
4063
+ ] })
4064
+ ] }),
4065
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex justify-end space-x-3 pt-4 border-t border-gray-200 dark:border-gray-700", children: [
4066
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4067
+ "button",
4068
+ {
4069
+ onClick: handleCancel,
4070
+ className: `px-4 py-2 rounded-lg transition-colors ${isDark ? "bg-gray-700 text-gray-200 hover:bg-gray-600" : "bg-gray-100 text-gray-700 hover:bg-gray-200"}`,
4071
+ children: "Cancel"
4072
+ }
4073
+ ),
4074
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4075
+ "button",
4076
+ {
4077
+ onClick: handleSave,
4078
+ className: "px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors shadow-lg",
4079
+ children: "Save Changes"
4080
+ }
4081
+ )
4082
+ ] })
4083
+ ]
4084
+ }
4085
+ )
4086
+ }
4087
+ )
4088
+ ] });
4089
+ }
4090
+
1510
4091
  // src/lib/FuzzyMatcher.ts
1511
4092
  var FuzzyMatcher = class {
1512
4093
  /**
@@ -1629,11 +4210,19 @@ function findBestMatch(query, tools) {
1629
4210
  0 && (module.exports = {
1630
4211
  AutoNavigationContext,
1631
4212
  ChatBubble,
4213
+ ChatBubbleSettingsModal,
4214
+ ChatBubbleVariant,
1632
4215
  ChatInputProvider,
1633
4216
  ChatProvider,
4217
+ CodeBlock,
4218
+ Components,
1634
4219
  DemoAIInterface,
1635
4220
  FuzzyMatcher,
4221
+ MermaidDiagram,
4222
+ MessageRenderer,
1636
4223
  NavigationContextProvider,
4224
+ PageLayout,
4225
+ SubtitleOverlay,
1637
4226
  SupernalProvider,
1638
4227
  ToolManager,
1639
4228
  findBestMatch,