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