diff --git a/web/App.tsx b/web/App.tsx
new file mode 100644
index 0000000..a3d2910
--- /dev/null
+++ b/web/App.tsx
@@ -0,0 +1,126 @@
+
+import React, { useState } from 'react';
+import { HashRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom';
+import { Bot, Phone, Book, User, LayoutDashboard, Mic2, Video, GitBranch, Zap, PanelLeftClose, PanelLeftOpen, History as HistoryIcon } from 'lucide-react';
+
+import { AssistantsPage } from './pages/Assistants';
+import { KnowledgeBasePage } from './pages/KnowledgeBase';
+import { HistoryPage } from './pages/History';
+import { ProfilePage } from './pages/Profile';
+import { DashboardPage } from './pages/Dashboard';
+import { VoiceLibraryPage } from './pages/VoiceLibrary';
+import { WorkflowsPage } from './pages/Workflows';
+import { WorkflowEditorPage } from './pages/WorkflowEditor';
+import { AutoTestPage } from './pages/AutoTest';
+
+const SidebarItem: React.FC<{ to: string; icon: React.ReactNode; label: string; active: boolean; isCollapsed: boolean }> = ({ to, icon, label, active, isCollapsed }) => (
+
+
{icon}
+ {!isCollapsed && {label}}
+
+);
+
+const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const location = useLocation();
+ const [isCollapsed, setIsCollapsed] = useState(false);
+
+ const navItems = [
+ { path: '/', label: '首页', icon: },
+ { path: '/assistants', label: '小助手', icon: },
+ { path: '/voices', label: '声音库', icon: },
+ { path: '/history', label: '历史记录', icon: },
+ { path: '/knowledge', label: '知识库', icon: },
+ { path: '/workflows', label: '工作流', icon: },
+ { path: '/auto-test', label: '测试助手', icon: },
+ { path: '/profile', label: '个人中心', icon: },
+ ];
+
+ return (
+
+ {/* Sidebar with Glass effect and collapse logic */}
+
+
+ {/* Main Content */}
+
+
+
+ {children}
+
+
+
+ );
+};
+
+const App: React.FC = () => {
+ return (
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ );
+};
+
+export default App;
diff --git a/web/README.md b/web/README.md
index aab401a..4ec187a 100644
--- a/web/README.md
+++ b/web/README.md
@@ -1 +1,20 @@
-# Frontend Service
\ No newline at end of file
+
+

+
+
+# Run and deploy your AI Studio app
+
+This contains everything you need to run your app locally.
+
+View your app in AI Studio: https://ai.studio/apps/drive/1GwAKXIF6lVjo1AZPHjVDL6w3hXkG_zFQ
+
+## Run Locally
+
+**Prerequisites:** Node.js
+
+
+1. Install dependencies:
+ `npm install`
+2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
+3. Run the app:
+ `npm run dev`
diff --git a/web/components/UI.tsx b/web/components/UI.tsx
new file mode 100644
index 0000000..2eb30d1
--- /dev/null
+++ b/web/components/UI.tsx
@@ -0,0 +1,152 @@
+
+import React from 'react';
+import { X } from 'lucide-react';
+
+// Button
+interface ButtonProps extends React.ButtonHTMLAttributes {
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive';
+ size?: 'sm' | 'md' | 'lg' | 'icon';
+}
+
+export const Button: React.FC = ({
+ className = '',
+ variant = 'primary',
+ size = 'md',
+ children,
+ ...props
+}) => {
+ const baseStyles = "inline-flex items-center justify-center rounded-md text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 active:scale-95";
+
+ const variants = {
+ // Primary: Glow effect
+ primary: "bg-primary text-primary-foreground shadow-[0_0_10px_rgba(6,182,212,0.5)] hover:bg-primary/90 hover:shadow-[0_0_15px_rgba(6,182,212,0.6)]",
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ outline: "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground hover:border-primary/50",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ };
+
+ const sizes = {
+ sm: "h-8 px-3 text-xs",
+ md: "h-9 px-4 py-2",
+ lg: "h-10 px-8",
+ icon: "h-9 w-9",
+ };
+
+ return (
+
+ );
+};
+
+// Input - Removed border, added subtle background
+interface InputProps extends React.InputHTMLAttributes {}
+
+export const Input: React.FC = ({ className = '', ...props }) => {
+ return (
+
+ );
+};
+
+// Card - Glassmorphism style, very subtle border
+export const Card: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => (
+
+ {children}
+
+);
+
+// Badge
+export const Badge: React.FC<{ children: React.ReactNode; variant?: 'default' | 'success' | 'warning' | 'outline' }> = ({ children, variant = 'default' }) => {
+ const styles = {
+ default: "border-transparent bg-primary/20 text-primary hover:bg-primary/30 border border-primary/20",
+ success: "border-transparent bg-green-500/20 text-green-400 border border-green-500/20",
+ warning: "border-transparent bg-yellow-500/20 text-yellow-400 border border-yellow-500/20",
+ outline: "text-foreground border border-white/10 hover:bg-accent hover:text-accent-foreground",
+ };
+ return (
+
+ {children}
+
+ );
+};
+
+// Table - Subtle borders
+export const TableHeader: React.FC<{ children: React.ReactNode }> = ({ children }) => {children};
+export const TableRow: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => {children}
;
+export const TableHead: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => {children} | ;
+export const TableCell: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => {children} | ;
+
+// Drawer (Side Sheet)
+interface DrawerProps {
+ isOpen: boolean;
+ onClose: () => void;
+ title: string;
+ children: React.ReactNode;
+}
+
+export const Drawer: React.FC = ({ isOpen, onClose, title, children }) => {
+ if (!isOpen) return null;
+
+ return (
+
+ {/* Backdrop */}
+
+
+ {/* Drawer Content */}
+
+
+
{title}
+
+
+
+ {children}
+
+
+
+ );
+};
+
+// Dialog (Modal)
+interface DialogProps {
+ isOpen: boolean;
+ onClose: () => void;
+ title: string;
+ children: React.ReactNode;
+ footer?: React.ReactNode;
+}
+
+export const Dialog: React.FC = ({ isOpen, onClose, title, children, footer }) => {
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
+
{title}
+
+
+ {children}
+
+ {footer && (
+
+ {footer}
+
+ )}
+
+
+
+ );
+};
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..e4201fe
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+ AI视频助手
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/index.tsx b/web/index.tsx
new file mode 100644
index 0000000..6ca5361
--- /dev/null
+++ b/web/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+const rootElement = document.getElementById('root');
+if (!rootElement) {
+ throw new Error("Could not find root element to mount to");
+}
+
+const root = ReactDOM.createRoot(rootElement);
+root.render(
+
+
+
+);
\ No newline at end of file
diff --git a/web/metadata.json b/web/metadata.json
new file mode 100644
index 0000000..863ed15
--- /dev/null
+++ b/web/metadata.json
@@ -0,0 +1,9 @@
+
+{
+ "name": "AI视频助手",
+ "description": "A minimalist AI Assistant management system featuring agent configuration, knowledge base management, call logs, and a debugging suite.",
+ "requestFramePermissions": [
+ "microphone",
+ "camera"
+ ]
+}
diff --git a/web/package-lock.json b/web/package-lock.json
new file mode 100644
index 0000000..512869b
--- /dev/null
+++ b/web/package-lock.json
@@ -0,0 +1,2777 @@
+{
+ "name": "ai-videoassistant",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ai-videoassistant",
+ "version": "0.0.0",
+ "dependencies": {
+ "@google/genai": "^1.39.0",
+ "lucide-react": "^0.563.0",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
+ "react-router-dom": "^7.13.0"
+ },
+ "devDependencies": {
+ "@types/node": "^22.14.0",
+ "@vitejs/plugin-react": "^5.0.0",
+ "typescript": "~5.8.2",
+ "vite": "^6.2.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz",
+ "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@google/genai": {
+ "version": "1.39.0",
+ "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.39.0.tgz",
+ "integrity": "sha512-Vz7AQsOdBeiIcxmXIQNy/hzDvyAOE1lSpWA10itUQza7h3aQFF6QSGaQ7o1GYsjMD3XslK4Ee/Ol0eLhRXb7gA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "google-auth-library": "^10.3.0",
+ "protobufjs": "^7.5.4",
+ "ws": "^8.18.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@modelcontextprotocol/sdk": "^1.25.2"
+ },
+ "peerDependenciesMeta": {
+ "@modelcontextprotocol/sdk": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.53",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
+ "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+ "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.19.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz",
+ "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz",
+ "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.5",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.53",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.19",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001766",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
+ "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "license": "MIT"
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.283",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz",
+ "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gaxios": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz",
+ "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^7.0.1",
+ "node-fetch": "^3.3.2",
+ "rimraf": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/gcp-metadata": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
+ "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "gaxios": "^7.0.0",
+ "google-logging-utils": "^1.0.0",
+ "json-bigint": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/google-auth-library": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz",
+ "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "gaxios": "^7.0.0",
+ "gcp-metadata": "^8.0.0",
+ "google-logging-utils": "^1.0.0",
+ "gtoken": "^8.0.0",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/google-logging-utils": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
+ "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/gtoken": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz",
+ "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==",
+ "license": "MIT",
+ "dependencies": {
+ "gaxios": "^7.0.0",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.563.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz",
+ "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "license": "MIT",
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/protobufjs": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
+ "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
+ "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
+ "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.13.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "5.0.10",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
+ "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^10.3.7"
+ },
+ "bin": {
+ "rimraf": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
+ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/web/package.json b/web/package.json
new file mode 100644
index 0000000..a0ba80d
--- /dev/null
+++ b/web/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "ai视频助手",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "lucide-react": "^0.563.0",
+ "react-router-dom": "^7.13.0",
+ "@google/genai": "^1.39.0",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4"
+ },
+ "devDependencies": {
+ "@types/node": "^22.14.0",
+ "@vitejs/plugin-react": "^5.0.0",
+ "typescript": "~5.8.2",
+ "vite": "^6.2.0"
+ }
+}
diff --git a/web/pages/Assistants.tsx b/web/pages/Assistants.tsx
new file mode 100644
index 0000000..a22ce32
--- /dev/null
+++ b/web/pages/Assistants.tsx
@@ -0,0 +1,1129 @@
+
+import React, { useState, useEffect, useRef } from 'react';
+import { Plus, Search, Play, Copy, Trash2, Edit2, Mic, MessageSquare, Save, Video, PhoneOff, Camera, ArrowLeftRight, Send, Phone, MoreHorizontal, Rocket, AlertTriangle, PhoneCall, CameraOff, Image, Images, CloudSun, Calendar, TrendingUp, Coins, Wrench, Globe, Terminal, X, ClipboardCheck, Sparkles, Volume2, Timer, ChevronDown, Link as LinkIcon, Database, Server, Zap, ExternalLink, Key } from 'lucide-react';
+import { Button, Input, Card, Badge, Drawer, Dialog } from '../components/UI';
+import { mockAssistants, mockKnowledgeBases, mockVoices } from '../services/mockData';
+import { Assistant, TabValue } from '../types';
+import { GoogleGenAI } from "@google/genai";
+
+interface ToolItem {
+ id: string;
+ name: string;
+ icon: React.ReactNode;
+ desc: string;
+ category: 'system' | 'query';
+ isCustom?: boolean;
+}
+
+export const AssistantsPage: React.FC = () => {
+ const [assistants, setAssistants] = useState(mockAssistants);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [selectedId, setSelectedId] = useState(null);
+ const [activeTab, setActiveTab] = useState(TabValue.GLOBAL);
+ const [debugOpen, setDebugOpen] = useState(false);
+ const [hotwordInput, setHotwordInput] = useState('');
+
+ // Publish Modal State
+ const [isPublishModalOpen, setIsPublishModalOpen] = useState(false);
+ const [publishTab, setPublishTab] = useState<'web' | 'api'>('web');
+
+ // Custom Tools State
+ const [customTools, setCustomTools] = useState([]);
+ const [hiddenToolIds, setHiddenToolIds] = useState([]);
+ const [isAddToolModalOpen, setIsAddToolModalOpen] = useState(false);
+ const [addingToCategory, setAddingToCategory] = useState<'system' | 'query'>('system');
+
+ // New Tool Form State
+ const [newToolName, setNewToolName] = useState('');
+ const [newToolDesc, setNewToolDesc] = useState('');
+
+ const [deleteId, setDeleteId] = useState(null);
+ const [copySuccess, setCopySuccess] = useState(false);
+ const [saveLoading, setSaveLoading] = useState(false);
+
+ const selectedAssistant = assistants.find(a => a.id === selectedId) || null;
+
+ const filteredAssistants = assistants.filter(a =>
+ a.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ const handleCreate = () => {
+ const newId = Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
+ const newAssistant: Assistant = {
+ id: newId,
+ name: 'New Assistant',
+ callCount: 0,
+ opener: '',
+ prompt: '',
+ knowledgeBaseId: '',
+ language: 'zh',
+ voice: mockVoices[0]?.id || '',
+ speed: 1,
+ hotwords: [],
+ tools: [],
+ interruptionSensitivity: 500,
+ configMode: 'platform',
+ };
+ setAssistants([...assistants, newAssistant]);
+ setSelectedId(newId);
+ setActiveTab(TabValue.GLOBAL);
+ };
+
+ const handleSave = () => {
+ setSaveLoading(true);
+ // Simulate API call
+ setTimeout(() => {
+ setSaveLoading(false);
+ // In a real app, logic to persist selectedAssistant would go here
+ }, 800);
+ };
+
+ const handleCopyId = (id: string, text?: string) => {
+ navigator.clipboard.writeText(text || id);
+ setCopySuccess(true);
+ setTimeout(() => setCopySuccess(false), 2000);
+ };
+
+ const handleCopy = (e: React.MouseEvent, assistant: Assistant) => {
+ e.stopPropagation();
+ const newId = Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
+ const newAssistant = { ...assistant, id: newId, name: `${assistant.name} (Copy)` };
+ setAssistants([...assistants, newAssistant]);
+ };
+
+ const handleDeleteClick = (e: React.MouseEvent, id: string) => {
+ e.stopPropagation();
+ setDeleteId(id);
+ };
+
+ const confirmDelete = () => {
+ if (deleteId) {
+ setAssistants(prev => prev.filter(a => a.id !== deleteId));
+ if (selectedId === deleteId) setSelectedId(null);
+ setDeleteId(null);
+ }
+ };
+
+ const updateAssistant = (field: keyof Assistant, value: any) => {
+ if (!selectedId) return;
+ setAssistants(prev => prev.map(a => a.id === selectedId ? { ...a, [field]: value } : a));
+
+ if (field === 'configMode') {
+ if (value === 'platform') {
+ setActiveTab(TabValue.GLOBAL);
+ } else if (value === 'dify' || value === 'fastgpt') {
+ setActiveTab(TabValue.LINK);
+ }
+ }
+ };
+
+ const toggleTool = (toolId: string) => {
+ if (!selectedAssistant) return;
+ const currentTools = selectedAssistant.tools || [];
+ const newTools = currentTools.includes(toolId)
+ ? currentTools.filter(id => id !== toolId)
+ : [...currentTools, toolId];
+ updateAssistant('tools', newTools);
+ };
+
+ const deleteTool = (e: React.MouseEvent, toolId: string) => {
+ e.stopPropagation();
+ setAssistants(prev => prev.map(a => ({
+ ...a,
+ tools: a.tools?.filter(id => id !== toolId) || []
+ })));
+
+ if (customTools.some(t => t.id === toolId)) {
+ setCustomTools(prev => prev.filter(t => t.id !== toolId));
+ } else {
+ setHiddenToolIds(prev => [...prev, toolId]);
+ }
+ };
+
+ const addHotword = () => {
+ if (hotwordInput.trim() && selectedAssistant) {
+ updateAssistant('hotwords', [...selectedAssistant.hotwords, hotwordInput.trim()]);
+ setHotwordInput('');
+ }
+ };
+
+ const removeHotword = (word: string) => {
+ if (selectedAssistant) {
+ updateAssistant('hotwords', selectedAssistant.hotwords.filter(w => w !== word));
+ }
+ };
+
+ const handleAddCustomTool = () => {
+ if (!newToolName.trim()) return;
+ const newTool: ToolItem = {
+ id: `custom_${Date.now()}`,
+ name: newToolName,
+ desc: newToolDesc,
+ category: addingToCategory,
+ icon: addingToCategory === 'system' ? : ,
+ isCustom: true
+ };
+ setCustomTools([...customTools, newTool]);
+ setIsAddToolModalOpen(false);
+ setNewToolName('');
+ setNewToolDesc('');
+ };
+
+ const openAddToolModal = (e: React.MouseEvent, cat: 'system' | 'query') => {
+ e.stopPropagation();
+ setAddingToCategory(cat);
+ setIsAddToolModalOpen(true);
+ };
+
+ const baseSystemTools: ToolItem[] = [
+ { id: 'cam_open', name: '打开相机', icon: , desc: '允许 AI 开启摄像头流', category: 'system' },
+ { id: 'cam_close', name: '关闭相机', icon: , desc: '允许 AI 停止摄像头流', category: 'system' },
+ { id: 'take_photo', name: '拍照', icon: , desc: 'AI 触发单张拍摄', category: 'system' },
+ { id: 'burst_3', name: '连拍三张', icon: , desc: 'AI 触发快速连拍', category: 'system' },
+ ];
+
+ const baseQueryTools: ToolItem[] = [
+ { id: 'q_weather', name: '天气查询', icon: , desc: '查询实时及未来天气', category: 'query' },
+ { id: 'q_calendar', name: '日历查询', icon: , desc: '查询日程及节假日信息', category: 'query' },
+ { id: 'q_stock', name: '股价查询', icon: , desc: '查询股票实时行情', category: 'query' },
+ { id: 'q_exchange', name: '汇率查询', icon: , desc: '查询多国货币汇率', category: 'query' },
+ ];
+
+ const systemTools = [...baseSystemTools, ...customTools.filter(t => t.category === 'system')].filter(t => !hiddenToolIds.includes(t.id));
+ const queryTools = [...baseQueryTools, ...customTools.filter(t => t.category === 'query')].filter(t => !hiddenToolIds.includes(t.id));
+
+ const isExternalConfig = selectedAssistant?.configMode === 'dify' || selectedAssistant?.configMode === 'fastgpt';
+ const isNoneConfig = selectedAssistant?.configMode === 'none' || !selectedAssistant?.configMode;
+
+ return (
+
+ {/* LEFT COLUMN: List */}
+
+
+
小助手列表
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+ {filteredAssistants.map(assistant => (
+
setSelectedId(assistant.id)}
+ className={`group relative flex flex-col p-4 rounded-xl border transition-all cursor-pointer ${
+ selectedId === assistant.id
+ ? 'bg-primary/10 border-primary/40 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
+ : 'bg-card/30 border-white/5 hover:bg-white/5 hover:border-white/10'
+ }`}
+ >
+
+
+ {assistant.name}
+
+ {assistant.configMode && assistant.configMode !== 'none' && (
+
+
+ {assistant.configMode === 'platform' ? '内置' : assistant.configMode}
+
+
+ )}
+
+
+
+
+
{assistant.callCount} 次通话
+
+
+
+
+
+
+
+ ))}
+ {filteredAssistants.length === 0 && (
+
+ 未找到小助手
+
+ )}
+
+
+
+ {/* RIGHT COLUMN: Config Panel */}
+
+ {selectedAssistant ? (
+ <>
+ {/* Header Area */}
+
+
+
+
+
+
+ UUID: {selectedAssistant.id}
+
+
+
+
updateAssistant('name', e.target.value)}
+ className="font-bold bg-white/5 border-white/10 focus:border-primary/50 text-base"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!isNoneConfig && (
+
+ {selectedAssistant.configMode === 'platform' ? (
+ <>
+
+
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+
+ )}
+
+
+
+ {isNoneConfig ? (
+
+ ) : (
+
+ {activeTab === TabValue.LINK && isExternalConfig && (
+
+
+
+
+
+ 接入 {selectedAssistant.configMode === 'dify' ? 'Dify' : 'FastGPT'} 引擎
+
+
+ 配置后,视频通话过程中的对话逻辑、知识库检索以及工作流将由外部引擎托管。
+
+
+
+
+
+
+ updateAssistant('apiUrl', e.target.value)}
+ placeholder={selectedAssistant.configMode === 'dify' ? "https://api.dify.ai/v1" : "https://api.fastgpt.in/api/v1"}
+ className="bg-white/5 border-white/10 focus:border-primary/50 font-mono text-xs"
+ />
+
+
+
+
+ updateAssistant('apiKey', e.target.value)}
+ placeholder="请输入应用 API 密钥..."
+ className="bg-white/5 border-white/10 focus:border-primary/50 font-mono text-xs"
+ />
+
+
+ )}
+
+ {activeTab === TabValue.GLOBAL && selectedAssistant.configMode === 'platform' && (
+
+
+
+
updateAssistant('opener', e.target.value)}
+ placeholder="例如:您好,我是您的专属AI助手..."
+ className="bg-white/5 border-white/10 focus:border-primary/50"
+ />
+
接通通话后的第一句话。
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {activeTab === TabValue.VOICE && !isNoneConfig && (
+
+
+
+
+
+
+
+
+
+ 音色配置同步自声音库。如需添加更多音色,请前往“声音库”模块。
+
+
+
+
+
+
+
+
+ updateAssistant('interruptionSensitivity', parseInt(e.target.value) || 0)}
+ className="w-20 h-8 text-right pr-7 text-xs font-mono bg-black/40 border-white/5"
+ />
+ ms
+
+
+
+
+ updateAssistant('interruptionSensitivity', parseInt(e.target.value))}
+ className="flex-1 h-1.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-primary"
+ />
+
+
+ 0ms (Extreme)
+ 1000ms
+ 2000ms (Lazy)
+
+
+ * 定义用户说话多长时间后 AI 应当停止当前的发言并响应。数值越小响应越快,但也更容易被噪音误导打断。
+
+
+
+
+
+
+ setHotwordInput(e.target.value)}
+ placeholder="输入专有名词或高频词汇..."
+ onKeyDown={(e) => e.key === 'Enter' && addHotword()}
+ className="bg-white/5 border-white/10"
+ />
+
+
+
+ {selectedAssistant.hotwords.length === 0 && (
+ 暂无热词
+ )}
+ {selectedAssistant.hotwords.map((word, idx) => (
+
+ {word}
+
+
+ ))}
+
+
添加热词可以提高语音识别特定词汇的准确率。
+
+
+ )}
+
+ {activeTab === TabValue.TOOLS && selectedAssistant.configMode === 'platform' && (
+
+
+
+
+ 系统指令
+
+
+
+
+ {systemTools.map(tool => (
+
toggleTool(tool.id)}
+ className={`p-4 rounded-xl border transition-all cursor-pointer group relative flex items-start space-x-3 ${selectedAssistant.tools?.includes(tool.id) ? 'bg-primary/10 border-primary/40 shadow-[0_0_15px_rgba(6,182,212,0.1)]' : 'bg-card/30 border-white/5 hover:bg-white/5 hover:border-white/10'}`}
+ >
+
+ {tool.icon}
+
+
+
+
{tool.name}
+
+ {selectedAssistant.tools?.includes(tool.id) &&
}
+
+
+
{tool.desc}
+
+
+
+ ))}
+
+
+
+
+
+
+ 信息查询
+
+
+
+
+ {queryTools.map(tool => (
+
toggleTool(tool.id)}
+ className={`p-4 rounded-xl border transition-all cursor-pointer group relative flex items-start space-x-3 ${selectedAssistant.tools?.includes(tool.id) ? 'bg-blue-500/10 border-blue-500/40 shadow-[0_0_15px_rgba(59,130,246,0.1)]' : 'bg-card/30 border-white/5 hover:bg-white/5 hover:border-white/10'}`}
+ >
+
+ {tool.icon}
+
+
+
+
{tool.name}
+
+ {selectedAssistant.tools?.includes(tool.id) &&
}
+
+
+
{tool.desc}
+
+
+
+ ))}
+
+
+
+
+ 提示:启用工具后,AI 将能在对话中自动识别并调用相关功能以协助用户。
+
+
+ )}
+
+ )}
+
+ >
+ ) : (
+
+
+
+
+
请选择一个小助手
+
从左侧列表选择或创建一个新的小助手以开始配置
+
+ )}
+
+
+ {/* Publish Modal */}
+
+
+ {selectedAssistant && (
+
setDebugOpen(false)}
+ assistant={selectedAssistant}
+ />
+ )}
+
+ {/* Add Custom Tool Modal */}
+
+
+ {/* Delete Confirmation Dialog */}
+
+
+ );
+};
+
+// Icon helper
+const BotIcon = ({className}: {className?: string}) => (
+
+);
+
+// --- Debug Drawer Component ---
+export const DebugDrawer: React.FC<{ isOpen: boolean; onClose: () => void; assistant: Assistant }> = ({ isOpen, onClose, assistant }) => {
+ const [mode, setMode] = useState<'text' | 'voice' | 'video'>('text');
+ const [messages, setMessages] = useState<{role: 'user' | 'model', text: string}[]>([]);
+ const [inputText, setInputText] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [callStatus, setCallStatus] = useState<'idle' | 'calling' | 'active'>('idle');
+
+ // Media State
+ const videoRef = useRef(null);
+ const streamRef = useRef(null);
+ const scrollRef = useRef(null);
+
+ const [devices, setDevices] = useState([]);
+ const [selectedCamera, setSelectedCamera] = useState('');
+ const [selectedMic, setSelectedMic] = useState('');
+ const [isSwapped, setIsSwapped] = useState(false);
+
+ // Initialize
+ useEffect(() => {
+ if (isOpen) {
+ if (mode === 'text') {
+ setMessages([{ role: 'model', text: assistant.opener || "Hello!" }]);
+ } else {
+ setMessages([]);
+ setCallStatus('idle');
+ }
+ } else {
+ setMode('text');
+ stopMedia();
+ setIsSwapped(false);
+ setCallStatus('idle');
+ }
+ }, [isOpen, assistant, mode]);
+
+ // Auto-scroll logic
+ useEffect(() => {
+ if (scrollRef.current) {
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
+ }
+ }, [messages, mode]);
+
+ // Fetch Devices
+ useEffect(() => {
+ if (isOpen && mode === 'video') {
+ const getDevices = async () => {
+ try {
+ await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
+ const dev = await navigator.mediaDevices.enumerateDevices();
+ setDevices(dev);
+ const cams = dev.filter(d => d.kind === 'videoinput');
+ const mics = dev.filter(d => d.kind === 'audioinput');
+ if (cams.length > 0 && !selectedCamera) setSelectedCamera(cams[0].deviceId);
+ if (mics.length > 0 && !selectedMic) setSelectedMic(mics[0].deviceId);
+ } catch (e) {
+ console.error("Error enumerating devices", e);
+ }
+ };
+ getDevices();
+ }
+ }, [isOpen, mode]);
+
+ const stopMedia = () => {
+ if (streamRef.current) {
+ streamRef.current.getTracks().forEach(track => track.stop());
+ streamRef.current = null;
+ }
+ };
+
+ useEffect(() => {
+ const handleStream = async () => {
+ if (isOpen && mode === 'video' && callStatus === 'active') {
+ try {
+ stopMedia();
+ const constraints = {
+ video: selectedCamera ? { deviceId: { exact: selectedCamera } } : true,
+ audio: selectedMic ? { deviceId: { exact: selectedMic } } : true
+ };
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
+ streamRef.current = stream;
+ if (videoRef.current) {
+ videoRef.current.srcObject = stream;
+ }
+ } catch (err) {
+ console.error("Failed to access camera/mic:", err);
+ }
+ } else if (callStatus !== 'active') {
+ stopMedia();
+ }
+ };
+
+ handleStream();
+ return () => stopMedia();
+ }, [mode, isOpen, selectedCamera, selectedMic, callStatus]);
+
+ const handleCall = () => {
+ setCallStatus('calling');
+ setTimeout(() => {
+ setCallStatus('active');
+ setMessages([{ role: 'model', text: assistant.opener || "Hello!" }]);
+ }, 1500);
+ };
+
+ const handleHangup = () => {
+ stopMedia();
+ setCallStatus('idle');
+ setMessages([]);
+ };
+
+ const handleSend = async () => {
+ if (!inputText.trim()) return;
+ const userMsg = inputText;
+ setMessages(prev => [...prev, { role: 'user', text: userMsg }]);
+ setInputText('');
+ setIsLoading(true);
+
+ try {
+ if (process.env.API_KEY) {
+ const ai = new GoogleGenAI({ apiKey: process.env.API_KEY });
+ const chat = ai.chats.create({
+ model: "gemini-3-flash-preview",
+ config: { systemInstruction: assistant.prompt },
+ history: messages.map(m => ({ role: m.role, parts: [{ text: m.text }] }))
+ });
+ const result = await chat.sendMessage({ message: userMsg });
+ setMessages(prev => [...prev, { role: 'model', text: result.text || '' }]);
+ } else {
+ setTimeout(() => {
+ setMessages(prev => [...prev, { role: 'model', text: `[Mock Response]: Received "${userMsg}"` }]);
+ setIsLoading(false);
+ }, 1000);
+ }
+ } catch (e) {
+ console.error(e);
+ setMessages(prev => [...prev, { role: 'model', text: "Error: Failed to connect to AI service." }]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const TranscriptionLog = () => (
+
+ {messages.length === 0 &&
暂无转写记录
}
+ {messages.map((m, i) => (
+
+
+ {m.role === 'user' ? 'Me' : 'AI'}
+ {m.text}
+
+
+ ))}
+ {isLoading &&
Thinking...
}
+
+ );
+
+ const renderLocalVideo = (isSmall: boolean) => (
+
+ );
+
+ const renderRemoteVideo = (isSmall: boolean) => (
+
+
+ {!isSmall &&
{assistant.name}
}
+
+ );
+
+ return (
+ { handleHangup(); onClose(); }} title={`调试: ${assistant.name}`}>
+
+
+ {(['text', 'voice', 'video'] as const).map(m => (
+
+ ))}
+
+
+
+ {mode === 'text' ? (
+
+ ) : callStatus === 'idle' ? (
+
+
+
+
+ {mode === 'voice' ? : }
+
+
+
+
准备就绪
+
点击下方按钮开启人机交互测试
+
+
+
+ ) : callStatus === 'calling' ? (
+
+
+
+
CALLING...
+
正在连接 AI 服务
+
+
+
+ ) : (
+
+ {mode === 'voice' ? (
+
+ ) : (
+
+
+
+
+
+
+
+
{isSwapped ? renderLocalVideo(false) : renderRemoteVideo(false)}
+
{isSwapped ? renderRemoteVideo(true) : renderLocalVideo(true)}
+
+
+
+
+
+ )}
+
+
+ )}
+
+
+
+
+ setInputText(e.target.value)} placeholder={mode === 'text' ? "输入消息..." : "输入文本模拟交互..."} onKeyDown={e => e.key === 'Enter' && handleSend()} disabled={isLoading || (mode !== 'text' && callStatus !== 'active')} className="flex-1" />
+
+
+
+
+
+ );
+};
diff --git a/web/pages/AutoTest.tsx b/web/pages/AutoTest.tsx
new file mode 100644
index 0000000..3dfd694
--- /dev/null
+++ b/web/pages/AutoTest.tsx
@@ -0,0 +1,314 @@
+
+import React, { useState } from 'react';
+import { Plus, Search, Play, Copy, Trash2, Zap, MessageSquare, Mic, AlertTriangle, ClipboardCheck, X } from 'lucide-react';
+import { Button, Input, Card, Badge, Dialog } from '../components/UI';
+import { mockAutoTestAssistants, mockAssistants } from '../services/mockData';
+import { AutoTestAssistant, TestType, TestMethod } from '../types';
+
+export const AutoTestPage: React.FC = () => {
+ const [testAssistants, setTestAssistants] = useState(mockAutoTestAssistants);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [selectedId, setSelectedId] = useState(null);
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
+ const [deleteId, setDeleteId] = useState(null);
+ const [copySuccess, setCopySuccess] = useState(false);
+
+ const filteredTests = testAssistants.filter(t =>
+ t.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ const selectedTest = testAssistants.find(t => t.id === selectedId) || null;
+
+ const handleCreate = () => {
+ const newId = crypto.randomUUID();
+ const newTest: AutoTestAssistant = {
+ id: newId,
+ name: '新测试任务',
+ type: TestType.FIXED,
+ method: TestMethod.TEXT,
+ targetAssistantId: mockAssistants[0]?.id || '',
+ fixedWorkflowSteps: [],
+ intelligentPrompt: '',
+ createdAt: new Date().toISOString().split('T')[0],
+ };
+ setTestAssistants([newTest, ...testAssistants]);
+ setSelectedId(newId);
+ };
+
+ const handleCopy = (e: React.MouseEvent, test: AutoTestAssistant) => {
+ e.stopPropagation();
+ const newId = crypto.randomUUID();
+ const newTest: AutoTestAssistant = {
+ ...test,
+ id: newId,
+ name: `${test.name} (复制)`,
+ createdAt: new Date().toISOString().split('T')[0],
+ };
+ setTestAssistants([newTest, ...testAssistants]);
+ setSelectedId(newId);
+ };
+
+ const updateTest = (field: keyof AutoTestAssistant, value: any) => {
+ if (!selectedId) return;
+ setTestAssistants(prev => prev.map(t => t.id === selectedId ? { ...t, [field]: value } : t));
+ };
+
+ const handleDeleteClick = (e: React.MouseEvent, id: string) => {
+ e.stopPropagation();
+ setDeleteId(id);
+ setIsDeleteModalOpen(true);
+ };
+
+ const confirmDelete = () => {
+ if (deleteId) {
+ setTestAssistants(prev => prev.filter(t => t.id !== deleteId));
+ if (selectedId === deleteId) setSelectedId(null);
+ setIsDeleteModalOpen(false);
+ setDeleteId(null);
+ }
+ };
+
+ const handleCopyId = (id: string) => {
+ navigator.clipboard.writeText(id);
+ setCopySuccess(true);
+ setTimeout(() => setCopySuccess(false), 2000);
+ };
+
+ return (
+
+ {/* Left List */}
+
+
测试助手列表
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+ {filteredTests.map(test => (
+
setSelectedId(test.id)}
+ className={`group relative flex flex-col p-4 rounded-xl border transition-all cursor-pointer ${
+ selectedId === test.id
+ ? 'bg-primary/10 border-primary/40 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
+ : 'bg-card/30 border-white/5 hover:bg-white/5 hover:border-white/10'
+ }`}
+ >
+
+
+ {test.name}
+
+
+
+ {test.type === TestType.FIXED ? '固定' : '智能'}
+
+
+
+
+ 创建于: {test.createdAt}
+
+
+ {/* Hover Actions Toolbar */}
+
+
+
+
+
+ ))}
+ {filteredTests.length === 0 && (
+
+ 未找到测试助手
+
+ )}
+
+
+
+ {/* Right Config Panel */}
+
+ {selectedTest ? (
+
+
+
+
+
+
+
+
updateTest('name', e.target.value)}
+ className="font-bold bg-white/5 border-white/10 focus:border-primary/50 text-base"
+ />
+
+
+
+
+
+
+
+ {/* Basic Config */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Test Logic Type */}
+
+
+
+
updateTest('type', TestType.FIXED)}
+ >
+
+ 固定流程
+
+ 按照预设的消息序列依次发送给目标小助手,验证其回复是否符合预期。
+
+
updateTest('type', TestType.INTELLIGENT)}
+ >
+
+ 智能提示词
+
+ 由 AI 扮演用户,根据设定的角色和场景与目标小助手进行动态对话。
+
+
+
+
+ {/* Content Config */}
+ {selectedTest.type === TestType.FIXED ? (
+
+
+
+ {selectedTest.fixedWorkflowSteps.map((step, idx) => (
+
+
{idx + 1}
+
{
+ const newSteps = [...selectedTest.fixedWorkflowSteps];
+ newSteps[idx] = e.target.value;
+ updateTest('fixedWorkflowSteps', newSteps);
+ }}
+ placeholder="输入用户消息..."
+ />
+
+
+ ))}
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+ ) : (
+
+
+
+
+
请选择或创建一个测试任务
+
用于自动化验证小助手的逻辑表现
+
+ )}
+
+
+ {/* Delete Confirmation */}
+
+
+ );
+};
diff --git a/web/pages/CallLogs.tsx b/web/pages/CallLogs.tsx
new file mode 100644
index 0000000..3422d7e
--- /dev/null
+++ b/web/pages/CallLogs.tsx
@@ -0,0 +1,133 @@
+
+import React, { useState } from 'react';
+import { Download, Search, Calendar, Filter } from 'lucide-react';
+import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Badge } from '../components/UI';
+import { mockCallLogs } from '../services/mockData';
+
+export const CallLogsPage: React.FC = () => {
+ const [logs] = useState(mockCallLogs);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [statusFilter, setStatusFilter] = useState<'all' | 'connected' | 'missed'>('all');
+ const [sourceFilter, setSourceFilter] = useState<'all' | 'debug' | 'external'>('all');
+
+ const filteredLogs = logs.filter(log => {
+ const matchesSearch = log.agentName.toLowerCase().includes(searchTerm.toLowerCase());
+ const matchesStatus = statusFilter === 'all' || log.status === statusFilter;
+ const matchesSource = sourceFilter === 'all' || log.source === sourceFilter;
+ return matchesSearch && matchesStatus && matchesSource;
+ });
+
+ const handleExport = () => {
+ // Generate CSV content
+ const headers = ['ID', 'Agent', 'Source', 'Status', 'Start Time', 'Duration'];
+ const rows = filteredLogs.map(log => [
+ log.id,
+ log.agentName,
+ log.source,
+ log.status,
+ log.startTime,
+ log.duration
+ ].join(','));
+ const csvContent = "data:text/csv;charset=utf-8," + [headers.join(','), ...headers.join(',')].join('\n');
+ const encodedUri = encodeURI(csvContent);
+ const link = document.createElement("a");
+ link.setAttribute("href", encodedUri);
+ link.setAttribute("download", "call_logs.csv");
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ };
+
+ return (
+
+
+
视频通话记录
+
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编号
+ 代理小助手
+ 来源
+ 接听状态
+ 通话接通时间
+ 通话时长
+
+
+
+ {filteredLogs.map(log => (
+
+ #{log.id}
+ {log.agentName}
+
+ {log.source === 'debug' ? '调试' : '外部'}
+
+
+
+ {log.status === 'connected' ? '已接通' : '未接通'}
+
+
+ {log.startTime}
+ {log.duration}
+
+ ))}
+ {filteredLogs.length === 0 && (
+
+ 暂无记录
+
+
+
+
+
+
+ )}
+
+
+
+
+ );
+};
diff --git a/web/pages/Dashboard.tsx b/web/pages/Dashboard.tsx
new file mode 100644
index 0000000..7ba8460
--- /dev/null
+++ b/web/pages/Dashboard.tsx
@@ -0,0 +1,309 @@
+
+import React, { useState, useMemo } from 'react';
+import { Phone, CheckCircle, Clock, UserCheck, Activity, Filter, ChevronDown, BarChart3, HelpCircle, Mail, Sparkles, Terminal, Box, Zap, ShieldCheck } from 'lucide-react';
+import { Card, Button } from '../components/UI';
+import { mockAssistants, getDashboardStats } from '../services/mockData';
+
+export const DashboardPage: React.FC = () => {
+ const [timeRange, setTimeRange] = useState<'week' | 'month' | 'year'>('week');
+ const [selectedAssistantId, setSelectedAssistantId] = useState('all');
+
+ const stats = useMemo(() => {
+ return getDashboardStats(timeRange, selectedAssistantId);
+ }, [timeRange, selectedAssistantId]);
+
+ return (
+
+
+
+ {/* 1. Utility Row (Top Navigation Actions) */}
+
+
+
+
+
+ {/* 2. Welcome Header */}
+
+
+
+ 欢迎, Admin User
+
+
+ 系统当前运行环境:
+
+
+ HEALTHY
+
+
+
+
+
+ {/* 3. Section Header: Title + Filters */}
+
+
+
+
+
+
+
用量标准
+ Metrics Overview
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {(['week', 'month', 'year'] as const).map((r) => (
+
+ ))}
+
+
+
+
+ {/* 4. Metrics Grid (Cards) */}
+
+ }
+ trend="+12.5% UP"
+ />
+ }
+ trend="+2.1% UP"
+ />
+ }
+ trend="-0.5% LOW"
+ />
+ }
+ trend="+5% STABLE"
+ />
+
+
+ {/* 5. Charts Section */}
+
+
+
+
+
+
+ 通话趋势 (Performance Insight)
+
+
REAL-TIME DATA PROCESSING PIPELINE ENABLED
+
+
+
+
+
+
+
+
+
+ {/* 6. Platform Feature Intro - Moved to Bottom, Full Width */}
+
+
+
+
+
+
+
+
+ AI视频助手是一个领先的多模态智能体管理平台,致力于通过先进的 AI 技术为企业和个人提供高效、低延迟、拟人化的音视频通话解决方案。🚀
+
+
+
+
+
+
+ 🤖
+
多模态智能体
+
+
+ 支持构建具备文本对话、高保真语音输出以及双向实时视频通话能力的智能助手,覆盖 7x24h 智能客服场景。
+
+
+
+
+
+ 📚
+
动态知识检索
+
+
+ 深度集成 RAG 技术,允许上传私有 PDF/DOCX 文档,让智能体在通话中基于企业私域知识库提供精准、权威的回复。
+
+
+
+
+
+ 🎙️
+
音色库与克隆
+
+
+ 集成多家主流 TTS 引擎,支持极致的声音克隆与微调,为您的品牌定制专属的、富有情感表现力的真人音色。
+
+
+
+
+
+ 🛡️
+
端到端测试
+
+
+ 内置自动化测试助手,可通过固定流程或 AI 智能模拟用户进行压力测试与逻辑验证,确保发布前的服务稳定性。
+
+
+
+
+
+
+
+ );
+};
+
+// --- Sub Components ---
+
+const StatCard: React.FC<{ title: string; value: string; icon: React.ReactNode; trend?: string }> = ({ title, value, icon, trend }) => (
+
+
+
+
{value}
+ {trend && (
+
+ {trend}
+
+ )}
+
+
+);
+
+const SimpleAreaChart: React.FC<{ data: { label: string, value: number }[] }> = ({ data }) => {
+ if (!data || data.length === 0) return null;
+
+ const height = 300;
+ const width = 1400;
+ const padding = 30;
+
+ const maxValue = Math.max(...data.map(d => d.value)) * 1.2;
+ const points = data.map((d, i) => {
+ const x = (i / (data.length - 1)) * (width - padding * 2) + padding;
+ const y = height - (d.value / maxValue) * (height - padding * 2) - padding;
+ return `${x},${y}`;
+ }).join(' ');
+
+ const firstPoint = points.split(' ')[0];
+ const lastPoint = points.split(' ')[points.split(' ').length - 1];
+ const fillPath = `${points} ${lastPoint.split(',')[0]},${height} ${firstPoint.split(',')[0]},${height}`;
+
+ return (
+
+
+
+ {/* X-Axis Labels */}
+
+ {data.filter((_, i) => i % Math.ceil(data.length / 7) === 0).map((d, i) => (
+ {d.label}
+ ))}
+
+
+ );
+};
diff --git a/web/pages/History.tsx b/web/pages/History.tsx
new file mode 100644
index 0000000..818d503
--- /dev/null
+++ b/web/pages/History.tsx
@@ -0,0 +1,128 @@
+
+import React, { useState } from 'react';
+import { Download, Search, Calendar, Filter } from 'lucide-react';
+import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Badge } from '../components/UI';
+import { mockCallLogs } from '../services/mockData';
+
+export const HistoryPage: React.FC = () => {
+ const [logs] = useState(mockCallLogs);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [statusFilter, setStatusFilter] = useState<'all' | 'connected' | 'missed'>('all');
+ const [sourceFilter, setSourceFilter] = useState<'all' | 'debug' | 'external'>('all');
+
+ const filteredLogs = logs.filter(log => {
+ const matchesSearch = log.agentName.toLowerCase().includes(searchTerm.toLowerCase());
+ const matchesStatus = statusFilter === 'all' || log.status === statusFilter;
+ const matchesSource = sourceFilter === 'all' || log.source === sourceFilter;
+ return matchesSearch && matchesStatus && matchesSource;
+ });
+
+ const handleExport = () => {
+ // Generate CSV content
+ const headers = ['ID', 'Agent', 'Source', 'Status', 'Start Time', 'Duration'];
+ const rows = filteredLogs.map(log => [
+ log.id,
+ log.agentName,
+ log.source,
+ log.status,
+ log.startTime,
+ log.duration
+ ].join(','));
+ const csvContent = "data:text/csv;charset=utf-8," + [headers.join(','), ...headers.join(',')].join('\n');
+ const encodedUri = encodeURI(csvContent);
+ const link = document.createElement("a");
+ link.setAttribute("href", encodedUri);
+ link.setAttribute("download", "history_logs.csv");
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ };
+
+ return (
+
+
+
历史记录
+
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编号
+ 代理小助手
+ 来源
+ 接听状态
+ 通话接通时间
+ 通话时长
+
+
+
+ {filteredLogs.map(log => (
+
+ #{log.id}
+ {log.agentName}
+
+ {log.source === 'debug' ? '调试' : '外部'}
+
+
+
+ {log.status === 'connected' ? '已接通' : '未接通'}
+
+
+ {log.startTime}
+ {log.duration}
+
+ ))}
+ {filteredLogs.length === 0 && (
+
+ 暂无记录
+
+ )}
+
+
+
+
+ );
+};
diff --git a/web/pages/KnowledgeBase.tsx b/web/pages/KnowledgeBase.tsx
new file mode 100644
index 0000000..81f3803
--- /dev/null
+++ b/web/pages/KnowledgeBase.tsx
@@ -0,0 +1,298 @@
+
+import React, { useState, useRef } from 'react';
+import { Search, Plus, FileText, Upload, ArrowLeft, CloudUpload, File as FileIcon, X } from 'lucide-react';
+import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Card, Dialog } from '../components/UI';
+import { mockKnowledgeBases } from '../services/mockData';
+import { KnowledgeBase } from '../types';
+
+export const KnowledgeBasePage: React.FC = () => {
+ const [view, setView] = useState<'list' | 'detail'>('list');
+ const [selectedKb, setSelectedKb] = useState(null);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [kbs, setKbs] = useState(mockKnowledgeBases);
+ const [isUploadOpen, setIsUploadOpen] = useState(false);
+ const [isCreateKbOpen, setIsCreateKbOpen] = useState(false);
+ const [newKbName, setNewKbName] = useState('');
+
+ const filteredKbs = kbs.filter(kb => kb.name.toLowerCase().includes(searchTerm.toLowerCase()));
+
+ const handleSelect = (kb: KnowledgeBase) => {
+ setSelectedKb(kb);
+ setView('detail');
+ };
+
+ const handleImportClick = () => {
+ setIsUploadOpen(true);
+ };
+
+ const handleCreateKb = () => {
+ if (!newKbName.trim()) return;
+
+ const newKb: KnowledgeBase = {
+ id: `kb_${Date.now()}`,
+ name: newKbName.trim(),
+ creator: 'Admin User',
+ createdAt: new Date().toISOString().split('T')[0],
+ documents: []
+ };
+
+ setKbs([newKb, ...kbs]);
+ setIsCreateKbOpen(false);
+ setNewKbName('');
+ };
+
+ if (view === 'detail' && selectedKb) {
+ return (
+
+ setView('list')}
+ onImport={handleImportClick}
+ />
+ setIsUploadOpen(false)} />
+
+ );
+ }
+
+ return (
+
+
+
知识库
+
+
+ {/* Search Bar - Layout aligned with History Page and width filled */}
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+ {filteredKbs.map(kb => (
+
+ handleSelect(kb)}>
+
+
{kb.name}
+
+
文档数量: {kb.documents.length}
+
创建人: {kb.creator}
+
创建时间: {kb.createdAt}
+
+
+
+ ))}
+
+ {/* Add New Placeholder */}
+
setIsCreateKbOpen(true)}
+ className="border border-dashed border-white/10 rounded-xl p-6 flex flex-col items-center justify-center text-muted-foreground hover:bg-white/5 hover:border-primary/30 transition-all cursor-pointer min-h-[200px]"
+ >
+
+
新建知识库
+
+
+
+ {/* New Knowledge Base Dialog */}
+
+
+ );
+};
+
+const KnowledgeBaseDetail: React.FC<{
+ kb: KnowledgeBase;
+ onBack: () => void;
+ onImport: () => void;
+}> = ({ kb, onBack, onImport }) => {
+ const [docSearch, setDocSearch] = useState('');
+ const filteredDocs = kb.documents.filter(d => d.name.toLowerCase().includes(docSearch.toLowerCase()));
+
+ return (
+
+
+
+
+
+
{kb.name}
+
创建于 {kb.createdAt} · by {kb.creator}
+
+
+
+
+
+
+
+
文档列表
+
+ setDocSearch(e.target.value)}
+ className="bg-black/20 border-transparent focus:bg-black/40"
+ />
+
+
+
+
+
+ 文档名称
+ 大小
+ 上传时间
+ 操作
+
+
+
+ {filteredDocs.length > 0 ? filteredDocs.map(doc => (
+
+
+ {doc.name}
+
+ {doc.size}
+ {doc.uploadDate}
+
+
+
+
+ )) : (
+
+ 暂无文档
+
+ )}
+
+
+
+
+ );
+};
+
+const UploadModal: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => {
+ const [dragActive, setDragActive] = useState(false);
+ const [files, setFiles] = useState([]);
+ const inputRef = useRef(null);
+
+ const handleDrag = (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (e.type === "dragenter" || e.type === "dragover") {
+ setDragActive(true);
+ } else if (e.type === "dragleave") {
+ setDragActive(false);
+ }
+ };
+
+ const handleDrop = (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setDragActive(false);
+ if (e.dataTransfer.files && e.dataTransfer.files[0]) {
+ setFiles(prev => [...prev, ...Array.from(e.dataTransfer.files)]);
+ }
+ };
+
+ const handleChange = (e: React.ChangeEvent) => {
+ e.preventDefault();
+ if (e.target.files && e.target.files[0]) {
+ setFiles(prev => [...prev, ...Array.from(e.target.files || [])]);
+ }
+ };
+
+ const removeFile = (idx: number) => {
+ setFiles(prev => prev.filter((_, i) => i !== idx));
+ };
+
+ return (
+
+ );
+};
diff --git a/web/pages/Profile.tsx b/web/pages/Profile.tsx
new file mode 100644
index 0000000..9ce0dc8
--- /dev/null
+++ b/web/pages/Profile.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { User, Globe, LogOut, Settings } from 'lucide-react';
+import { Button, Card, Input } from '../components/UI';
+
+export const ProfilePage: React.FC = () => {
+ return (
+
+
+
+ A
+
+
+
Admin User
+
admin@example.com
+
+
+
+
+
+ 账户信息
+
+
+
+
+
+
+
+ 系统设置
+
+
+
+ 语言选择 / Language
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/web/pages/VoiceLibrary.tsx b/web/pages/VoiceLibrary.tsx
new file mode 100644
index 0000000..fa63f88
--- /dev/null
+++ b/web/pages/VoiceLibrary.tsx
@@ -0,0 +1,471 @@
+
+import React, { useState, useRef } from 'react';
+import { Search, Mic2, Play, Pause, Upload, X, Filter, Plus, Volume2, Sparkles, Wand2, ChevronDown } from 'lucide-react';
+import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Dialog, Badge } from '../components/UI';
+import { mockVoices } from '../services/mockData';
+import { Voice } from '../types';
+
+export const VoiceLibraryPage: React.FC = () => {
+ const [voices, setVoices] = useState(mockVoices);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [vendorFilter, setVendorFilter] = useState<'all' | 'Ali' | 'Volcano' | 'Minimax' | '硅基流动'>('all');
+ const [genderFilter, setGenderFilter] = useState<'all' | 'Male' | 'Female'>('all');
+ const [langFilter, setLangFilter] = useState<'all' | 'zh' | 'en'>('all');
+
+ const [playingVoiceId, setPlayingVoiceId] = useState(null);
+ const [isCloneModalOpen, setIsCloneModalOpen] = useState(false);
+ const [isAddModalOpen, setIsAddModalOpen] = useState(false);
+
+ const filteredVoices = voices.filter(voice => {
+ const matchesSearch = voice.name.toLowerCase().includes(searchTerm.toLowerCase());
+ const matchesVendor = vendorFilter === 'all' || voice.vendor === vendorFilter;
+ const matchesGender = genderFilter === 'all' || voice.gender === genderFilter;
+ const matchesLang = langFilter === 'all' || voice.language === langFilter;
+ return matchesSearch && matchesVendor && matchesGender && matchesLang;
+ });
+
+ const handlePlayToggle = (id: string) => {
+ if (playingVoiceId === id) {
+ setPlayingVoiceId(null);
+ } else {
+ setPlayingVoiceId(id);
+ setTimeout(() => {
+ setPlayingVoiceId((current) => current === id ? null : current);
+ }, 3000);
+ }
+ };
+
+ const handleAddSuccess = (newVoice: Voice) => {
+ setVoices([newVoice, ...voices]);
+ setIsAddModalOpen(false);
+ setIsCloneModalOpen(false);
+ };
+
+ return (
+
+
+
声音库
+
+
+
+
+
+
+ {/* Filter Bar */}
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 声音名称
+ 厂商
+ 性别
+ 语言
+ 试听
+
+
+
+ {filteredVoices.map(voice => (
+
+
+
+
+ {voice.vendor === '硅基流动' && }
+ {voice.name}
+
+ {voice.description && {voice.description}}
+
+
+
+ {voice.vendor}
+
+ {voice.gender === 'Male' ? '男' : '女'}
+ {voice.language === 'zh' ? '中文' : 'English'}
+
+
+
+
+ ))}
+ {filteredVoices.length === 0 && (
+
+ 暂无声音数据
+
+ )}
+
+
+
+
+
setIsAddModalOpen(false)}
+ onSuccess={handleAddSuccess}
+ />
+
+ setIsCloneModalOpen(false)}
+ onSuccess={handleAddSuccess}
+ />
+
+ );
+};
+
+// --- Unified Add Voice Modal ---
+const AddVoiceModal: React.FC<{
+ isOpen: boolean;
+ onClose: () => void;
+ onSuccess: (voice: Voice) => void;
+}> = ({ isOpen, onClose, onSuccess }) => {
+ const [vendor, setVendor] = useState<'硅基流动' | 'Ali' | 'Volcano' | 'Minimax'>('硅基流动');
+ const [name, setName] = useState('');
+
+ const [sfModel, setSfModel] = useState('fishaudio/fish-speech-1.5');
+ const [sfVoiceId, setSfVoiceId] = useState('fishaudio:amy');
+ const [sfSpeed, setSfSpeed] = useState(1);
+ const [sfGain, setSfGain] = useState(0);
+
+ const [model, setModel] = useState('');
+ const [voiceKey, setVoiceKey] = useState('');
+ const [gender, setGender] = useState('Female');
+ const [language, setLanguage] = useState('zh');
+ const [description, setDescription] = useState('');
+
+ const [testInput, setTestInput] = useState('你好,正在测试语音合成效果。');
+ const [isAuditioning, setIsAuditioning] = useState(false);
+
+ const handleAudition = () => {
+ if (!testInput.trim()) return;
+ setIsAuditioning(true);
+ setTimeout(() => setIsAuditioning(false), 2000);
+ };
+
+ const handleSubmit = () => {
+ if (!name) { alert("请填写声音显示名称"); return; }
+
+ let newVoice: Voice = {
+ id: `${vendor === '硅基流动' ? 'sf' : 'gen'}-${Date.now()}`,
+ name: name,
+ vendor: vendor,
+ gender: gender,
+ language: language,
+ description: description || (vendor === '硅基流动' ? `Model: ${sfModel}` : `Model: ${model}`)
+ };
+
+ onSuccess(newVoice);
+ setName('');
+ setVendor('硅基流动');
+ setDescription('');
+ };
+
+ return (
+
+ );
+};
+
+const CloneVoiceModal: React.FC<{
+ isOpen: boolean;
+ onClose: () => void;
+ onSuccess: (voice: Voice) => void
+}> = ({ isOpen, onClose, onSuccess }) => {
+ const [name, setName] = useState('');
+ const [description, setDescription] = useState('');
+ const [file, setFile] = useState(null);
+ const inputRef = useRef(null);
+
+ const handleFileChange = (e: React.ChangeEvent) => {
+ if (e.target.files && e.target.files[0]) {
+ setFile(e.target.files[0]);
+ }
+ };
+
+ const handleSubmit = () => {
+ if (!name || !file) {
+ alert("请填写名称并上传音频文件");
+ return;
+ }
+
+ const newVoice: Voice = {
+ id: `v-${Date.now()}`,
+ name: name,
+ vendor: 'Volcano',
+ gender: 'Female',
+ language: 'zh',
+ description: description || 'User cloned voice'
+ };
+
+ onSuccess(newVoice);
+ setName('');
+ setDescription('');
+ setFile(null);
+ };
+
+ return (
+
+ );
+};
diff --git a/web/pages/WorkflowEditor.tsx b/web/pages/WorkflowEditor.tsx
new file mode 100644
index 0000000..b3990a5
--- /dev/null
+++ b/web/pages/WorkflowEditor.tsx
@@ -0,0 +1,402 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
+import { ArrowLeft, Play, Save, Rocket, Plus, Bot, UserCheck, Wrench, Ban, Zap, X, Copy, MousePointer2 } from 'lucide-react';
+import { Button, Input, Badge } from '../components/UI';
+import { mockAssistants, mockKnowledgeBases, mockWorkflows } from '../services/mockData';
+import { WorkflowNode, WorkflowEdge, Workflow } from '../types';
+import { DebugDrawer } from './Assistants';
+
+export const WorkflowEditorPage: React.FC = () => {
+ const navigate = useNavigate();
+ const { id } = useParams();
+ const [searchParams] = useSearchParams();
+
+ // Template data for new workflows
+ const templateName = searchParams.get('name');
+ const templateType = searchParams.get('template');
+
+ // Find initial workflow or create a new one
+ const existingWf = mockWorkflows.find(w => w.id === id);
+ const [name, setName] = useState(templateName || existingWf?.name || '新工作流');
+ const [nodes, setNodes] = useState(() => {
+ if (existingWf) return existingWf.nodes;
+ if (templateType === 'lead') {
+ return [
+ {
+ name: 'introduction',
+ type: 'conversation',
+ isStart: true,
+ metadata: { position: { x: 100, y: 100 } },
+ prompt: "You are Morgan from GrowthPartners. Start with: 'Hello, this is Morgan from GrowthPartners. We help businesses improve their operational efficiency through custom software solutions. Do you have a few minutes to chat about how we might be able to help your business?'",
+ messagePlan: { firstMessage: "Hello, this is Morgan from GrowthPartners. Do you have a few minutes to chat?" }
+ },
+ {
+ name: 'need_discovery',
+ type: 'conversation',
+ metadata: { position: { x: 450, y: 250 } },
+ prompt: "Conduct need discovery by asking about business challenges...",
+ variableExtractionPlan: {
+ output: [
+ { title: 'industry', type: 'string', description: 'user industry' },
+ { title: 'pain_points', type: 'string', description: 'main challenges' }
+ ]
+ }
+ },
+ {
+ name: 'hangup_node',
+ type: 'end',
+ metadata: { position: { x: 450, y: 550 } },
+ tool: {
+ type: 'endCall',
+ function: { name: 'hangup', parameters: {} },
+ messages: [{ type: 'request-start', content: 'Thank you for your time!', blocking: true }]
+ }
+ }
+ ];
+ }
+ return [
+ {
+ name: 'start_node',
+ type: 'conversation',
+ isStart: true,
+ metadata: { position: { x: 200, y: 200 } },
+ prompt: '欢迎对话系统...',
+ messagePlan: { firstMessage: '你好!' }
+ }
+ ];
+ });
+ const [edges, setEdges] = useState(existingWf?.edges || []);
+
+ const [selectedNodeName, setSelectedNodeName] = useState(null);
+ const [isAddMenuOpen, setIsAddMenuOpen] = useState(false);
+ const [isDebugOpen, setIsDebugOpen] = useState(false);
+ const [zoom, setZoom] = useState(1);
+ const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
+
+ const [draggingNodeName, setDraggingNodeName] = useState(null);
+ const [isPanning, setIsPanning] = useState(false);
+ const dragOffset = useRef({ x: 0, y: 0 });
+ const panStart = useRef({ x: 0, y: 0 });
+
+ const selectedNode = nodes.find(n => n.name === selectedNodeName);
+
+ // Scroll Zoom handler
+ const handleWheel = (e: React.WheelEvent) => {
+ e.preventDefault();
+ const zoomSpeed = 0.001;
+ const newZoom = Math.min(Math.max(zoom - e.deltaY * zoomSpeed, 0.2), 3);
+ setZoom(newZoom);
+ };
+
+ // Middle mouse click handler for panning
+ const handleMouseDown = (e: React.MouseEvent) => {
+ // Button 1 is middle mouse
+ if (e.button === 1) {
+ e.preventDefault();
+ setIsPanning(true);
+ panStart.current = { x: e.clientX - panOffset.x, y: e.clientY - panOffset.y };
+ }
+ };
+
+ const handleNodeMouseDown = (e: React.MouseEvent, nodeName: string) => {
+ if (e.button !== 0) return; // Only left click for nodes
+ e.stopPropagation();
+ setSelectedNodeName(nodeName);
+ setDraggingNodeName(nodeName);
+ const node = nodes.find(n => n.name === nodeName);
+ if (node) {
+ dragOffset.current = {
+ x: e.clientX - node.metadata.position.x * zoom,
+ y: e.clientY - node.metadata.position.y * zoom
+ };
+ }
+ };
+
+ useEffect(() => {
+ const handleMouseMove = (e: MouseEvent) => {
+ if (draggingNodeName) {
+ setNodes(prev => prev.map(n =>
+ n.name === draggingNodeName
+ ? { ...n, metadata: { ...n.metadata, position: { x: (e.clientX - dragOffset.current.x) / zoom, y: (e.clientY - dragOffset.current.y) / zoom } } }
+ : n
+ ));
+ } else if (isPanning) {
+ setPanOffset({
+ x: e.clientX - panStart.current.x,
+ y: e.clientY - panStart.current.y
+ });
+ }
+ };
+ const handleMouseUp = (e: MouseEvent) => {
+ setDraggingNodeName(null);
+ setIsPanning(false);
+ };
+
+ window.addEventListener('mousemove', handleMouseMove);
+ window.addEventListener('mouseup', handleMouseUp);
+ return () => {
+ window.removeEventListener('mousemove', handleMouseMove);
+ window.removeEventListener('mouseup', handleMouseUp);
+ };
+ }, [draggingNodeName, isPanning, zoom]);
+
+ const addNode = (type: WorkflowNode['type']) => {
+ const newNode: WorkflowNode = {
+ name: `${type}_${Date.now()}`,
+ type,
+ metadata: { position: { x: (300 - panOffset.x) / zoom, y: (300 - panOffset.y) / zoom } },
+ prompt: type === 'conversation' ? '输入该节点的 Prompt...' : '',
+ tool: type === 'end' ? { type: 'endCall', function: { name: 'hangup', parameters: {} } } : undefined
+ };
+ setNodes([...nodes, newNode]);
+ setIsAddMenuOpen(false);
+ };
+
+ const updateNodeData = (field: string, value: any) => {
+ if (!selectedNodeName) return;
+ setNodes(prev => prev.map(n => n.name === selectedNodeName ? { ...n, [field]: value } : n));
+ };
+
+ const handleSave = () => {
+ const now = new Date().toISOString().replace('T', ' ').substring(0, 16);
+ const updatedWorkflow: Workflow = {
+ id: id || `wf_${Date.now()}`,
+ name,
+ nodeCount: nodes.length,
+ createdAt: existingWf?.createdAt || now,
+ updatedAt: now,
+ nodes,
+ edges,
+ };
+
+ if (id) {
+ const idx = mockWorkflows.findIndex(w => w.id === id);
+ if (idx !== -1) mockWorkflows[idx] = updatedWorkflow;
+ } else {
+ mockWorkflows.push(updatedWorkflow);
+ }
+ alert('保存成功!工作流已同步至列表。');
+ navigate('/workflows');
+ };
+
+ return (
+
+ {/* Editor Header */}
+
+
+ {/* Canvas Area */}
+
+ {/* Floating Controls */}
+
+
+ {isAddMenuOpen && (
+
+
+
+
+
+
+ )}
+
+ {Math.round(zoom * 100)}%
+ |
+ POS: {Math.round(panOffset.x)}, {Math.round(panOffset.y)}
+
+
+
+ {/* Scalable Container */}
+
+ {nodes.map(node => (
+
handleNodeMouseDown(e, node.name)}
+ style={{ left: node.metadata.position.x, top: node.metadata.position.y }}
+ className={`absolute w-56 p-4 rounded-xl border bg-card/70 backdrop-blur-sm cursor-grab active:cursor-grabbing group transition-shadow ${selectedNodeName === node.name ? 'border-primary shadow-[0_0_30px_rgba(6,182,212,0.3)]' : 'border-white/10 hover:border-white/30'}`}
+ >
+
+
+
+ {node.name}
+
+ {node.isStart &&
START}
+
+
+ {node.prompt || (node.tool ? `Action: ${node.tool.type}` : '待配置逻辑...')}
+
+
+ {/* Port simulation */}
+
+
+
+ ))}
+
+
+ {/* Bottom Right: Minimap */}
+
+
Minimap
+
+
+
+ {nodes.map((n, i) => {
+ // Simple scaling for minimap
+ const mx = (n.metadata.position.x / 10) % 100;
+ const my = (n.metadata.position.y / 10) % 100;
+ return (
+
+ );
+ })}
+
+
+ {/* Viewport visualization */}
+
+
+
+
+ {/* Navigator Guide */}
+
+ 鼠标中键拖拽画布 | 滚轮缩放
+
+
+
+ {/* Right Settings Panel */}
+ {selectedNode && (
+
+
+
节点设置
+
+
+
+
+
+ updateNodeData('name', e.target.value)}
+ className="h-8 text-xs font-mono text-white bg-white/10"
+ />
+
+
+
+
+ {selectedNode.type.toUpperCase()}
+
+
+ {selectedNode.type === 'conversation' && (
+ <>
+
+
+
+
+
+ updateNodeData('messagePlan', { ...selectedNode.messagePlan, firstMessage: e.target.value })}
+ className="h-8 text-xs text-white"
+ placeholder="进入该节点时 AI 主动发起的消息..."
+ />
+
+ >
+ )}
+
+ {selectedNode.type === 'end' && (
+
+
结束对话节点
+
到达此节点后,系统将根据配置执行挂断操作。
+
+ )}
+
+
+
+
+
+
+ )}
+
+ {/* Debug Side Drawer */}
+
setIsDebugOpen(false)}
+ assistant={mockAssistants[0]}
+ />
+
+ );
+};
+
+const NodeIcon = ({ type }: { type: WorkflowNode['type'] }) => {
+ switch (type) {
+ case 'conversation': return ;
+ case 'human': return ;
+ case 'tool': return ;
+ case 'end': return ;
+ default: return ;
+ }
+};
diff --git a/web/pages/Workflows.tsx b/web/pages/Workflows.tsx
new file mode 100644
index 0000000..467cbbf
--- /dev/null
+++ b/web/pages/Workflows.tsx
@@ -0,0 +1,232 @@
+
+import React, { useState, useRef } from 'react';
+import { Search, Plus, Upload, MoreHorizontal, Code, Edit2, Copy, Trash2, Calendar, CloudUpload, File as FileIcon, X, Layout, FilePlus } from 'lucide-react';
+import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Dialog, Card } from '../components/UI';
+import { mockWorkflows } from '../services/mockData';
+import { useNavigate } from 'react-router-dom';
+
+export const WorkflowsPage: React.FC = () => {
+ const navigate = useNavigate();
+ const [workflows, setWorkflows] = useState(mockWorkflows);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [isUploadOpen, setIsUploadOpen] = useState(false);
+ const [isCreateOpen, setIsCreateOpen] = useState(false);
+ const [activeMenu, setActiveMenu] = useState(null);
+
+ const [newWfName, setNewWfName] = useState('');
+ const [selectedTemplate, setSelectedTemplate] = useState<'blank' | 'lead'>('blank');
+
+ const filteredWorkflows = workflows.filter(wf =>
+ wf.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ const handleCreateWorkflow = () => {
+ if (!newWfName.trim()) {
+ alert('请输入工作流名称');
+ return;
+ }
+ setIsCreateOpen(false);
+ navigate(`/workflows/new?name=${encodeURIComponent(newWfName)}&template=${selectedTemplate}`);
+ };
+
+ const handleDeleteWorkflow = (id: string) => {
+ if (confirm('确定要删除这个工作流吗?')) {
+ setWorkflows(prev => prev.filter(w => w.id !== id));
+ setActiveMenu(null);
+ }
+ };
+
+ return (
+
+
+
工作流
+
+
+
+
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ 名称
+ 节点数量
+ 创建时间
+ 更新时间
+ 操作
+
+
+
+ {filteredWorkflows.map(wf => (
+
+
+
+
+ {wf.nodeCount} 个节点
+ {wf.createdAt}
+ {wf.updatedAt}
+
+
+
+ {activeMenu === wf.id && (
+
+
+
+
+
+
+
+ )}
+
+
+ ))}
+ {filteredWorkflows.length === 0 && (
+
+ 暂无工作流数据
+
+ )}
+
+
+
+
+
setIsUploadOpen(false)} />
+
+
+
+ );
+};
+
+const UploadJsonModal: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => {
+ const [dragActive, setDragActive] = useState(false);
+ const [file, setFile] = useState(null);
+ const inputRef = useRef(null);
+
+ const handleDrag = (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setDragActive(e.type === "dragenter" || e.type === "dragover");
+ };
+
+ const handleDrop = (e: React.DragEvent) => {
+ e.preventDefault(); e.stopPropagation();
+ setDragActive(false);
+ if (e.dataTransfer.files?.[0]) setFile(e.dataTransfer.files[0]);
+ };
+
+ return (
+
+ );
+};
diff --git a/web/services/mockData.ts b/web/services/mockData.ts
new file mode 100644
index 0000000..b651110
--- /dev/null
+++ b/web/services/mockData.ts
@@ -0,0 +1,194 @@
+
+import { Assistant, CallLog, KnowledgeBase, Voice, Workflow, AutoTestAssistant, TestType, TestMethod } from '../types';
+
+export const mockVoices: Voice[] = [
+ { id: 'v1', name: 'Xiaoyun', vendor: 'Ali', gender: 'Female', language: 'zh', description: 'Gentle and professional.' },
+ { id: 'v2', name: 'Kevin', vendor: 'Volcano', gender: 'Male', language: 'en', description: 'Deep and authoritative.' },
+ { id: 'v3', name: 'Abby', vendor: 'Minimax', gender: 'Female', language: 'en', description: 'Cheerful and lively.' },
+ { id: 'v4', name: 'Guang', vendor: 'Ali', gender: 'Male', language: 'zh', description: 'Standard newscast style.' },
+ { id: 'v5', name: 'Doubao', vendor: 'Volcano', gender: 'Female', language: 'zh', description: 'Cute and young.' },
+];
+
+export const mockAssistants: Assistant[] = [
+ {
+ id: '1',
+ name: 'Customer Support Bot',
+ callCount: 154,
+ opener: 'Hello, how can I help you today?',
+ prompt: 'You are a helpful customer service agent.',
+ knowledgeBaseId: 'kb1',
+ language: 'en',
+ voice: 'v3',
+ speed: 1.0,
+ hotwords: ['refund', 'order'],
+ interruptionSensitivity: 500,
+ },
+ {
+ id: '2',
+ name: 'Sales Agent',
+ callCount: 89,
+ opener: 'Hi! Are you interested in our new product?',
+ prompt: 'You are an energetic sales representative.',
+ knowledgeBaseId: 'kb2',
+ language: 'zh',
+ voice: 'v1',
+ speed: 1.1,
+ hotwords: ['price', 'discount'],
+ interruptionSensitivity: 300,
+ },
+];
+
+export let mockWorkflows: Workflow[] = [
+ {
+ id: 'wf1',
+ name: 'Lead Qualification Agent',
+ nodeCount: 11,
+ createdAt: '2024-03-01 10:00',
+ updatedAt: '2024-03-05 14:30',
+ nodes: [
+ {
+ name: "introduction",
+ type: "conversation",
+ isStart: true,
+ metadata: { position: { x: 100, y: 100 } },
+ prompt: "You are Morgan from GrowthPartners. Start with: 'Hello, this is Morgan from GrowthPartners. We help businesses improve their operational efficiency through custom software solutions. Do you have a few minutes to chat about how we might be able to help your business?' Use a friendly, consultative tone.",
+ messagePlan: { firstMessage: "Hello, this is Morgan from GrowthPartners. Do you have a few minutes to chat about how we might be able to help your business?" }
+ },
+ {
+ name: "need_discovery",
+ type: "conversation",
+ metadata: { position: { x: 400, y: 150 } },
+ prompt: "Conduct need discovery by asking about: 1) Their business and industry, 2) Current systems/processes they use, 3) Biggest challenges with current approach...",
+ variableExtractionPlan: {
+ output: [
+ { type: "string", title: "industry", description: "the user's industry or business type" },
+ { type: "string", title: "company_size", description: "approximate number of employees" }
+ ]
+ }
+ }
+ ],
+ edges: [
+ { from: "introduction", to: "need_discovery" }
+ ]
+ },
+ {
+ id: 'wf2',
+ name: '售后退款流程',
+ nodeCount: 5,
+ createdAt: '2024-03-01 10:00',
+ updatedAt: '2024-03-05 14:30',
+ nodes: [],
+ edges: []
+ },
+];
+
+export const mockKnowledgeBases: KnowledgeBase[] = [
+ {
+ id: 'kb1',
+ name: 'Product Manuals',
+ creator: 'Admin',
+ createdAt: '2023-10-15',
+ documents: [
+ { id: 'd1', name: 'User Guide v1.pdf', size: '2.4 MB', uploadDate: '2023-10-15' },
+ { id: 'd2', name: 'Warranty Info.docx', size: '1.1 MB', uploadDate: '2023-10-16' },
+ ],
+ },
+ {
+ id: 'kb2',
+ name: 'Sales Scripts',
+ creator: 'Sales Lead',
+ createdAt: '2023-11-01',
+ documents: [
+ { id: 'd3', name: 'Objection Handling.pdf', size: '500 KB', uploadDate: '2023-11-01' },
+ ],
+ },
+];
+
+export const mockCallLogs: CallLog[] = [
+ {
+ id: 'c1',
+ source: 'external',
+ status: 'connected',
+ startTime: '2023-11-20 10:30:00',
+ duration: '5m 23s',
+ agentName: 'Customer Support Bot',
+ },
+ {
+ id: 'c2',
+ source: 'debug',
+ status: 'connected',
+ startTime: '2023-11-20 11:15:00',
+ duration: '1m 10s',
+ agentName: 'Sales Agent',
+ },
+ {
+ id: 'c3',
+ source: 'external',
+ status: 'missed',
+ startTime: '2023-11-20 12:00:00',
+ duration: '0s',
+ agentName: 'Customer Support Bot',
+ },
+];
+
+export const mockAutoTestAssistants: AutoTestAssistant[] = [
+ {
+ id: 'at1',
+ name: '退款流程压力测试',
+ type: TestType.FIXED,
+ method: TestMethod.TEXT,
+ targetAssistantId: '1',
+ fixedWorkflowSteps: ['你好,我要退款', '订单号是123456', '谢谢'],
+ intelligentPrompt: '',
+ createdAt: '2024-03-10 09:00'
+ },
+ {
+ id: 'at2',
+ name: '愤怒的客户模拟',
+ type: TestType.INTELLIGENT,
+ method: TestMethod.AUDIO,
+ targetAssistantId: '1',
+ fixedWorkflowSteps: [],
+ intelligentPrompt: '你是一个非常愤怒的客户,因为订单延迟了一周。你需要表达你的不满,并要求立即解决。你的语气必须很冲,不接受简单的道歉。',
+ createdAt: '2024-03-11 14:20'
+ }
+];
+
+export interface DashboardStats {
+ totalCalls: number;
+ answerRate: number;
+ avgDuration: string;
+ humanTransferCount: number;
+ trend: { label: string; value: number }[];
+}
+
+export const getDashboardStats = (timeRange: 'week' | 'month' | 'year', assistantId: string): DashboardStats => {
+ const multiplier = assistantId === 'all' ? 1 : (assistantId === '1' ? 0.6 : 0.4);
+ const rangeMultiplier = timeRange === 'week' ? 1 : (timeRange === 'month' ? 4 : 52);
+ const baseCalls = Math.floor(100 * rangeMultiplier * multiplier);
+ const transfers = Math.floor(baseCalls * 0.15);
+
+ let points = 7;
+ if (timeRange === 'month') points = 30;
+ if (timeRange === 'year') points = 12;
+
+ const trend = Array.from({ length: points }, (_, i) => {
+ let label = `Day ${i + 1}`;
+ if (timeRange === 'year') {
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ label = months[i];
+ }
+ return {
+ label,
+ value: Math.floor(Math.random() * 50 * multiplier) + 10
+ };
+ });
+
+ return {
+ totalCalls: baseCalls,
+ answerRate: 85 + Math.floor(Math.random() * 10),
+ avgDuration: `${Math.floor(2 + Math.random() * 3)}m ${Math.floor(Math.random() * 60)}s`,
+ humanTransferCount: transfers,
+ trend
+ };
+};
diff --git a/web/tsconfig.json b/web/tsconfig.json
new file mode 100644
index 0000000..2c6eed5
--- /dev/null
+++ b/web/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "experimentalDecorators": true,
+ "useDefineForClassFields": false,
+ "module": "ESNext",
+ "lib": [
+ "ES2022",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "skipLibCheck": true,
+ "types": [
+ "node"
+ ],
+ "moduleResolution": "bundler",
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "allowJs": true,
+ "jsx": "react-jsx",
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ },
+ "allowImportingTsExtensions": true,
+ "noEmit": true
+ }
+}
\ No newline at end of file
diff --git a/web/types.ts b/web/types.ts
new file mode 100644
index 0000000..4ee4ba2
--- /dev/null
+++ b/web/types.ts
@@ -0,0 +1,129 @@
+
+export interface Assistant {
+ id: string;
+ name: string;
+ callCount: number;
+ opener: string;
+ prompt: string;
+ knowledgeBaseId: string;
+ language: 'zh' | 'en';
+ voice: string; // This will now store the ID of the voice from Voice Library
+ speed: number;
+ hotwords: string[];
+ tools?: string[]; // IDs of enabled tools
+ interruptionSensitivity?: number; // In ms
+ configMode?: 'platform' | 'dify' | 'fastgpt' | 'none';
+ apiUrl?: string;
+ apiKey?: string;
+}
+
+export interface Voice {
+ id: string;
+ name: string;
+ vendor: string;
+ gender: string;
+ language: string;
+ description: string;
+}
+
+export interface KnowledgeBase {
+ id: string;
+ name: string;
+ creator: string;
+ createdAt: string;
+ documents: KnowledgeDocument[];
+}
+
+export interface KnowledgeDocument {
+ id: string;
+ name: string;
+ size: string;
+ uploadDate: string;
+}
+
+export interface CallLog {
+ id: string;
+ source: 'debug' | 'external';
+ status: 'connected' | 'missed';
+ startTime: string;
+ duration: string;
+ agentName: string;
+}
+
+export interface Workflow {
+ id: string;
+ name: string;
+ nodeCount: number;
+ createdAt: string;
+ updatedAt: string;
+ nodes: WorkflowNode[];
+ edges: WorkflowEdge[];
+ globalPrompt?: string;
+}
+
+export interface WorkflowNode {
+ name: string;
+ type: 'conversation' | 'tool' | 'human' | 'end';
+ isStart?: boolean;
+ metadata: {
+ position: { x: number; y: number };
+ };
+ prompt?: string;
+ messagePlan?: {
+ firstMessage?: string;
+ };
+ variableExtractionPlan?: {
+ output: Array<{
+ type: string;
+ title: string;
+ description: string;
+ }>;
+ };
+ tool?: {
+ type: string;
+ function: {
+ name: string;
+ parameters: any;
+ };
+ destinations?: any[];
+ messages?: any[];
+ };
+ globalNodePlan?: {
+ enabled: boolean;
+ enterCondition: string;
+ };
+}
+
+export interface WorkflowEdge {
+ from: string;
+ to: string;
+ label?: string;
+}
+
+export enum TabValue {
+ GLOBAL = 'global',
+ VOICE = 'voice',
+ TOOLS = 'tools',
+ LINK = 'link'
+}
+
+export enum TestType {
+ FIXED = 'fixed',
+ INTELLIGENT = 'intelligent'
+}
+
+export enum TestMethod {
+ TEXT = 'text',
+ AUDIO = 'audio'
+}
+
+export interface AutoTestAssistant {
+ id: string;
+ name: string;
+ type: TestType;
+ method: TestMethod;
+ targetAssistantId: string;
+ fixedWorkflowSteps: string[];
+ intelligentPrompt: string;
+ createdAt: string;
+}
diff --git a/web/vite.config.ts b/web/vite.config.ts
new file mode 100644
index 0000000..ee5fb8d
--- /dev/null
+++ b/web/vite.config.ts
@@ -0,0 +1,23 @@
+import path from 'path';
+import { defineConfig, loadEnv } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig(({ mode }) => {
+ const env = loadEnv(mode, '.', '');
+ return {
+ server: {
+ port: 3000,
+ host: '0.0.0.0',
+ },
+ plugins: [react()],
+ define: {
+ 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
+ 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
+ },
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, '.'),
+ }
+ }
+ };
+});