diff --git a/.replit b/.replit new file mode 100644 index 000000000..cb396e143 --- /dev/null +++ b/.replit @@ -0,0 +1,60 @@ +modules = ["python-3.12", "nodejs-20", "rust-stable", "bash"] +[agent] +expertMode = true +integrations = ["github:1.0.0"] + +[nix] +channel = "stable-25_05" +packages = ["libxcrypt"] + +[[ports]] +localPort = 5000 +externalPort = 80 + +[[ports]] +localPort = 8000 +externalPort = 8000 + +[workflows] +runButton = "Project" + +[[workflows.workflow]] +name = "Project" +mode = "parallel" +author = "agent" + +[[workflows.workflow.tasks]] +task = "workflow.run" +args = "Start application" + +[[workflows.workflow.tasks]] +task = "workflow.run" +args = "Backend API" + +[[workflows.workflow]] +name = "Start application" +author = "agent" + +[workflows.workflow.metadata] +outputType = "webview" + +[[workflows.workflow.tasks]] +task = "shell.exec" +args = "python run.py" +waitForPort = 5000 + +[[workflows.workflow]] +name = "Backend API" +author = "agent" + +[[workflows.workflow.tasks]] +task = "shell.exec" +args = "python backend/main.py" +waitForPort = 8000 + +[workflows.workflow.metadata] +outputType = "console" + +[deployment] +deploymentTarget = "autoscale" +run = ["python", "run.py"] diff --git a/attached_assets/Pasted-Build-a-clean-modern-web-dashboard-for-managing-and-int_1776597969858.txt b/attached_assets/Pasted-Build-a-clean-modern-web-dashboard-for-managing-and-int_1776597969858.txt new file mode 100644 index 000000000..647546721 --- /dev/null +++ b/attached_assets/Pasted-Build-a-clean-modern-web-dashboard-for-managing-and-int_1776597969858.txt @@ -0,0 +1,168 @@ +Build a clean, modern web dashboard for managing and interacting with AI agents powered by PraisonAI. Use React + Vite for the frontend, and a simple Python FastAPI backend. Keep it simple but functional — no overengineering. + +--- + +## TECH STACK +- Frontend: React + Vite + TailwindCSS (CDN, no build step for Tailwind) +- Backend: FastAPI (Python) +- Storage: JSON files (no database needed yet, keep it simple) +- API calls: fetch() to the FastAPI backend + +--- + +## PROJECT STRUCTURE +/backend + main.py # FastAPI app + agents_store.json # persists agent definitions + history_store.json # persists conversation history + +/frontend + index.html + src/ + App.jsx + components/ + Sidebar.jsx + AgentList.jsx + AgentDetail.jsx + ChatPanel.jsx + ActivityLog.jsx + AgentForm.jsx + +--- + +## BACKEND (FastAPI - main.py) + +Create these REST endpoints: + +### Agents CRUD +GET /agents → list all agents +POST /agents → create agent +PUT /agents/{id} → update agent +DELETE /agents/{id} → delete agent +GET /agents/{id} → get single agent details + +### Chat +POST /agents/{id}/chat → send message to agent, returns response + activity log +GET /agents/{id}/history → get full conversation history +DELETE /agents/{id}/history → clear history + +### Activity +GET /agents/{id}/activity → get activity log (tools used, connections, actions) + +Each agent object should look like: +{ + "id": "uuid", + "name": "string", + "role": "string", + "instructions": "string", + "tools": ["list of tool names"], + "connections": ["other agent ids or external APIs"], + "llm": "string (e.g. gpt-4o-mini)", + "created_at": "timestamp", + "status": "active | inactive" +} + +Each chat call should: +1. Load agent config +2. Run a PraisonAI agent with that config +3. Capture stdout/tool calls/actions into an activity log entry +4. Save message + response + activity to history +5. Return all of it in one response + +--- + +## FRONTEND LAYOUT + +Split into 3 columns: + +### LEFT SIDEBAR (narrow) +- App logo/name "AgentOS" +- List of all agents (name + status dot green/grey) +- "+ New Agent" button at bottom +- Click agent → loads it in center panel + +### CENTER PANEL (main) +Two tabs at top: "Chat" | "Details" + +#### Chat tab: +- Conversation history (bubbles: user right, agent left) +- Each agent message has a collapsible "Activity" section below it showing: + - What tools were called + - What external connections were used + - Raw output / reasoning steps +- Input box at bottom + Send button +- "Clear History" button top right + +#### Details tab: +- Shows agent config (name, role, instructions, LLM, tools, connections) +- Edit button → opens inline form to modify +- Shows stats: total messages, last active, tools used count + +### RIGHT PANEL (narrower) +- "Activity Feed" — real-time log of what the selected agent has done +- Each entry: timestamp, action type (tool_call, api_call, message, error), short description +- Color coded: blue=tool, green=success, red=error, grey=message +- Scrollable, newest at top + +--- + +## AGENT FORM (Add/Edit modal or inline) +Fields: +- Name (text input) +- Role (text input, short description) +- Instructions / System Prompt (large textarea) +- LLM Model (dropdown: gpt-4o-mini, gpt-4o, claude-3-haiku, claude-sonnet, gemini-flash) +- Tools (multi-select checkboxes): web_search, code_interpreter, file_reader, calculator, wikipedia +- Connections (text inputs, add/remove rows): label + URL or agent name +- Status toggle (active/inactive) +Save / Cancel buttons + +--- + +## STYLE GUIDE +- Dark theme (background #0f1117, panels #1a1d27, accent #6c63ff purple) +- Clean sans-serif font (Inter from Google Fonts) +- Subtle borders (#2a2d3e) +- Agent status dot: green (#22c55e) active, grey (#6b7280) inactive +- Activity log colors: blue (#3b82f6) tool, green (#22c55e) success, red (#ef4444) error +- Rounded corners (8px), subtle shadows +- Mobile responsive (sidebar collapses to hamburger on small screens) + +--- + +## SAMPLE DATA +Pre-populate agents_store.json with 2 example agents so the UI is not empty: + +Agent 1: +- Name: "Research Agent" +- Role: "Searches the web and summarizes information" +- Tools: web_search, wikipedia +- LLM: gpt-4o-mini + +Agent 2: +- Name: "Code Agent" +- Role: "Writes and explains code" +- Tools: code_interpreter, calculator +- LLM: gpt-4o-mini + +--- + +## IMPORTANT CONSTRAINTS +- No TypeScript (plain JSX only) +- No Redux (useState/useEffect only) +- No external UI libraries (just Tailwind utility classes) +- JSON file storage only (no SQLite, no Postgres) +- Keep each file under 200 lines if possible, split into components +- PraisonAI backend calls should use praisonaiagents library +- Add CORS middleware to FastAPI so frontend can call backend freely +- Read OPENAI_API_KEY from environment variable (os.environ) + +--- + +## DELIVERABLE +- Working React frontend that talks to FastAPI backend +- I can add/edit/delete agents via the UI +- I can chat with any agent and see what it does +- History is saved and persists across page refreshes +- Activity log updates after each interaction +- README with how to run both frontend and backend \ No newline at end of file diff --git a/backend/agents_store.json b/backend/agents_store.json new file mode 100644 index 000000000..dc7af65f7 --- /dev/null +++ b/backend/agents_store.json @@ -0,0 +1,24 @@ +[ + { + "id": "agent-001", + "name": "Research Agent", + "role": "Searches the web and summarizes information", + "instructions": "You are a research assistant. Search the web and Wikipedia to find accurate, up-to-date information. Summarize findings clearly and concisely. Always cite your sources.", + "tools": ["web_search", "wikipedia"], + "connections": [], + "llm": "gpt-4o-mini", + "created_at": "2025-01-01T00:00:00Z", + "status": "active" + }, + { + "id": "agent-002", + "name": "Code Agent", + "role": "Writes and explains code", + "instructions": "You are a coding assistant. Write clean, well-commented code. Explain how code works step by step. Help debug issues and suggest improvements.", + "tools": ["code_interpreter", "calculator"], + "connections": [], + "llm": "gpt-4o-mini", + "created_at": "2025-01-01T00:00:00Z", + "status": "active" + } +] diff --git a/backend/db.py b/backend/db.py new file mode 100644 index 000000000..cfad2323d --- /dev/null +++ b/backend/db.py @@ -0,0 +1,278 @@ +"""SQLite persistence for AgentOS agents and chat history.""" +import json +import os +import sqlite3 +from contextlib import contextmanager +from datetime import datetime, timezone +from typing import Dict, Iterator, List, Optional + +BASE_DIR = os.path.dirname(__file__) +DB_FILE = os.environ.get("AGENTOS_DB", os.path.join(BASE_DIR, "agentos.db")) +LEGACY_AGENTS_FILE = os.path.join(BASE_DIR, "agents_store.json") +LEGACY_HISTORY_FILE = os.path.join(BASE_DIR, "history_store.json") + +ALLOWED_STATUS = {"active", "auditing", "decommissioned"} + +SCHEMA = """ +CREATE TABLE IF NOT EXISTS agents ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + instructions TEXT NOT NULL DEFAULT '', + model TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'active' + CHECK (status IN ('active', 'auditing', 'decommissioned')), + generation INTEGER NOT NULL DEFAULT 1, + parent_id TEXT REFERENCES agents(id) ON DELETE SET NULL, + performance_score REAL, + token_spend INTEGER NOT NULL DEFAULT 0, + exit_summary TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS chat_history ( + id TEXT PRIMARY KEY, + agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE, + user_message TEXT NOT NULL, + agent_response TEXT NOT NULL, + activity TEXT NOT NULL DEFAULT '[]', + timestamp TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_chat_history_agent_ts + ON chat_history(agent_id, timestamp); +""" + + +@contextmanager +def get_conn() -> Iterator[sqlite3.Connection]: + conn = sqlite3.connect(DB_FILE) + conn.row_factory = sqlite3.Row + conn.execute("PRAGMA foreign_keys = ON") + try: + yield conn + conn.commit() + finally: + conn.close() + + +def _now() -> str: + # Timezone-aware UTC timestamp, ISO-8601 with trailing Z to match prior shape. + return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z" + + +def _row_to_dict(row: sqlite3.Row) -> Dict: + d = dict(row) + # Legacy alias so existing frontend/clients that read `llm` still work. + d["llm"] = d["model"] + return d + + +def init_db() -> None: + with get_conn() as conn: + conn.executescript(SCHEMA) + cur = conn.execute("SELECT COUNT(*) FROM agents") + if cur.fetchone()[0] == 0: + _migrate_from_json(conn) + cur = conn.execute("SELECT COUNT(*) FROM chat_history") + if cur.fetchone()[0] == 0: + _migrate_history_from_json(conn) + + +def _migrate_from_json(conn: sqlite3.Connection) -> None: + if not os.path.exists(LEGACY_AGENTS_FILE): + return + try: + with open(LEGACY_AGENTS_FILE) as f: + legacy = json.load(f) + except (OSError, json.JSONDecodeError): + return + for a in legacy: + status = a.get("status", "active") + if status not in ALLOWED_STATUS: + status = "active" + created = a.get("created_at") or _now() + conn.execute( + """ + INSERT OR IGNORE INTO agents + (id, name, instructions, model, status, generation, parent_id, + performance_score, token_spend, exit_summary, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, 1, NULL, NULL, 0, NULL, ?, ?) + """, + ( + a["id"], + a.get("name", "Unnamed"), + a.get("instructions", ""), + a.get("llm") or a.get("model") or "gpt-4o-mini", + status, + created, + created, + ), + ) + + +def _migrate_history_from_json(conn: sqlite3.Connection) -> None: + if not os.path.exists(LEGACY_HISTORY_FILE): + return + try: + with open(LEGACY_HISTORY_FILE) as f: + legacy = json.load(f) + except (OSError, json.JSONDecodeError): + return + if not isinstance(legacy, dict): + return + for agent_id, entries in legacy.items(): + if not isinstance(entries, list): + continue + for entry in entries: + conn.execute( + """ + INSERT OR IGNORE INTO chat_history + (id, agent_id, user_message, agent_response, activity, timestamp) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + entry.get("id") or os.urandom(4).hex(), + agent_id, + entry.get("user_message", ""), + entry.get("agent_response", ""), + json.dumps(entry.get("activity", [])), + entry.get("timestamp") or _now(), + ), + ) + + +def list_agents() -> List[Dict]: + with get_conn() as conn: + rows = conn.execute( + "SELECT * FROM agents ORDER BY created_at ASC" + ).fetchall() + return [_row_to_dict(r) for r in rows] + + +def get_agent(agent_id: str) -> Optional[Dict]: + with get_conn() as conn: + row = conn.execute( + "SELECT * FROM agents WHERE id = ?", (agent_id,) + ).fetchone() + return _row_to_dict(row) if row else None + + +def create_agent(agent: Dict) -> Dict: + now = _now() + with get_conn() as conn: + conn.execute( + """ + INSERT INTO agents + (id, name, instructions, model, status, generation, parent_id, + performance_score, token_spend, exit_summary, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + agent["id"], + agent["name"], + agent.get("instructions", ""), + agent["model"], + agent.get("status", "active"), + agent.get("generation", 1), + agent.get("parent_id"), + agent.get("performance_score"), + agent.get("token_spend", 0), + agent.get("exit_summary"), + now, + now, + ), + ) + return get_agent(agent["id"]) # type: ignore[return-value] + + +UPDATABLE_FIELDS = { + "name", + "instructions", + "model", + "status", + "generation", + "parent_id", + "performance_score", + "token_spend", + "exit_summary", +} + + +def update_agent(agent_id: str, fields: Dict) -> Optional[Dict]: + sets = {k: v for k, v in fields.items() if k in UPDATABLE_FIELDS} + if not sets: + return get_agent(agent_id) + columns = ", ".join(f"{k} = ?" for k in sets) + ", updated_at = ?" + values = list(sets.values()) + [_now(), agent_id] + with get_conn() as conn: + cur = conn.execute( + f"UPDATE agents SET {columns} WHERE id = ?", values + ) + if cur.rowcount == 0: + return None + return get_agent(agent_id) + + +def delete_agent(agent_id: str) -> bool: + with get_conn() as conn: + cur = conn.execute("DELETE FROM agents WHERE id = ?", (agent_id,)) + return cur.rowcount > 0 + + +# ----- chat history -------------------------------------------------------- + + +def _row_to_history(row: sqlite3.Row) -> Dict: + d = dict(row) + try: + d["activity"] = json.loads(d.get("activity") or "[]") + except json.JSONDecodeError: + d["activity"] = [] + d.pop("agent_id", None) + return d + + +def add_history_entry(agent_id: str, entry: Dict) -> Dict: + with get_conn() as conn: + conn.execute( + """ + INSERT INTO chat_history + (id, agent_id, user_message, agent_response, activity, timestamp) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + entry["id"], + agent_id, + entry["user_message"], + entry["agent_response"], + json.dumps(entry.get("activity", [])), + entry["timestamp"], + ), + ) + return entry + + +def list_history(agent_id: str) -> List[Dict]: + with get_conn() as conn: + rows = conn.execute( + "SELECT * FROM chat_history WHERE agent_id = ? ORDER BY timestamp ASC", + (agent_id,), + ).fetchall() + return [_row_to_history(r) for r in rows] + + +def clear_history(agent_id: str) -> None: + with get_conn() as conn: + conn.execute("DELETE FROM chat_history WHERE agent_id = ?", (agent_id,)) + + +def list_activity(agent_id: str, limit: int = 100) -> List[Dict]: + """Flattened activity across all of an agent's chat entries, newest first, capped.""" + entries = list_history(agent_id) + activity: List[Dict] = [] + for entry in reversed(entries): + activity.extend(entry.get("activity", [])) + if len(activity) >= limit: + break + return activity[:limit] diff --git a/backend/history_store.json b/backend/history_store.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/backend/history_store.json @@ -0,0 +1 @@ +{} diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 000000000..eae326119 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,223 @@ +"""AgentOS FastAPI backend.""" +import os +import uuid +from contextlib import asynccontextmanager +from datetime import datetime, timezone +from typing import Dict, List, Optional + +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel, ConfigDict + +try: + from . import db as agentdb # package import (uvicorn backend.main:app) +except ImportError: # pragma: no cover - script execution fallback + import sys + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import db as agentdb # type: ignore + + +@asynccontextmanager +async def lifespan(app: FastAPI): + agentdb.init_db() + yield + + +app = FastAPI(title="AgentOS API", lifespan=lifespan) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +def _now_iso() -> str: + return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z" + + +class AgentCreate(BaseModel): + # Accept unknown legacy fields (role, tools, connections) without error. + # `protected_namespaces=()` silences Pydantic v2 warnings about the + # `model` field name colliding with its protected namespace. + model_config = ConfigDict(extra="ignore", protected_namespaces=()) + + name: str + instructions: str = "" + model: Optional[str] = None + llm: Optional[str] = None # legacy alias for `model` + status: str = "active" + generation: int = 1 + parent_id: Optional[str] = None + performance_score: Optional[float] = None + token_spend: int = 0 + exit_summary: Optional[str] = None + + +class AgentUpdate(BaseModel): + model_config = ConfigDict(extra="ignore", protected_namespaces=()) + + name: Optional[str] = None + instructions: Optional[str] = None + model: Optional[str] = None + llm: Optional[str] = None # legacy alias for `model` + status: Optional[str] = None + generation: Optional[int] = None + parent_id: Optional[str] = None + performance_score: Optional[float] = None + token_spend: Optional[int] = None + exit_summary: Optional[str] = None + + +class ChatMessage(BaseModel): + message: str + + +def _validate_status(status: str) -> None: + if status not in agentdb.ALLOWED_STATUS: + raise HTTPException( + status_code=400, + detail=f"status must be one of {sorted(agentdb.ALLOWED_STATUS)}", + ) + + +@app.get("/agents") +def list_agents(): + return agentdb.list_agents() + + +@app.get("/agents/{agent_id}") +def get_agent(agent_id: str): + agent = agentdb.get_agent(agent_id) + if not agent: + raise HTTPException(status_code=404, detail="Agent not found") + return agent + + +@app.post("/agents", status_code=201) +def create_agent(data: AgentCreate): + _validate_status(data.status) + if data.parent_id and not agentdb.get_agent(data.parent_id): + raise HTTPException(status_code=400, detail="parent_id does not exist") + record = { + "id": f"agent-{uuid.uuid4().hex[:8]}", + "name": data.name, + "instructions": data.instructions, + "model": data.model or data.llm or "gpt-4o-mini", + "status": data.status, + "generation": data.generation, + "parent_id": data.parent_id, + "performance_score": data.performance_score, + "token_spend": data.token_spend, + "exit_summary": data.exit_summary, + } + return agentdb.create_agent(record) + + +@app.put("/agents/{agent_id}") +def update_agent(agent_id: str, data: AgentUpdate): + fields = data.model_dump(exclude_none=True) + # Legacy `llm` alias -> `model` + if "llm" in fields and "model" not in fields: + fields["model"] = fields["llm"] + fields.pop("llm", None) + + if "status" in fields: + _validate_status(fields["status"]) + if fields.get("parent_id") and not agentdb.get_agent(fields["parent_id"]): + raise HTTPException(status_code=400, detail="parent_id does not exist") + + updated = agentdb.update_agent(agent_id, fields) + if updated is None: + raise HTTPException(status_code=404, detail="Agent not found") + return updated + + +@app.delete("/agents/{agent_id}", status_code=204) +def delete_agent(agent_id: str): + # chat_history rows cascade via FK ON DELETE CASCADE + if not agentdb.delete_agent(agent_id): + raise HTTPException(status_code=404, detail="Agent not found") + + +@app.post("/agents/{agent_id}/chat") +def chat_with_agent(agent_id: str, body: ChatMessage): + agent = agentdb.get_agent(agent_id) + if not agent: + raise HTTPException(status_code=404, detail="Agent not found") + + activity: List[Dict] = [] + response_text = "" + + api_key = os.environ.get("OPENAI_API_KEY", "") + if api_key: + try: + from praisonaiagents import Agent + activity.append(_log("tool", f"Initializing {agent['model']} agent")) + pa_agent = Agent( + name=agent["name"], + instructions=agent["instructions"], + llm=agent["model"], + ) + activity.append(_log("tool", "Running agent inference")) + result = pa_agent.start(body.message) + response_text = str(result) + activity.append(_log("success", "Agent response generated")) + except Exception as e: + activity.append(_log("error", f"Agent error: {str(e)[:120]}")) + response_text = f"Agent encountered an error: {str(e)[:200]}" + else: + activity.append(_log("message", "No OPENAI_API_KEY set — using demo mode")) + response_text = _demo_response(agent, body.message) + activity.append(_log("success", "Demo response generated")) + + entry = { + "id": uuid.uuid4().hex[:8], + "user_message": body.message, + "agent_response": response_text, + "activity": activity, + "timestamp": _now_iso(), + } + agentdb.add_history_entry(agent_id, entry) + return entry + + +@app.get("/agents/{agent_id}/history") +def get_history(agent_id: str): + return agentdb.list_history(agent_id) + + +@app.delete("/agents/{agent_id}/history", status_code=204) +def clear_history(agent_id: str): + agentdb.clear_history(agent_id) + + +@app.get("/agents/{agent_id}/activity") +def get_activity(agent_id: str): + return agentdb.list_activity(agent_id, limit=100) + + +def _log(action_type: str, description: str) -> Dict: + return { + "id": uuid.uuid4().hex[:8], + "timestamp": _now_iso(), + "type": action_type, + "description": description, + } + + +def _demo_response(agent: Dict, message: str) -> str: + name = agent.get("name", "Agent") + return ( + f"Hi! I'm {name}. " + f"You asked: \"{message}\"\n\n" + f"I'm running on {agent.get('model', 'unknown model')}. " + f"To enable real AI responses, set your OPENAI_API_KEY environment variable." + ) + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="localhost", port=8000) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 000000000..d46fa6908 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,47 @@ + + + + + + AgentOS + + + + + + + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 000000000..8b06b9b84 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1677 @@ +{ + "name": "agentos-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agentos-frontend", + "version": "1.0.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.0", + "vite": "^5.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@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.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@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/baseline-browser-mapping": { + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/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.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/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/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "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/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "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.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/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.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 000000000..dd4abb28d --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "agentos-frontend", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0 --port 5000", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.0", + "vite": "^5.0.0" + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 000000000..998737836 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,271 @@ +import { useState, useEffect, useCallback, useRef } from 'react' +import Sidebar from './components/Sidebar' +import ChatPanel from './components/ChatPanel' +import AgentDetail from './components/AgentDetail' +import ActivityLog from './components/ActivityLog' +import AgentForm from './components/AgentForm' + +const API = '/api' + +export default function App() { + const [agents, setAgents] = useState([]) + const [selectedId, setSelectedId] = useState(null) + const [activeTab, setActiveTab] = useState('chat') + const [showForm, setShowForm] = useState(false) + const [editingAgent, setEditingAgent] = useState(null) + const [sidebarOpen, setSidebarOpen] = useState(true) + const [history, setHistory] = useState([]) + const [activity, setActivity] = useState([]) + const [loading, setLoading] = useState(false) + + // Track the current selectedId synchronously so async callbacks can detect stale responses. + const selectedIdRef = useRef(selectedId) + useEffect(() => { selectedIdRef.current = selectedId }, [selectedId]) + + const fetchAgents = useCallback(async () => { + try { + const res = await fetch(`${API}/agents`) + if (!res.ok) throw new Error(`Fetch agents failed: ${res.status}`) + const data = await res.json() + if (!Array.isArray(data)) throw new Error('Expected array of agents') + setAgents(data) + if (!selectedIdRef.current && data.length > 0) setSelectedId(data[0].id) + } catch (e) { + console.error('Failed to fetch agents', e) + } + }, []) + + const fetchHistory = useCallback(async (id) => { + if (!id) return + try { + const res = await fetch(`${API}/agents/${id}/history`) + if (!res.ok) return + const data = await res.json() + if (selectedIdRef.current === id) setHistory(data) + } catch (e) { console.error(e) } + }, []) + + const fetchActivity = useCallback(async (id) => { + if (!id) return + try { + const res = await fetch(`${API}/agents/${id}/activity`) + if (!res.ok) return + const data = await res.json() + if (selectedIdRef.current === id) setActivity(data) + } catch (e) { console.error(e) } + }, []) + + useEffect(() => { fetchAgents() }, []) + + useEffect(() => { + if (selectedId) { + fetchHistory(selectedId) + fetchActivity(selectedId) + } + }, [selectedId, fetchHistory, fetchActivity]) + + const handleSelectAgent = (id) => { + setSelectedId(id) + setActiveTab('chat') + } + + const reportError = async (res, action) => { + let detail = '' + try { detail = (await res.json()).detail || '' } catch { /* body may be empty */ } + const msg = `${action} failed (${res.status})${detail ? `: ${detail}` : ''}` + console.error(msg) + window.alert(msg) + } + + const handleSendMessage = async (message) => { + if (!selectedId) return + const agentId = selectedId // capture before any await + setLoading(true) + try { + const res = await fetch(`${API}/agents/${agentId}/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message }), + }) + if (!res.ok) { await reportError(res, 'Send message'); return } + const entry = await res.json() + if (selectedIdRef.current !== agentId) return // agent switched while awaiting + setHistory(prev => [...prev, entry]) + setActivity(prev => [...(Array.isArray(entry.activity) ? entry.activity : []), ...prev]) + } catch (e) { + console.error(e) + window.alert('Network error while sending message.') + } finally { + setLoading(false) + } + } + + const handleClearHistory = async () => { + if (!selectedId) return + try { + const res = await fetch(`${API}/agents/${selectedId}/history`, { method: 'DELETE' }) + if (!res.ok) { await reportError(res, 'Clear history'); return } + setHistory([]) + setActivity([]) + } catch (e) { + console.error(e) + window.alert('Network error while clearing history.') + } + } + + const handleSaveAgent = async (data) => { + try { + if (editingAgent) { + const res = await fetch(`${API}/agents/${editingAgent.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + }) + if (!res.ok) { await reportError(res, 'Update agent'); return } + const updated = await res.json() + setAgents(prev => prev.map(a => a.id === updated.id ? updated : a)) + } else { + const res = await fetch(`${API}/agents`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + }) + if (!res.ok) { await reportError(res, 'Create agent'); return } + const created = await res.json() + setAgents(prev => [...prev, created]) + setSelectedId(created.id) + } + setShowForm(false) + setEditingAgent(null) + } catch (e) { + console.error(e) + window.alert('Network error while saving agent.') + } + } + + const handleDeleteAgent = async (id) => { + if (!window.confirm('Delete this agent?')) return + try { + const res = await fetch(`${API}/agents/${id}`, { method: 'DELETE' }) + if (!res.ok) { await reportError(res, 'Delete agent'); return } + setAgents(prev => prev.filter(a => a.id !== id)) + if (selectedId === id) { + const remaining = agents.filter(a => a.id !== id) + setSelectedId(remaining.length > 0 ? remaining[0].id : null) + } + } catch (e) { + console.error(e) + window.alert('Network error while deleting agent.') + } + } + + const selectedAgent = agents.find(a => a.id === selectedId) + + return ( +
+ {/* Mobile overlay */} + {sidebarOpen && ( +
setSidebarOpen(false)} /> + )} + + {/* Sidebar */} +
+ { setEditingAgent(null); setShowForm(true) }} + onClose={() => setSidebarOpen(false)} + /> +
+ + {/* Main content */} +
+ {/* Center panel */} +
+ {/* Header */} +
+ + {selectedAgent ? ( + <> +
+
+ {selectedAgent.name} + — {selectedAgent.model} +
+
+ + +
+ + ) : ( + No agent selected + )} +
+ + {/* Panel content */} +
+ {!selectedAgent ? ( +
+ + + +

Select or create an agent

+

Choose an agent from the sidebar to get started

+
+ ) : activeTab === 'chat' ? ( + + ) : ( + { setEditingAgent(selectedAgent); setShowForm(true) }} + onDelete={() => handleDeleteAgent(selectedAgent.id)} + /> + )} +
+
+ + {/* Right activity panel */} +
+ +
+
+ + {/* Agent form modal */} + {showForm && ( + { setShowForm(false); setEditingAgent(null) }} + /> + )} +
+ ) +} diff --git a/frontend/src/components/ActivityLog.jsx b/frontend/src/components/ActivityLog.jsx new file mode 100644 index 000000000..bbc62c8a2 --- /dev/null +++ b/frontend/src/components/ActivityLog.jsx @@ -0,0 +1,61 @@ +const TYPE_CONFIG = { + tool: { color: '#3b82f6', bg: 'rgba(59,130,246,0.1)', label: 'TOOL', icon: '⚙️' }, + success: { color: '#22c55e', bg: 'rgba(34,197,94,0.1)', label: 'OK', icon: '✓' }, + error: { color: '#ef4444', bg: 'rgba(239,68,68,0.1)', label: 'ERR', icon: '✗' }, + message: { color: '#6b7280', bg: 'rgba(107,114,128,0.1)', label: 'MSG', icon: '●' }, +} + +function ActivityItem({ item }) { + const cfg = TYPE_CONFIG[item.type] || TYPE_CONFIG.message + return ( +
+
+ {cfg.icon} +
+
+

+ {item.description} +

+

+ {new Date(item.timestamp).toLocaleTimeString()} +

+
+
+ ) +} + +export default function ActivityLog({ activity }) { + return ( +
+
+
+

Activity Feed

+ + {activity.length} + +
+ + {/* Legend */} +
+ {Object.entries(TYPE_CONFIG).map(([key, cfg]) => ( +
+ {cfg.icon} + {key} +
+ ))} +
+
+ +
+ {activity.length === 0 ? ( +
+
📋
+

No activity yet.
Send a message to see logs.

+
+ ) : ( + activity.map(item => ) + )} +
+
+ ) +} diff --git a/frontend/src/components/AgentDetail.jsx b/frontend/src/components/AgentDetail.jsx new file mode 100644 index 000000000..daab6b309 --- /dev/null +++ b/frontend/src/components/AgentDetail.jsx @@ -0,0 +1,124 @@ +const STATUS_STYLE = { + active: { dot: '#22c55e', bg: 'rgba(34,197,94,0.15)', text: '#22c55e' }, + auditing: { dot: '#f59e0b', bg: 'rgba(245,158,11,0.15)', text: '#f59e0b' }, + decommissioned:{ dot: '#6b7280', bg: 'rgba(107,114,128,0.15)', text: '#9ca3af' }, +} + +const statusStyle = (s) => STATUS_STYLE[s] ?? STATUS_STYLE.decommissioned + +export default function AgentDetail({ agent, history, onEdit, onDelete }) { + const totalMessages = history.length + const lastActive = history.length > 0 + ? new Date(history[history.length - 1].timestamp).toLocaleString() + : 'Never' + + const style = statusStyle(agent.status) + + return ( +
+ {/* Header */} +
+
+
+ {agent.name.charAt(0).toUpperCase()} +
+
+

{agent.name}

+

{agent.model}

+
+
+
+ + {agent.status} + + + +
+
+ + {/* Stats */} +
+ {[ + { label: 'Messages', value: totalMessages }, + { label: 'Generation', value: agent.generation ?? 1 }, + { label: 'Token Spend', value: (agent.token_spend ?? 0).toLocaleString() }, + { label: 'Last Active', value: lastActive, small: true }, + ].map(stat => ( +
+

{stat.label}

+

{stat.value}

+
+ ))} +
+ + {/* Instructions */} +
+

+ {agent.instructions || No instructions set} +

+
+ + {/* Config */} +
+
+ + {agent.model} + +
+
+ + + {agent.status} + +
+
+ + {/* Performance */} + {(agent.performance_score !== null && agent.performance_score !== undefined) && ( +
+

+ {Number(agent.performance_score).toFixed(3)} +

+
+ )} + + {/* Lineage */} + {agent.parent_id && ( +
+

{agent.parent_id}

+
+ )} + + {/* Exit summary (only relevant for decommissioned) */} + {agent.exit_summary && ( +
+

+ {agent.exit_summary} +

+
+ )} +
+ ) +} + +function Section({ title, children }) { + return ( +
+

{title}

+ {children} +
+ ) +} diff --git a/frontend/src/components/AgentForm.jsx b/frontend/src/components/AgentForm.jsx new file mode 100644 index 000000000..4962d4363 --- /dev/null +++ b/frontend/src/components/AgentForm.jsx @@ -0,0 +1,174 @@ +import { useState, useEffect } from 'react' + +const LLM_MODELS = [ + 'gpt-4o-mini', + 'gpt-4o', + 'claude-3-haiku', + 'claude-3-5-sonnet', + 'gemini-1.5-flash', +] + +const STATUSES = ['active', 'auditing', 'decommissioned'] + +const defaultForm = (agent) => ({ + name: agent?.name || '', + instructions: agent?.instructions || '', + model: agent?.model || agent?.llm || 'gpt-4o-mini', + status: agent?.status || 'active', +}) + +export default function AgentForm({ agent, onSave, onCancel }) { + const [form, setForm] = useState(() => defaultForm(agent)) + + // Re-sync if the parent passes a different agent (e.g. switching edit targets). + useEffect(() => { setForm(defaultForm(agent)) }, [agent?.id]) + + // Dismiss on Escape key. + useEffect(() => { + const onKey = (e) => { if (e.key === 'Escape') onCancel() } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [onCancel]) + + const set = (key, val) => setForm(prev => ({ ...prev, [key]: val })) + + const handleSubmit = (e) => { + e.preventDefault() + if (!form.name.trim()) return + onSave(form) + } + + return ( +
{ if (e.target === e.currentTarget) onCancel() }} + > +
+
+ {/* Header */} +
+

{agent ? 'Edit Agent' : 'New Agent'}

+ +
+ +
+ {/* Name */} + + set('name', e.target.value)} + placeholder="Research Agent" + required + className="w-full px-3 py-2.5 rounded-lg text-sm outline-none" + style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }} + onFocus={e => e.target.style.borderColor = '#6c63ff'} + onBlur={e => e.target.style.borderColor = '#2a2d3e'} + /> + + + {/* Instructions */} + +