diff --git a/webui/README.md b/webui/README.md
new file mode 100644
index 000000000..89cec5543
--- /dev/null
+++ b/webui/README.md
@@ -0,0 +1,176 @@
+# MS-Agent WebUI Backend - 开发者指南
+
+## 概述
+
+MS-Agent WebUI后端负责管理前端与ms-agent框架之间的通信,通过WebSocket实现实时交互,支持多种项目类型的运行和监控。
+
+## 架构设计
+
+### 核心组件
+
+```
+┌─────────────┐ WebSocket ┌──────────────────┐
+│ Frontend │ ◄────────────────────────► │ WebSocket Handler│
+└─────────────┘ └────────┬─────────┘
+ │
+ │
+ ▼
+┌──────────────────┐ ┌──────────────────────┐
+│Project Discovery │ │ Agent Runner │
+└──────────────────┘ └──────────┬───────────┘
+ │
+ ▼
+┌──────────────────┐ ┌──────────────────────┐
+│ Session Manager │ │ ms-agent Process │
+└──────────────────┘ └──────────────────────┘
+```
+
+### 文件职责
+
+| 文件 | 职责 |
+|------|--------|
+| `main.py` | FastAPI应用入口,路由配置 |
+| `api.py` | REST API端点定义 |
+| `websocket_handler.py` | WebSocket连接管理和消息处理 |
+| `agent_runner.py` | ms-agent进程管理和输出解析 |
+| `project_discovery.py` | 项目发现和类型识别 |
+| `session_manager.py` | 会话状态管理 |
+| `config_manager.py` | 配置文件管理 |
+| `shared.py` | 共享实例初始化 |
+
+## 请求处理流程
+
+### 1. 项目发现阶段
+
+**文件**: `project_discovery.py`
+
+系统启动时扫描项目目录,根据配置文件识别项目类型:
+
+```python
+def _analyze_project(self, name: str, path: str):
+ # 检查配置文件确定项目类型
+ workflow_file = os.path.join(path, 'workflow.yaml')
+ agent_file = os.path.join(path, 'agent.yaml')
+ run_file = os.path.join(path, 'run.py')
+
+ if os.path.exists(workflow_file):
+ project_type = 'workflow' # 工作流项目
+ config_file = workflow_file
+ elif os.path.exists(agent_file):
+ project_type = 'agent' # 代理项目
+ config_file = agent_file
+ elif os.path.exists(run_file):
+ project_type = 'script' # 脚本项目
+ config_file = run_file
+```
+
+**项目类型说明**:
+- **workflow**: 使用`workflow.yaml`配置,通过ms-agent CLI运行
+- **agent**: 使用`agent.yaml`配置,通过ms-agent CLI运行
+- **script**: 使用`run.py`脚本,直接Python执行
+
+### 2. WebSocket连接阶段
+
+**文件**: `websocket_handler.py`
+
+前端通过WebSocket连接到后端:
+
+```python
+@router.websocket("/session/{session_id}")
+async def websocket_session(websocket: WebSocket, session_id: str):
+ await connection_manager.connect(websocket, session_id)
+
+ try:
+ while True:
+ data = await websocket.receive_json()
+ await handle_session_message(session_id, data, websocket)
+ except WebSocketDisconnect:
+ connection_manager.disconnect(websocket, session_id)
+```
+
+**支持的消息类型**:
+- `start`: 启动代理
+- `stop`: 停止代理
+- `send_input`: 向运行中的代理发送输入
+- `get_status`: 获取当前状态
+
+### 3. 代理启动阶段
+
+**文件**: `websocket_handler.py`
+
+处理启动请求,创建AgentRunner实例:
+
+```python
+async def start_agent(session_id: str, data: Dict[str, Any], websocket: WebSocket):
+ # 1. 获取会话信息
+ session = session_manager.get_session(session_id)
+
+ # 2. 获取项目信息
+ project = project_discovery.get_project(session['project_id'])
+
+ # 3. 创建AgentRunner
+ runner = AgentRunner(
+ session_id=session_id,
+ project=project, # 包含项目类型和配置文件路径
+ config_manager=config_manager,
+ on_output=lambda msg: asyncio.create_task(on_agent_output(session_id, msg)),
+ on_log=lambda log: asyncio.create_task(on_agent_log(session_id, log)),
+ on_progress=lambda prog: asyncio.create_task(on_agent_progress(session_id, prog)),
+ on_complete=lambda result: asyncio.create_task(on_agent_complete(session_id, result)),
+ on_error=lambda err: asyncio.create_task(on_agent_error(session_id, err))
+ )
+
+ # 4. 启动代理
+ task = asyncio.create_task(runner.start(data.get('query', '')))
+```
+
+### 4. 命令构建阶段
+
+**文件**: `agent_runner.py`
+
+根据项目类型构建对应的ms-agent命令:
+
+```python
+def _build_command(self, query: str) -> list:
+ project_type = self.project.get('type')
+ config_file = self.project.get('config_file', '')
+
+ if project_type == 'workflow' or project_type == 'agent':
+ # workflow/agent类型:使用ms-agent CLI
+ cmd = [
+ 'ms-agent', 'run',
+ '--config', config_file, # workflow.yaml 或 agent.yaml
+ '--trust_remote_code', 'true'
+ ]
+
+ if query:
+ cmd.extend(['--query', query])
+
+ # 添加MCP服务器配置
+ mcp_file = self.config_manager.get_mcp_file_path()
+ if os.path.exists(mcp_file):
+ cmd.extend(['--mcp_server_file', mcp_file])
+
+ # 添加LLM配置
+ llm_config = self.config_manager.get_llm_config()
+ if llm_config.get('api_key'):
+ provider = llm_config.get('provider', 'modelscope')
+ if provider == 'modelscope':
+ cmd.extend(['--modelscope_api_key', llm_config['api_key']])
+ elif provider == 'openai':
+ cmd.extend(['--openai_api_key', llm_config['api_key']])
+
+ elif project_type == 'script':
+ # script类型:直接运行Python脚本
+ cmd = [python, self.project['config_file']] # run.py
+
+ return cmd
+```
+
+## 不同项目类型的命令对应
+
+| 项目类型 | 配置文件 | ms-agent命令 |
+|---------|----------|--------------|
+| **workflow** | `workflow.yaml` | `ms-agent run --config workflow.yaml --trust_remote_code true --query "xxx" --mcp_server_file xxx.json --modelscope_api_key xxx` |
+| **agent** | `agent.yaml` | `ms-agent run --config agent.yaml --trust_remote_code true --query "xxx" --mcp_server_file xxx.json --modelscope_api_key xxx` |
+| **script** | `run.py` | `python run.py` |
diff --git a/webui/backend/agent_runner.py b/webui/backend/agent_runner.py
new file mode 100644
index 000000000..afe50ea16
--- /dev/null
+++ b/webui/backend/agent_runner.py
@@ -0,0 +1,448 @@
+# Copyright (c) Alibaba, Inc. and its affiliates.
+"""
+Agent runner for MS-Agent Web UI
+Manages the execution of ms-agent through subprocess with log streaming.
+"""
+import asyncio
+import os
+import re
+import signal
+import subprocess
+import sys
+from datetime import datetime
+from typing import Any, Callable, Dict, Optional
+
+
+class AgentRunner:
+ """Runs ms-agent as a subprocess with output streaming"""
+
+ def __init__(self,
+ session_id: str,
+ project: Dict[str, Any],
+ config_manager,
+ on_output: Callable[[Dict[str, Any]], None] = None,
+ on_log: Callable[[Dict[str, Any]], None] = None,
+ on_progress: Callable[[Dict[str, Any]], None] = None,
+ on_complete: Callable[[Dict[str, Any]], None] = None,
+ on_error: Callable[[Dict[str, Any]], None] = None):
+ self.session_id = session_id
+ self.project = project
+ self.config_manager = config_manager
+ self.on_output = on_output
+ self.on_log = on_log
+ self.on_progress = on_progress
+ self.on_complete = on_complete
+ self.on_error = on_error
+
+ self.process: Optional[asyncio.subprocess.Process] = None
+ self.is_running = False
+ self._accumulated_output = ''
+ self._current_step = None
+ self._workflow_steps = []
+ self._stop_requested = False
+
+ async def start(self, query: str):
+ """Start the agent"""
+ try:
+ self._stop_requested = False
+ self.is_running = True
+
+ # Build command based on project type
+ cmd = self._build_command(query)
+ env = self._build_env()
+
+ print('[Runner] Starting agent with command:')
+ print(f"[Runner] {' '.join(cmd)}")
+ print(f"[Runner] Working directory: {self.project['path']}")
+
+ # Log the command
+ if self.on_log:
+ self.on_log({
+ 'level': 'info',
+ 'message': f'Starting agent: {" ".join(cmd[:5])}...',
+ 'timestamp': datetime.now().isoformat()
+ })
+
+ # Start subprocess
+ self.process = await asyncio.create_subprocess_exec(
+ *cmd,
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.STDOUT,
+ stdin=asyncio.subprocess.PIPE,
+ env=env,
+ cwd=self.project['path'],
+ start_new_session=True)
+
+ print(f'[Runner] Process started with PID: {self.process.pid}')
+
+ # Start output reader
+ await self._read_output()
+
+ except Exception as e:
+ print(f'[Runner] ERROR: {e}')
+ import traceback
+ traceback.print_exc()
+ if self.on_error:
+ self.on_error({'message': str(e), 'type': 'startup_error'})
+
+ async def stop(self):
+ """Stop the agent"""
+ self._stop_requested = True
+ self.is_running = False
+ if not self.process:
+ return
+
+ try:
+ # If already exited, nothing to do
+ if self.process.returncode is not None:
+ return
+
+ # Prefer terminating the whole process group to stop child processes too
+ try:
+ os.killpg(self.process.pid, signal.SIGTERM)
+ except Exception:
+ # Fallback to terminating only the parent
+ try:
+ self.process.terminate()
+ except Exception:
+ pass
+
+ try:
+ await asyncio.wait_for(self.process.wait(), timeout=5.0)
+ except asyncio.TimeoutError:
+ try:
+ os.killpg(self.process.pid, signal.SIGKILL)
+ except Exception:
+ try:
+ self.process.kill()
+ except Exception:
+ pass
+ except Exception:
+ pass
+
+ async def send_input(self, text: str):
+ """Send input to the agent"""
+ if self.process and self.process.stdin:
+ self.process.stdin.write((text + '\n').encode())
+ await self.process.stdin.drain()
+
+ def _build_command(self, query: str) -> list:
+ """Build the command to run the agent"""
+ project_type = self.project.get('type')
+ project_path = self.project['path']
+ config_file = self.project.get('config_file', '')
+
+ # Get python executable
+ python = sys.executable
+
+ # Get MCP config file path
+ mcp_file = self.config_manager.get_mcp_file_path()
+
+ if project_type == 'workflow' or project_type == 'agent':
+ # Use ms-agent CLI command (installed via entry point)
+ cmd = [
+ 'ms-agent', 'run', '--config', config_file,
+ '--trust_remote_code', 'true'
+ ]
+
+ if query:
+ cmd.extend(['--query', query])
+
+ if os.path.exists(mcp_file):
+ cmd.extend(['--mcp_server_file', mcp_file])
+
+ # Add LLM config
+ llm_config = self.config_manager.get_llm_config()
+ if llm_config.get('api_key'):
+ provider = llm_config.get('provider', 'modelscope')
+ if provider == 'modelscope':
+ cmd.extend(['--modelscope_api_key', llm_config['api_key']])
+ elif provider == 'openai':
+ cmd.extend(['--openai_api_key', llm_config['api_key']])
+
+ elif project_type == 'script':
+ # Run the script directly
+ cmd = [python, self.project['config_file']]
+ else:
+ cmd = [python, '-m', 'ms_agent', 'run', '--config', project_path]
+
+ return cmd
+
+ def _build_env(self) -> Dict[str, str]:
+ """Build environment variables"""
+ env = os.environ.copy()
+
+ # Add config env vars
+ env.update(self.config_manager.get_env_vars())
+
+ # Set PYTHONUNBUFFERED for real-time output
+ env['PYTHONUNBUFFERED'] = '1'
+
+ return env
+
+ async def _read_output(self):
+ """Read and process output from the subprocess"""
+ print('[Runner] Starting to read output...')
+ try:
+ while self.is_running and self.process and self.process.stdout:
+ line = await self.process.stdout.readline()
+ if not line:
+ print('[Runner] No more output, breaking...')
+ break
+
+ text = line.decode('utf-8', errors='replace').rstrip()
+ print(f'[Runner] Output: {text[:200]}'
+ if len(text) > 200 else f'[Runner] Output: {text}')
+ await self._process_line(text)
+
+ # Wait for process to complete
+ if self.process:
+ return_code = await self.process.wait()
+ print(f'[Runner] Process exited with code: {return_code}')
+
+ # If stop was requested, do not report as completion/error
+ if self._stop_requested:
+ if self.on_log:
+ self.on_log({
+ 'level': 'info',
+ 'message': 'Agent stopped by user',
+ 'timestamp': datetime.now().isoformat()
+ })
+ return
+
+ if return_code == 0:
+ if self.on_complete:
+ self.on_complete({
+ 'status':
+ 'success',
+ 'message':
+ 'Agent completed successfully'
+ })
+ else:
+ if self.on_error:
+ self.on_error({
+ 'message': f'Agent exited with code {return_code}',
+ 'type': 'exit_error',
+ 'code': return_code
+ })
+
+ except Exception as e:
+ print(f'[Runner] Read error: {e}')
+ import traceback
+ traceback.print_exc()
+ if not self._stop_requested and self.on_error:
+ self.on_error({'message': str(e), 'type': 'read_error'})
+ finally:
+ self.is_running = False
+ print('[Runner] Finished reading output')
+
+ async def _process_line(self, line: str):
+ """Process a line of output"""
+ # Log the line
+ if self.on_log:
+ log_level = self._detect_log_level(line)
+ await self.on_log({
+ 'level': log_level,
+ 'message': line,
+ 'timestamp': datetime.now().isoformat()
+ })
+
+ # Parse for special patterns
+ await self._detect_patterns(line)
+
+ def _detect_log_level(self, line: str) -> str:
+ """Detect log level from line"""
+ line_lower = line.lower()
+ if '[error' in line_lower or 'error:' in line_lower:
+ return 'error'
+ elif '[warn' in line_lower or 'warning:' in line_lower:
+ return 'warning'
+ elif '[debug' in line_lower:
+ return 'debug'
+ return 'info'
+
+ async def _detect_patterns(self, line: str):
+ """Detect special patterns in output"""
+ # Detect workflow step beginning: "[tag] Agent tag task beginning."
+ begin_match = re.search(
+ r'\[([^\]]+)\]\s*Agent\s+\S+\s+task\s+beginning', line)
+ if begin_match:
+ step_name = begin_match.group(1)
+
+ # Skip sub-steps (contain -r0-, -diversity-, etc.)
+ if '-r' in step_name and '-' in step_name.split('-r')[-1]:
+ print(f'[Runner] Skipping sub-step: {step_name}')
+ return
+
+ print(f'[Runner] Detected step beginning: {step_name}')
+
+ # If there's a previous step running, mark it as completed first
+ if self._current_step and self._current_step != step_name:
+ prev_step = self._current_step
+ print(f'[Runner] Auto-completing previous step: {prev_step}')
+ if self.on_output:
+ self.on_output({
+ 'type': 'step_complete',
+ 'content': prev_step,
+ 'role': 'assistant',
+ 'metadata': {
+ 'step': prev_step,
+ 'status': 'completed'
+ }
+ })
+
+ self._current_step = step_name
+ if step_name not in self._workflow_steps:
+ self._workflow_steps.append(step_name)
+
+ # Build step status - all previous steps completed, current running
+ step_status = {}
+ for i, s in enumerate(self._workflow_steps):
+ if s == step_name:
+ step_status[s] = 'running'
+ elif i < self._workflow_steps.index(step_name):
+ step_status[s] = 'completed'
+ else:
+ step_status[s] = 'pending'
+
+ if self.on_progress:
+ self.on_progress({
+ 'type': 'workflow',
+ 'current_step': step_name,
+ 'steps': self._workflow_steps.copy(),
+ 'step_status': step_status
+ })
+
+ # Send step start message
+ if self.on_output:
+ self.on_output({
+ 'type': 'step_start',
+ 'content': step_name,
+ 'role': 'assistant',
+ 'metadata': {
+ 'step': step_name,
+ 'status': 'running'
+ }
+ })
+ return
+
+ # Detect workflow step finished: "[tag] Agent tag task finished."
+ end_match = re.search(r'\[([^\]]+)\]\s*Agent\s+\S+\s+task\s+finished',
+ line)
+ if end_match:
+ step_name = end_match.group(1)
+
+ # Skip sub-steps
+ if '-r' in step_name and '-' in step_name.split('-r')[-1]:
+ return
+
+ print(f'[Runner] Detected step finished: {step_name}')
+
+ # Build step status dict - all steps up to current are completed
+ step_status = {}
+ for s in self._workflow_steps:
+ step_status[s] = 'completed' if self._workflow_steps.index(
+ s) <= self._workflow_steps.index(step_name) else 'pending'
+
+ if self.on_progress:
+ self.on_progress({
+ 'type': 'workflow',
+ 'current_step': step_name,
+ 'steps': self._workflow_steps.copy(),
+ 'step_status': step_status
+ })
+
+ # Send step complete message
+ if self.on_output:
+ self.on_output({
+ 'type': 'step_complete',
+ 'content': step_name,
+ 'role': 'assistant',
+ 'metadata': {
+ 'step': step_name,
+ 'status': 'completed'
+ }
+ })
+ return
+
+ # Detect assistant output: "[tag] [assistant]:"
+ if '[assistant]:' in line:
+ self._accumulated_output = ''
+ return
+
+ # Detect tool calls: "[tag] [tool_calling]:"
+ if '[tool_calling]:' in line:
+ if self.on_output:
+ self.on_output({
+ 'type': 'tool_call',
+ 'content': 'Calling tool...',
+ 'role': 'assistant'
+ })
+ return
+
+ # Detect file writing
+ file_match = re.search(r'writing file:?\s*["\']?([^\s"\']+)["\']?',
+ line.lower())
+ if not file_match:
+ file_match = re.search(
+ r'creating file:?\s*["\']?([^\s"\']+)["\']?', line.lower())
+ if file_match and self.on_progress:
+ filename = file_match.group(1)
+ self.on_progress({
+ 'type': 'file',
+ 'file': filename,
+ 'status': 'writing'
+ })
+ return
+
+ # Detect file written/created/saved - multiple patterns
+ file_keywords = [
+ 'file created', 'file written', 'file saved', 'saved to:',
+ 'wrote to', 'generated:', 'output:'
+ ]
+ if any(keyword in line.lower() for keyword in file_keywords):
+ # Try to extract filename with extension
+ file_match = re.search(
+ r'["\']?([^\s"\'\[\]]+\.[a-zA-Z0-9]+)["\']?', line)
+ if file_match and self.on_progress:
+ filename = file_match.group(1)
+ print(f'[Runner] Detected file output: {filename}')
+ # Send as output file
+ if self.on_output:
+ self.on_output({
+ 'type': 'file_output',
+ 'content': filename,
+ 'role': 'assistant',
+ 'metadata': {
+ 'filename': filename
+ }
+ })
+ self.on_progress({
+ 'type': 'file',
+ 'file': filename,
+ 'status': 'completed'
+ })
+ return
+
+ # Detect output file paths (e.g., "output/user_story.txt" standalone)
+ output_path_match = re.search(
+ r'(?:^|\s)((?:output|projects)/[^\s]+\.[a-zA-Z0-9]+)(?:\s|$)',
+ line)
+ if output_path_match and self.on_progress:
+ filename = output_path_match.group(1)
+ print(f'[Runner] Detected output path: {filename}')
+ if self.on_output:
+ self.on_output({
+ 'type': 'file_output',
+ 'content': filename,
+ 'role': 'assistant',
+ 'metadata': {
+ 'filename': filename
+ }
+ })
+ self.on_progress({
+ 'type': 'file',
+ 'file': filename,
+ 'status': 'completed'
+ })
+ return
diff --git a/webui/backend/api.py b/webui/backend/api.py
new file mode 100644
index 000000000..c33eda324
--- /dev/null
+++ b/webui/backend/api.py
@@ -0,0 +1,382 @@
+# Copyright (c) Alibaba, Inc. and its affiliates.
+"""
+API endpoints for the MS-Agent Web UI
+"""
+import os
+import uuid
+from typing import Any, Dict, List, Optional
+
+from fastapi import APIRouter, HTTPException
+from pydantic import BaseModel
+# Import shared instances
+from shared import config_manager, project_discovery, session_manager
+
+router = APIRouter()
+
+
+# Request/Response Models
+class ProjectInfo(BaseModel):
+ id: str
+ name: str
+ display_name: str
+ description: str
+ type: str # 'workflow' or 'agent'
+ path: str
+ has_readme: bool
+
+
+class SessionCreate(BaseModel):
+ project_id: str
+ query: Optional[str] = None
+
+
+class SessionInfo(BaseModel):
+ id: str
+ project_id: str
+ project_name: str
+ status: str
+ created_at: str
+
+
+class LLMConfig(BaseModel):
+ provider: str = 'openai'
+ model: str = 'qwen3-coder-plus'
+ api_key: Optional[str] = None
+ base_url: Optional[str] = None
+ temperature: float = 0.7
+ max_tokens: int = 4096
+
+
+class MCPServer(BaseModel):
+ name: str
+ type: str # 'stdio' or 'sse'
+ command: Optional[str] = None
+ args: Optional[List[str]] = None
+ url: Optional[str] = None
+ env: Optional[Dict[str, str]] = None
+
+
+class GlobalConfig(BaseModel):
+ llm: LLMConfig
+ mcp_servers: Dict[str, Any]
+ theme: str = 'dark'
+ output_dir: str = './output'
+
+
+# Project Endpoints
+@router.get('/projects', response_model=List[ProjectInfo])
+async def list_projects():
+ """List all available projects"""
+ return project_discovery.discover_projects()
+
+
+@router.get('/projects/{project_id}')
+async def get_project(project_id: str):
+ """Get detailed information about a specific project"""
+ project = project_discovery.get_project(project_id)
+ if not project:
+ raise HTTPException(status_code=404, detail='Project not found')
+ return project
+
+
+@router.get('/projects/{project_id}/readme')
+async def get_project_readme(project_id: str):
+ """Get the README content for a project"""
+ readme = project_discovery.get_project_readme(project_id)
+ if readme is None:
+ raise HTTPException(status_code=404, detail='README not found')
+ return {'content': readme}
+
+
+# Session Endpoints
+@router.post('/sessions', response_model=SessionInfo)
+async def create_session(session_data: SessionCreate):
+ """Create a new session for a project"""
+ project = project_discovery.get_project(session_data.project_id)
+ if not project:
+ raise HTTPException(status_code=404, detail='Project not found')
+
+ session = session_manager.create_session(
+ project_id=session_data.project_id, project_name=project['name'])
+ return session
+
+
+@router.get('/sessions', response_model=List[SessionInfo])
+async def list_sessions():
+ """List all active sessions"""
+ return session_manager.list_sessions()
+
+
+@router.get('/sessions/{session_id}')
+async def get_session(session_id: str):
+ """Get session details"""
+ session = session_manager.get_session(session_id)
+ if not session:
+ raise HTTPException(status_code=404, detail='Session not found')
+ return session
+
+
+@router.delete('/sessions/{session_id}')
+async def delete_session(session_id: str):
+ """Delete a session"""
+ success = session_manager.delete_session(session_id)
+ if not success:
+ raise HTTPException(status_code=404, detail='Session not found')
+ return {'status': 'deleted'}
+
+
+@router.get('/sessions/{session_id}/messages')
+async def get_session_messages(session_id: str):
+ """Get all messages for a session"""
+ messages = session_manager.get_messages(session_id)
+ if messages is None:
+ raise HTTPException(status_code=404, detail='Session not found')
+ return {'messages': messages}
+
+
+# Configuration Endpoints
+@router.get('/config')
+async def get_config():
+ """Get global configuration"""
+ return config_manager.get_config()
+
+
+@router.put('/config')
+async def update_config(config: GlobalConfig):
+ """Update global configuration"""
+ config_manager.update_config(config.model_dump())
+ return {'status': 'updated'}
+
+
+@router.get('/config/llm')
+async def get_llm_config():
+ """Get LLM configuration"""
+ return config_manager.get_llm_config()
+
+
+@router.put('/config/llm')
+async def update_llm_config(config: LLMConfig):
+ """Update LLM configuration"""
+ config_manager.update_llm_config(config.model_dump())
+ return {'status': 'updated'}
+
+
+@router.get('/config/mcp')
+async def get_mcp_config():
+ """Get MCP servers configuration"""
+ return config_manager.get_mcp_config()
+
+
+@router.put('/config/mcp')
+async def update_mcp_config(servers: Dict[str, Any]):
+ """Update MCP servers configuration"""
+ config_manager.update_mcp_config(servers)
+ return {'status': 'updated'}
+
+
+@router.post('/config/mcp/servers')
+async def add_mcp_server(server: MCPServer):
+ """Add a new MCP server"""
+ config_manager.add_mcp_server(server.name,
+ server.model_dump(exclude={'name'}))
+ return {'status': 'added'}
+
+
+@router.delete('/config/mcp/servers/{server_name}')
+async def remove_mcp_server(server_name: str):
+ """Remove an MCP server"""
+ success = config_manager.remove_mcp_server(server_name)
+ if not success:
+ raise HTTPException(status_code=404, detail='Server not found')
+ return {'status': 'removed'}
+
+
+# Available models endpoint
+@router.get('/models')
+async def list_available_models():
+ """List available LLM models"""
+ return {
+ 'models': [
+ {
+ 'provider': 'modelscope',
+ 'model': 'Qwen/Qwen3-235B-A22B-Instruct-2507',
+ 'display_name': 'Qwen3-235B (Recommended)'
+ },
+ {
+ 'provider': 'modelscope',
+ 'model': 'Qwen/Qwen2.5-72B-Instruct',
+ 'display_name': 'Qwen2.5-72B'
+ },
+ {
+ 'provider': 'modelscope',
+ 'model': 'Qwen/Qwen2.5-32B-Instruct',
+ 'display_name': 'Qwen2.5-32B'
+ },
+ {
+ 'provider': 'modelscope',
+ 'model': 'deepseek-ai/DeepSeek-V3',
+ 'display_name': 'DeepSeek-V3'
+ },
+ {
+ 'provider': 'openai',
+ 'model': 'gpt-4o',
+ 'display_name': 'GPT-4o'
+ },
+ {
+ 'provider': 'openai',
+ 'model': 'gpt-4o-mini',
+ 'display_name': 'GPT-4o Mini'
+ },
+ {
+ 'provider': 'anthropic',
+ 'model': 'claude-3-5-sonnet-20241022',
+ 'display_name': 'Claude 3.5 Sonnet'
+ },
+ ]
+ }
+
+
+# File content endpoint
+class FileReadRequest(BaseModel):
+ path: str
+ session_id: Optional[str] = None
+
+
+@router.get('/files/list')
+async def list_output_files():
+ """List all files in the output directory as a tree structure"""
+ base_dir = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+ output_dir = os.path.join(base_dir, 'ms-agent', 'output')
+
+ # Folders to exclude
+ exclude_dirs = {
+ 'node_modules', '__pycache__', '.git', '.venv', 'venv', 'dist', 'build'
+ }
+
+ def build_tree(dir_path: str) -> dict:
+ """Recursively build a tree structure"""
+ result = {'folders': {}, 'files': []}
+
+ if not os.path.exists(dir_path):
+ return result
+
+ try:
+ items = os.listdir(dir_path)
+ except PermissionError:
+ return result
+
+ for item in sorted(items):
+ # Skip hidden files/folders and excluded directories
+ if item.startswith('.') or item in exclude_dirs:
+ continue
+
+ full_path = os.path.join(dir_path, item)
+
+ if os.path.isdir(full_path):
+ # Recursively build subtree
+ subtree = build_tree(full_path)
+ # Only include folder if it has content
+ if subtree['folders'] or subtree['files']:
+ result['folders'][item] = subtree
+ else:
+ result['files'].append({
+ 'name': item,
+ 'path': full_path,
+ 'size': os.path.getsize(full_path),
+ 'modified': os.path.getmtime(full_path)
+ })
+
+ # Sort files by modification time (newest first)
+ result['files'].sort(key=lambda x: x['modified'], reverse=True)
+
+ return result
+
+ tree = build_tree(output_dir)
+ return {'tree': tree, 'output_dir': output_dir}
+
+
+@router.post('/files/read')
+async def read_file_content(request: FileReadRequest):
+ """Read content of a generated file"""
+ file_path = request.path
+
+ # Get base directories for security check
+ base_dir = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+ output_dir = os.path.join(base_dir, 'ms-agent', 'output')
+ projects_dir = os.path.join(base_dir, 'ms-agent', 'projects')
+
+ # Resolve the file path
+ if not os.path.isabs(file_path):
+ # Try output dir first
+ full_path = os.path.join(output_dir, file_path)
+ if not os.path.exists(full_path):
+ # Try projects dir
+ full_path = os.path.join(projects_dir, file_path)
+ else:
+ full_path = file_path
+
+ # Normalize path
+ full_path = os.path.normpath(full_path)
+
+ # Security check: ensure file is within allowed directories
+ allowed_dirs = [output_dir, projects_dir]
+ is_allowed = any(
+ full_path.startswith(os.path.normpath(d)) for d in allowed_dirs)
+
+ if not is_allowed:
+ raise HTTPException(
+ status_code=403,
+ detail='Access denied: file outside allowed directories')
+
+ if not os.path.exists(full_path):
+ raise HTTPException(
+ status_code=404, detail=f'File not found: {file_path}')
+
+ if not os.path.isfile(full_path):
+ raise HTTPException(status_code=400, detail='Path is not a file')
+
+ # Check file size (limit to 1MB)
+ file_size = os.path.getsize(full_path)
+ if file_size > 1024 * 1024:
+ raise HTTPException(status_code=400, detail='File too large (max 1MB)')
+
+ try:
+ with open(full_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Detect language from extension
+ ext = os.path.splitext(full_path)[1].lower()
+ lang_map = {
+ '.py': 'python',
+ '.js': 'javascript',
+ '.ts': 'typescript',
+ '.tsx': 'typescript',
+ '.jsx': 'javascript',
+ '.json': 'json',
+ '.yaml': 'yaml',
+ '.yml': 'yaml',
+ '.md': 'markdown',
+ '.html': 'html',
+ '.css': 'css',
+ '.txt': 'text',
+ '.sh': 'bash',
+ '.java': 'java',
+ '.go': 'go',
+ '.rs': 'rust',
+ }
+ language = lang_map.get(ext, 'text')
+
+ return {
+ 'content': content,
+ 'path': full_path,
+ 'filename': os.path.basename(full_path),
+ 'language': language,
+ 'size': file_size
+ }
+ except UnicodeDecodeError:
+ raise HTTPException(status_code=400, detail='File is not a text file')
+ except Exception as e:
+ raise HTTPException(
+ status_code=500, detail=f'Error reading file: {str(e)}')
diff --git a/webui/backend/config_manager.py b/webui/backend/config_manager.py
new file mode 100644
index 000000000..0af0f2d4c
--- /dev/null
+++ b/webui/backend/config_manager.py
@@ -0,0 +1,161 @@
+# Copyright (c) Alibaba, Inc. and its affiliates.
+"""
+Configuration management for MS-Agent Web UI
+Handles global settings, LLM configuration, and MCP server configuration.
+"""
+import os
+from threading import Lock
+from typing import Any, Dict, Optional
+
+import json
+
+
+class ConfigManager:
+ """Manages global configuration for the Web UI"""
+
+ DEFAULT_CONFIG = {
+ 'llm': {
+ 'provider': 'modelscope',
+ 'model': 'Qwen/Qwen3-235B-A22B-Instruct-2507',
+ 'api_key': '',
+ 'base_url': 'https://api-inference.modelscope.cn/v1/',
+ 'temperature': 0.7,
+ 'max_tokens': 4096
+ },
+ 'mcp_servers': {},
+ 'theme': 'dark',
+ 'output_dir': './output'
+ }
+
+ def __init__(self, config_dir: str):
+ self.config_dir = config_dir
+ self.config_file = os.path.join(config_dir, 'settings.json')
+ self.mcp_file = os.path.join(config_dir, 'mcp_servers.json')
+ self._lock = Lock()
+ self._config: Optional[Dict[str, Any]] = None
+ self._ensure_config_dir()
+
+ def _ensure_config_dir(self):
+ """Ensure config directory exists"""
+ os.makedirs(self.config_dir, exist_ok=True)
+
+ def _load_config(self) -> Dict[str, Any]:
+ """Load configuration from file"""
+ if self._config is not None:
+ return self._config
+
+ if os.path.exists(self.config_file):
+ try:
+ with open(self.config_file, 'r', encoding='utf-8') as f:
+ self._config = json.load(f)
+ except Exception:
+ self._config = self.DEFAULT_CONFIG.copy()
+ else:
+ self._config = self.DEFAULT_CONFIG.copy()
+
+ # Load MCP servers from separate file if exists
+ if os.path.exists(self.mcp_file):
+ try:
+ with open(self.mcp_file, 'r', encoding='utf-8') as f:
+ mcp_data = json.load(f)
+ if 'mcpServers' in mcp_data:
+ self._config['mcp_servers'] = mcp_data['mcpServers']
+ else:
+ self._config['mcp_servers'] = mcp_data
+ except Exception:
+ pass
+
+ return self._config
+
+ def _save_config(self):
+ """Save configuration to file"""
+ with self._lock:
+ # Save main config (without mcp_servers)
+ config_to_save = {
+ k: v
+ for k, v in self._config.items() if k != 'mcp_servers'
+ }
+ with open(self.config_file, 'w', encoding='utf-8') as f:
+ json.dump(config_to_save, f, indent=2)
+
+ # Save MCP servers to separate file (compatible with ms-agent format)
+ mcp_data = {'mcpServers': self._config.get('mcp_servers', {})}
+ with open(self.mcp_file, 'w', encoding='utf-8') as f:
+ json.dump(mcp_data, f, indent=2)
+
+ def get_config(self) -> Dict[str, Any]:
+ """Get the full configuration"""
+ return self._load_config().copy()
+
+ def update_config(self, config: Dict[str, Any]):
+ """Update the full configuration"""
+ self._load_config()
+ self._config.update(config)
+ self._save_config()
+
+ def get_llm_config(self) -> Dict[str, Any]:
+ """Get LLM configuration"""
+ config = self._load_config()
+ return config.get('llm', self.DEFAULT_CONFIG['llm'])
+
+ def update_llm_config(self, llm_config: Dict[str, Any]):
+ """Update LLM configuration"""
+ self._load_config()
+ self._config['llm'] = llm_config
+ self._save_config()
+
+ def get_mcp_config(self) -> Dict[str, Any]:
+ """Get MCP servers configuration"""
+ config = self._load_config()
+ return {'mcpServers': config.get('mcp_servers', {})}
+
+ def update_mcp_config(self, mcp_config: Dict[str, Any]):
+ """Update MCP servers configuration"""
+ self._load_config()
+ if 'mcpServers' in mcp_config:
+ self._config['mcp_servers'] = mcp_config['mcpServers']
+ else:
+ self._config['mcp_servers'] = mcp_config
+ self._save_config()
+
+ def add_mcp_server(self, name: str, server_config: Dict[str, Any]):
+ """Add a new MCP server"""
+ self._load_config()
+ if 'mcp_servers' not in self._config:
+ self._config['mcp_servers'] = {}
+ self._config['mcp_servers'][name] = server_config
+ self._save_config()
+
+ def remove_mcp_server(self, name: str) -> bool:
+ """Remove an MCP server"""
+ self._load_config()
+ if name in self._config.get('mcp_servers', {}):
+ del self._config['mcp_servers'][name]
+ self._save_config()
+ return True
+ return False
+
+ def get_mcp_file_path(self) -> str:
+ """Get the path to the MCP servers file"""
+ return self.mcp_file
+
+ def get_env_vars(self) -> Dict[str, str]:
+ """Get environment variables for running agents"""
+ config = self._load_config()
+ llm = config.get('llm', {})
+
+ env_vars = {}
+
+ if llm.get('api_key'):
+ provider = llm.get('provider', 'modelscope')
+ if provider == 'modelscope':
+ env_vars['MODELSCOPE_API_KEY'] = llm['api_key']
+ elif provider == 'openai':
+ env_vars['OPENAI_API_KEY'] = llm['api_key']
+ elif provider == 'anthropic':
+ env_vars['ANTHROPIC_API_KEY'] = llm['api_key']
+
+ if llm.get('base_url'):
+ env_vars['OPENAI_BASE_URL'] = llm['base_url']
+
+ return env_vars
diff --git a/webui/backend/main.py b/webui/backend/main.py
new file mode 100644
index 000000000..2afcebbe3
--- /dev/null
+++ b/webui/backend/main.py
@@ -0,0 +1,85 @@
+# Copyright (c) Alibaba, Inc. and its affiliates.
+"""
+MS-Agent Web UI Backend Server
+Provides REST API and WebSocket endpoints for the ms-agent framework.
+"""
+import os
+import sys
+
+import uvicorn
+from api import router as api_router
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import FileResponse
+from fastapi.staticfiles import StaticFiles
+from websocket_handler import router as ws_router
+
+# Add ms-agent to path
+MS_AGENT_PATH = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '..', '..', 'ms-agent'))
+if MS_AGENT_PATH not in sys.path:
+ sys.path.insert(0, MS_AGENT_PATH)
+
+app = FastAPI(
+ title='MS-Agent Web UI',
+ description='Web interface for the MS-Agent framework',
+ version='1.0.0')
+
+# CORS configuration
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=['*'],
+ allow_credentials=True,
+ allow_methods=['*'],
+ allow_headers=['*'],
+)
+
+# Include API and WebSocket routers
+app.include_router(api_router, prefix='/api')
+app.include_router(ws_router, prefix='/ws')
+
+# Serve static files in production
+STATIC_DIR = os.path.join(os.path.dirname(__file__), '..', 'frontend', 'dist')
+if os.path.exists(STATIC_DIR):
+ app.mount(
+ '/assets',
+ StaticFiles(directory=os.path.join(STATIC_DIR, 'assets')),
+ name='assets')
+
+ @app.get('/{full_path:path}')
+ async def serve_spa(full_path: str):
+ """Serve the SPA for all non-API routes"""
+ file_path = os.path.join(STATIC_DIR, full_path)
+ if os.path.exists(file_path) and os.path.isfile(file_path):
+ return FileResponse(file_path)
+ return FileResponse(os.path.join(STATIC_DIR, 'index.html'))
+
+
+@app.get('/health')
+async def health_check():
+ """Health check endpoint"""
+ return {'status': 'healthy', 'service': 'ms-agent-webui'}
+
+
+def main():
+ """Start the server"""
+ import argparse
+ parser = argparse.ArgumentParser(description='MS-Agent Web UI Server')
+ parser.add_argument('--host', default='0.0.0.0', help='Host to bind')
+ parser.add_argument('--port', type=int, default=7860, help='Port to bind')
+ parser.add_argument(
+ '--reload', action='store_true', help='Enable auto-reload')
+ args = parser.parse_args()
+
+ print(f"\n{'='*60}")
+ print(' MS-Agent Web UI Server')
+ print(f"{'='*60}")
+ print(f' Server running at: http://{args.host}:{args.port}')
+ print(f' API documentation: http://{args.host}:{args.port}/docs')
+ print(f"{'='*60}\n")
+
+ uvicorn.run('main:app', host=args.host, port=args.port, reload=args.reload)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/webui/backend/project_discovery.py b/webui/backend/project_discovery.py
new file mode 100644
index 000000000..6a74da127
--- /dev/null
+++ b/webui/backend/project_discovery.py
@@ -0,0 +1,155 @@
+# Copyright (c) Alibaba, Inc. and its affiliates.
+"""
+Project discovery module for MS-Agent Web UI
+Discovers and manages available projects from the ms-agent/projects directory.
+"""
+import os
+import re
+from typing import Any, Dict, List, Optional
+
+
+class ProjectDiscovery:
+ """Discovers and manages projects from the ms-agent projects directory"""
+
+ def __init__(self, projects_dir: str):
+ self.projects_dir = projects_dir
+ self._projects_cache: Optional[List[Dict[str, Any]]] = None
+
+ def discover_projects(self,
+ force_refresh: bool = False) -> List[Dict[str, Any]]:
+ """Discover all available projects"""
+ if self._projects_cache is not None and not force_refresh:
+ return self._projects_cache
+
+ projects = []
+
+ if not os.path.exists(self.projects_dir):
+ return projects
+
+ for item in os.listdir(self.projects_dir):
+ item_path = os.path.join(self.projects_dir, item)
+ if os.path.isdir(item_path) and not item.startswith('.'):
+ project_info = self._analyze_project(item, item_path)
+ if project_info:
+ projects.append(project_info)
+
+ # Sort by display name
+ projects.sort(key=lambda x: x['display_name'])
+ self._projects_cache = projects
+ return projects
+
+ def _analyze_project(self, name: str,
+ path: str) -> Optional[Dict[str, Any]]:
+ """Analyze a project directory and extract its information"""
+ # Check for workflow.yaml or agent.yaml
+ workflow_file = os.path.join(path, 'workflow.yaml')
+ agent_file = os.path.join(path, 'agent.yaml')
+ run_file = os.path.join(path, 'run.py')
+ readme_file = os.path.join(path, 'README.md')
+
+ # Determine project type
+ if os.path.exists(workflow_file):
+ project_type = 'workflow'
+ config_file = workflow_file
+ elif os.path.exists(agent_file):
+ project_type = 'agent'
+ config_file = agent_file
+ elif os.path.exists(run_file):
+ project_type = 'script'
+ config_file = run_file
+ else:
+ # Skip directories without valid config
+ return None
+
+ # Generate display name from directory name
+ display_name = self._format_display_name(name)
+
+ # Extract description from README if available
+ description = self._extract_description(readme_file) if os.path.exists(
+ readme_file) else ''
+
+ return {
+ 'id': name,
+ 'name': name,
+ 'display_name': display_name,
+ 'description': description,
+ 'type': project_type,
+ 'path': path,
+ 'has_readme': os.path.exists(readme_file),
+ 'config_file': config_file
+ }
+
+ def _format_display_name(self, name: str) -> str:
+ """Convert directory name to display name"""
+ # Replace underscores with spaces and title case
+ display = name.replace('_', ' ').replace('-', ' ')
+ # Handle camelCase
+ display = re.sub(r'([a-z])([A-Z])', r'\1 \2', display)
+ return display.title()
+
+ def _extract_description(self, readme_path: str) -> str:
+ """Extract first paragraph from README as description"""
+ try:
+ with open(readme_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Skip title and find first paragraph
+ lines = content.split('\n')
+ description_lines = []
+ in_description = False
+
+ for line in lines:
+ stripped = line.strip()
+ # Skip headers and empty lines at the beginning
+ if not in_description:
+ if stripped and not stripped.startswith(
+ '#') and not stripped.startswith('['):
+ in_description = True
+ description_lines.append(stripped)
+ else:
+ if stripped and not stripped.startswith('#'):
+ description_lines.append(stripped)
+ elif not stripped and description_lines:
+ break
+
+ description = ' '.join(description_lines)
+ # Truncate if too long
+ if len(description) > 300:
+ description = description[:297] + '...'
+ return description
+ except Exception:
+ return ''
+
+ def get_project(self, project_id: str) -> Optional[Dict[str, Any]]:
+ """Get a specific project by ID"""
+ projects = self.discover_projects()
+ for project in projects:
+ if project['id'] == project_id:
+ return project
+ return None
+
+ def get_project_readme(self, project_id: str) -> Optional[str]:
+ """Get the README content for a project"""
+ project = self.get_project(project_id)
+ if not project or not project['has_readme']:
+ return None
+
+ readme_path = os.path.join(project['path'], 'README.md')
+ try:
+ with open(readme_path, 'r', encoding='utf-8') as f:
+ return f.read()
+ except Exception:
+ return None
+
+ def get_project_config(self, project_id: str) -> Optional[Dict[str, Any]]:
+ """Get the configuration for a project"""
+ project = self.get_project(project_id)
+ if not project:
+ return None
+
+ try:
+ import yaml
+ with open(project['config_file'], 'r', encoding='utf-8') as f:
+ return yaml.safe_load(f)
+ except Exception:
+ return None
diff --git a/webui/backend/session_manager.py b/webui/backend/session_manager.py
new file mode 100644
index 000000000..d588fbaf3
--- /dev/null
+++ b/webui/backend/session_manager.py
@@ -0,0 +1,136 @@
+# Copyright (c) Alibaba, Inc. and its affiliates.
+"""
+Session management for MS-Agent Web UI
+Handles session lifecycle and message history.
+"""
+import uuid
+from datetime import datetime
+from threading import Lock
+from typing import Any, Dict, List, Optional
+
+
+class SessionManager:
+ """Manages user sessions and their message history"""
+
+ def __init__(self):
+ self._sessions: Dict[str, Dict[str, Any]] = {}
+ self._messages: Dict[str, List[Dict[str, Any]]] = {}
+ self._lock = Lock()
+
+ def create_session(self, project_id: str,
+ project_name: str) -> Dict[str, Any]:
+ """Create a new session"""
+ session_id = str(uuid.uuid4())
+ session = {
+ 'id': session_id,
+ 'project_id': project_id,
+ 'project_name': project_name,
+ 'status': 'idle', # idle, running, completed, error
+ 'created_at': datetime.now().isoformat(),
+ 'workflow_progress': None,
+ 'file_progress': None,
+ 'current_step': None
+ }
+
+ with self._lock:
+ self._sessions[session_id] = session
+ self._messages[session_id] = []
+
+ return session
+
+ def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
+ """Get session by ID"""
+ return self._sessions.get(session_id)
+
+ def update_session(self, session_id: str, updates: Dict[str, Any]) -> bool:
+ """Update session data"""
+ if session_id not in self._sessions:
+ return False
+
+ with self._lock:
+ self._sessions[session_id].update(updates)
+ return True
+
+ def delete_session(self, session_id: str) -> bool:
+ """Delete a session"""
+ with self._lock:
+ if session_id in self._sessions:
+ del self._sessions[session_id]
+ if session_id in self._messages:
+ del self._messages[session_id]
+ return True
+ return False
+
+ def list_sessions(self) -> List[Dict[str, Any]]:
+ """List all sessions"""
+ return list(self._sessions.values())
+
+ def add_message(self,
+ session_id: str,
+ role: str,
+ content: str,
+ message_type: str = 'text',
+ metadata: Dict[str, Any] = None) -> bool:
+ """Add a message to a session"""
+ if session_id not in self._sessions:
+ return False
+
+ message = {
+ 'id': str(uuid.uuid4()),
+ 'role': role, # user, assistant, system, tool
+ 'content': content,
+ 'type': message_type, # text, tool_call, tool_result, error, log
+ 'timestamp': datetime.now().isoformat(),
+ 'metadata': metadata or {}
+ }
+
+ with self._lock:
+ if session_id not in self._messages:
+ self._messages[session_id] = []
+ self._messages[session_id].append(message)
+
+ return True
+
+ def get_messages(self, session_id: str) -> Optional[List[Dict[str, Any]]]:
+ """Get all messages for a session"""
+ if session_id not in self._sessions:
+ return None
+ return self._messages.get(session_id, [])
+
+ def update_last_message(self, session_id: str, content: str) -> bool:
+ """Update the content of the last message (for streaming)"""
+ if session_id not in self._messages or not self._messages[session_id]:
+ return False
+
+ with self._lock:
+ self._messages[session_id][-1]['content'] = content
+ return True
+
+ def set_workflow_progress(self, session_id: str,
+ progress: Dict[str, Any]) -> bool:
+ """Set workflow progress for a session"""
+ if session_id not in self._sessions:
+ return False
+
+ with self._lock:
+ self._sessions[session_id]['workflow_progress'] = progress
+ return True
+
+ def set_file_progress(self, session_id: str, progress: Dict[str,
+ Any]) -> bool:
+ """Set file writing progress for a session"""
+ if session_id not in self._sessions:
+ return False
+
+ with self._lock:
+ self._sessions[session_id]['file_progress'] = progress
+ return True
+
+ def set_current_step(self, session_id: str, step: str) -> bool:
+ """Set the current workflow step"""
+ if session_id not in self._sessions:
+ return False
+
+ with self._lock:
+ self._sessions[session_id]['current_step'] = step
+ return True
diff --git a/webui/backend/shared.py b/webui/backend/shared.py
new file mode 100644
index 000000000..08dbd5775
--- /dev/null
+++ b/webui/backend/shared.py
@@ -0,0 +1,25 @@
+# Copyright (c) Alibaba, Inc. and its affiliates.
+"""
+Shared instances for backend modules.
+Ensures api.py and websocket_handler.py use the same manager instances.
+"""
+import os
+
+from config_manager import ConfigManager
+from project_discovery import ProjectDiscovery
+from session_manager import SessionManager
+
+# Initialize paths
+BASE_DIR = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+PROJECTS_DIR = os.path.join(BASE_DIR, 'projects')
+CONFIG_DIR = os.path.join(BASE_DIR, 'webui', 'config')
+
+# Shared instances
+project_discovery = ProjectDiscovery(PROJECTS_DIR)
+config_manager = ConfigManager(CONFIG_DIR)
+session_manager = SessionManager()
+
+print('[Shared] Initialized managers')
+print(f'[Shared] Projects dir: {PROJECTS_DIR}')
+print(f'[Shared] Config dir: {CONFIG_DIR}')
diff --git a/webui/backend/websocket_handler.py b/webui/backend/websocket_handler.py
new file mode 100644
index 000000000..649969171
--- /dev/null
+++ b/webui/backend/websocket_handler.py
@@ -0,0 +1,317 @@
+# Copyright (c) Alibaba, Inc. and its affiliates.
+"""
+WebSocket handler for real-time communication
+Handles agent execution, log streaming, and progress updates.
+"""
+import asyncio
+import os
+from typing import Any, Dict, Set
+
+import json
+from agent_runner import AgentRunner
+from fastapi import APIRouter, WebSocket, WebSocketDisconnect
+# Import shared instances
+from shared import config_manager, project_discovery, session_manager
+
+router = APIRouter()
+
+
+class ConnectionManager:
+ """Manages WebSocket connections"""
+
+ def __init__(self):
+ self.active_connections: Dict[str, Set[WebSocket]] = {}
+ self.log_connections: Set[WebSocket] = set()
+
+ async def connect(self, websocket: WebSocket, session_id: str):
+ """Connect a client to a session"""
+ await websocket.accept()
+ if session_id not in self.active_connections:
+ self.active_connections[session_id] = set()
+ self.active_connections[session_id].add(websocket)
+
+ async def connect_logs(self, websocket: WebSocket):
+ """Connect a client to log stream"""
+ await websocket.accept()
+ self.log_connections.add(websocket)
+
+ def disconnect(self, websocket: WebSocket, session_id: str = None):
+ """Disconnect a client"""
+ if session_id and session_id in self.active_connections:
+ self.active_connections[session_id].discard(websocket)
+ if not self.active_connections[session_id]:
+ del self.active_connections[session_id]
+ self.log_connections.discard(websocket)
+
+ async def send_to_session(self, session_id: str, message: Dict[str, Any]):
+ """Send message to all clients in a session"""
+ if session_id in self.active_connections:
+ disconnected = set()
+ for connection in self.active_connections[session_id]:
+ try:
+ await connection.send_json(message)
+ except Exception:
+ disconnected.add(connection)
+ for conn in disconnected:
+ self.active_connections[session_id].discard(conn)
+
+ async def broadcast_log(self, log_entry: Dict[str, Any]):
+ """Broadcast log entry to all log connections"""
+ disconnected = set()
+ for connection in self.log_connections:
+ try:
+ await connection.send_json(log_entry)
+ except Exception:
+ disconnected.add(connection)
+ for conn in disconnected:
+ self.log_connections.discard(conn)
+
+
+connection_manager = ConnectionManager()
+agent_runners: Dict[str, AgentRunner] = {}
+agent_tasks: Dict[str, asyncio.Task] = {}
+
+
+@router.websocket('/session/{session_id}')
+async def websocket_session(websocket: WebSocket, session_id: str):
+ """WebSocket endpoint for session communication"""
+ print(f'[WS] Client connecting to session: {session_id}')
+ await connection_manager.connect(websocket, session_id)
+ print(f'[WS] Client connected to session: {session_id}')
+
+ try:
+ while True:
+ data = await websocket.receive_json()
+ print(f'[WS] Received message: {data}')
+ await handle_session_message(session_id, data, websocket)
+ except WebSocketDisconnect:
+ print(f'[WS] Client disconnected from session: {session_id}')
+ connection_manager.disconnect(websocket, session_id)
+ # Stop agent if running
+ if session_id in agent_runners:
+ await agent_runners[session_id].stop()
+ del agent_runners[session_id]
+ if session_id in agent_tasks:
+ agent_tasks[session_id].cancel()
+ del agent_tasks[session_id]
+
+
+@router.websocket('/logs')
+async def websocket_logs(websocket: WebSocket):
+ """WebSocket endpoint for log streaming"""
+ await connection_manager.connect_logs(websocket)
+
+ try:
+ while True:
+ # Keep connection alive
+ await websocket.receive_text()
+ except WebSocketDisconnect:
+ connection_manager.disconnect(websocket)
+
+
+async def handle_session_message(session_id: str, data: Dict[str, Any],
+ websocket: WebSocket):
+ """Handle incoming WebSocket messages"""
+ action = data.get('action')
+
+ if action == 'start':
+ await start_agent(session_id, data, websocket)
+ elif action == 'stop':
+ await stop_agent(session_id)
+ elif action == 'send_input':
+ await send_input(session_id, data)
+ elif action == 'get_status':
+ await send_status(session_id, websocket)
+
+
+async def start_agent(session_id: str, data: Dict[str, Any],
+ websocket: WebSocket):
+ """Start an agent for a session"""
+ print(f'[Agent] Starting agent for session: {session_id}')
+
+ session = session_manager.get_session(session_id)
+ if not session:
+ print(f'[Agent] ERROR: Session not found: {session_id}')
+ await websocket.send_json({
+ 'type': 'error',
+ 'message': 'Session not found'
+ })
+ return
+
+ project = project_discovery.get_project(session['project_id'])
+ if not project:
+ print(f"[Agent] ERROR: Project not found: {session['project_id']}")
+ await websocket.send_json({
+ 'type': 'error',
+ 'message': 'Project not found'
+ })
+ return
+
+ print(
+ f"[Agent] Project: {project['id']}, type: {project['type']}, config: {project['config_file']}"
+ )
+
+ query = data.get('query', '')
+ print(f'[Agent] Query: {query[:100]}...'
+ if len(query) > 100 else f'[Agent] Query: {query}')
+
+ # Add user message to session (but don't broadcast - frontend already has it)
+ session_manager.add_message(session_id, 'user', query, 'text')
+
+ # Create agent runner
+ runner = AgentRunner(
+ session_id=session_id,
+ project=project,
+ config_manager=config_manager,
+ on_output=lambda msg: asyncio.create_task(
+ on_agent_output(session_id, msg)),
+ on_log=lambda log: asyncio.create_task(on_agent_log(session_id, log)),
+ on_progress=lambda prog: asyncio.create_task(
+ on_agent_progress(session_id, prog)),
+ on_complete=lambda result: asyncio.create_task(
+ on_agent_complete(session_id, result)),
+ on_error=lambda err: asyncio.create_task(
+ on_agent_error(session_id, err)))
+
+ agent_runners[session_id] = runner
+ session_manager.update_session(session_id, {'status': 'running'})
+
+ # Notify session started
+ await connection_manager.send_to_session(session_id, {
+ 'type': 'status',
+ 'status': 'running'
+ })
+
+ # Start agent in background so the WS loop can still receive stop/input messages
+ task = asyncio.create_task(runner.start(query))
+ agent_tasks[session_id] = task
+
+ def _cleanup(_task: asyncio.Task):
+ agent_tasks.pop(session_id, None)
+
+ task.add_done_callback(_cleanup)
+
+
+async def stop_agent(session_id: str):
+ """Stop a running agent"""
+ if session_id in agent_runners:
+ await agent_runners[session_id].stop()
+ del agent_runners[session_id]
+ if session_id in agent_tasks:
+ agent_tasks[session_id].cancel()
+ del agent_tasks[session_id]
+
+ session_manager.update_session(session_id, {'status': 'stopped'})
+ await connection_manager.send_to_session(session_id, {
+ 'type': 'status',
+ 'status': 'stopped'
+ })
+
+
+async def send_input(session_id: str, data: Dict[str, Any]):
+ """Send input to a running agent"""
+ if session_id in agent_runners:
+ await agent_runners[session_id].send_input(data.get('input', ''))
+
+
+async def send_status(session_id: str, websocket: WebSocket):
+ """Send current status to a client"""
+ session = session_manager.get_session(session_id)
+ if session:
+ await websocket.send_json({
+ 'type':
+ 'status',
+ 'session':
+ session,
+ 'messages':
+ session_manager.get_messages(session_id)
+ })
+
+
+async def on_agent_output(session_id: str, message: Dict[str, Any]):
+ """Handle agent output"""
+ msg_type = message.get('type', 'text')
+ content = message.get('content', '')
+ role = message.get('role', 'assistant')
+
+ if msg_type == 'stream':
+ # Streaming update
+ await connection_manager.send_to_session(
+ session_id, {
+ 'type': 'stream',
+ 'content': content,
+ 'done': message.get('done', False)
+ })
+ if message.get('done'):
+ session_manager.add_message(session_id, role, content, 'text')
+ else:
+ session_manager.add_message(session_id, role, content, msg_type,
+ message.get('metadata'))
+ await connection_manager.send_to_session(
+ session_id, {
+ 'type': 'message',
+ 'role': role,
+ 'content': content,
+ 'message_type': msg_type,
+ 'metadata': message.get('metadata')
+ })
+
+
+async def on_agent_log(session_id: str, log: Dict[str, Any]):
+ """Handle agent log"""
+ await connection_manager.send_to_session(session_id, {
+ 'type': 'log',
+ **log
+ })
+ await connection_manager.broadcast_log({'session_id': session_id, **log})
+
+
+async def on_agent_progress(session_id: str, progress: Dict[str, Any]):
+ """Handle progress update"""
+ progress_type = progress.get('type', 'workflow')
+
+ if progress_type == 'workflow':
+ session_manager.set_workflow_progress(session_id, progress)
+ session_manager.set_current_step(session_id,
+ progress.get('current_step'))
+ elif progress_type == 'file':
+ session_manager.set_file_progress(session_id, progress)
+
+ await connection_manager.send_to_session(session_id, {
+ 'type': 'progress',
+ **progress
+ })
+
+
+async def on_agent_complete(session_id: str, result: Dict[str, Any]):
+ """Handle agent completion"""
+ session_manager.update_session(session_id, {'status': 'completed'})
+
+ if session_id in agent_runners:
+ del agent_runners[session_id]
+ if session_id in agent_tasks:
+ agent_tasks[session_id].cancel()
+ del agent_tasks[session_id]
+
+ await connection_manager.send_to_session(session_id, {
+ 'type': 'complete',
+ 'result': result
+ })
+
+
+async def on_agent_error(session_id: str, error: Dict[str, Any]):
+ """Handle agent error"""
+ session_manager.update_session(session_id, {'status': 'error'})
+ session_manager.add_message(session_id, 'system',
+ error.get('message', 'Unknown error'), 'error')
+
+ if session_id in agent_runners:
+ del agent_runners[session_id]
+ if session_id in agent_tasks:
+ agent_tasks[session_id].cancel()
+ del agent_tasks[session_id]
+
+ await connection_manager.send_to_session(session_id, {
+ 'type': 'error',
+ **error
+ })
diff --git a/webui/config/mcp_servers.json b/webui/config/mcp_servers.json
new file mode 100644
index 000000000..da39e4ffa
--- /dev/null
+++ b/webui/config/mcp_servers.json
@@ -0,0 +1,3 @@
+{
+ "mcpServers": {}
+}
diff --git a/webui/config/settings.json b/webui/config/settings.json
new file mode 100644
index 000000000..26d2bd591
--- /dev/null
+++ b/webui/config/settings.json
@@ -0,0 +1,12 @@
+{
+ "llm": {
+ "provider": "openai",
+ "model": "qwen3-coder-plus",
+ "api_key": "",
+ "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
+ "temperature": 0.7,
+ "max_tokens": 32768
+ },
+ "theme": "dark",
+ "output_dir": "./output"
+}
diff --git a/webui/frontend/index.html b/webui/frontend/index.html
new file mode 100644
index 000000000..fed31a34a
--- /dev/null
+++ b/webui/frontend/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ MS-Agent | Intelligent Agent Platform
+
+
+
+
+
+
+
+
+
+
diff --git a/webui/frontend/package-lock.json b/webui/frontend/package-lock.json
new file mode 100644
index 000000000..14c664a38
--- /dev/null
+++ b/webui/frontend/package-lock.json
@@ -0,0 +1,3969 @@
+{
+ "name": "ms-agent-webui",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ms-agent-webui",
+ "version": "1.0.0",
+ "dependencies": {
+ "@emotion/react": "^11.11.3",
+ "@emotion/styled": "^11.11.0",
+ "@mui/icons-material": "^5.15.6",
+ "@mui/material": "^5.15.6",
+ "framer-motion": "^11.0.3",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-markdown": "^9.0.1",
+ "react-syntax-highlighter": "^15.5.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "@types/react-syntax-highlighter": "^15.5.11",
+ "@vitejs/plugin-react": "^4.2.1",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.12"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@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/core/node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@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.27.2",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@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.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@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.4",
+ "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "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.npmmirror.com/@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.npmmirror.com/@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/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.13.5",
+ "resolved": "https://registry.npmmirror.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
+ "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.3.3",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmmirror.com/@emotion/cache/-/cache-11.14.0.tgz",
+ "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
+ "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmmirror.com/@emotion/react/-/react-11.14.0.tgz",
+ "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/cache": "^11.14.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmmirror.com/@emotion/serialize/-/serialize-1.3.3.tgz",
+ "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.2",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/@emotion/sheet/-/sheet-1.4.0.tgz",
+ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/styled": {
+ "version": "11.14.1",
+ "resolved": "https://registry.npmmirror.com/@emotion/styled/-/styled-11.14.1.tgz",
+ "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/is-prop-valid": "^1.3.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.0.0-rc.0",
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
+ "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmmirror.com/@emotion/utils/-/utils-1.4.2.tgz",
+ "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmmirror.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "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.npmmirror.com/@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.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mui/core-downloads-tracker": {
+ "version": "5.18.0",
+ "resolved": "https://registry.npmmirror.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.18.0.tgz",
+ "integrity": "sha512-jbhwoQ1AY200PSSOrNXmrFCaSDSJWP7qk6urkTmIirvRXDROkqe+QwcLlUiw/PrREwsIF/vm3/dAXvjlMHF0RA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ }
+ },
+ "node_modules/@mui/icons-material": {
+ "version": "5.18.0",
+ "resolved": "https://registry.npmmirror.com/@mui/icons-material/-/icons-material-5.18.0.tgz",
+ "integrity": "sha512-1s0vEZj5XFXDMmz3Arl/R7IncFqJ+WQ95LDp1roHWGDE2oCO3IS4/hmiOv1/8SD9r6B7tv9GLiqVZYHo+6PkTg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/material": "^5.0.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material": {
+ "version": "5.18.0",
+ "resolved": "https://registry.npmmirror.com/@mui/material/-/material-5.18.0.tgz",
+ "integrity": "sha512-bbH/HaJZpFtXGvWg3TsBWG4eyt3gah3E7nCNU8GLyRjVoWcA91Vm/T+sjHfUcwgJSw9iLtucfHBoq+qW/T30aA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@mui/core-downloads-tracker": "^5.18.0",
+ "@mui/system": "^5.18.0",
+ "@mui/types": "~7.2.15",
+ "@mui/utils": "^5.17.1",
+ "@popperjs/core": "^2.11.8",
+ "@types/react-transition-group": "^4.4.10",
+ "clsx": "^2.1.0",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.0.0",
+ "react-transition-group": "^4.4.5"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/private-theming": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmmirror.com/@mui/private-theming/-/private-theming-5.17.1.tgz",
+ "integrity": "sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@mui/utils": "^5.17.1",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/styled-engine": {
+ "version": "5.18.0",
+ "resolved": "https://registry.npmmirror.com/@mui/styled-engine/-/styled-engine-5.18.0.tgz",
+ "integrity": "sha512-BN/vKV/O6uaQh2z5rXV+MBlVrEkwoS/TK75rFQ2mjxA7+NBo8qtTAOA4UaM0XeJfn7kh2wZ+xQw2HAx0u+TiBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@emotion/cache": "^11.13.5",
+ "@emotion/serialize": "^1.3.3",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.4.1",
+ "@emotion/styled": "^11.3.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/system": {
+ "version": "5.18.0",
+ "resolved": "https://registry.npmmirror.com/@mui/system/-/system-5.18.0.tgz",
+ "integrity": "sha512-ojZGVcRWqWhu557cdO3pWHloIGJdzVtxs3rk0F9L+x55LsUjcMUVkEhiF7E4TMxZoF9MmIHGGs0ZX3FDLAf0Xw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@mui/private-theming": "^5.17.1",
+ "@mui/styled-engine": "^5.18.0",
+ "@mui/types": "~7.2.15",
+ "@mui/utils": "^5.17.1",
+ "clsx": "^2.1.0",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/types": {
+ "version": "7.2.24",
+ "resolved": "https://registry.npmmirror.com/@mui/types/-/types-7.2.24.tgz",
+ "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/utils": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmmirror.com/@mui/utils/-/utils-5.17.1.tgz",
+ "integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@mui/types": "~7.2.15",
+ "@types/prop-types": "^15.7.12",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
+ "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz",
+ "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
+ "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz",
+ "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz",
+ "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz",
+ "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz",
+ "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz",
+ "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz",
+ "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz",
+ "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz",
+ "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz",
+ "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz",
+ "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz",
+ "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz",
+ "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz",
+ "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz",
+ "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz",
+ "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz",
+ "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz",
+ "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz",
+ "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz",
+ "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree-jsx": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmmirror.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.27",
+ "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.27.tgz",
+ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/react-syntax-highlighter": {
+ "version": "15.5.13",
+ "resolved": "https://registry.npmmirror.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
+ "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmmirror.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.13",
+ "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.13.tgz",
+ "integrity": "sha512-WhtvB2NG2wjr04+h77sg3klAIwrgOqnjS49GGudnUPGFFgg7G17y7Qecqp+2Dr5kUDxNRBca0SK7cG8JwzkWDQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmmirror.com/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",
+ "peer": true,
+ "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/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001763",
+ "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001763.tgz",
+ "integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==",
+ "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/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "license": "MIT"
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmmirror.com/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/decode-named-character-reference": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
+ "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.267",
+ "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
+ "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-util-is-identifier-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/fault": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/fault/-/fault-1.0.4.tgz",
+ "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
+ "license": "MIT",
+ "dependencies": {
+ "format": "^0.2.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "license": "MIT"
+ },
+ "node_modules/format": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmmirror.com/format/-/format-0.2.2.tgz",
+ "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "11.18.2",
+ "resolved": "https://registry.npmmirror.com/framer-motion/-/framer-motion-11.18.2.tgz",
+ "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^11.18.1",
+ "motion-utils": "^11.18.1",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmmirror.com/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/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmmirror.com/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/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hast-util-parse-selector": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
+ "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-jsx-runtime": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-js": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hastscript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-6.0.0.tgz",
+ "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^1.0.0",
+ "hast-util-parse-selector": "^2.0.0",
+ "property-information": "^5.0.0",
+ "space-separated-tokens": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hastscript/node_modules/@types/hast": {
+ "version": "2.3.10",
+ "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-2.3.10.tgz",
+ "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2"
+ }
+ },
+ "node_modules/hastscript/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/hastscript/node_modules/comma-separated-tokens": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
+ "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/hastscript/node_modules/property-information": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmmirror.com/property-information/-/property-information-5.6.0.tgz",
+ "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/hastscript/node_modules/space-separated-tokens": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
+ "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/highlight.js": {
+ "version": "10.7.3",
+ "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz",
+ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/highlightjs-vue": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
+ "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
+ "license": "CC0-1.0"
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/html-url-attributes": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
+ "license": "MIT"
+ },
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmmirror.com/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/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lowlight": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz",
+ "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
+ "license": "MIT",
+ "dependencies": {
+ "fault": "^1.0.0",
+ "highlight.js": "~10.7.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmmirror.com/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/mdast-util-from-markdown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-expression": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdxjs-esm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/motion-dom": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmmirror.com/motion-dom/-/motion-dom-11.18.1.tgz",
+ "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^11.18.1"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmmirror.com/motion-utils/-/motion-utils-11.18.1.tgz",
+ "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-entities": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz",
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-entities/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmmirror.com/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/prismjs": {
+ "version": "1.30.0",
+ "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz",
+ "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/property-information": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.1.0.tgz",
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmmirror.com/react-is/-/react-is-19.2.3.tgz",
+ "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==",
+ "license": "MIT"
+ },
+ "node_modules/react-markdown": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmmirror.com/react-markdown/-/react-markdown-9.1.0.tgz",
+ "integrity": "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "hast-util-to-jsx-runtime": "^2.0.0",
+ "html-url-attributes": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.0.0",
+ "unified": "^11.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18",
+ "react": ">=18"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-syntax-highlighter": {
+ "version": "15.6.6",
+ "resolved": "https://registry.npmmirror.com/react-syntax-highlighter/-/react-syntax-highlighter-15.6.6.tgz",
+ "integrity": "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "highlight.js": "^10.4.1",
+ "highlightjs-vue": "^1.0.0",
+ "lowlight": "^1.17.0",
+ "prismjs": "^1.30.0",
+ "refractor": "^3.6.0"
+ },
+ "peerDependencies": {
+ "react": ">= 0.14.0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/refractor": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/refractor/-/refractor-3.6.0.tgz",
+ "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
+ "license": "MIT",
+ "dependencies": {
+ "hastscript": "^6.0.0",
+ "parse-entities": "^2.0.0",
+ "prismjs": "~1.27.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/character-entities": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-1.2.4.tgz",
+ "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/character-entities-legacy": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
+ "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/character-reference-invalid": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
+ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-alphabetical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+ "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-alphanumerical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+ "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^1.0.0",
+ "is-decimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-decimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-1.0.4.tgz",
+ "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-hexadecimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
+ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/parse-entities": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-2.0.0.tgz",
+ "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^1.0.0",
+ "character-entities-legacy": "^1.0.0",
+ "character-reference-invalid": "^1.0.0",
+ "is-alphanumerical": "^1.0.0",
+ "is-decimal": "^1.0.0",
+ "is-hexadecimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/prismjs": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.27.0.tgz",
+ "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz",
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "11.1.2",
+ "resolved": "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-11.1.2.tgz",
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.55.1.tgz",
+ "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==",
+ "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.55.1",
+ "@rollup/rollup-android-arm64": "4.55.1",
+ "@rollup/rollup-darwin-arm64": "4.55.1",
+ "@rollup/rollup-darwin-x64": "4.55.1",
+ "@rollup/rollup-freebsd-arm64": "4.55.1",
+ "@rollup/rollup-freebsd-x64": "4.55.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.55.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.55.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.55.1",
+ "@rollup/rollup-linux-arm64-musl": "4.55.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.55.1",
+ "@rollup/rollup-linux-loong64-musl": "4.55.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.55.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.55.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.55.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.55.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-musl": "4.55.1",
+ "@rollup/rollup-openbsd-x64": "4.55.1",
+ "@rollup/rollup-openharmony-arm64": "4.55.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.55.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.55.1",
+ "@rollup/rollup-win32-x64-gnu": "4.55.1",
+ "@rollup/rollup-win32-x64-msvc": "4.55.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/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/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/style-to-js": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.21.tgz",
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "style-to-object": "1.0.14"
+ }
+ },
+ "node_modules/style-to-object": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.14.tgz",
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
+ "license": "MIT",
+ "dependencies": {
+ "inline-style-parser": "0.2.7"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "license": "MIT"
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmmirror.com/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz",
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/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/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz",
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.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",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/webui/frontend/package.json b/webui/frontend/package.json
new file mode 100644
index 000000000..8a88cb6bb
--- /dev/null
+++ b/webui/frontend/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "ms-agent-webui",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@emotion/react": "^11.11.3",
+ "@emotion/styled": "^11.11.0",
+ "@mui/icons-material": "^5.15.6",
+ "@mui/material": "^5.15.6",
+ "framer-motion": "^11.0.3",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-markdown": "^9.0.1",
+ "react-syntax-highlighter": "^15.5.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "@types/react-syntax-highlighter": "^15.5.11",
+ "@vitejs/plugin-react": "^4.2.1",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.12"
+ }
+}
diff --git a/webui/frontend/public/favicon.svg b/webui/frontend/public/favicon.svg
new file mode 100644
index 000000000..be490a284
--- /dev/null
+++ b/webui/frontend/public/favicon.svg
@@ -0,0 +1,10 @@
+
diff --git a/webui/frontend/src/App.tsx b/webui/frontend/src/App.tsx
new file mode 100644
index 000000000..c2b5a8cff
--- /dev/null
+++ b/webui/frontend/src/App.tsx
@@ -0,0 +1,55 @@
+import React, { useState } from 'react';
+import { Box } from '@mui/material';
+import { AnimatePresence } from 'framer-motion';
+import { useSession } from './context/SessionContext';
+import SearchView from './components/SearchView';
+import ConversationView from './components/ConversationView';
+import Layout from './components/Layout';
+
+const App: React.FC = () => {
+ const { currentSession } = useSession();
+ const [showSettings, setShowSettings] = useState(false);
+ const [showLogs, setShowLogs] = useState(false);
+
+ return (
+ setShowSettings(true)}
+ onToggleLogs={() => setShowLogs(!showLogs)}
+ showLogs={showLogs}
+ >
+
+
+ {!currentSession ? (
+
+ ) : (
+
+ )}
+
+
+
+ {showSettings && (
+
+ setShowSettings(false)}
+ />
+
+ )}
+
+ );
+};
+
+// Lazy load settings dialog
+const SettingsDialogLazy = React.lazy(() => import('./components/SettingsDialog'));
+
+export default App;
diff --git a/webui/frontend/src/components/ConversationView.tsx b/webui/frontend/src/components/ConversationView.tsx
new file mode 100644
index 000000000..99c4dcb94
--- /dev/null
+++ b/webui/frontend/src/components/ConversationView.tsx
@@ -0,0 +1,1013 @@
+import React, { useState, useEffect, useRef, useCallback } from 'react';
+import {
+ Box,
+ TextField,
+ IconButton,
+ Typography,
+ Paper,
+ InputAdornment,
+ useTheme,
+ alpha,
+ Chip,
+ Divider,
+ Avatar,
+ Tooltip,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ CircularProgress,
+} from '@mui/material';
+import {
+ Send as SendIcon,
+ Stop as StopIcon,
+ Person as PersonIcon,
+ AutoAwesome as BotIcon,
+ PlayArrow as RunningIcon,
+ InsertDriveFile as FileIcon,
+ Code as CodeIcon,
+ Description as DocIcon,
+ Image as ImageIcon,
+ CheckCircle as CompleteIcon,
+ HourglassTop as StartIcon,
+ Close as CloseIcon,
+ ContentCopy as CopyIcon,
+ Folder as FolderIcon,
+ FolderOpen as FolderOpenIcon,
+ ChevronRight as ChevronRightIcon,
+ ExpandMore as ExpandMoreIcon,
+} from '@mui/icons-material';
+import { motion, AnimatePresence } from 'framer-motion';
+import { useSession, Message, Session } from '../context/SessionContext';
+import WorkflowProgress from './WorkflowProgress';
+import FileProgress from './FileProgress';
+import LogViewer from './LogViewer';
+import MessageContent from './MessageContent';
+
+interface ConversationViewProps {
+ showLogs: boolean;
+}
+
+const ConversationView: React.FC = ({ showLogs }) => {
+ const theme = useTheme();
+ const {
+ currentSession,
+ messages,
+ streamingContent,
+ isStreaming,
+ isLoading,
+ sendMessage,
+ stopAgent,
+ logs,
+ } = useSession();
+
+ const completedSteps = React.useMemo(() => {
+ const set = new Set();
+ for (const m of messages) {
+ if (m.type === 'step_complete' && m.content) set.add(m.content);
+ }
+ return set;
+ }, [messages]);
+
+ const [input, setInput] = useState('');
+ const [outputFilesOpen, setOutputFilesOpen] = useState(false);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const [outputTree, setOutputTree] = useState({folders: {}, files: []});
+ const [expandedFolders, setExpandedFolders] = useState>(new Set());
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [fileContent, setFileContent] = useState(null);
+ const [fileLoading, setFileLoading] = useState(false);
+ const messagesEndRef = useRef(null);
+ const inputRef = useRef(null);
+
+ // Auto-scroll to bottom
+ useEffect(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }, [messages, streamingContent]);
+
+ // Focus input on mount
+ useEffect(() => {
+ inputRef.current?.focus();
+ }, []);
+
+ const handleSend = useCallback(() => {
+ if (!input.trim() || isLoading) return;
+ sendMessage(input);
+ setInput('');
+ }, [input, isLoading, sendMessage]);
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSend();
+ }
+ };
+
+ const loadOutputFiles = async () => {
+ try {
+ const response = await fetch('/api/files/list');
+ if (response.ok) {
+ const data = await response.json();
+ setOutputTree(data.tree || {folders: {}, files: []});
+ // Expand root level by default
+ setExpandedFolders(new Set(['']));
+ }
+ } catch (err) {
+ console.error('Failed to load output files:', err);
+ }
+ };
+
+ const toggleFolder = (folder: string) => {
+ setExpandedFolders(prev => {
+ const next = new Set(prev);
+ if (next.has(folder)) {
+ next.delete(folder);
+ } else {
+ next.add(folder);
+ }
+ return next;
+ });
+ };
+
+ const handleOpenOutputFiles = () => {
+ loadOutputFiles();
+ setOutputFilesOpen(true);
+ setSelectedFile(null);
+ setFileContent(null);
+ };
+
+ const handleViewFile = async (path: string) => {
+ setSelectedFile(path);
+ setFileLoading(true);
+ try {
+ const response = await fetch('/api/files/read', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ path }),
+ });
+ if (response.ok) {
+ const data = await response.json();
+ setFileContent(data.content);
+ }
+ } catch (err) {
+ console.error('Failed to load file:', err);
+ } finally {
+ setFileLoading(false);
+ }
+ };
+
+ return (
+
+ {/* Session Header */}
+
+
+
+ : undefined}
+ label={currentSession?.status}
+ size="small"
+ color={
+ currentSession?.status === 'running' ? 'info' :
+ currentSession?.status === 'completed' ? 'success' :
+ currentSession?.status === 'error' ? 'error' : 'default'
+ }
+ sx={{
+ textTransform: 'capitalize',
+ borderRadius: '8px',
+ '& .MuiChip-icon': { ml: 0.5 },
+ }}
+ />
+
+
+ {/* Workflow Progress */}
+ {currentSession?.workflow_progress && (
+
+
+
+ )}
+
+ {/* File Progress */}
+ {currentSession?.file_progress && (
+
+ )}
+
+ {/* View Output Files Button */}
+
+ }
+ label="Output Files"
+ size="small"
+ onClick={handleOpenOutputFiles}
+ sx={{
+ backgroundColor: alpha(theme.palette.warning.main, 0.1),
+ color: theme.palette.warning.main,
+ cursor: 'pointer',
+ '&:hover': {
+ backgroundColor: alpha(theme.palette.warning.main, 0.2),
+ },
+ }}
+ />
+
+
+
+ {/* Output Files Dialog */}
+
+
+ {/* Main Content Area */}
+
+ {/* Messages Area */}
+
+ {/* Messages List */}
+
+
+ {messages.map((message) => (
+
+ ))}
+
+ {/* Streaming Content */}
+ {isStreaming && streamingContent && (
+
+
+
+ )}
+
+ {/* Loading Indicator */}
+ {isLoading && !isStreaming && messages.length > 0 && (() => {
+ // Find current running step
+ const runningSteps = messages.filter(m => m.type === 'step_start');
+ const completedSteps = messages.filter(m => m.type === 'step_complete');
+ const currentStep = runningSteps.length > completedSteps.length
+ ? runningSteps[runningSteps.length - 1]?.content?.replace(/_/g, ' ')
+ : null;
+
+ return (
+
+
+
+
+
+
+
+
+ {[0, 1, 2].map((i) => (
+
+
+
+ ))}
+
+
+ {currentStep ? (
+ <>
+
+ {currentStep}
+
+ in progress...
+ >
+ ) : 'Processing...'}
+
+
+
+
+
+ );
+ })()}
+
+
+
+
+ {/* Input Area */}
+
+ setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ disabled={isLoading}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: '12px',
+ backgroundColor: theme.palette.background.paper,
+ },
+ }}
+ InputProps={{
+ endAdornment: (
+
+ {isLoading ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ ),
+ }}
+ />
+
+
+
+ {/* Logs Panel */}
+ {showLogs && (
+ <>
+
+
+ >
+ )}
+
+
+ );
+};
+
+interface MessageBubbleProps {
+ message: Message;
+ isStreaming?: boolean;
+ sessionStatus?: Session['status'];
+ completedSteps?: Set;
+}
+
+const MessageBubble: React.FC = ({ message, isStreaming, sessionStatus, completedSteps }) => {
+ const theme = useTheme();
+ const isUser = message.role === 'user';
+ const isError = message.type === 'error';
+ const isSystem = message.role === 'system';
+ const isFileOutput = message.type === 'file_output';
+ const isStepStart = message.type === 'step_start';
+ const isStepComplete = message.type === 'step_complete';
+ const isToolCall = message.type === 'tool_call';
+
+ // Skip empty messages
+ if (!message.content?.trim()) return null;
+
+ // Skip old format system messages
+ if (isSystem && message.content.startsWith('Starting step:')) return null;
+ if (isSystem && message.content.startsWith('Completed step:')) return null;
+
+ // Step start/complete display
+ if (isStepStart || isStepComplete) {
+ // If a step has a completion record, hide the earlier start record to avoid duplicates.
+ if (isStepStart && completedSteps?.has(message.content)) {
+ return null;
+ }
+
+ const stepName = message.content.replace(/_/g, ' ');
+ const isComplete = isStepComplete || (isStepStart && !!completedSteps?.has(message.content));
+ const isStopped = isStepStart && !isComplete && sessionStatus === 'stopped';
+ const accentColor = isComplete
+ ? theme.palette.success.main
+ : isStopped
+ ? theme.palette.warning.main
+ : theme.palette.info.main;
+
+ return (
+
+
+
+ {isComplete ? : }
+
+
+
+ {stepName}
+
+
+ {isComplete ? 'Completed' : isStopped ? 'Stopped' : 'Running...'}
+
+
+
+
+ );
+ }
+
+ // Tool call - skip display (we show step progress instead)
+ if (isToolCall) {
+ return null;
+ }
+
+ // File output display as compact chip
+ if (isFileOutput) {
+ return ;
+ }
+
+ return (
+
+
+ {/* Avatar */}
+
+
+ {isUser ? : }
+
+
+
+ {/* Message Content */}
+
+
+
+ {isStreaming && (
+
+ )}
+
+
+
+ );
+};
+
+export default ConversationView;
+
+// Recursive FileTreeView component
+interface TreeNode {
+ folders: Record;
+ files: Array<{name: string; path: string; size: number; modified: number}>;
+}
+
+interface FileTreeViewProps {
+ tree: TreeNode;
+ path: string;
+ expandedFolders: Set;
+ toggleFolder: (path: string) => void;
+ selectedFile: string | null;
+ onSelectFile: (path: string) => void;
+ depth?: number;
+}
+
+const FileTreeView: React.FC = ({
+ tree, path, expandedFolders, toggleFolder, selectedFile, onSelectFile, depth = 0
+}) => {
+ const theme = useTheme();
+ const stripProgrammerPrefix = (name: string) => {
+ if (!name.startsWith('programmer-')) return name;
+ const stripped = name.slice('programmer-'.length);
+ return stripped.length > 0 ? stripped : name;
+ };
+ const hasContent = Object.keys(tree.folders).length > 0 || tree.files.length > 0;
+
+ if (!hasContent && depth === 0) {
+ return (
+
+ No files yet
+
+ );
+ }
+
+ return (
+ <>
+ {/* Folders */}
+ {Object.entries(tree.folders).map(([folderName, subtree]) => {
+ const folderPath = path ? `${path}/${folderName}` : folderName;
+ const isExpanded = expandedFolders.has(folderPath);
+
+ return (
+
+ toggleFolder(folderPath)}
+ sx={{
+ py: 0.5,
+ pl: depth * 2 + 1,
+ pr: 1,
+ cursor: 'pointer',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 0.5,
+ '&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.05) },
+ }}
+ >
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+ {folderName}
+
+
+ {isExpanded && (
+
+ )}
+
+ );
+ })}
+
+ {/* Files */}
+ {tree.files.map((file) => (
+ onSelectFile(file.path)}
+ sx={{
+ py: 0.5,
+ pl: depth * 2 + 3.5,
+ pr: 1,
+ cursor: 'pointer',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 0.5,
+ backgroundColor: selectedFile === file.path ? alpha(theme.palette.primary.main, 0.1) : 'transparent',
+ '&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.05) },
+ }}
+ >
+
+
+ {stripProgrammerPrefix(file.name)}
+
+
+ ))}
+ >
+ );
+};
+
+// Separate component for file output with dialog
+const FileOutputChip: React.FC<{ filename: string }> = ({ filename }) => {
+ const theme = useTheme();
+ const shortName = filename.split('/').pop() || filename;
+ const displayName = shortName.startsWith('programmer-') ? shortName.slice('programmer-'.length) : shortName;
+ const [dialogOpen, setDialogOpen] = useState(false);
+ const [fileContent, setFileContent] = useState(null);
+ const [fileLoading, setFileLoading] = useState(false);
+ const [fileError, setFileError] = useState(null);
+ const [fileLang, setFileLang] = useState('text');
+
+ const getFileIcon = (fname: string) => {
+ const ext = fname.split('.').pop()?.toLowerCase();
+ if (['js', 'ts', 'tsx', 'jsx', 'py', 'java', 'cpp', 'c', 'go', 'rs'].includes(ext || '')) {
+ return ;
+ }
+ if (['md', 'txt', 'json', 'yaml', 'yml', 'xml', 'html', 'css'].includes(ext || '')) {
+ return ;
+ }
+ if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext || '')) {
+ return ;
+ }
+ return ;
+ };
+
+ const handleViewFile = async () => {
+ setDialogOpen(true);
+ setFileLoading(true);
+ setFileError(null);
+
+ try {
+ const response = await fetch('/api/files/read', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ path: filename }),
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.detail || 'Failed to load file');
+ }
+
+ const data = await response.json();
+ setFileContent(data.content);
+ setFileLang(data.language || 'text');
+ } catch (err) {
+ setFileError(err instanceof Error ? err.message : 'Failed to load file');
+ } finally {
+ setFileLoading(false);
+ }
+ };
+
+ const handleCopy = () => {
+ if (fileContent) {
+ navigator.clipboard.writeText(fileContent);
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+ Click to view
+
+
+
+
+ {/* File Viewer Dialog */}
+
+ >
+ );
+};
diff --git a/webui/frontend/src/components/FileProgress.tsx b/webui/frontend/src/components/FileProgress.tsx
new file mode 100644
index 000000000..e401edbb8
--- /dev/null
+++ b/webui/frontend/src/components/FileProgress.tsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import { Box, Typography, Chip, useTheme, alpha, CircularProgress } from '@mui/material';
+import { CheckCircle as CheckIcon } from '@mui/icons-material';
+import { motion } from 'framer-motion';
+import { FileProgress as FileProgressType } from '../context/SessionContext';
+
+interface FileProgressProps {
+ progress: FileProgressType;
+}
+
+const FileProgress: React.FC = ({ progress }) => {
+ const theme = useTheme();
+ const { file, status } = progress;
+ const isWriting = status === 'writing';
+
+ // Extract filename from path
+ const filename = file.split('/').pop() || file;
+
+ return (
+
+
+
+ File:
+
+ : }
+ label={filename}
+ sx={{
+ height: 24,
+ fontSize: '0.7rem',
+ maxWidth: 200,
+ backgroundColor: isWriting
+ ? alpha(theme.palette.warning.main, 0.1)
+ : alpha(theme.palette.success.main, 0.1),
+ color: isWriting
+ ? theme.palette.warning.main
+ : theme.palette.success.main,
+ '& .MuiChip-icon': {
+ color: 'inherit',
+ },
+ '& .MuiChip-label': {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ },
+ }}
+ />
+
+
+ );
+};
+
+export default FileProgress;
diff --git a/webui/frontend/src/components/Layout.tsx b/webui/frontend/src/components/Layout.tsx
new file mode 100644
index 000000000..d2370a32c
--- /dev/null
+++ b/webui/frontend/src/components/Layout.tsx
@@ -0,0 +1,237 @@
+import React, { ReactNode } from 'react';
+import {
+ Box,
+ AppBar,
+ Toolbar,
+ Typography,
+ IconButton,
+ Tooltip,
+ useTheme,
+ alpha,
+} from '@mui/material';
+import {
+ Settings as SettingsIcon,
+ DarkMode as DarkModeIcon,
+ LightMode as LightModeIcon,
+ Terminal as TerminalIcon,
+ GitHub as GitHubIcon,
+} from '@mui/icons-material';
+import { motion } from 'framer-motion';
+import { useThemeContext } from '../context/ThemeContext';
+
+interface LayoutProps {
+ children: ReactNode;
+ onOpenSettings: () => void;
+ onToggleLogs: () => void;
+ showLogs: boolean;
+}
+
+const Layout: React.FC = ({ children, onOpenSettings, onToggleLogs, showLogs }) => {
+ const theme = useTheme();
+ const { mode, toggleTheme } = useThemeContext();
+
+ return (
+
+ {/* Header */}
+
+
+ {/* Logo */}
+
+
+
+
+ MS
+
+
+
+
+ MS-Agent
+
+
+ Intelligent Platform
+
+
+
+
+
+ {/* Actions */}
+
+
+
+
+
+
+
+
+
+
+ {mode === 'dark' ? : }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Main Content */}
+
+ {children}
+
+
+ {/* Footer */}
+
+
+ Powered by ModelScope
+
+
+
+ © 2024 Alibaba Inc.
+
+
+
+ );
+};
+
+export default Layout;
diff --git a/webui/frontend/src/components/LogViewer.tsx b/webui/frontend/src/components/LogViewer.tsx
new file mode 100644
index 000000000..9961ddf67
--- /dev/null
+++ b/webui/frontend/src/components/LogViewer.tsx
@@ -0,0 +1,219 @@
+import React, { useRef, useEffect, useState } from 'react';
+import {
+ Box,
+ Typography,
+ IconButton,
+ TextField,
+ InputAdornment,
+ useTheme,
+ alpha,
+ Chip,
+ Tooltip,
+} from '@mui/material';
+import {
+ Clear as ClearIcon,
+ Search as SearchIcon,
+ Download as DownloadIcon,
+} from '@mui/icons-material';
+import { motion, AnimatePresence } from 'framer-motion';
+import { LogEntry } from '../context/SessionContext';
+
+interface LogViewerProps {
+ logs: LogEntry[];
+ onClear?: () => void;
+}
+
+const LogViewer: React.FC = ({ logs, onClear }) => {
+ const theme = useTheme();
+ const logsEndRef = useRef(null);
+ const [filter, setFilter] = useState('');
+ const [levelFilter, setLevelFilter] = useState(null);
+
+ // Auto-scroll to bottom
+ useEffect(() => {
+ logsEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }, [logs]);
+
+ const filteredLogs = logs.filter((log) => {
+ const matchesSearch = !filter || log.message.toLowerCase().includes(filter.toLowerCase());
+ const matchesLevel = !levelFilter || log.level === levelFilter;
+ return matchesSearch && matchesLevel;
+ });
+
+ const getLevelColor = (level: LogEntry['level']) => {
+ switch (level) {
+ case 'error': return theme.palette.error.main;
+ case 'warning': return theme.palette.warning.main;
+ case 'debug': return theme.palette.info.main;
+ default: return theme.palette.text.secondary;
+ }
+ };
+
+ const handleDownload = () => {
+ const content = logs.map((log) =>
+ `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`
+ ).join('\n');
+
+ const blob = new Blob([content], { type: 'text/plain' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `ms-agent-logs-${new Date().toISOString().slice(0, 10)}.txt`;
+ a.click();
+ URL.revokeObjectURL(url);
+ };
+
+ return (
+
+ {/* Header */}
+
+
+ Logs
+
+
+
+
+
+
+
+
+ {onClear && (
+
+
+
+
+
+ )}
+
+
+ {/* Filters */}
+
+ setFilter(e.target.value)}
+ sx={{ mb: 1 }}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+ {['info', 'warning', 'error', 'debug'].map((level) => (
+ setLevelFilter(levelFilter === level ? null : level)}
+ sx={{
+ height: 22,
+ fontSize: '0.65rem',
+ textTransform: 'uppercase',
+ backgroundColor: levelFilter === level
+ ? alpha(getLevelColor(level as LogEntry['level']), 0.2)
+ : 'transparent',
+ border: `1px solid ${alpha(getLevelColor(level as LogEntry['level']), 0.3)}`,
+ color: getLevelColor(level as LogEntry['level']),
+ }}
+ />
+ ))}
+
+
+
+ {/* Log List */}
+
+
+ {filteredLogs.map((log, index) => (
+
+
+
+
+ [{log.level}]
+
+
+ {log.message}
+
+
+
+ {new Date(log.timestamp).toLocaleTimeString()}
+
+
+
+ ))}
+
+
+
+
+ );
+};
+
+export default LogViewer;
diff --git a/webui/frontend/src/components/MessageContent.tsx b/webui/frontend/src/components/MessageContent.tsx
new file mode 100644
index 000000000..cbac868e2
--- /dev/null
+++ b/webui/frontend/src/components/MessageContent.tsx
@@ -0,0 +1,190 @@
+import React, { useMemo } from 'react';
+import { Box, Typography, useTheme } from '@mui/material';
+import ReactMarkdown from 'react-markdown';
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
+import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
+
+interface MessageContentProps {
+ content: string;
+}
+
+const MessageContent: React.FC = ({ content }) => {
+ const theme = useTheme();
+ const isDark = theme.palette.mode === 'dark';
+
+ const components = useMemo(() => ({
+ code({ node, inline, className, children, ...props }: any) {
+ const match = /language-(\w+)/.exec(className || '');
+ const language = match ? match[1] : '';
+
+ if (!inline && language) {
+ return (
+
+
+
+ {language}
+
+
+
+ {String(children).replace(/\n$/, '')}
+
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+ },
+ p({ children }: any) {
+ return (
+
+ {children}
+
+ );
+ },
+ ul({ children }: any) {
+ return (
+
+ {children}
+
+ );
+ },
+ ol({ children }: any) {
+ return (
+
+ {children}
+
+ );
+ },
+ h1({ children }: any) {
+ return (
+
+ {children}
+
+ );
+ },
+ h2({ children }: any) {
+ return (
+
+ {children}
+
+ );
+ },
+ h3({ children }: any) {
+ return (
+
+ {children}
+
+ );
+ },
+ blockquote({ children }: any) {
+ return (
+
+ {children}
+
+ );
+ },
+ a({ href, children }: any) {
+ return (
+
+ {children}
+
+ );
+ },
+ }), [isDark, theme]);
+
+ return (
+ *:first-child': { mt: 0 },
+ '& > *:last-child': { mb: 0 },
+ }}
+ >
+
+ {content}
+
+
+ );
+};
+
+export default MessageContent;
diff --git a/webui/frontend/src/components/SearchView.tsx b/webui/frontend/src/components/SearchView.tsx
new file mode 100644
index 000000000..ec724eafa
--- /dev/null
+++ b/webui/frontend/src/components/SearchView.tsx
@@ -0,0 +1,359 @@
+import React, { useState, useCallback } from 'react';
+import {
+ Box,
+ TextField,
+ Typography,
+ Card,
+ CardContent,
+ Chip,
+ InputAdornment,
+ IconButton,
+ useTheme,
+ alpha,
+ Grid,
+ Tooltip,
+} from '@mui/material';
+import {
+ Search as SearchIcon,
+ ArrowForward as ArrowForwardIcon,
+ Code as CodeIcon,
+ Psychology as PsychologyIcon,
+ Science as ScienceIcon,
+ Description as DescriptionIcon,
+ Movie as MovieIcon,
+ AccountTree as WorkflowIcon,
+} from '@mui/icons-material';
+import { motion, AnimatePresence } from 'framer-motion';
+import { useSession, Project } from '../context/SessionContext';
+
+const projectIcons: Record = {
+ code_genesis: ,
+ agent_skills: ,
+ deep_research: ,
+ doc_research: ,
+ fin_research: ,
+ singularity_cinema: ,
+};
+
+const SearchView: React.FC = () => {
+ const theme = useTheme();
+ const { projects, createSession, selectSession } = useSession();
+ const [query, setQuery] = useState('');
+ const [selectedProject, setSelectedProject] = useState(null);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleProjectSelect = (project: Project) => {
+ setSelectedProject(project);
+ };
+
+ const handleSubmit = useCallback(async () => {
+ if (!selectedProject || !query.trim()) return;
+
+ console.log('[SearchView] Submitting with project:', selectedProject.id, 'query:', query);
+ setIsSubmitting(true);
+ try {
+ const session = await createSession(selectedProject.id);
+ console.log('[SearchView] Session created:', session);
+ if (session) {
+ // Pass the session object directly to avoid race condition
+ selectSession(session.id, query, session);
+ }
+ } catch (error) {
+ console.error('[SearchView] Error creating session:', error);
+ } finally {
+ setIsSubmitting(false);
+ }
+ }, [selectedProject, query, createSession, selectSession]);
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSubmit();
+ }
+ };
+
+ return (
+
+ {/* Hero Section */}
+
+
+ Intelligent Agent Platform
+
+
+ Harness the power of AI agents for research, coding, and creative tasks
+
+
+
+ {/* Search Input */}
+
+
+ setQuery(e.target.value)}
+ onKeyDown={handleKeyDown}
+ disabled={!selectedProject}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: '16px',
+ backgroundColor: alpha(theme.palette.background.paper, 0.8),
+ backdropFilter: 'blur(10px)',
+ border: `2px solid ${alpha(theme.palette.primary.main, selectedProject ? 0.3 : 0.1)}`,
+ transition: 'all 0.3s ease',
+ '&:hover': {
+ border: `2px solid ${alpha(theme.palette.primary.main, 0.5)}`,
+ },
+ '&.Mui-focused': {
+ border: `2px solid ${theme.palette.primary.main}`,
+ boxShadow: `0 0 20px ${alpha(theme.palette.primary.main, 0.15)}`,
+ },
+ },
+ '& .MuiOutlinedInput-notchedOutline': {
+ border: 'none',
+ },
+ }}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ endAdornment: (
+
+
+
+
+
+
+
+
+
+ ),
+ }}
+ />
+
+ {/* Selected Project Badge */}
+
+ {selectedProject && (
+
+
+ }
+ label={selectedProject.display_name}
+ onDelete={() => setSelectedProject(null)}
+ sx={{
+ backgroundColor: alpha(theme.palette.primary.main, 0.15),
+ borderColor: theme.palette.primary.main,
+ '& .MuiChip-icon': {
+ color: theme.palette.primary.main,
+ },
+ }}
+ variant="outlined"
+ />
+
+
+ )}
+
+
+
+
+ {/* Project Cards */}
+
+
+ Select a Project
+
+
+
+ {projects.map((project, index) => (
+
+
+ handleProjectSelect(project)}
+ sx={{
+ cursor: 'pointer',
+ position: 'relative',
+ overflow: 'hidden',
+ border: selectedProject?.id === project.id
+ ? `2px solid ${theme.palette.primary.main}`
+ : `1px solid ${theme.palette.divider}`,
+ transition: 'all 0.3s ease',
+ '&:hover': {
+ transform: 'translateY(-4px)',
+ boxShadow: `0 12px 24px ${alpha(theme.palette.common.black, 0.15)}`,
+ border: `2px solid ${alpha(theme.palette.primary.main, 0.5)}`,
+ },
+ '&::before': {
+ content: '""',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ height: 3,
+ background: selectedProject?.id === project.id
+ ? `linear-gradient(90deg, ${theme.palette.primary.main}, ${theme.palette.primary.light})`
+ : 'transparent',
+ },
+ }}
+ >
+
+
+
+ {projectIcons[project.id] || }
+
+
+
+ {project.display_name}
+
+
+
+
+
+
+
+ {project.description || 'No description available'}
+
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default SearchView;
diff --git a/webui/frontend/src/components/SettingsDialog.tsx b/webui/frontend/src/components/SettingsDialog.tsx
new file mode 100644
index 000000000..6a273be81
--- /dev/null
+++ b/webui/frontend/src/components/SettingsDialog.tsx
@@ -0,0 +1,438 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ TextField,
+ Box,
+ Typography,
+ Tabs,
+ Tab,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ Slider,
+ IconButton,
+ Divider,
+ Paper,
+ useTheme,
+ Alert,
+ Chip,
+ Tooltip,
+} from '@mui/material';
+import {
+ Close as CloseIcon,
+ Add as AddIcon,
+ Delete as DeleteIcon,
+ Save as SaveIcon,
+} from '@mui/icons-material';
+
+interface SettingsDialogProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+interface LLMConfig {
+ provider: string;
+ model: string;
+ api_key: string;
+ base_url: string;
+ temperature: number;
+ max_tokens: number;
+}
+
+interface MCPServer {
+ type: 'stdio' | 'sse';
+ command?: string;
+ args?: string[];
+ url?: string;
+ env?: Record;
+}
+
+interface TabPanelProps {
+ children?: React.ReactNode;
+ index: number;
+ value: number;
+}
+
+const TabPanel: React.FC = ({ children, value, index }) => (
+
+ {value === index && children}
+
+);
+
+const SettingsDialog: React.FC = ({ open, onClose }) => {
+ const theme = useTheme();
+ const [tabValue, setTabValue] = useState(0);
+ const [llmConfig, setLlmConfig] = useState({
+ provider: 'modelscope',
+ model: 'Qwen/Qwen3-235B-A22B-Instruct-2507',
+ api_key: '',
+ base_url: 'https://api-inference.modelscope.cn/v1/',
+ temperature: 0.7,
+ max_tokens: 4096,
+ });
+ const [mcpServers, setMcpServers] = useState>({});
+ const [newServerName, setNewServerName] = useState('');
+ const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
+
+ // Load config on mount
+ useEffect(() => {
+ if (open) {
+ loadConfig();
+ }
+ }, [open]);
+
+ const loadConfig = async () => {
+ try {
+ const [llmRes, mcpRes] = await Promise.all([
+ fetch('/api/config/llm'),
+ fetch('/api/config/mcp'),
+ ]);
+
+ if (llmRes.ok) {
+ const data = await llmRes.json();
+ setLlmConfig(data);
+ }
+
+ if (mcpRes.ok) {
+ const data = await mcpRes.json();
+ setMcpServers(data.mcpServers || {});
+ }
+ } catch (error) {
+ console.error('Failed to load config:', error);
+ }
+ };
+
+ const handleSave = async () => {
+ setSaveStatus('saving');
+ try {
+ const llmRes = await fetch('/api/config/llm', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(llmConfig),
+ });
+
+ const mcpRes = await fetch('/api/config/mcp', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ mcpServers: mcpServers }),
+ });
+
+ if (llmRes.ok && mcpRes.ok) {
+ setSaveStatus('saved');
+ setTimeout(() => setSaveStatus('idle'), 2000);
+ } else {
+ setSaveStatus('error');
+ }
+ } catch (error) {
+ setSaveStatus('error');
+ }
+ };
+
+ const handleAddMCPServer = () => {
+ if (!newServerName.trim()) return;
+
+ setMcpServers((prev) => ({
+ ...prev,
+ [newServerName]: { type: 'sse', url: '' },
+ }));
+ setNewServerName('');
+ };
+
+ const handleRemoveMCPServer = (name: string) => {
+ setMcpServers((prev) => {
+ const newServers = { ...prev };
+ delete newServers[name];
+ return newServers;
+ });
+ };
+
+ const handleMCPServerChange = (name: string, field: keyof MCPServer, value: any) => {
+ setMcpServers((prev) => ({
+ ...prev,
+ [name]: { ...prev[name], [field]: value },
+ }));
+ };
+
+ const providers = [
+ { value: 'modelscope', label: 'ModelScope', baseUrl: 'https://api-inference.modelscope.cn/v1/' },
+ { value: 'openai', label: 'OpenAI', baseUrl: 'https://api.openai.com/v1/' },
+ { value: 'anthropic', label: 'Anthropic', baseUrl: 'https://api.anthropic.com/v1/' },
+ { value: 'deepseek', label: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1/' },
+ { value: 'custom', label: 'Custom', baseUrl: '' },
+ ];
+
+ const models: Record = {
+ modelscope: ['Qwen/Qwen3-235B-A22B-Instruct-2507', 'Qwen/Qwen2.5-72B-Instruct', 'Qwen/Qwen2.5-32B-Instruct'],
+ openai: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo'],
+ anthropic: ['claude-3-5-sonnet-20241022', 'claude-3-opus-20240229'],
+ deepseek: ['deepseek-chat', 'deepseek-coder'],
+ custom: [],
+ };
+
+ return (
+
+ );
+};
+
+export default SettingsDialog;
diff --git a/webui/frontend/src/components/WorkflowProgress.tsx b/webui/frontend/src/components/WorkflowProgress.tsx
new file mode 100644
index 000000000..d863337b1
--- /dev/null
+++ b/webui/frontend/src/components/WorkflowProgress.tsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import { Box, Typography, Chip, useTheme, alpha, LinearProgress } from '@mui/material';
+import { CheckCircle as CheckIcon, RadioButtonUnchecked as PendingIcon } from '@mui/icons-material';
+import { motion } from 'framer-motion';
+import { WorkflowProgress as WorkflowProgressType } from '../context/SessionContext';
+
+interface WorkflowProgressProps {
+ progress: WorkflowProgressType;
+}
+
+const WorkflowProgress: React.FC = ({ progress }) => {
+ const theme = useTheme();
+ const { steps, step_status } = progress;
+
+ // Calculate progress based on completed steps
+ const completedCount = steps.filter(s => step_status[s] === 'completed').length;
+ const runningCount = steps.filter(s => step_status[s] === 'running').length;
+ const progressPercent = steps.length > 0 ? ((completedCount + runningCount * 0.5) / steps.length) * 100 : 0;
+
+ return (
+
+
+ Workflow:
+
+
+
+ {steps.map((step, index) => {
+ const status = step_status[step] || 'pending';
+ const isCompleted = status === 'completed';
+ const isCurrent = status === 'running';
+
+ return (
+
+ : isCurrent ? undefined : }
+ label={step.replace(/_/g, ' ')}
+ sx={{
+ height: 24,
+ fontSize: '0.7rem',
+ backgroundColor: isCompleted
+ ? alpha(theme.palette.success.main, 0.1)
+ : isCurrent
+ ? alpha(theme.palette.info.main, 0.15)
+ : alpha(theme.palette.action.disabled, 0.1),
+ color: isCompleted
+ ? theme.palette.success.main
+ : isCurrent
+ ? theme.palette.info.main
+ : theme.palette.text.secondary,
+ border: isCurrent ? `1px solid ${theme.palette.info.main}` : 'none',
+ '& .MuiChip-icon': {
+ color: 'inherit',
+ },
+ }}
+ />
+
+ );
+ })}
+
+
+
+
+
+
+ );
+};
+
+export default WorkflowProgress;
diff --git a/webui/frontend/src/context/SessionContext.tsx b/webui/frontend/src/context/SessionContext.tsx
new file mode 100644
index 000000000..ae3d5bc31
--- /dev/null
+++ b/webui/frontend/src/context/SessionContext.tsx
@@ -0,0 +1,414 @@
+import React, { createContext, useContext, useState, useEffect, useCallback, useRef, ReactNode } from 'react';
+
+export interface Message {
+ id: string;
+ role: 'user' | 'assistant' | 'system' | 'tool';
+ content: string;
+ type: 'text' | 'tool_call' | 'tool_result' | 'error' | 'log' | 'file_output' | 'step_start' | 'step_complete';
+ timestamp: string;
+ metadata?: Record;
+}
+
+export interface Project {
+ id: string;
+ name: string;
+ display_name: string;
+ description: string;
+ type: 'workflow' | 'agent' | 'script';
+ path: string;
+ has_readme: boolean;
+}
+
+export interface WorkflowProgress {
+ current_step: string;
+ steps: string[];
+ step_status: Record;
+}
+
+export interface FileProgress {
+ file: string;
+ status: 'writing' | 'completed';
+}
+
+export interface Session {
+ id: string;
+ project_id: string;
+ project_name: string;
+ status: 'idle' | 'running' | 'completed' | 'error' | 'stopped';
+ created_at: string;
+ workflow_progress?: WorkflowProgress;
+ file_progress?: FileProgress;
+ current_step?: string;
+}
+
+export interface LogEntry {
+ level: 'info' | 'warning' | 'error' | 'debug';
+ message: string;
+ timestamp: string;
+ session_id?: string;
+}
+
+interface SessionContextType {
+ projects: Project[];
+ sessions: Session[];
+ currentSession: Session | null;
+ messages: Message[];
+ logs: LogEntry[];
+ streamingContent: string;
+ isStreaming: boolean;
+ isLoading: boolean;
+ loadProjects: () => Promise;
+ createSession: (projectId: string) => Promise;
+ selectSession: (sessionId: string, initialQuery?: string, sessionObj?: Session) => void;
+ sendMessage: (content: string) => void;
+ stopAgent: () => void;
+ clearLogs: () => void;
+}
+
+const SessionContext = createContext(undefined);
+
+const API_BASE = '/api';
+const WS_BASE = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`;
+
+const PROJECT_DESCRIPTION_OVERRIDES: Record = {
+ deep_research:
+ 'This project provides a framework for deep research, enabling agents to autonomously explore and execute complex tasks.',
+ code_genesis:
+ 'This project provides a code generation workflow that helps agents plan, scaffold, and refine software projects end-to-end.',
+ agent_skills:
+ 'This project provides a collection of reusable agent skills and tools to automate tasks and extend agent capabilities.',
+ doc_research:
+ 'This project provides a document research workflow for ingesting, searching, and summarizing documents with agent assistance.',
+ fin_research:
+ 'This project provides a financial research workflow that combines data analysis and information gathering to produce structured reports.',
+ singularity_cinema:
+ 'This project provides a creative workflow for generating stories, scripts, and media ideas with agent collaboration.',
+};
+
+export const SessionProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
+ const [projects, setProjects] = useState([]);
+ const [sessions, setSessions] = useState([]);
+ const [currentSession, setCurrentSession] = useState(null);
+ const [messages, setMessages] = useState([]);
+ const [logs, setLogs] = useState([]);
+ const [streamingContent, setStreamingContent] = useState('');
+ const [isStreaming, setIsStreaming] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [ws, setWs] = useState(null);
+ const pendingQueryRef = useRef(null);
+
+ // Load projects
+ const loadProjects = useCallback(async () => {
+ try {
+ const response = await fetch(`${API_BASE}/projects`);
+ if (response.ok) {
+ const data = await response.json();
+ const projectsWithOverrides: Project[] = (Array.isArray(data) ? data : []).map((project: Project) => {
+ const overrideDescription = project?.id ? PROJECT_DESCRIPTION_OVERRIDES[project.id] : undefined;
+ if (overrideDescription) {
+ return { ...project, description: overrideDescription };
+ }
+ return project;
+ });
+ setProjects(projectsWithOverrides);
+ }
+ } catch (error) {
+ console.error('Failed to load projects:', error);
+ }
+ }, []);
+
+ // Create session
+ const createSession = useCallback(async (projectId: string): Promise => {
+ try {
+ const response = await fetch(`${API_BASE}/sessions`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ project_id: projectId }),
+ });
+
+ if (response.ok) {
+ const session = await response.json();
+ setSessions(prev => [...prev, session]);
+ return session;
+ }
+ } catch (error) {
+ console.error('Failed to create session:', error);
+ }
+ return null;
+ }, []);
+
+ // Connect WebSocket for session
+ const connectWebSocket = useCallback((sessionId: string, initialQuery?: string) => {
+ if (ws) {
+ ws.close();
+ }
+
+ // Store pending query to send after connection
+ if (initialQuery) {
+ pendingQueryRef.current = initialQuery;
+ }
+
+ const socket = new WebSocket(`${WS_BASE}/session/${sessionId}`);
+
+ socket.onopen = () => {
+ console.log('WebSocket connected');
+ // Send pending query if exists
+ if (pendingQueryRef.current && socket.readyState === WebSocket.OPEN) {
+ const query = pendingQueryRef.current;
+ pendingQueryRef.current = null;
+
+ // Add user message locally
+ setMessages(prev => [...prev, {
+ id: Date.now().toString(),
+ role: 'user',
+ content: query,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ }]);
+
+ socket.send(JSON.stringify({
+ action: 'start',
+ query: query,
+ }));
+
+ setIsLoading(true);
+ }
+ };
+
+ socket.onmessage = (event) => {
+ const data = JSON.parse(event.data);
+ handleWebSocketMessage(data);
+ };
+
+ socket.onclose = () => {
+ console.log('WebSocket disconnected');
+ };
+
+ socket.onerror = (error) => {
+ console.error('WebSocket error:', error);
+ };
+
+ setWs(socket);
+ }, [ws]);
+
+ // Handle WebSocket messages
+ const handleWebSocketMessage = useCallback((data: Record) => {
+ const type = data.type as string;
+
+ switch (type) {
+ case 'message':
+ setMessages(prev => [...prev, {
+ id: Date.now().toString(),
+ role: data.role as Message['role'],
+ content: data.content as string,
+ type: (data.message_type as Message['type']) || 'text',
+ timestamp: new Date().toISOString(),
+ metadata: data.metadata as Record,
+ }]);
+ break;
+
+ case 'stream':
+ setStreamingContent(data.content as string);
+ setIsStreaming(!data.done);
+ if (data.done) {
+ setMessages(prev => [...prev, {
+ id: Date.now().toString(),
+ role: 'assistant',
+ content: data.content as string,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ }]);
+ setStreamingContent('');
+ }
+ break;
+
+ case 'log':
+ setLogs(prev => [...prev, {
+ level: data.level as LogEntry['level'],
+ message: data.message as string,
+ timestamp: data.timestamp as string,
+ session_id: currentSession?.id,
+ }]);
+ break;
+
+ case 'progress':
+ setCurrentSession(prev => {
+ if (!prev) return prev;
+
+ const progressType = data.type as string;
+ if (progressType === 'workflow') {
+ return {
+ ...prev,
+ workflow_progress: {
+ current_step: data.current_step as string,
+ steps: data.steps as string[],
+ step_status: data.step_status as WorkflowProgress['step_status'],
+ },
+ current_step: data.current_step as string,
+ };
+ } else if (progressType === 'file') {
+ return {
+ ...prev,
+ file_progress: {
+ file: data.file as string,
+ status: data.status as FileProgress['status'],
+ },
+ };
+ }
+ return prev;
+ });
+ break;
+
+ case 'status':
+ {
+ const nextStatus = (data.status as Session['status'] | undefined) ?? ((data as any)?.session?.status as Session['status'] | undefined);
+ if (nextStatus) {
+ setCurrentSession(prev => {
+ if (!prev) return prev;
+ if (nextStatus !== 'running') {
+ return { ...prev, status: nextStatus, workflow_progress: undefined, file_progress: undefined, current_step: undefined };
+ }
+ return { ...prev, status: nextStatus };
+ });
+ setSessions(prev => prev.map(s => (s.id === currentSession?.id ? { ...s, status: nextStatus } : s)));
+ setIsLoading(nextStatus === 'running');
+ if (nextStatus !== 'running') {
+ setIsStreaming(false);
+ setStreamingContent('');
+ }
+ }
+ }
+ break;
+
+ case 'complete':
+ setCurrentSession(prev => {
+ if (!prev) return prev;
+ return { ...prev, status: 'completed' };
+ });
+ setSessions(prev => prev.map(s => (s.id === currentSession?.id ? { ...s, status: 'completed' } : s)));
+ setIsLoading(false);
+ break;
+
+ case 'error':
+ setCurrentSession(prev => {
+ if (!prev) return prev;
+ return { ...prev, status: 'error' };
+ });
+ setSessions(prev => prev.map(s => (s.id === currentSession?.id ? { ...s, status: 'error' } : s)));
+ setMessages(prev => [...prev, {
+ id: Date.now().toString(),
+ role: 'system',
+ content: data.message as string,
+ type: 'error',
+ timestamp: new Date().toISOString(),
+ }]);
+ setIsLoading(false);
+ break;
+ }
+ }, [currentSession?.id]);
+
+ // Select session (can pass session object directly for newly created sessions)
+ const selectSession = useCallback((sessionId: string, initialQuery?: string, sessionObj?: Session) => {
+ // Use passed session object or find from sessions array
+ const session = sessionObj || sessions.find(s => s.id === sessionId);
+ if (session) {
+ console.log('[Session] Selecting session:', session.id);
+ setCurrentSession(session);
+ setMessages([]);
+ setLogs([]);
+ setStreamingContent('');
+ connectWebSocket(sessionId, initialQuery);
+ } else {
+ console.error('[Session] Session not found:', sessionId);
+ }
+ }, [sessions, connectWebSocket]);
+
+ // Send message
+ const sendMessage = useCallback((content: string) => {
+ if (!currentSession || !ws || ws.readyState !== WebSocket.OPEN) return;
+
+ // Add user message locally
+ setMessages(prev => [...prev, {
+ id: Date.now().toString(),
+ role: 'user',
+ content,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ }]);
+
+ // Send to server
+ ws.send(JSON.stringify({
+ action: 'start',
+ query: content,
+ }));
+
+ setIsLoading(true);
+ }, [currentSession, ws]);
+
+ // Stop agent
+ const stopAgent = useCallback(() => {
+ if (ws && ws.readyState === WebSocket.OPEN) {
+ ws.send(JSON.stringify({ action: 'stop' }));
+ }
+
+ // Optimistic UI update: reflect stop immediately without waiting for backend
+ setCurrentSession(prev => {
+ if (!prev) return prev;
+ return { ...prev, status: 'stopped', workflow_progress: undefined, file_progress: undefined, current_step: undefined };
+ });
+ setSessions(prev => prev.map(s => (s.id === currentSession?.id ? { ...s, status: 'stopped' } : s)));
+ setIsLoading(false);
+ setIsStreaming(false);
+ setStreamingContent('');
+ }, [ws, currentSession?.id]);
+
+ // Clear logs
+ const clearLogs = useCallback(() => {
+ setLogs([]);
+ }, []);
+
+ // Initial load
+ useEffect(() => {
+ loadProjects();
+ }, [loadProjects]);
+
+ // Cleanup WebSocket on unmount
+ useEffect(() => {
+ return () => {
+ if (ws) {
+ ws.close();
+ }
+ };
+ }, [ws]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useSession = () => {
+ const context = useContext(SessionContext);
+ if (!context) {
+ throw new Error('useSession must be used within a SessionProvider');
+ }
+ return context;
+};
diff --git a/webui/frontend/src/context/ThemeContext.tsx b/webui/frontend/src/context/ThemeContext.tsx
new file mode 100644
index 000000000..dac05dc7a
--- /dev/null
+++ b/webui/frontend/src/context/ThemeContext.tsx
@@ -0,0 +1,281 @@
+import React, { createContext, useContext, useState, useMemo, ReactNode } from 'react';
+import { createTheme, Theme, PaletteMode } from '@mui/material';
+
+// Luxury color palettes
+const darkPalette = {
+ primary: {
+ main: '#C9A962', // Gold accent
+ light: '#E5D4A1',
+ dark: '#A08840',
+ contrastText: '#0A0A0A',
+ },
+ secondary: {
+ main: '#6B7280',
+ light: '#9CA3AF',
+ dark: '#4B5563',
+ contrastText: '#FFFFFF',
+ },
+ background: {
+ default: '#0A0A0A',
+ paper: '#141414',
+ },
+ text: {
+ primary: '#F5F5F5',
+ secondary: '#A0A0A0',
+ },
+ divider: 'rgba(201, 169, 98, 0.12)',
+ error: {
+ main: '#EF4444',
+ light: '#F87171',
+ dark: '#DC2626',
+ },
+ success: {
+ main: '#10B981',
+ light: '#34D399',
+ dark: '#059669',
+ },
+ warning: {
+ main: '#F59E0B',
+ light: '#FBBF24',
+ dark: '#D97706',
+ },
+ info: {
+ main: '#3B82F6',
+ light: '#60A5FA',
+ dark: '#2563EB',
+ },
+};
+
+const lightPalette = {
+ primary: {
+ main: '#1A1A1A',
+ light: '#404040',
+ dark: '#0A0A0A',
+ contrastText: '#FFFFFF',
+ },
+ secondary: {
+ main: '#C9A962',
+ light: '#E5D4A1',
+ dark: '#A08840',
+ contrastText: '#0A0A0A',
+ },
+ background: {
+ default: '#FAFAFA',
+ paper: '#FFFFFF',
+ },
+ text: {
+ primary: '#1A1A1A',
+ secondary: '#6B7280',
+ },
+ divider: 'rgba(0, 0, 0, 0.08)',
+ error: {
+ main: '#DC2626',
+ light: '#EF4444',
+ dark: '#B91C1C',
+ },
+ success: {
+ main: '#059669',
+ light: '#10B981',
+ dark: '#047857',
+ },
+ warning: {
+ main: '#D97706',
+ light: '#F59E0B',
+ dark: '#B45309',
+ },
+ info: {
+ main: '#2563EB',
+ light: '#3B82F6',
+ dark: '#1D4ED8',
+ },
+};
+
+const createAppTheme = (mode: PaletteMode): Theme => {
+ const palette = mode === 'dark' ? darkPalette : lightPalette;
+
+ return createTheme({
+ palette: {
+ mode,
+ ...palette,
+ },
+ typography: {
+ fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
+ h1: {
+ fontSize: '3rem',
+ fontWeight: 600,
+ letterSpacing: '-0.02em',
+ },
+ h2: {
+ fontSize: '2.25rem',
+ fontWeight: 600,
+ letterSpacing: '-0.02em',
+ },
+ h3: {
+ fontSize: '1.875rem',
+ fontWeight: 600,
+ letterSpacing: '-0.01em',
+ },
+ h4: {
+ fontSize: '1.5rem',
+ fontWeight: 600,
+ },
+ h5: {
+ fontSize: '1.25rem',
+ fontWeight: 500,
+ },
+ h6: {
+ fontSize: '1rem',
+ fontWeight: 500,
+ },
+ body1: {
+ fontSize: '1rem',
+ lineHeight: 1.6,
+ },
+ body2: {
+ fontSize: '0.875rem',
+ lineHeight: 1.5,
+ },
+ button: {
+ textTransform: 'none',
+ fontWeight: 500,
+ },
+ },
+ shape: {
+ borderRadius: 12,
+ },
+ components: {
+ MuiButton: {
+ styleOverrides: {
+ root: {
+ borderRadius: 8,
+ padding: '10px 24px',
+ fontSize: '0.875rem',
+ fontWeight: 500,
+ boxShadow: 'none',
+ '&:hover': {
+ boxShadow: 'none',
+ },
+ },
+ contained: {
+ '&:hover': {
+ transform: 'translateY(-1px)',
+ transition: 'transform 0.2s ease',
+ },
+ },
+ outlined: {
+ borderWidth: 1.5,
+ '&:hover': {
+ borderWidth: 1.5,
+ },
+ },
+ },
+ },
+ MuiPaper: {
+ styleOverrides: {
+ root: {
+ backgroundImage: 'none',
+ },
+ elevation1: {
+ boxShadow: mode === 'dark'
+ ? '0 1px 3px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.2)'
+ : '0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04)',
+ },
+ elevation2: {
+ boxShadow: mode === 'dark'
+ ? '0 4px 6px rgba(0,0,0,0.3), 0 2px 4px rgba(0,0,0,0.2)'
+ : '0 4px 6px rgba(0,0,0,0.06), 0 2px 4px rgba(0,0,0,0.04)',
+ },
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: {
+ borderRadius: 16,
+ border: `1px solid ${palette.divider}`,
+ },
+ },
+ },
+ MuiTextField: {
+ styleOverrides: {
+ root: {
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 10,
+ '&:hover .MuiOutlinedInput-notchedOutline': {
+ borderColor: palette.primary.main,
+ },
+ },
+ },
+ },
+ },
+ MuiChip: {
+ styleOverrides: {
+ root: {
+ borderRadius: 6,
+ fontWeight: 500,
+ },
+ },
+ },
+ MuiTooltip: {
+ styleOverrides: {
+ tooltip: {
+ backgroundColor: mode === 'dark' ? '#2D2D2D' : '#1A1A1A',
+ fontSize: '0.75rem',
+ padding: '8px 12px',
+ borderRadius: 6,
+ },
+ },
+ },
+ MuiDialog: {
+ styleOverrides: {
+ paper: {
+ borderRadius: 20,
+ },
+ },
+ },
+ MuiDrawer: {
+ styleOverrides: {
+ paper: {
+ borderRight: `1px solid ${palette.divider}`,
+ },
+ },
+ },
+ },
+ });
+};
+
+interface ThemeContextType {
+ mode: PaletteMode;
+ theme: Theme;
+ toggleTheme: () => void;
+}
+
+const ThemeContext = createContext(undefined);
+
+export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
+ const [mode, setMode] = useState(() => {
+ const stored = localStorage.getItem('theme-mode');
+ return (stored as PaletteMode) || 'dark';
+ });
+
+ const theme = useMemo(() => createAppTheme(mode), [mode]);
+
+ const toggleTheme = () => {
+ const newMode = mode === 'dark' ? 'light' : 'dark';
+ setMode(newMode);
+ localStorage.setItem('theme-mode', newMode);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useThemeContext = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useThemeContext must be used within a ThemeProvider');
+ }
+ return context;
+};
diff --git a/webui/frontend/src/main.tsx b/webui/frontend/src/main.tsx
new file mode 100644
index 000000000..7fd1eb284
--- /dev/null
+++ b/webui/frontend/src/main.tsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import { ThemeProvider as MuiThemeProvider, CssBaseline } from '@mui/material'
+import App from './App'
+import { ThemeProvider, useThemeContext } from './context/ThemeContext'
+import { SessionProvider } from './context/SessionContext'
+
+const ThemedApp: React.FC = () => {
+ const { theme } = useThemeContext();
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+
+ ,
+)
diff --git a/webui/frontend/tsconfig.json b/webui/frontend/tsconfig.json
new file mode 100644
index 000000000..3934b8f6d
--- /dev/null
+++ b/webui/frontend/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/webui/frontend/tsconfig.node.json b/webui/frontend/tsconfig.node.json
new file mode 100644
index 000000000..97ede7ee6
--- /dev/null
+++ b/webui/frontend/tsconfig.node.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/webui/frontend/vite.config.ts b/webui/frontend/vite.config.ts
new file mode 100644
index 000000000..63159c4b1
--- /dev/null
+++ b/webui/frontend/vite.config.ts
@@ -0,0 +1,23 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 5173,
+ proxy: {
+ '/api': {
+ target: 'http://localhost:7860',
+ changeOrigin: true
+ },
+ '/ws': {
+ target: 'ws://localhost:7860',
+ ws: true
+ }
+ }
+ },
+ build: {
+ outDir: 'dist',
+ sourcemap: true
+ }
+})
diff --git a/webui/requirements.txt b/webui/requirements.txt
new file mode 100644
index 000000000..c8aa95ee8
--- /dev/null
+++ b/webui/requirements.txt
@@ -0,0 +1,3 @@
+aiohttp
+ms-agent
+pandas
diff --git a/webui/start.sh b/webui/start.sh
new file mode 100755
index 000000000..bf8a38336
--- /dev/null
+++ b/webui/start.sh
@@ -0,0 +1,159 @@
+#!/bin/bash
+
+# MS-Agent Web UI Startup Script
+# This script starts both the backend server and frontend development server
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Get the directory where the script is located
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+echo -e "${BLUE}"
+echo "╔════════════════════════════════════════════════════════════╗"
+echo "║ MS-Agent Web UI ║"
+echo "║ Intelligent Agent Platform ║"
+echo "╚════════════════════════════════════════════════════════════╝"
+echo -e "${NC}"
+
+# Check for Python 3.10+
+PYTHON_CMD=""
+for py in python3.12 python3.11 python3.10; do
+ if command -v $py &> /dev/null; then
+ PYTHON_CMD=$py
+ break
+ fi
+done
+
+if [ -z "$PYTHON_CMD" ]; then
+ echo -e "${RED}Error: Python 3.10 or higher is required but not found.${NC}"
+ echo -e "${YELLOW}Please install Python 3.10+ and try again.${NC}"
+ exit 1
+fi
+
+echo -e "${GREEN}Using Python: $PYTHON_CMD ($(${PYTHON_CMD} --version))${NC}"
+
+# Check for Node.js
+if ! command -v node &> /dev/null; then
+ echo -e "${RED}Error: Node.js is required but not installed.${NC}"
+ exit 1
+fi
+
+# Create virtual environment if not exists
+VENV_DIR="$SCRIPT_DIR/.venv"
+if [ ! -d "$VENV_DIR" ]; then
+ echo -e "${YELLOW}Creating Python virtual environment with ${PYTHON_CMD}...${NC}"
+ $PYTHON_CMD -m venv "$VENV_DIR"
+fi
+
+# Activate virtual environment
+source "$VENV_DIR/bin/activate"
+
+# Install Python dependencies
+echo -e "${YELLOW}Installing Python dependencies...${NC}"
+pip install -q -r "$SCRIPT_DIR/requirements.txt"
+
+# Install ms-agent in development mode if not installed
+if ! python -c "import ms_agent" 2>/dev/null; then
+ echo -e "${YELLOW}Installing ms-agent...${NC}"
+ pip install -q -e "$SCRIPT_DIR/../ms-agent"
+fi
+
+
+# Install frontend dependencies if needed
+if [ ! -d "$SCRIPT_DIR/frontend/node_modules" ]; then
+ echo -e "${YELLOW}Installing frontend dependencies...${NC}"
+ cd "$SCRIPT_DIR/frontend"
+ npm install
+ cd "$SCRIPT_DIR"
+fi
+
+# Parse command line arguments
+MODE="dev"
+PORT=7860
+HOST="0.0.0.0"
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --production|-p)
+ MODE="production"
+ shift
+ ;;
+ --port)
+ PORT="$2"
+ shift 2
+ ;;
+ --host)
+ HOST="$2"
+ shift 2
+ ;;
+ *)
+ echo "Unknown option: $1"
+ exit 1
+ ;;
+ esac
+done
+
+# Build frontend for production
+if [ "$MODE" = "production" ]; then
+ echo -e "${YELLOW}Building frontend for production...${NC}"
+ cd "$SCRIPT_DIR/frontend"
+ npm run build
+ cd "$SCRIPT_DIR"
+fi
+
+# Function to cleanup background processes
+cleanup() {
+ echo -e "\n${YELLOW}Shutting down...${NC}"
+ if [ ! -z "$BACKEND_PID" ]; then
+ kill $BACKEND_PID 2>/dev/null
+ fi
+ if [ ! -z "$FRONTEND_PID" ]; then
+ kill $FRONTEND_PID 2>/dev/null
+ fi
+ exit 0
+}
+
+trap cleanup SIGINT SIGTERM
+
+# Start backend server
+echo -e "${GREEN}Starting backend server on port $PORT...${NC}"
+cd "$SCRIPT_DIR/backend"
+if [ "$MODE" = "production" ]; then
+ python main.py --host "$HOST" --port "$PORT" &
+else
+ python main.py --host "$HOST" --port "$PORT" --reload &
+fi
+BACKEND_PID=$!
+cd "$SCRIPT_DIR"
+
+# Wait for backend to start
+sleep 2
+
+if [ "$MODE" = "dev" ]; then
+ # Start frontend development server
+ echo -e "${GREEN}Starting frontend development server...${NC}"
+ cd "$SCRIPT_DIR/frontend"
+ npm run dev &
+ FRONTEND_PID=$!
+ cd "$SCRIPT_DIR"
+
+ echo -e "\n${GREEN}✓ Development servers are running!${NC}"
+ echo -e " Backend: ${BLUE}http://localhost:$PORT${NC}"
+ echo -e " Frontend: ${BLUE}http://localhost:5173${NC}"
+ echo -e " API Docs: ${BLUE}http://localhost:$PORT/docs${NC}"
+else
+ echo -e "\n${GREEN}✓ Production server is running!${NC}"
+ echo -e " Server: ${BLUE}http://$HOST:$PORT${NC}"
+fi
+
+echo -e "\n${YELLOW}Press Ctrl+C to stop the servers${NC}\n"
+
+# Wait for processes
+wait