Manish Kumar commited on
Commit ·
a6ee555
1
Parent(s): 057f830
inicial commit
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +28 -0
- .env +21 -0
- .env.example +22 -0
- .idea/.gitignore +10 -0
- .idea/Levi.iml +8 -0
- .idea/inspectionProfiles/Project_Default.xml +13 -0
- .idea/inspectionProfiles/profiles_settings.xml +6 -0
- .idea/modules.xml +8 -0
- .idea/new.iml +12 -0
- .idea/vcs.xml +6 -0
- Dockerfile +52 -0
- README.md +107 -1
- backend/app/__init__.py +1 -0
- backend/app/config.py +57 -0
- backend/app/llm/__init__.py +1 -0
- backend/app/llm/base.py +45 -0
- backend/app/llm/local.py +168 -0
- backend/app/llm/manager.py +89 -0
- backend/app/main.py +317 -0
- backend/app/middleware.py +73 -0
- backend/app/models.py +50 -0
- backend/app/utils.py +83 -0
- backend/requirements.txt +10 -0
- backend/run.py +11 -0
- docker/Dockerfile.backend +45 -0
- docker/Dockerfile.frontend +13 -0
- docker/docker-compose.yml +36 -0
- docs/API.md +109 -0
- docs/DEPLOYMENT.md +47 -0
- frontend/.gitignore +24 -0
- frontend/.oxlintrc.json +8 -0
- frontend/README.md +32 -0
- frontend/index.html +14 -0
- frontend/package-lock.json +1909 -0
- frontend/package.json +34 -0
- frontend/public/favicon.svg +1 -0
- frontend/public/icons.svg +24 -0
- frontend/src/App.css +184 -0
- frontend/src/App.tsx +193 -0
- frontend/src/assets/hero.png +0 -0
- frontend/src/assets/react.svg +1 -0
- frontend/src/assets/vite.svg +1 -0
- frontend/src/components/AboutPage.tsx +79 -0
- frontend/src/components/ChatInterface.tsx +460 -0
- frontend/src/components/CodePlayground.tsx +271 -0
- frontend/src/components/Dashboard.tsx +257 -0
- frontend/src/components/MonacoEditorWrapper.tsx +88 -0
- frontend/src/components/Navbar.tsx +141 -0
- frontend/src/components/Settings.tsx +337 -0
- frontend/src/components/Sidebar.tsx +178 -0
.dockerignore
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ignore Node.js dependencies
|
| 2 |
+
frontend/node_modules/
|
| 3 |
+
frontend/dist/
|
| 4 |
+
frontend/.vite/
|
| 5 |
+
|
| 6 |
+
# Ignore Python environments and caches
|
| 7 |
+
**/__pycache__/
|
| 8 |
+
*.pyc
|
| 9 |
+
*.pyo
|
| 10 |
+
*.pyd
|
| 11 |
+
.Python
|
| 12 |
+
env/
|
| 13 |
+
venv/
|
| 14 |
+
.venv/
|
| 15 |
+
pip-log.txt
|
| 16 |
+
pip-delete-this-directory.txt
|
| 17 |
+
|
| 18 |
+
# Ignore models directory (it is mounted as a volume at runtime)
|
| 19 |
+
models/
|
| 20 |
+
|
| 21 |
+
# Ignore logs and dev tools
|
| 22 |
+
.git/
|
| 23 |
+
.gitignore
|
| 24 |
+
.dockerignore
|
| 25 |
+
.agents/
|
| 26 |
+
.gemini/
|
| 27 |
+
*.log
|
| 28 |
+
docker-compose.override.yml
|
.env
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Coding Assistant - Active Environment Configuration
|
| 2 |
+
|
| 3 |
+
PORT=8000
|
| 4 |
+
HOST=0.0.0.0
|
| 5 |
+
DEBUG=true
|
| 6 |
+
CORS_ORIGINS=http://localhost:5173,http://localhost:3000,http://localhost:8000
|
| 7 |
+
|
| 8 |
+
# SmolLM2 Model Settings
|
| 9 |
+
LOCAL_MODEL_PATH=models/SmolLM2-360M-Instruct-Q4_K_M.gguf
|
| 10 |
+
LOCAL_MODEL_REPO=bartowski/SmolLM2-360M-Instruct-GGUF
|
| 11 |
+
LOCAL_MODEL_FILE=SmolLM2-360M-Instruct-Q4_K_M.gguf
|
| 12 |
+
|
| 13 |
+
# Generation Parameters
|
| 14 |
+
DEFAULT_TEMPERATURE=0.7
|
| 15 |
+
DEFAULT_MAX_TOKENS=1024
|
| 16 |
+
DEFAULT_TOP_P=0.9
|
| 17 |
+
DEFAULT_CONTEXT_LENGTH=2048
|
| 18 |
+
|
| 19 |
+
# Security & Limits
|
| 20 |
+
RATE_LIMIT_PER_MINUTE=100
|
| 21 |
+
SECRET_KEY=dev-secret-key-32-chars-long-minimum-required!
|
.env.example
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Coding Assistant - Environment Configuration
|
| 2 |
+
|
| 3 |
+
# Backend Server Configuration
|
| 4 |
+
PORT=8000
|
| 5 |
+
HOST=0.0.0.0
|
| 6 |
+
DEBUG=false
|
| 7 |
+
CORS_ORIGINS=http://localhost:5173,http://localhost:3000,https://*.railway.app,https://*.render.com
|
| 8 |
+
|
| 9 |
+
# SmolLM2 Model Settings
|
| 10 |
+
LOCAL_MODEL_PATH=models/SmolLM2-360M-Instruct-Q4_K_M.gguf
|
| 11 |
+
LOCAL_MODEL_REPO=bartowski/SmolLM2-360M-Instruct-GGUF
|
| 12 |
+
LOCAL_MODEL_FILE=SmolLM2-360M-Instruct-Q4_K_M.gguf
|
| 13 |
+
|
| 14 |
+
# Generation Parameters
|
| 15 |
+
DEFAULT_TEMPERATURE=0.7
|
| 16 |
+
DEFAULT_MAX_TOKENS=1024
|
| 17 |
+
DEFAULT_TOP_P=0.9
|
| 18 |
+
DEFAULT_CONTEXT_LENGTH=2048
|
| 19 |
+
|
| 20 |
+
# Security & Limits
|
| 21 |
+
RATE_LIMIT_PER_MINUTE=60
|
| 22 |
+
SECRET_KEY=generate_a_secure_random_key_here
|
.idea/.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Default ignored files
|
| 2 |
+
/shelf/
|
| 3 |
+
/workspace.xml
|
| 4 |
+
# Editor-based HTTP Client requests
|
| 5 |
+
/httpRequests/
|
| 6 |
+
# Ignored default folder with query files
|
| 7 |
+
/queries/
|
| 8 |
+
# Datasource local storage ignored files
|
| 9 |
+
/dataSources/
|
| 10 |
+
/dataSources.local.xml
|
.idea/Levi.iml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<module type="PYTHON_MODULE" version="4">
|
| 3 |
+
<component name="NewModuleRootManager">
|
| 4 |
+
<content url="file://$MODULE_DIR$" />
|
| 5 |
+
<orderEntry type="inheritedJdk" />
|
| 6 |
+
<orderEntry type="sourceFolder" forTests="false" />
|
| 7 |
+
</component>
|
| 8 |
+
</module>
|
.idea/inspectionProfiles/Project_Default.xml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<component name="InspectionProjectProfileManager">
|
| 2 |
+
<profile version="1.0">
|
| 3 |
+
<option name="myName" value="Project Default" />
|
| 4 |
+
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
| 5 |
+
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
| 6 |
+
<option name="ignoredIdentifiers">
|
| 7 |
+
<list>
|
| 8 |
+
<option value="llama_index.*" />
|
| 9 |
+
</list>
|
| 10 |
+
</option>
|
| 11 |
+
</inspection_tool>
|
| 12 |
+
</profile>
|
| 13 |
+
</component>
|
.idea/inspectionProfiles/profiles_settings.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<component name="InspectionProjectProfileManager">
|
| 2 |
+
<settings>
|
| 3 |
+
<option name="USE_PROJECT_PROFILE" value="false" />
|
| 4 |
+
<version value="1.0" />
|
| 5 |
+
</settings>
|
| 6 |
+
</component>
|
.idea/modules.xml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="ProjectModuleManager">
|
| 4 |
+
<modules>
|
| 5 |
+
<module fileurl="file://$PROJECT_DIR$/.idea/Levi.iml" filepath="$PROJECT_DIR$/.idea/Levi.iml" />
|
| 6 |
+
</modules>
|
| 7 |
+
</component>
|
| 8 |
+
</project>
|
.idea/new.iml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<module type="PYTHON_MODULE" version="4">
|
| 3 |
+
<component name="NewModuleRootManager">
|
| 4 |
+
<content url="file://$MODULE_DIR$" />
|
| 5 |
+
<orderEntry type="inheritedJdk" />
|
| 6 |
+
<orderEntry type="sourceFolder" forTests="false" />
|
| 7 |
+
</component>
|
| 8 |
+
<component name="PyDocumentationSettings">
|
| 9 |
+
<option name="format" value="PLAIN" />
|
| 10 |
+
<option name="myDocStringFormat" value="Plain" />
|
| 11 |
+
</component>
|
| 12 |
+
</module>
|
.idea/vcs.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="VcsDirectoryMappings">
|
| 4 |
+
<mapping directory="" vcs="Git" />
|
| 5 |
+
</component>
|
| 6 |
+
</project>
|
Dockerfile
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stage 1: Build React Frontend
|
| 2 |
+
FROM node:20-slim as frontend-builder
|
| 3 |
+
|
| 4 |
+
WORKDIR /frontend
|
| 5 |
+
|
| 6 |
+
# Copy package config and lock files
|
| 7 |
+
COPY frontend/package*.json ./
|
| 8 |
+
|
| 9 |
+
# Install packages
|
| 10 |
+
RUN npm ci
|
| 11 |
+
|
| 12 |
+
# Copy frontend source files
|
| 13 |
+
COPY frontend/ ./
|
| 14 |
+
|
| 15 |
+
# Build production static bundle
|
| 16 |
+
RUN npm run build
|
| 17 |
+
|
| 18 |
+
# Stage 2: Build Python Backend & Package app
|
| 19 |
+
FROM python:3.11-slim
|
| 20 |
+
|
| 21 |
+
WORKDIR /app
|
| 22 |
+
|
| 23 |
+
# Install compilation tools for building llama-cpp-python in backend
|
| 24 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 25 |
+
build-essential \
|
| 26 |
+
gcc \
|
| 27 |
+
g++ \
|
| 28 |
+
make \
|
| 29 |
+
python3-dev \
|
| 30 |
+
git \
|
| 31 |
+
libgomp1 \
|
| 32 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 33 |
+
|
| 34 |
+
# Copy requirements and install dependencies
|
| 35 |
+
COPY backend/requirements.txt ./backend/
|
| 36 |
+
RUN pip install --no-cache-dir -r ./backend/requirements.txt
|
| 37 |
+
|
| 38 |
+
# Copy backend source code
|
| 39 |
+
COPY backend/ ./backend/
|
| 40 |
+
|
| 41 |
+
# Copy static frontend build from Stage 1 into frontend/dist
|
| 42 |
+
COPY --from=frontend-builder /frontend/dist ./frontend/dist
|
| 43 |
+
|
| 44 |
+
# Setup environments
|
| 45 |
+
ENV PORT=8000
|
| 46 |
+
ENV HOST=0.0.0.0
|
| 47 |
+
ENV PYTHONPATH=/app
|
| 48 |
+
|
| 49 |
+
EXPOSE 8000
|
| 50 |
+
|
| 51 |
+
# Start unified server
|
| 52 |
+
CMD ["python", "backend/run.py"]
|
README.md
CHANGED
|
@@ -1 +1,107 @@
|
|
| 1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Antigravity AI Coding Assistant ⚡
|
| 2 |
+
|
| 3 |
+
A production-ready, resource-optimized, containerized AI Coding Assistant using **Qwen2.5-Coder-0.5B-Instruct** as its core engine. Built with a Python FastAPI backend and a stunning dark-theme React + Vite + TypeScript frontend.
|
| 4 |
+
|
| 5 |
+
```mermaid
|
| 6 |
+
graph TD
|
| 7 |
+
User([User]) <--> |HTTP / SSE| FE[React SPA - Vite + TS]
|
| 8 |
+
subgraph Backend [FastAPI Server]
|
| 9 |
+
API[API Endpoints] <--> MGR[LLM Manager]
|
| 10 |
+
MGR --> |Check RAM / Models| Decision{Load Local GGUF?}
|
| 11 |
+
Decision -->|Yes: RAM >= 1.5GB| GGUF[Local llama-cpp-python]
|
| 12 |
+
Decision -->|No: Low memory / Failed| HF[Hugging Face Cloud API]
|
| 13 |
+
end
|
| 14 |
+
GGUF <--> |Read / Write| Models[(models/ folder)]
|
| 15 |
+
HF <--> |HTTPS Request| HFHub[Hugging Face Inference Hub]
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## 🌟 Key Features
|
| 21 |
+
|
| 22 |
+
* **Cascading Fallback Pipeline:** Automatically runs local Qwen GGUF inference. If RAM is constrained (e.g. Render Free, Railway Starter), it transparently falls back to Hugging Face serverless API.
|
| 23 |
+
* **Interactive Code Playground:** Features Monaco Editor (VS Code core) with language syntax highlight selectors and quick AI commands (`Explain`, `Find Bugs`, `Refactor`, `Generate Tests`, `Summarize`).
|
| 24 |
+
* **Advanced Chat Window:** Smooth response streaming (Server-Sent Events), code block copy buttons, and drag-and-drop file imports.
|
| 25 |
+
* **Production Deployment Ready:** Pre-configured Dockerfiles, Docker Compose, Railway config, and Render deployment specifications.
|
| 26 |
+
* **Performance Telemetry:** Live dashboards displaying generation speed (tokens/sec), latency, request tallies, and RAM footprint.
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## 🛠️ Tech Stack
|
| 31 |
+
|
| 32 |
+
* **Frontend:** React 19, Vite, TypeScript, Tailwind CSS, Monaco Editor, Lucide Icons, Framer Motion
|
| 33 |
+
* **Backend:** FastAPI, Python 3.11+, Uvicorn, llama-cpp-python, Hugging Face Hub Client, Psutil
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
## 🚀 Quick Start (Local Setup)
|
| 38 |
+
|
| 39 |
+
### Prerequisites
|
| 40 |
+
* Python 3.11+
|
| 41 |
+
* Node.js 20+
|
| 42 |
+
|
| 43 |
+
### Step 1: Clone and Setup Workspace
|
| 44 |
+
Clone this repository and navigate to the project directory:
|
| 45 |
+
```bash
|
| 46 |
+
git clone https://github.com/your-repo/antigravity-coder.git
|
| 47 |
+
cd antigravity-coder
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### Step 2: Install and Download GGUF Model
|
| 51 |
+
Use our automated installer script:
|
| 52 |
+
```bash
|
| 53 |
+
# On Linux/macOS
|
| 54 |
+
chmod +x scripts/setup.sh
|
| 55 |
+
./scripts/setup.sh
|
| 56 |
+
|
| 57 |
+
# On Windows (PowerShell)
|
| 58 |
+
pip install -r backend/requirements.txt
|
| 59 |
+
cd frontend; npm install; npm run build; cd ..
|
| 60 |
+
python scripts/download_model.py
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Step 3: Run the Application
|
| 64 |
+
Start the backend server:
|
| 65 |
+
```bash
|
| 66 |
+
# Run backend (activates virtual env if created)
|
| 67 |
+
python backend/run.py
|
| 68 |
+
```
|
| 69 |
+
This runs the API server on `http://localhost:8000` and automatically compiles & hosts the React frontend assets. Navigate to [http://localhost:8000](http://localhost:8000) to view the application!
|
| 70 |
+
|
| 71 |
+
For hot-reloading frontend development:
|
| 72 |
+
```bash
|
| 73 |
+
cd frontend
|
| 74 |
+
npm run dev
|
| 75 |
+
```
|
| 76 |
+
Open [http://localhost:5173](http://localhost:5173) in your browser.
|
| 77 |
+
|
| 78 |
+
---
|
| 79 |
+
|
| 80 |
+
## 🐳 Running with Docker
|
| 81 |
+
|
| 82 |
+
Run both services in hot-reloading development mode using Docker Compose:
|
| 83 |
+
```bash
|
| 84 |
+
docker compose -f docker/docker-compose.yml up --build
|
| 85 |
+
```
|
| 86 |
+
Build and run the production-ready unified container (hosting both frontend and API on port 8000):
|
| 87 |
+
```bash
|
| 88 |
+
docker build -t antigravity-coder .
|
| 89 |
+
docker run -p 8000:8000 antigravity-coder
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
## 🌐 Cloud Deployment
|
| 95 |
+
|
| 96 |
+
Detailed step-by-step guides for deployment configurations:
|
| 97 |
+
* [Railway Deployment Guide](docs/DEPLOYMENT.md#railway)
|
| 98 |
+
* [Render Deployment Guide](docs/DEPLOYMENT.md#render)
|
| 99 |
+
* [API Reference Documentation](docs/API.md)
|
| 100 |
+
|
| 101 |
+
---
|
| 102 |
+
|
| 103 |
+
## 🔒 Security
|
| 104 |
+
|
| 105 |
+
* Standard CORS origin protections.
|
| 106 |
+
* Secure in-memory sliding rate limiter per client IP.
|
| 107 |
+
* Configuration parsing using Pydantic Settings from `.env` files. Secrets are never hardcoded.
|
backend/app/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# AI Coding Assistant Backend Package
|
backend/app/config.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import List
|
| 3 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 4 |
+
from pydantic import Field
|
| 5 |
+
|
| 6 |
+
class Settings(BaseSettings):
|
| 7 |
+
# Server settings
|
| 8 |
+
PORT: int = Field(default=8000, validation_alias="PORT")
|
| 9 |
+
HOST: str = Field(default="0.0.0.0", validation_alias="HOST")
|
| 10 |
+
DEBUG: bool = Field(default=False, validation_alias="DEBUG")
|
| 11 |
+
CORS_ORIGINS: str = Field(
|
| 12 |
+
default="http://localhost:5173,http://localhost:3000,http://localhost:8000",
|
| 13 |
+
validation_alias="CORS_ORIGINS"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# LLM Settings
|
| 17 |
+
INFERENCE_MODE: str = Field(default="local", validation_alias="INFERENCE_MODE")
|
| 18 |
+
|
| 19 |
+
# Local GGUF Settings
|
| 20 |
+
LOCAL_MODEL_PATH: str = Field(
|
| 21 |
+
default="models/SmolLM2-360M-Instruct-Q4_K_M.gguf",
|
| 22 |
+
validation_alias="LOCAL_MODEL_PATH"
|
| 23 |
+
)
|
| 24 |
+
LOCAL_MODEL_REPO: str = Field(
|
| 25 |
+
default="bartowski/SmolLM2-360M-Instruct-GGUF",
|
| 26 |
+
validation_alias="LOCAL_MODEL_REPO"
|
| 27 |
+
)
|
| 28 |
+
LOCAL_MODEL_FILE: str = Field(
|
| 29 |
+
default="SmolLM2-360M-Instruct-Q4_K_M.gguf",
|
| 30 |
+
validation_alias="LOCAL_MODEL_FILE"
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# Generation Defaults
|
| 34 |
+
DEFAULT_TEMPERATURE: float = Field(default=0.7, validation_alias="DEFAULT_TEMPERATURE")
|
| 35 |
+
DEFAULT_MAX_TOKENS: int = Field(default=1024, validation_alias="DEFAULT_MAX_TOKENS")
|
| 36 |
+
DEFAULT_TOP_P: float = Field(default=0.9, validation_alias="DEFAULT_TOP_P")
|
| 37 |
+
DEFAULT_CONTEXT_LENGTH: int = Field(default=2048, validation_alias="DEFAULT_CONTEXT_LENGTH")
|
| 38 |
+
|
| 39 |
+
# Security / Limits
|
| 40 |
+
RATE_LIMIT_PER_MINUTE: int = Field(default=60, validation_alias="RATE_LIMIT_PER_MINUTE")
|
| 41 |
+
SECRET_KEY: str = Field(
|
| 42 |
+
default="dev-secret-key-must-be-changed-in-production-environments!",
|
| 43 |
+
validation_alias="SECRET_KEY"
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
@property
|
| 47 |
+
def cors_origins_list(self) -> List[str]:
|
| 48 |
+
return [origin.strip() for origin in self.CORS_ORIGINS.split(",") if origin.strip()]
|
| 49 |
+
|
| 50 |
+
model_config = SettingsConfigDict(
|
| 51 |
+
env_file=".env",
|
| 52 |
+
env_file_encoding="utf-8",
|
| 53 |
+
extra="ignore"
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
# Instantiate singleton settings
|
| 57 |
+
settings = Settings()
|
backend/app/llm/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# LLM Provider Adapters Package
|
backend/app/llm/base.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import ABC, abstractmethod
|
| 2 |
+
from typing import AsyncIterator, List, Dict, Any, Optional
|
| 3 |
+
|
| 4 |
+
class BaseLLMProvider(ABC):
|
| 5 |
+
@abstractmethod
|
| 6 |
+
async def initialize(self) -> bool:
|
| 7 |
+
"""Initialize the LLM provider. Returns True if successful."""
|
| 8 |
+
pass
|
| 9 |
+
|
| 10 |
+
@abstractmethod
|
| 11 |
+
async def generate(
|
| 12 |
+
self,
|
| 13 |
+
prompt: str,
|
| 14 |
+
system_prompt: Optional[str] = None,
|
| 15 |
+
messages: Optional[List[Dict[str, str]]] = None,
|
| 16 |
+
temperature: float = 0.7,
|
| 17 |
+
max_tokens: int = 1024,
|
| 18 |
+
top_p: float = 0.9,
|
| 19 |
+
) -> Dict[str, Any]:
|
| 20 |
+
"""
|
| 21 |
+
Execute non-streaming completion.
|
| 22 |
+
Returns a dict with format: { "content": str, "usage": dict }
|
| 23 |
+
"""
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
@abstractmethod
|
| 27 |
+
async def generate_stream(
|
| 28 |
+
self,
|
| 29 |
+
prompt: str,
|
| 30 |
+
system_prompt: Optional[str] = None,
|
| 31 |
+
messages: Optional[List[Dict[str, str]]] = None,
|
| 32 |
+
temperature: float = 0.7,
|
| 33 |
+
max_tokens: int = 1024,
|
| 34 |
+
top_p: float = 0.9,
|
| 35 |
+
) -> AsyncIterator[str]:
|
| 36 |
+
"""
|
| 37 |
+
Execute streaming completion.
|
| 38 |
+
Yields text chunks.
|
| 39 |
+
"""
|
| 40 |
+
pass
|
| 41 |
+
|
| 42 |
+
@abstractmethod
|
| 43 |
+
def get_info(self) -> Dict[str, Any]:
|
| 44 |
+
"""Return diagnostic and config information for the provider."""
|
| 45 |
+
pass
|
backend/app/llm/local.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import logging
|
| 4 |
+
import asyncio
|
| 5 |
+
from typing import AsyncIterator, List, Dict, Any, Optional
|
| 6 |
+
from backend.app.llm.base import BaseLLMProvider
|
| 7 |
+
from backend.app.config import settings
|
| 8 |
+
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
# Safe imports for environment portability
|
| 12 |
+
try:
|
| 13 |
+
from llama_cpp import Llama
|
| 14 |
+
except ImportError:
|
| 15 |
+
Llama = None
|
| 16 |
+
logger.warning("llama-cpp-python is not installed. Local inference will be unavailable.")
|
| 17 |
+
|
| 18 |
+
class LocalLLMProvider(BaseLLMProvider):
|
| 19 |
+
def __init__(self):
|
| 20 |
+
self.llm = None
|
| 21 |
+
self.initialized = False
|
| 22 |
+
self.model_path = os.path.abspath(settings.LOCAL_MODEL_PATH)
|
| 23 |
+
self.context_length = settings.DEFAULT_CONTEXT_LENGTH
|
| 24 |
+
|
| 25 |
+
async def initialize(self) -> bool:
|
| 26 |
+
if self.initialized:
|
| 27 |
+
return True
|
| 28 |
+
|
| 29 |
+
if Llama is None:
|
| 30 |
+
logger.error("Cannot initialize LocalLLMProvider: llama-cpp-python not installed.")
|
| 31 |
+
return False
|
| 32 |
+
|
| 33 |
+
if not os.path.exists(self.model_path):
|
| 34 |
+
logger.error(f"Local GGUF model file not found at: {self.model_path}")
|
| 35 |
+
return False
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
# Determine thread count (default to CPU cores minus 1, min 1)
|
| 39 |
+
threads = max(1, (os.cpu_count() or 2) - 1)
|
| 40 |
+
|
| 41 |
+
logger.info(f"Loading local model from {self.model_path} with {threads} threads and context {self.context_length}...")
|
| 42 |
+
|
| 43 |
+
# Since loading the model blocks, run it in a separate thread to keep event loop active
|
| 44 |
+
def load_model():
|
| 45 |
+
return Llama(
|
| 46 |
+
model_path=self.model_path,
|
| 47 |
+
n_ctx=self.context_length,
|
| 48 |
+
n_threads=threads,
|
| 49 |
+
verbose=settings.DEBUG
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
self.llm = await asyncio.to_thread(load_model)
|
| 53 |
+
self.initialized = True
|
| 54 |
+
logger.info("Local model successfully loaded!")
|
| 55 |
+
return True
|
| 56 |
+
except Exception as e:
|
| 57 |
+
logger.error(f"Failed to load local model: {e}", exc_info=True)
|
| 58 |
+
self.initialized = False
|
| 59 |
+
self.llm = None
|
| 60 |
+
return False
|
| 61 |
+
|
| 62 |
+
def _format_prompt(self, prompt: str, system_prompt: Optional[str] = None, messages: Optional[List[Dict[str, str]]] = None) -> str:
|
| 63 |
+
# Build standard Qwen ChatML prompt format
|
| 64 |
+
formatted = ""
|
| 65 |
+
|
| 66 |
+
# Determine system prompt
|
| 67 |
+
sys_p = system_prompt or "You are Qwen, a helpful, precise, and state-of-the-art AI programming assistant."
|
| 68 |
+
|
| 69 |
+
# Check if messages already contain a system prompt
|
| 70 |
+
has_system = any(m.get("role") == "system" for m in messages) if messages else False
|
| 71 |
+
if not has_system:
|
| 72 |
+
formatted += f"<|im_start|>system\n{sys_p}<|im_end|>\n"
|
| 73 |
+
|
| 74 |
+
if messages:
|
| 75 |
+
for msg in messages:
|
| 76 |
+
role = msg.get("role", "user")
|
| 77 |
+
content = msg.get("content", "")
|
| 78 |
+
formatted += f"<|im_start|>{role}\n{content}<|im_end|>\n"
|
| 79 |
+
else:
|
| 80 |
+
# If no chat history is provided, construct a simple message pair
|
| 81 |
+
formatted += f"<|im_start|>user\n{prompt}<|im_end|>\n"
|
| 82 |
+
|
| 83 |
+
formatted += "<|im_start|>assistant\n"
|
| 84 |
+
return formatted
|
| 85 |
+
|
| 86 |
+
async def generate(
|
| 87 |
+
self,
|
| 88 |
+
prompt: str,
|
| 89 |
+
system_prompt: Optional[str] = None,
|
| 90 |
+
messages: Optional[List[Dict[str, str]]] = None,
|
| 91 |
+
temperature: float = 0.7,
|
| 92 |
+
max_tokens: int = 1024,
|
| 93 |
+
top_p: float = 0.9,
|
| 94 |
+
) -> Dict[str, Any]:
|
| 95 |
+
if not await self.initialize():
|
| 96 |
+
raise RuntimeError("Local LLM provider is not initialized.")
|
| 97 |
+
|
| 98 |
+
formatted_prompt = self._format_prompt(prompt, system_prompt, messages)
|
| 99 |
+
|
| 100 |
+
def run_inference():
|
| 101 |
+
return self.llm(
|
| 102 |
+
prompt=formatted_prompt,
|
| 103 |
+
max_tokens=max_tokens,
|
| 104 |
+
temperature=temperature,
|
| 105 |
+
top_p=top_p,
|
| 106 |
+
stop=["<|im_end|>", "<|im_start|>", "im_end", "im_start"],
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
response = await asyncio.to_thread(run_inference)
|
| 110 |
+
|
| 111 |
+
content = response["choices"][0]["text"]
|
| 112 |
+
prompt_tokens = response["usage"]["prompt_tokens"]
|
| 113 |
+
completion_tokens = response["usage"]["completion_tokens"]
|
| 114 |
+
|
| 115 |
+
return {
|
| 116 |
+
"content": content,
|
| 117 |
+
"usage": {
|
| 118 |
+
"prompt_tokens": prompt_tokens,
|
| 119 |
+
"completion_tokens": completion_tokens,
|
| 120 |
+
"total_tokens": prompt_tokens + completion_tokens
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
async def generate_stream(
|
| 125 |
+
self,
|
| 126 |
+
prompt: str,
|
| 127 |
+
system_prompt: Optional[str] = None,
|
| 128 |
+
messages: Optional[List[Dict[str, str]]] = None,
|
| 129 |
+
temperature: float = 0.7,
|
| 130 |
+
max_tokens: int = 1024,
|
| 131 |
+
top_p: float = 0.9,
|
| 132 |
+
) -> AsyncIterator[str]:
|
| 133 |
+
if not await self.initialize():
|
| 134 |
+
raise RuntimeError("Local LLM provider is not initialized.")
|
| 135 |
+
|
| 136 |
+
formatted_prompt = self._format_prompt(prompt, system_prompt, messages)
|
| 137 |
+
|
| 138 |
+
# Generator for streaming
|
| 139 |
+
def run_stream():
|
| 140 |
+
return self.llm(
|
| 141 |
+
prompt=formatted_prompt,
|
| 142 |
+
max_tokens=max_tokens,
|
| 143 |
+
temperature=temperature,
|
| 144 |
+
top_p=top_p,
|
| 145 |
+
stop=["<|im_end|>", "<|im_start|>", "im_end", "im_start"],
|
| 146 |
+
stream=True
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
stream = await asyncio.to_thread(run_stream)
|
| 150 |
+
|
| 151 |
+
async def async_generator():
|
| 152 |
+
for chunk in stream:
|
| 153 |
+
text = chunk["choices"][0]["text"]
|
| 154 |
+
if text:
|
| 155 |
+
yield text
|
| 156 |
+
# Yield CPU control to event loop
|
| 157 |
+
await asyncio.sleep(0)
|
| 158 |
+
|
| 159 |
+
return async_generator()
|
| 160 |
+
|
| 161 |
+
def get_info(self) -> Dict[str, Any]:
|
| 162 |
+
return {
|
| 163 |
+
"provider_name": "local",
|
| 164 |
+
"initialized": self.initialized,
|
| 165 |
+
"model_path": self.model_path,
|
| 166 |
+
"context_length": self.context_length,
|
| 167 |
+
"device": "CPU" # Llama.cpp runs on CPU in basic config, can use GPU via CUDA wrappers
|
| 168 |
+
}
|
backend/app/llm/manager.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
import asyncio
|
| 4 |
+
from typing import AsyncIterator, Dict, Any, Optional
|
| 5 |
+
from backend.app.config import settings
|
| 6 |
+
from backend.app.llm.base import BaseLLMProvider
|
| 7 |
+
from backend.app.llm.local import LocalLLMProvider
|
| 8 |
+
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
class LLMManager:
|
| 12 |
+
def __init__(self):
|
| 13 |
+
self.provider: Optional[BaseLLMProvider] = None
|
| 14 |
+
self.is_downloading: bool = False
|
| 15 |
+
|
| 16 |
+
async def ensure_model_downloaded(self) -> bool:
|
| 17 |
+
"""Downloads the GGUF model file if not exists."""
|
| 18 |
+
model_path = os.path.abspath(settings.LOCAL_MODEL_PATH)
|
| 19 |
+
if os.path.exists(model_path):
|
| 20 |
+
logger.info(f"Model file already exists at: {model_path}")
|
| 21 |
+
return True
|
| 22 |
+
|
| 23 |
+
# Ensure directory exists
|
| 24 |
+
os.makedirs(os.path.dirname(model_path), exist_ok=True)
|
| 25 |
+
|
| 26 |
+
self.is_downloading = True
|
| 27 |
+
logger.info(f"Downloading model {settings.LOCAL_MODEL_FILE} from repo {settings.LOCAL_MODEL_REPO}...")
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
from huggingface_hub import hf_hub_download
|
| 31 |
+
|
| 32 |
+
def download_job():
|
| 33 |
+
return hf_hub_download(
|
| 34 |
+
repo_id=settings.LOCAL_MODEL_REPO,
|
| 35 |
+
filename=settings.LOCAL_MODEL_FILE,
|
| 36 |
+
local_dir=os.path.dirname(model_path),
|
| 37 |
+
local_dir_use_symlinks=False
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
# Download model in background thread to not block the main application loop
|
| 41 |
+
await asyncio.to_thread(download_job)
|
| 42 |
+
logger.info("Model download complete!")
|
| 43 |
+
self.is_downloading = False
|
| 44 |
+
return True
|
| 45 |
+
except Exception as e:
|
| 46 |
+
logger.error(f"Failed to download model automatically: {e}", exc_info=True)
|
| 47 |
+
self.is_downloading = False
|
| 48 |
+
return False
|
| 49 |
+
|
| 50 |
+
async def setup_provider(self) -> BaseLLMProvider:
|
| 51 |
+
"""
|
| 52 |
+
Initializes the local LLM provider strictly.
|
| 53 |
+
"""
|
| 54 |
+
logger.info("Setting up local provider for SmolLM2...")
|
| 55 |
+
await self.ensure_model_downloaded()
|
| 56 |
+
|
| 57 |
+
local = LocalLLMProvider()
|
| 58 |
+
# Initialize in background, do not raise exception on failure to support diagnostics
|
| 59 |
+
await local.initialize()
|
| 60 |
+
self.provider = local
|
| 61 |
+
return local
|
| 62 |
+
|
| 63 |
+
async def get_active_provider(self) -> BaseLLMProvider:
|
| 64 |
+
"""Returns the active provider. Lazily initializes if needed."""
|
| 65 |
+
if not self.provider:
|
| 66 |
+
await self.setup_provider()
|
| 67 |
+
return self.provider
|
| 68 |
+
|
| 69 |
+
async def generate(self, *args, **kwargs) -> Dict[str, Any]:
|
| 70 |
+
p = await self.get_active_provider()
|
| 71 |
+
return await p.generate(*args, **kwargs)
|
| 72 |
+
|
| 73 |
+
async def generate_stream(self, *args, **kwargs) -> AsyncIterator[str]:
|
| 74 |
+
p = await self.get_active_provider()
|
| 75 |
+
return await p.generate_stream(*args, **kwargs)
|
| 76 |
+
|
| 77 |
+
async def get_status_info(self) -> Dict[str, Any]:
|
| 78 |
+
"""Returns diagnostic and current provider stats."""
|
| 79 |
+
p = await self.get_active_provider()
|
| 80 |
+
info = p.get_info()
|
| 81 |
+
info.update({
|
| 82 |
+
"configured_mode": "local",
|
| 83 |
+
"active_mode": "local",
|
| 84 |
+
"is_downloading": self.is_downloading
|
| 85 |
+
})
|
| 86 |
+
return info
|
| 87 |
+
|
| 88 |
+
# Instantiate global singleton manager
|
| 89 |
+
llm_manager = LLMManager()
|
backend/app/main.py
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
import uuid
|
| 4 |
+
import logging
|
| 5 |
+
import asyncio
|
| 6 |
+
import psutil
|
| 7 |
+
from contextlib import asynccontextmanager
|
| 8 |
+
from typing import AsyncGenerator, Dict, Any
|
| 9 |
+
|
| 10 |
+
from fastapi import FastAPI, Depends, HTTPException, status
|
| 11 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 12 |
+
from fastapi.responses import StreamingResponse, JSONResponse
|
| 13 |
+
from fastapi.staticfiles import StaticFiles
|
| 14 |
+
|
| 15 |
+
from backend.app.config import settings
|
| 16 |
+
from backend.app.models import (
|
| 17 |
+
ChatRequest, ChatResponse, ChatResponseChunk,
|
| 18 |
+
CodeRequest, CompletionRequest, ModelInfoResponse
|
| 19 |
+
)
|
| 20 |
+
from backend.app.middleware import RateLimitMiddleware, LoggingMiddleware
|
| 21 |
+
from backend.app.llm.manager import llm_manager
|
| 22 |
+
from backend.app.utils import (
|
| 23 |
+
estimate_tokens, format_sse_chunk,
|
| 24 |
+
get_code_prompt, get_completion_prompt
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
# Setup logging configuration
|
| 28 |
+
logging.basicConfig(
|
| 29 |
+
level=logging.INFO if not settings.DEBUG else logging.DEBUG,
|
| 30 |
+
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s"
|
| 31 |
+
)
|
| 32 |
+
logger = logging.getLogger("backend.app.main")
|
| 33 |
+
|
| 34 |
+
# Metrics memory store
|
| 35 |
+
class SystemMetrics:
|
| 36 |
+
def __init__(self):
|
| 37 |
+
self.total_requests = 0
|
| 38 |
+
self.total_prompt_tokens = 0
|
| 39 |
+
self.total_completion_tokens = 0
|
| 40 |
+
self.total_generation_time_sec = 0.0
|
| 41 |
+
|
| 42 |
+
def add(self, prompt_tokens: int, completion_tokens: int, duration: float):
|
| 43 |
+
self.total_requests += 1
|
| 44 |
+
self.total_prompt_tokens += prompt_tokens
|
| 45 |
+
self.total_completion_tokens += completion_tokens
|
| 46 |
+
self.total_generation_time_sec += duration
|
| 47 |
+
|
| 48 |
+
def get_metrics_report(self) -> Dict[str, Any]:
|
| 49 |
+
avg_latency = (
|
| 50 |
+
self.total_generation_time_sec / self.total_requests
|
| 51 |
+
if self.total_requests > 0 else 0.0
|
| 52 |
+
)
|
| 53 |
+
return {
|
| 54 |
+
"total_requests": self.total_requests,
|
| 55 |
+
"total_prompt_tokens": self.total_prompt_tokens,
|
| 56 |
+
"total_completion_tokens": self.total_completion_tokens,
|
| 57 |
+
"total_tokens": self.total_prompt_tokens + self.total_completion_tokens,
|
| 58 |
+
"average_latency_seconds": round(avg_latency, 4),
|
| 59 |
+
"total_generation_time_seconds": round(self.total_generation_time_sec, 2)
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
metrics = SystemMetrics()
|
| 63 |
+
|
| 64 |
+
@asynccontextmanager
|
| 65 |
+
async def lifespan(app: FastAPI):
|
| 66 |
+
# Model warm-up on startup (non-blocking for fast server boot)
|
| 67 |
+
logger.info("Initializing LLM Manager and warming up model in the background...")
|
| 68 |
+
asyncio.create_task(llm_manager.setup_provider())
|
| 69 |
+
yield
|
| 70 |
+
# Shutdown operations
|
| 71 |
+
logger.info("Server shutting down.")
|
| 72 |
+
|
| 73 |
+
# Main app instantiation
|
| 74 |
+
app = FastAPI(
|
| 75 |
+
title="AI Coding Assistant API",
|
| 76 |
+
version="1.0.0",
|
| 77 |
+
lifespan=lifespan
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
# Add Middleware
|
| 81 |
+
app.add_middleware(LoggingMiddleware)
|
| 82 |
+
app.add_middleware(RateLimitMiddleware, limit=settings.RATE_LIMIT_PER_MINUTE)
|
| 83 |
+
|
| 84 |
+
app.add_middleware(
|
| 85 |
+
CORSMiddleware,
|
| 86 |
+
allow_origins=settings.cors_origins_list,
|
| 87 |
+
allow_credentials=True,
|
| 88 |
+
allow_methods=["*"],
|
| 89 |
+
allow_headers=["*"],
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
# Stream Generator Helper
|
| 93 |
+
async def run_stream_generator(
|
| 94 |
+
prompt: str,
|
| 95 |
+
system_prompt: str = None,
|
| 96 |
+
messages: list = None,
|
| 97 |
+
temperature: float = 0.7,
|
| 98 |
+
max_tokens: int = 1024,
|
| 99 |
+
) -> AsyncGenerator[str, None]:
|
| 100 |
+
start_time = time.time()
|
| 101 |
+
generated_content = ""
|
| 102 |
+
prompt_tokens = estimate_tokens(prompt)
|
| 103 |
+
if messages:
|
| 104 |
+
for m in messages:
|
| 105 |
+
prompt_tokens += estimate_tokens(m.get("content", ""))
|
| 106 |
+
|
| 107 |
+
try:
|
| 108 |
+
stream = await llm_manager.generate_stream(
|
| 109 |
+
prompt=prompt,
|
| 110 |
+
system_prompt=system_prompt,
|
| 111 |
+
messages=messages,
|
| 112 |
+
temperature=temperature,
|
| 113 |
+
max_tokens=max_tokens
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
async for chunk in stream:
|
| 117 |
+
generated_content += chunk
|
| 118 |
+
# Format SSE yield
|
| 119 |
+
yield format_sse_chunk(content=chunk, done=False)
|
| 120 |
+
|
| 121 |
+
duration = time.time() - start_time
|
| 122 |
+
completion_tokens = estimate_tokens(generated_content)
|
| 123 |
+
metrics.add(prompt_tokens, completion_tokens, duration)
|
| 124 |
+
|
| 125 |
+
# Send final chunk with usage metrics
|
| 126 |
+
usage_data = {
|
| 127 |
+
"prompt_tokens": prompt_tokens,
|
| 128 |
+
"completion_tokens": completion_tokens,
|
| 129 |
+
"total_tokens": prompt_tokens + completion_tokens,
|
| 130 |
+
"duration_ms": int(duration * 1000),
|
| 131 |
+
"tokens_per_second": round(completion_tokens / duration, 2) if duration > 0 else 0
|
| 132 |
+
}
|
| 133 |
+
yield format_sse_chunk(content="", done=True, usage=usage_data)
|
| 134 |
+
|
| 135 |
+
except Exception as e:
|
| 136 |
+
logger.error(f"Error in running streaming generator: {e}")
|
| 137 |
+
yield format_sse_chunk(content=f"\n[Generation Error: {e}]", done=True)
|
| 138 |
+
|
| 139 |
+
# Standard Non-Stream Helper
|
| 140 |
+
async def run_standard_generation(
|
| 141 |
+
prompt: str,
|
| 142 |
+
system_prompt: str = None,
|
| 143 |
+
messages: list = None,
|
| 144 |
+
temperature: float = 0.7,
|
| 145 |
+
max_tokens: int = 1024,
|
| 146 |
+
) -> ChatResponse:
|
| 147 |
+
start_time = time.time()
|
| 148 |
+
try:
|
| 149 |
+
response_data = await llm_manager.generate(
|
| 150 |
+
prompt=prompt,
|
| 151 |
+
system_prompt=system_prompt,
|
| 152 |
+
messages=messages,
|
| 153 |
+
temperature=temperature,
|
| 154 |
+
max_tokens=max_tokens
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
duration = time.time() - start_time
|
| 158 |
+
usage = response_data.get("usage", {})
|
| 159 |
+
|
| 160 |
+
prompt_tokens = usage.get("prompt_tokens", estimate_tokens(prompt))
|
| 161 |
+
completion_tokens = usage.get("completion_tokens", estimate_tokens(response_data["content"]))
|
| 162 |
+
metrics.add(prompt_tokens, completion_tokens, duration)
|
| 163 |
+
|
| 164 |
+
# Build enriched usage dict
|
| 165 |
+
metrics_dict = {
|
| 166 |
+
"prompt_tokens": prompt_tokens,
|
| 167 |
+
"completion_tokens": completion_tokens,
|
| 168 |
+
"total_tokens": prompt_tokens + completion_tokens,
|
| 169 |
+
"duration_ms": int(duration * 1000),
|
| 170 |
+
"tokens_per_second": round(completion_tokens / duration, 2) if duration > 0 else 0
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
status_info = await llm_manager.get_status_info()
|
| 174 |
+
|
| 175 |
+
return ChatResponse(
|
| 176 |
+
id=str(uuid.uuid4()),
|
| 177 |
+
content=response_data["content"],
|
| 178 |
+
usage=metrics_dict,
|
| 179 |
+
model=status_info.get("model_id", status_info.get("model_path", "qwen-0.5b"))
|
| 180 |
+
)
|
| 181 |
+
except Exception as e:
|
| 182 |
+
logger.error(f"Error in standard generation: {e}")
|
| 183 |
+
raise HTTPException(
|
| 184 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 185 |
+
detail=str(e)
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
# API ENDPOINTS
|
| 189 |
+
@app.post("/chat")
|
| 190 |
+
async def chat(request: ChatRequest):
|
| 191 |
+
messages_list = [m.model_dump() for m in request.messages]
|
| 192 |
+
|
| 193 |
+
# Extract system prompt if present in payload
|
| 194 |
+
system_prompt = None
|
| 195 |
+
user_messages = []
|
| 196 |
+
for msg in messages_list:
|
| 197 |
+
if msg["role"] == "system":
|
| 198 |
+
system_prompt = msg["content"]
|
| 199 |
+
else:
|
| 200 |
+
user_messages.append(msg)
|
| 201 |
+
|
| 202 |
+
# The last user message is the primary prompt
|
| 203 |
+
last_prompt = user_messages[-1]["content"] if user_messages else ""
|
| 204 |
+
|
| 205 |
+
if request.stream:
|
| 206 |
+
return StreamingResponse(
|
| 207 |
+
run_stream_generator(
|
| 208 |
+
prompt=last_prompt,
|
| 209 |
+
system_prompt=system_prompt,
|
| 210 |
+
messages=messages_list,
|
| 211 |
+
temperature=request.temperature or settings.DEFAULT_TEMPERATURE,
|
| 212 |
+
max_tokens=request.max_tokens or settings.DEFAULT_MAX_TOKENS
|
| 213 |
+
),
|
| 214 |
+
media_type="text/event-stream"
|
| 215 |
+
)
|
| 216 |
+
else:
|
| 217 |
+
return await run_standard_generation(
|
| 218 |
+
prompt=last_prompt,
|
| 219 |
+
system_prompt=system_prompt,
|
| 220 |
+
messages=messages_list,
|
| 221 |
+
temperature=request.temperature or settings.DEFAULT_TEMPERATURE,
|
| 222 |
+
max_tokens=request.max_tokens or settings.DEFAULT_MAX_TOKENS
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
@app.post("/complete")
|
| 226 |
+
async def complete(request: CompletionRequest):
|
| 227 |
+
prompt = get_completion_prompt(request.prefix, request.suffix, request.language or "python")
|
| 228 |
+
|
| 229 |
+
# We want low temperature for completion tasks
|
| 230 |
+
temp = request.temperature or 0.2
|
| 231 |
+
max_t = request.max_tokens or 128
|
| 232 |
+
|
| 233 |
+
if request.stream:
|
| 234 |
+
return StreamingResponse(
|
| 235 |
+
run_stream_generator(prompt=prompt, temperature=temp, max_tokens=max_t),
|
| 236 |
+
media_type="text/event-stream"
|
| 237 |
+
)
|
| 238 |
+
else:
|
| 239 |
+
return await run_standard_generation(prompt=prompt, temperature=temp, max_tokens=max_t)
|
| 240 |
+
|
| 241 |
+
# Helper decorator for modular code API endpoints
|
| 242 |
+
def make_code_endpoint(action: str):
|
| 243 |
+
async def endpoint(request: CodeRequest):
|
| 244 |
+
prompt = get_code_prompt(action, request.code, request.language or "python", request.context)
|
| 245 |
+
temp = request.temperature or settings.DEFAULT_TEMPERATURE
|
| 246 |
+
max_t = request.max_tokens or settings.DEFAULT_MAX_TOKENS
|
| 247 |
+
|
| 248 |
+
if request.stream:
|
| 249 |
+
return StreamingResponse(
|
| 250 |
+
run_stream_generator(prompt=prompt, temperature=temp, max_tokens=max_t),
|
| 251 |
+
media_type="text/event-stream"
|
| 252 |
+
)
|
| 253 |
+
else:
|
| 254 |
+
return await run_standard_generation(prompt=prompt, temperature=temp, max_tokens=max_t)
|
| 255 |
+
return endpoint
|
| 256 |
+
|
| 257 |
+
# Register code utility endpoints
|
| 258 |
+
app.post("/explain")(make_code_endpoint("explain"))
|
| 259 |
+
app.post("/debug")(make_code_endpoint("debug"))
|
| 260 |
+
app.post("/refactor")(make_code_endpoint("refactor"))
|
| 261 |
+
app.post("/generate-tests")(make_code_endpoint("generate-tests"))
|
| 262 |
+
app.post("/summarize")(make_code_endpoint("summarize"))
|
| 263 |
+
|
| 264 |
+
@app.get("/health")
|
| 265 |
+
async def health():
|
| 266 |
+
# Health check responds instantly for cloud pingers
|
| 267 |
+
status_info = await llm_manager.get_status_info()
|
| 268 |
+
return {
|
| 269 |
+
"status": "healthy",
|
| 270 |
+
"timestamp": time.time(),
|
| 271 |
+
"active_mode": status_info.get("active_mode"),
|
| 272 |
+
"local_model_loaded": status_info.get("initialized", False),
|
| 273 |
+
"is_downloading": status_info.get("is_downloading", False)
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
@app.get("/metrics")
|
| 277 |
+
async def get_metrics():
|
| 278 |
+
# Enriches metrics with current server state
|
| 279 |
+
report = metrics.get_metrics_report()
|
| 280 |
+
status_info = await llm_manager.get_status_info()
|
| 281 |
+
ram_stats = psutil.virtual_memory()
|
| 282 |
+
report.update({
|
| 283 |
+
"active_mode": "local",
|
| 284 |
+
"system_ram_gb": round(ram_stats.total / (1024 ** 3), 2),
|
| 285 |
+
"device": status_info.get("device", "CPU")
|
| 286 |
+
})
|
| 287 |
+
return report
|
| 288 |
+
|
| 289 |
+
@app.get("/model-info", response_model=ModelInfoResponse)
|
| 290 |
+
async def get_model_info():
|
| 291 |
+
status_info = await llm_manager.get_status_info()
|
| 292 |
+
|
| 293 |
+
# Calculate memory stats
|
| 294 |
+
ram_stats = psutil.virtual_memory()
|
| 295 |
+
total_gb = ram_stats.total / (1024 ** 3)
|
| 296 |
+
used_gb = ram_stats.used / (1024 ** 3)
|
| 297 |
+
|
| 298 |
+
local_exists = os.path.exists(settings.LOCAL_MODEL_PATH)
|
| 299 |
+
|
| 300 |
+
return ModelInfoResponse(
|
| 301 |
+
model_name=os.path.basename(settings.LOCAL_MODEL_PATH),
|
| 302 |
+
inference_mode="local",
|
| 303 |
+
status="loaded" if status_info.get("initialized") else "loading",
|
| 304 |
+
memory_usage_gb=round(used_gb, 2),
|
| 305 |
+
total_memory_gb=round(total_gb, 2),
|
| 306 |
+
local_model_exists=local_exists,
|
| 307 |
+
local_model_path=settings.LOCAL_MODEL_PATH,
|
| 308 |
+
device=status_info.get("device", "CPU")
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
# Serve Frontend static assets if available (production mode)
|
| 312 |
+
frontend_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "frontend", "dist"))
|
| 313 |
+
if os.path.exists(frontend_dir):
|
| 314 |
+
app.mount("/", StaticFiles(directory=frontend_dir, html=True), name="frontend")
|
| 315 |
+
logger.info(f"Frontend dist found. Serving frontend from {frontend_dir}")
|
| 316 |
+
else:
|
| 317 |
+
logger.warning(f"Frontend dist not found at {frontend_dir}. Running API-only server mode.")
|
backend/app/middleware.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
import logging
|
| 3 |
+
from collections import defaultdict
|
| 4 |
+
from fastapi import Request, Response, status
|
| 5 |
+
from fastapi.responses import JSONResponse
|
| 6 |
+
from starlette.middleware.base import BaseHTTPMiddleware
|
| 7 |
+
from backend.app.config import settings
|
| 8 |
+
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
class RateLimiter:
|
| 12 |
+
def __init__(self, requests_per_minute: int):
|
| 13 |
+
self.limit = requests_per_minute
|
| 14 |
+
self.clients = defaultdict(list)
|
| 15 |
+
|
| 16 |
+
def is_allowed(self, ip: str) -> bool:
|
| 17 |
+
now = time.time()
|
| 18 |
+
# Clean up requests older than 60 seconds
|
| 19 |
+
self.clients[ip] = [req_time for req_time in self.clients[ip] if now - req_time < 60]
|
| 20 |
+
|
| 21 |
+
if len(self.clients[ip]) >= self.limit:
|
| 22 |
+
return False
|
| 23 |
+
|
| 24 |
+
self.clients[ip].append(now)
|
| 25 |
+
return True
|
| 26 |
+
|
| 27 |
+
class RateLimitMiddleware(BaseHTTPMiddleware):
|
| 28 |
+
def __init__(self, app, limit: int = None):
|
| 29 |
+
super().__init__(app)
|
| 30 |
+
self.limiter = RateLimiter(limit or settings.RATE_LIMIT_PER_MINUTE)
|
| 31 |
+
|
| 32 |
+
async def dispatch(self, request: Request, call_next) -> Response:
|
| 33 |
+
# Bypass rate limits for health and metric endpoints
|
| 34 |
+
if request.url.path in ["/health", "/metrics", "/model-info"]:
|
| 35 |
+
return await call_next(request)
|
| 36 |
+
|
| 37 |
+
# Get client IP
|
| 38 |
+
client_ip = request.client.host if request.client else "unknown"
|
| 39 |
+
|
| 40 |
+
if not self.limiter.is_allowed(client_ip):
|
| 41 |
+
logger.warning(f"Rate limit exceeded for client: {client_ip} on path {request.url.path}")
|
| 42 |
+
return JSONResponse(
|
| 43 |
+
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
| 44 |
+
content={"detail": "Too many requests. Please try again in a minute."}
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
return await call_next(request)
|
| 48 |
+
|
| 49 |
+
class LoggingMiddleware(BaseHTTPMiddleware):
|
| 50 |
+
async def dispatch(self, request: Request, call_next) -> Response:
|
| 51 |
+
start_time = time.time()
|
| 52 |
+
client_ip = request.client.host if request.client else "unknown"
|
| 53 |
+
logger.info(f"Incoming: {request.method} {request.url.path} from {client_ip}")
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
response = await call_next(request)
|
| 57 |
+
duration = time.time() - start_time
|
| 58 |
+
logger.info(
|
| 59 |
+
f"Outgoing: {request.method} {request.url.path} - "
|
| 60 |
+
f"Status: {response.status_code} - Duration: {duration:.4f}s"
|
| 61 |
+
)
|
| 62 |
+
return response
|
| 63 |
+
except Exception as e:
|
| 64 |
+
duration = time.time() - start_time
|
| 65 |
+
logger.error(
|
| 66 |
+
f"Exception: {request.method} {request.url.path} failed - "
|
| 67 |
+
f"Error: {e} - Duration: {duration:.4f}s",
|
| 68 |
+
exc_info=True
|
| 69 |
+
)
|
| 70 |
+
return JSONResponse(
|
| 71 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 72 |
+
content={"detail": "Internal server error occurred."}
|
| 73 |
+
)
|
backend/app/models.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Optional, Dict, Any
|
| 2 |
+
from pydantic import BaseModel, Field
|
| 3 |
+
|
| 4 |
+
class ChatMessage(BaseModel):
|
| 5 |
+
role: str = Field(..., description="Role of the message author (system, user, assistant)")
|
| 6 |
+
content: str = Field(..., description="Content of the message")
|
| 7 |
+
|
| 8 |
+
class ChatRequest(BaseModel):
|
| 9 |
+
messages: List[ChatMessage] = Field(..., description="Conversation history including current prompt")
|
| 10 |
+
temperature: Optional[float] = Field(None, description="Controls randomness (0.0 to 1.0)")
|
| 11 |
+
max_tokens: Optional[int] = Field(None, description="Maximum number of tokens to generate")
|
| 12 |
+
top_p: Optional[float] = Field(None, description="Nucleus sampling threshold")
|
| 13 |
+
stream: Optional[bool] = Field(True, description="Whether to stream responses")
|
| 14 |
+
|
| 15 |
+
class CodeRequest(BaseModel):
|
| 16 |
+
code: str = Field(..., description="The code snippet to process")
|
| 17 |
+
language: Optional[str] = Field("python", description="Programming language of the code")
|
| 18 |
+
context: Optional[str] = Field(None, description="Additional developer instructions or query context")
|
| 19 |
+
temperature: Optional[float] = Field(None)
|
| 20 |
+
max_tokens: Optional[int] = Field(None)
|
| 21 |
+
stream: Optional[bool] = Field(True)
|
| 22 |
+
|
| 23 |
+
class CompletionRequest(BaseModel):
|
| 24 |
+
prefix: str = Field(..., description="Code before the cursor position")
|
| 25 |
+
suffix: Optional[str] = Field("", description="Code after the cursor position")
|
| 26 |
+
language: Optional[str] = Field("python")
|
| 27 |
+
max_tokens: Optional[int] = Field(128)
|
| 28 |
+
temperature: Optional[float] = Field(0.2)
|
| 29 |
+
stream: Optional[bool] = Field(False)
|
| 30 |
+
|
| 31 |
+
class ChatResponseChunk(BaseModel):
|
| 32 |
+
content: str
|
| 33 |
+
done: bool = False
|
| 34 |
+
usage: Optional[Dict[str, Any]] = None
|
| 35 |
+
|
| 36 |
+
class ChatResponse(BaseModel):
|
| 37 |
+
id: str
|
| 38 |
+
content: str
|
| 39 |
+
usage: Dict[str, Any]
|
| 40 |
+
model: str
|
| 41 |
+
|
| 42 |
+
class ModelInfoResponse(BaseModel):
|
| 43 |
+
model_name: str
|
| 44 |
+
inference_mode: str # "local" or "huggingface"
|
| 45 |
+
status: str # "loaded", "error", "fallback"
|
| 46 |
+
memory_usage_gb: float
|
| 47 |
+
total_memory_gb: float
|
| 48 |
+
local_model_exists: bool
|
| 49 |
+
local_model_path: str
|
| 50 |
+
device: str # "cpu", "cuda", etc.
|
backend/app/utils.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
from typing import Dict, Any, List, Optional
|
| 3 |
+
|
| 4 |
+
def estimate_tokens(text: str) -> int:
|
| 5 |
+
"""
|
| 6 |
+
Estimates token count for Qwen/GPT-like models without heavy tokenizers.
|
| 7 |
+
Rule of thumb: 1 token ≈ 4 characters, or ~1.3 tokens per word.
|
| 8 |
+
"""
|
| 9 |
+
if not text:
|
| 10 |
+
return 0
|
| 11 |
+
# Average of char-based and word-based estimations
|
| 12 |
+
char_est = len(text) / 4.0
|
| 13 |
+
word_est = len(text.split()) * 1.3
|
| 14 |
+
return int((char_est + word_est) / 2.0) + 1
|
| 15 |
+
|
| 16 |
+
def format_sse_chunk(content: str, done: bool = False, usage: Optional[Dict[str, Any]] = None) -> str:
|
| 17 |
+
"""Format data for Server-Sent Events transmission."""
|
| 18 |
+
data = {
|
| 19 |
+
"content": content,
|
| 20 |
+
"done": done,
|
| 21 |
+
"usage": usage
|
| 22 |
+
}
|
| 23 |
+
return f"data: {json_dumps(data)}\n\n"
|
| 24 |
+
|
| 25 |
+
def json_dumps(obj: Any) -> str:
|
| 26 |
+
# A safe helper for JSON serialization
|
| 27 |
+
import json
|
| 28 |
+
return json.dumps(obj)
|
| 29 |
+
|
| 30 |
+
def get_code_prompt(action: str, code: str, language: str, context: Optional[str] = None) -> str:
|
| 31 |
+
"""Returns specialized system/user prompts for code tasks."""
|
| 32 |
+
prompt_templates = {
|
| 33 |
+
"explain": (
|
| 34 |
+
"You are an expert software engineer. Explain this code step-by-step. "
|
| 35 |
+
"Highlight the flow of execution, core algorithms, and potential performance implications. "
|
| 36 |
+
"Write the explanation in clear, readable markdown.\n\n"
|
| 37 |
+
f"Language: {language}\n"
|
| 38 |
+
f"Code:\n```\n{code}\n```"
|
| 39 |
+
),
|
| 40 |
+
"debug": (
|
| 41 |
+
"You are a senior debugger. Identify errors, logical bugs, edge cases, memory leaks, "
|
| 42 |
+
"or security vulnerabilities in the code below. Explain each issue found and "
|
| 43 |
+
"provide a corrected version of the code, indicating what changes were made.\n\n"
|
| 44 |
+
f"Language: {language}\n"
|
| 45 |
+
f"Code:\n```\n{code}\n```"
|
| 46 |
+
),
|
| 47 |
+
"refactor": (
|
| 48 |
+
"You are a principal engineer. Refactor this code to improve its readability, "
|
| 49 |
+
"efficiency, and modularity. Adhere to SOLID principles and industry design patterns. "
|
| 50 |
+
"Provide the refactored code and list the improvements.\n\n"
|
| 51 |
+
f"Language: {language}\n"
|
| 52 |
+
f"Code:\n```\n{code}\n```"
|
| 53 |
+
),
|
| 54 |
+
"generate-tests": (
|
| 55 |
+
"You are a QA automation lead. Write comprehensive unit tests for the following code snippet. "
|
| 56 |
+
"Cover typical inputs, edge cases, and error conditions. Use standard testing libraries.\n\n"
|
| 57 |
+
f"Language: {language}\n"
|
| 58 |
+
f"Code:\n```\n{code}\n```"
|
| 59 |
+
),
|
| 60 |
+
"summarize": (
|
| 61 |
+
"Provide a brief, high-level summary of what this code does in 2-3 sentences. "
|
| 62 |
+
"Focus on the main inputs, transformations, and outputs.\n\n"
|
| 63 |
+
f"Language: {language}\n"
|
| 64 |
+
f"Code:\n```\n{code}\n```"
|
| 65 |
+
)
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
prompt = prompt_templates.get(action, f"Analyze the following code:\n\n```{language}\n{code}\n```")
|
| 69 |
+
if context:
|
| 70 |
+
prompt = f"Context/Instructions: {context}\n\n{prompt}"
|
| 71 |
+
return prompt
|
| 72 |
+
|
| 73 |
+
def get_completion_prompt(prefix: str, suffix: str, language: str) -> str:
|
| 74 |
+
"""Constructs instructions for code completion."""
|
| 75 |
+
return (
|
| 76 |
+
f"You are a code auto-completion utility. Continue the code for the {language} language. "
|
| 77 |
+
"Your task is to fill in the missing code between the Prefix and Suffix. "
|
| 78 |
+
"Return ONLY the code that should be inserted directly at the transition point. "
|
| 79 |
+
"Do NOT write any explanations. Do NOT wrap your response in markdown code blocks.\n\n"
|
| 80 |
+
f"--- PREFIX ---\n{prefix}\n"
|
| 81 |
+
f"--- SUFFIX ---\n{suffix}\n"
|
| 82 |
+
"--- INSERT COMPLETED CODE BELOW ---"
|
| 83 |
+
)
|
backend/requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.100.0
|
| 2 |
+
uvicorn>=0.22.0
|
| 3 |
+
pydantic>=2.0
|
| 4 |
+
pydantic-settings>=2.0
|
| 5 |
+
python-dotenv>=1.0.0
|
| 6 |
+
requests>=2.31.0
|
| 7 |
+
httpx>=0.24.0
|
| 8 |
+
huggingface_hub>=0.16.0
|
| 9 |
+
psutil>=5.9.0
|
| 10 |
+
llama-cpp-python>=0.2.0
|
backend/run.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import uvicorn
|
| 2 |
+
from backend.app.config import settings
|
| 3 |
+
|
| 4 |
+
if __name__ == "__main__":
|
| 5 |
+
print(f"Starting server on http://{settings.HOST}:{settings.PORT}")
|
| 6 |
+
uvicorn.run(
|
| 7 |
+
"backend.app.main:app",
|
| 8 |
+
host=settings.HOST,
|
| 9 |
+
port=settings.PORT,
|
| 10 |
+
reload=settings.DEBUG
|
| 11 |
+
)
|
docker/Dockerfile.backend
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multi-stage build to reduce final image size
|
| 2 |
+
FROM python:3.11-slim as builder
|
| 3 |
+
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
# Install compilation tools needed for compiling llama-cpp-python
|
| 7 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 8 |
+
build-essential \
|
| 9 |
+
gcc \
|
| 10 |
+
g++ \
|
| 11 |
+
make \
|
| 12 |
+
python3-dev \
|
| 13 |
+
git \
|
| 14 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 15 |
+
|
| 16 |
+
# Copy backend requirements
|
| 17 |
+
COPY backend/requirements.txt .
|
| 18 |
+
|
| 19 |
+
# Install dependencies and build llama-cpp-python
|
| 20 |
+
RUN pip install --no-cache-dir --user -r requirements.txt
|
| 21 |
+
|
| 22 |
+
# Final production stage
|
| 23 |
+
FROM python:3.11-slim
|
| 24 |
+
|
| 25 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 26 |
+
libgomp1 \
|
| 27 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 28 |
+
|
| 29 |
+
WORKDIR /app
|
| 30 |
+
|
| 31 |
+
# Copy installed site-packages from builder stage
|
| 32 |
+
COPY --from=builder /root/.local /root/.local
|
| 33 |
+
ENV PATH=/root/.local/bin:$PATH
|
| 34 |
+
|
| 35 |
+
# Copy backend codebase
|
| 36 |
+
COPY backend/ /app/backend/
|
| 37 |
+
|
| 38 |
+
ENV PORT=8000
|
| 39 |
+
ENV HOST=0.0.0.0
|
| 40 |
+
ENV PYTHONPATH=/app
|
| 41 |
+
|
| 42 |
+
EXPOSE 8000
|
| 43 |
+
|
| 44 |
+
# Start command
|
| 45 |
+
CMD ["python", "backend/run.py"]
|
docker/Dockerfile.frontend
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:20-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY frontend/package*.json ./
|
| 6 |
+
|
| 7 |
+
RUN npm install
|
| 8 |
+
|
| 9 |
+
COPY frontend/ ./
|
| 10 |
+
|
| 11 |
+
EXPOSE 5173
|
| 12 |
+
|
| 13 |
+
CMD ["npm", "run", "dev", "--", "--host"]
|
docker/docker-compose.yml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
backend:
|
| 5 |
+
build:
|
| 6 |
+
context: ../
|
| 7 |
+
dockerfile: docker/Dockerfile.backend
|
| 8 |
+
ports:
|
| 9 |
+
- "8000:8000"
|
| 10 |
+
volumes:
|
| 11 |
+
- ../backend:/app/backend
|
| 12 |
+
- ../models:/app/models
|
| 13 |
+
environment:
|
| 14 |
+
- PORT=8000
|
| 15 |
+
- HOST=0.0.0.0
|
| 16 |
+
- DEBUG=true
|
| 17 |
+
- INFERENCE_MODE=local
|
| 18 |
+
- LOCAL_MODEL_PATH=models/SmolLM2-360M-Instruct-Q4_K_M.gguf
|
| 19 |
+
- CORS_ORIGINS=http://localhost:5173,http://localhost:8000
|
| 20 |
+
restart: unless-stopped
|
| 21 |
+
|
| 22 |
+
frontend:
|
| 23 |
+
build:
|
| 24 |
+
context: ../
|
| 25 |
+
dockerfile: docker/Dockerfile.frontend
|
| 26 |
+
ports:
|
| 27 |
+
- "5173:5173"
|
| 28 |
+
volumes:
|
| 29 |
+
- ../frontend:/app
|
| 30 |
+
- /app/node_modules
|
| 31 |
+
environment:
|
| 32 |
+
- VITE_API_URL=http://backend:8000
|
| 33 |
+
command: npm run dev -- --host --force
|
| 34 |
+
depends_on:
|
| 35 |
+
- backend
|
| 36 |
+
restart: unless-stopped
|
docs/API.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# API Reference Documentation 📖
|
| 2 |
+
|
| 3 |
+
This document details the REST API specifications for the Antigravity Coding Assistant.
|
| 4 |
+
|
| 5 |
+
All APIs use JSON request bodies. Streaming endpoints support Server-Sent Events (`text/event-stream`).
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Endpoints Summary
|
| 10 |
+
|
| 11 |
+
| Method | Path | Description | Streaming Support |
|
| 12 |
+
| :--- | :--- | :--- | :--- |
|
| 13 |
+
| `POST` | `/chat` | Chat message handler | Yes |
|
| 14 |
+
| `POST` | `/complete` | Code auto-completion | Yes |
|
| 15 |
+
| `POST` | `/explain` | Generates explanation for a code block | Yes |
|
| 16 |
+
| `POST` | `/debug` | Locates bugs and provides fixes | Yes |
|
| 17 |
+
| `POST` | `/refactor` | Refactors code for quality & SOLID rules | Yes |
|
| 18 |
+
| `POST` | `/generate-tests`| Creates automated unit tests | Yes |
|
| 19 |
+
| `POST` | `/summarize` | Summarizes code in 2-3 sentences | Yes |
|
| 20 |
+
| `GET` | `/health` | Check backend service health | No |
|
| 21 |
+
| `GET` | `/metrics` | Get performance metrics telemetry | No |
|
| 22 |
+
| `GET` | `/model-info` | Check active LLM provider specifications| No |
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## Detailed Specifications
|
| 27 |
+
|
| 28 |
+
### 1. POST `/chat`
|
| 29 |
+
Generates conversational assistance based on prompt history.
|
| 30 |
+
|
| 31 |
+
**Request Payload:**
|
| 32 |
+
```json
|
| 33 |
+
{
|
| 34 |
+
"messages": [
|
| 35 |
+
{ "role": "system", "content": "You are a coding assistant." },
|
| 36 |
+
{ "role": "user", "content": "Write a bubble sort in Python." }
|
| 37 |
+
],
|
| 38 |
+
"temperature": 0.7,
|
| 39 |
+
"max_tokens": 1024,
|
| 40 |
+
"top_p": 0.9,
|
| 41 |
+
"stream": true
|
| 42 |
+
}
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
### 2. POST `/complete`
|
| 48 |
+
Fills in missing code between a prefix and suffix (Fill-in-the-Middle).
|
| 49 |
+
|
| 50 |
+
**Request Payload:**
|
| 51 |
+
```json
|
| 52 |
+
{
|
| 53 |
+
"prefix": "def add_numbers(a, b):\n ",
|
| 54 |
+
"suffix": "\n\nprint(add_numbers(5, 10))",
|
| 55 |
+
"language": "python",
|
| 56 |
+
"max_tokens": 64,
|
| 57 |
+
"temperature": 0.1,
|
| 58 |
+
"stream": false
|
| 59 |
+
}
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
---
|
| 63 |
+
|
| 64 |
+
### 3. POST `/explain` | `/debug` | `/refactor` | `/generate-tests` | `/summarize`
|
| 65 |
+
Specialized endpoints for code-focused instructions.
|
| 66 |
+
|
| 67 |
+
**Request Payload:**
|
| 68 |
+
```json
|
| 69 |
+
{
|
| 70 |
+
"code": "def double(x): return x * 2",
|
| 71 |
+
"language": "python",
|
| 72 |
+
"context": "Optimize this code",
|
| 73 |
+
"temperature": 0.7,
|
| 74 |
+
"max_tokens": 1024,
|
| 75 |
+
"stream": true
|
| 76 |
+
}
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
## Response Formats
|
| 82 |
+
|
| 83 |
+
### Non-Streaming Response
|
| 84 |
+
Returned when `stream` parameter is `false`:
|
| 85 |
+
```json
|
| 86 |
+
{
|
| 87 |
+
"id": "uuid-string-here",
|
| 88 |
+
"content": "Generated text or code review block here",
|
| 89 |
+
"usage": {
|
| 90 |
+
"prompt_tokens": 42,
|
| 91 |
+
"completion_tokens": 128,
|
| 92 |
+
"total_tokens": 170,
|
| 93 |
+
"duration_ms": 1240,
|
| 94 |
+
"tokens_per_second": 103.2
|
| 95 |
+
},
|
| 96 |
+
"model": "bartowski/Qwen2.5-Coder-0.5B-Instruct-GGUF"
|
| 97 |
+
}
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
### Streaming Response (SSE)
|
| 101 |
+
Returned when `stream` parameter is `true`. Standard SSE format:
|
| 102 |
+
```
|
| 103 |
+
data: {"content": "Hello", "done": false}
|
| 104 |
+
|
| 105 |
+
data: {"content": " World", "done": false}
|
| 106 |
+
|
| 107 |
+
data: {"content": "", "done": true, "usage": {"prompt_tokens": 12, "completion_tokens": 2, "total_tokens": 14, "duration_ms": 230, "tokens_per_second": 8.7}}
|
| 108 |
+
```
|
| 109 |
+
Each data line represents a single generated text token. The final SSE chunk contains `done: true` and the generation metrics.
|
docs/DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Cloud Deployment Guide 🌐
|
| 2 |
+
|
| 3 |
+
This document covers instructions on deploying the unified **Antigravity AI Coder** container to Railway and Render.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## <a name="railway"></a> 🚄 Railway Deployment (Preferred)
|
| 8 |
+
|
| 9 |
+
Railway is the recommended host because of its native support for Dockerfile builds, fast container deployments, and reliable persistent volumes.
|
| 10 |
+
|
| 11 |
+
### Step 1: Create a Railway Project
|
| 12 |
+
1. Log into your [Railway Console](https://railway.app/).
|
| 13 |
+
2. Click **New Project** -> **Deploy from GitHub repo**.
|
| 14 |
+
3. Select your repository.
|
| 15 |
+
|
| 16 |
+
### Step 2: Configure Environment Variables
|
| 17 |
+
Add the following variables in Railway's **Variables** tab:
|
| 18 |
+
* `PORT` = `8000`
|
| 19 |
+
* `HOST` = `0.0.0.0`
|
| 20 |
+
* `INFERENCE_MODE` = `auto` (Routes to Hugging Face Cloud if container RAM is limited)
|
| 21 |
+
* `HF_API_TOKEN` = `your_huggingface_api_token` (Strongly recommended to avoid rate limits)
|
| 22 |
+
* `HF_MODEL_ID` = `Qwen/Qwen2.5-Coder-0.5B-Instruct`
|
| 23 |
+
* `SECRET_KEY` = `generate-a-long-random-string`
|
| 24 |
+
|
| 25 |
+
### Step 3: Mount a Persistent Volume (Optional but Recommended)
|
| 26 |
+
To prevent the container from re-downloading the 397MB local model file on every restart:
|
| 27 |
+
1. In the service settings, click **Volume** -> **Add Volume**.
|
| 28 |
+
2. Mount the volume to: `/app/models`.
|
| 29 |
+
3. Save the changes. Railway will now persist the downloaded GGUF file inside this volume.
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
## <a name="render"></a> 💎 Render Deployment
|
| 34 |
+
|
| 35 |
+
Render supports Docker builds out of the box using our blueprint `render.yaml` configuration.
|
| 36 |
+
|
| 37 |
+
### Step 1: Deploy using render.yaml Blueprint
|
| 38 |
+
1. Log into your [Render Dashboard](https://dashboard.render.com/).
|
| 39 |
+
2. Click **New** -> **Blueprint**.
|
| 40 |
+
3. Link your repository. Render will automatically read the `render.yaml` file from your repo root.
|
| 41 |
+
4. Render will prompt you for:
|
| 42 |
+
* **Service Name:** `antigravity-ai-coder`
|
| 43 |
+
* **HF_API_TOKEN:** Provide your Hugging Face API key.
|
| 44 |
+
|
| 45 |
+
### Step 2: Custom Scaling & Memory Fallback
|
| 46 |
+
* **On Free Instances:** Render's free tier provides 512MB RAM. Since 512MB is below our local inference threshold, the backend will boot up, detect the system memory limit, and automatically switch to `huggingface` cloud fallback mode.
|
| 47 |
+
* **On Paid Instances:** If you upgrade to a Starter or Standard instance (>= 2GB RAM) and keep `INFERENCE_MODE=auto`, it will download and run the GGUF model locally. The blueprint allocates a 10GB persistent disk mounted to `/app/models` to store the model file.
|
frontend/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
frontend/.oxlintrc.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
| 3 |
+
"plugins": ["react", "typescript", "oxc"],
|
| 4 |
+
"rules": {
|
| 5 |
+
"react/rules-of-hooks": "error",
|
| 6 |
+
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
| 7 |
+
}
|
| 8 |
+
}
|
frontend/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# React + TypeScript + Vite
|
| 2 |
+
|
| 3 |
+
This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
|
| 4 |
+
|
| 5 |
+
Currently, two official plugins are available:
|
| 6 |
+
|
| 7 |
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
| 8 |
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
| 9 |
+
|
| 10 |
+
## React Compiler
|
| 11 |
+
|
| 12 |
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
| 13 |
+
|
| 14 |
+
## Expanding the Oxlint configuration
|
| 15 |
+
|
| 16 |
+
If you are developing a production application, we recommend enabling type-aware lint rules by installing `oxlint-tsgolint` and editing `.oxlintrc.json`:
|
| 17 |
+
|
| 18 |
+
```json
|
| 19 |
+
{
|
| 20 |
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
| 21 |
+
"plugins": ["react", "typescript", "oxc"],
|
| 22 |
+
"options": {
|
| 23 |
+
"typeAware": true
|
| 24 |
+
},
|
| 25 |
+
"rules": {
|
| 26 |
+
"react/rules-of-hooks": "error",
|
| 27 |
+
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
See the [Oxlint rules documentation](https://oxc.rs/docs/guide/usage/linter/rules) for the full list of rules and categories.
|
frontend/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en" class="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<meta name="description" content="Production-ready AI Coding Assistant running local Qwen2.5-Coder GGUF model with Hugging Face API fallback, Monaco Editor integration, and code playground." />
|
| 7 |
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⚡</text></svg>" />
|
| 8 |
+
<title>Antigravity AI Coder | Qwen2.5-Coder Assistant</title>
|
| 9 |
+
</head>
|
| 10 |
+
<body class="bg-background text-foreground transition-colors duration-200">
|
| 11 |
+
<div id="root"></div>
|
| 12 |
+
<script type="module" src="/src/main.tsx"></script>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
frontend/package-lock.json
ADDED
|
@@ -0,0 +1,1909 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"version": "0.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "frontend",
|
| 9 |
+
"version": "0.0.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"@monaco-editor/react": "^4.7.0",
|
| 12 |
+
"@tailwindcss/vite": "^4.3.2",
|
| 13 |
+
"@tanstack/react-query": "^5.101.2",
|
| 14 |
+
"clsx": "^2.1.1",
|
| 15 |
+
"framer-motion": "^12.42.2",
|
| 16 |
+
"lucide-react": "^1.23.0",
|
| 17 |
+
"react": "^19.2.7",
|
| 18 |
+
"react-dom": "^19.2.7",
|
| 19 |
+
"react-router-dom": "^7.18.1",
|
| 20 |
+
"tailwind-merge": "^3.6.0",
|
| 21 |
+
"tailwindcss": "^4.3.2"
|
| 22 |
+
},
|
| 23 |
+
"devDependencies": {
|
| 24 |
+
"@types/node": "^24.13.2",
|
| 25 |
+
"@types/react": "^19.2.17",
|
| 26 |
+
"@types/react-dom": "^19.2.3",
|
| 27 |
+
"@vitejs/plugin-react": "^6.0.3",
|
| 28 |
+
"oxlint": "^1.71.0",
|
| 29 |
+
"typescript": "~6.0.2",
|
| 30 |
+
"vite": "^8.1.1"
|
| 31 |
+
}
|
| 32 |
+
},
|
| 33 |
+
"node_modules/@emnapi/core": {
|
| 34 |
+
"version": "1.11.1",
|
| 35 |
+
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
|
| 36 |
+
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
|
| 37 |
+
"license": "MIT",
|
| 38 |
+
"optional": true,
|
| 39 |
+
"dependencies": {
|
| 40 |
+
"@emnapi/wasi-threads": "1.2.2",
|
| 41 |
+
"tslib": "^2.4.0"
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"node_modules/@emnapi/runtime": {
|
| 45 |
+
"version": "1.11.1",
|
| 46 |
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
|
| 47 |
+
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
|
| 48 |
+
"license": "MIT",
|
| 49 |
+
"optional": true,
|
| 50 |
+
"dependencies": {
|
| 51 |
+
"tslib": "^2.4.0"
|
| 52 |
+
}
|
| 53 |
+
},
|
| 54 |
+
"node_modules/@emnapi/wasi-threads": {
|
| 55 |
+
"version": "1.2.2",
|
| 56 |
+
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
|
| 57 |
+
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
|
| 58 |
+
"license": "MIT",
|
| 59 |
+
"optional": true,
|
| 60 |
+
"dependencies": {
|
| 61 |
+
"tslib": "^2.4.0"
|
| 62 |
+
}
|
| 63 |
+
},
|
| 64 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 65 |
+
"version": "0.3.13",
|
| 66 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 67 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 68 |
+
"license": "MIT",
|
| 69 |
+
"dependencies": {
|
| 70 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 71 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"node_modules/@jridgewell/remapping": {
|
| 75 |
+
"version": "2.3.5",
|
| 76 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 77 |
+
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
| 78 |
+
"license": "MIT",
|
| 79 |
+
"dependencies": {
|
| 80 |
+
"@jridgewell/gen-mapping": "^0.3.5",
|
| 81 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 82 |
+
}
|
| 83 |
+
},
|
| 84 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 85 |
+
"version": "3.1.2",
|
| 86 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 87 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 88 |
+
"license": "MIT",
|
| 89 |
+
"engines": {
|
| 90 |
+
"node": ">=6.0.0"
|
| 91 |
+
}
|
| 92 |
+
},
|
| 93 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 94 |
+
"version": "1.5.5",
|
| 95 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 96 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 97 |
+
"license": "MIT"
|
| 98 |
+
},
|
| 99 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 100 |
+
"version": "0.3.31",
|
| 101 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 102 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 103 |
+
"license": "MIT",
|
| 104 |
+
"dependencies": {
|
| 105 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 106 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 107 |
+
}
|
| 108 |
+
},
|
| 109 |
+
"node_modules/@monaco-editor/loader": {
|
| 110 |
+
"version": "1.7.0",
|
| 111 |
+
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz",
|
| 112 |
+
"integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==",
|
| 113 |
+
"license": "MIT",
|
| 114 |
+
"dependencies": {
|
| 115 |
+
"state-local": "^1.0.6"
|
| 116 |
+
}
|
| 117 |
+
},
|
| 118 |
+
"node_modules/@monaco-editor/react": {
|
| 119 |
+
"version": "4.7.0",
|
| 120 |
+
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
|
| 121 |
+
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
|
| 122 |
+
"license": "MIT",
|
| 123 |
+
"dependencies": {
|
| 124 |
+
"@monaco-editor/loader": "^1.5.0"
|
| 125 |
+
},
|
| 126 |
+
"peerDependencies": {
|
| 127 |
+
"monaco-editor": ">= 0.25.0 < 1",
|
| 128 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 129 |
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 130 |
+
}
|
| 131 |
+
},
|
| 132 |
+
"node_modules/@napi-rs/wasm-runtime": {
|
| 133 |
+
"version": "1.1.6",
|
| 134 |
+
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
|
| 135 |
+
"integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
|
| 136 |
+
"license": "MIT",
|
| 137 |
+
"optional": true,
|
| 138 |
+
"dependencies": {
|
| 139 |
+
"@tybys/wasm-util": "^0.10.3"
|
| 140 |
+
},
|
| 141 |
+
"funding": {
|
| 142 |
+
"type": "github",
|
| 143 |
+
"url": "https://github.com/sponsors/Brooooooklyn"
|
| 144 |
+
},
|
| 145 |
+
"peerDependencies": {
|
| 146 |
+
"@emnapi/core": "^1.7.1",
|
| 147 |
+
"@emnapi/runtime": "^1.7.1"
|
| 148 |
+
}
|
| 149 |
+
},
|
| 150 |
+
"node_modules/@oxc-project/types": {
|
| 151 |
+
"version": "0.138.0",
|
| 152 |
+
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz",
|
| 153 |
+
"integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==",
|
| 154 |
+
"license": "MIT",
|
| 155 |
+
"funding": {
|
| 156 |
+
"url": "https://github.com/sponsors/Boshen"
|
| 157 |
+
}
|
| 158 |
+
},
|
| 159 |
+
"node_modules/@oxlint/binding-android-arm-eabi": {
|
| 160 |
+
"version": "1.72.0",
|
| 161 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.72.0.tgz",
|
| 162 |
+
"integrity": "sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==",
|
| 163 |
+
"cpu": [
|
| 164 |
+
"arm"
|
| 165 |
+
],
|
| 166 |
+
"dev": true,
|
| 167 |
+
"license": "MIT",
|
| 168 |
+
"optional": true,
|
| 169 |
+
"os": [
|
| 170 |
+
"android"
|
| 171 |
+
],
|
| 172 |
+
"engines": {
|
| 173 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 174 |
+
}
|
| 175 |
+
},
|
| 176 |
+
"node_modules/@oxlint/binding-android-arm64": {
|
| 177 |
+
"version": "1.72.0",
|
| 178 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.72.0.tgz",
|
| 179 |
+
"integrity": "sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==",
|
| 180 |
+
"cpu": [
|
| 181 |
+
"arm64"
|
| 182 |
+
],
|
| 183 |
+
"dev": true,
|
| 184 |
+
"license": "MIT",
|
| 185 |
+
"optional": true,
|
| 186 |
+
"os": [
|
| 187 |
+
"android"
|
| 188 |
+
],
|
| 189 |
+
"engines": {
|
| 190 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 191 |
+
}
|
| 192 |
+
},
|
| 193 |
+
"node_modules/@oxlint/binding-darwin-arm64": {
|
| 194 |
+
"version": "1.72.0",
|
| 195 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.72.0.tgz",
|
| 196 |
+
"integrity": "sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==",
|
| 197 |
+
"cpu": [
|
| 198 |
+
"arm64"
|
| 199 |
+
],
|
| 200 |
+
"dev": true,
|
| 201 |
+
"license": "MIT",
|
| 202 |
+
"optional": true,
|
| 203 |
+
"os": [
|
| 204 |
+
"darwin"
|
| 205 |
+
],
|
| 206 |
+
"engines": {
|
| 207 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 208 |
+
}
|
| 209 |
+
},
|
| 210 |
+
"node_modules/@oxlint/binding-darwin-x64": {
|
| 211 |
+
"version": "1.72.0",
|
| 212 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.72.0.tgz",
|
| 213 |
+
"integrity": "sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==",
|
| 214 |
+
"cpu": [
|
| 215 |
+
"x64"
|
| 216 |
+
],
|
| 217 |
+
"dev": true,
|
| 218 |
+
"license": "MIT",
|
| 219 |
+
"optional": true,
|
| 220 |
+
"os": [
|
| 221 |
+
"darwin"
|
| 222 |
+
],
|
| 223 |
+
"engines": {
|
| 224 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 225 |
+
}
|
| 226 |
+
},
|
| 227 |
+
"node_modules/@oxlint/binding-freebsd-x64": {
|
| 228 |
+
"version": "1.72.0",
|
| 229 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.72.0.tgz",
|
| 230 |
+
"integrity": "sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==",
|
| 231 |
+
"cpu": [
|
| 232 |
+
"x64"
|
| 233 |
+
],
|
| 234 |
+
"dev": true,
|
| 235 |
+
"license": "MIT",
|
| 236 |
+
"optional": true,
|
| 237 |
+
"os": [
|
| 238 |
+
"freebsd"
|
| 239 |
+
],
|
| 240 |
+
"engines": {
|
| 241 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 242 |
+
}
|
| 243 |
+
},
|
| 244 |
+
"node_modules/@oxlint/binding-linux-arm-gnueabihf": {
|
| 245 |
+
"version": "1.72.0",
|
| 246 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.72.0.tgz",
|
| 247 |
+
"integrity": "sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==",
|
| 248 |
+
"cpu": [
|
| 249 |
+
"arm"
|
| 250 |
+
],
|
| 251 |
+
"dev": true,
|
| 252 |
+
"license": "MIT",
|
| 253 |
+
"optional": true,
|
| 254 |
+
"os": [
|
| 255 |
+
"linux"
|
| 256 |
+
],
|
| 257 |
+
"engines": {
|
| 258 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 259 |
+
}
|
| 260 |
+
},
|
| 261 |
+
"node_modules/@oxlint/binding-linux-arm-musleabihf": {
|
| 262 |
+
"version": "1.72.0",
|
| 263 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.72.0.tgz",
|
| 264 |
+
"integrity": "sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==",
|
| 265 |
+
"cpu": [
|
| 266 |
+
"arm"
|
| 267 |
+
],
|
| 268 |
+
"dev": true,
|
| 269 |
+
"license": "MIT",
|
| 270 |
+
"optional": true,
|
| 271 |
+
"os": [
|
| 272 |
+
"linux"
|
| 273 |
+
],
|
| 274 |
+
"engines": {
|
| 275 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 276 |
+
}
|
| 277 |
+
},
|
| 278 |
+
"node_modules/@oxlint/binding-linux-arm64-gnu": {
|
| 279 |
+
"version": "1.72.0",
|
| 280 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.72.0.tgz",
|
| 281 |
+
"integrity": "sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==",
|
| 282 |
+
"cpu": [
|
| 283 |
+
"arm64"
|
| 284 |
+
],
|
| 285 |
+
"dev": true,
|
| 286 |
+
"license": "MIT",
|
| 287 |
+
"optional": true,
|
| 288 |
+
"os": [
|
| 289 |
+
"linux"
|
| 290 |
+
],
|
| 291 |
+
"engines": {
|
| 292 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 293 |
+
}
|
| 294 |
+
},
|
| 295 |
+
"node_modules/@oxlint/binding-linux-arm64-musl": {
|
| 296 |
+
"version": "1.72.0",
|
| 297 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.72.0.tgz",
|
| 298 |
+
"integrity": "sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==",
|
| 299 |
+
"cpu": [
|
| 300 |
+
"arm64"
|
| 301 |
+
],
|
| 302 |
+
"dev": true,
|
| 303 |
+
"license": "MIT",
|
| 304 |
+
"optional": true,
|
| 305 |
+
"os": [
|
| 306 |
+
"linux"
|
| 307 |
+
],
|
| 308 |
+
"engines": {
|
| 309 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 310 |
+
}
|
| 311 |
+
},
|
| 312 |
+
"node_modules/@oxlint/binding-linux-ppc64-gnu": {
|
| 313 |
+
"version": "1.72.0",
|
| 314 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.72.0.tgz",
|
| 315 |
+
"integrity": "sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==",
|
| 316 |
+
"cpu": [
|
| 317 |
+
"ppc64"
|
| 318 |
+
],
|
| 319 |
+
"dev": true,
|
| 320 |
+
"license": "MIT",
|
| 321 |
+
"optional": true,
|
| 322 |
+
"os": [
|
| 323 |
+
"linux"
|
| 324 |
+
],
|
| 325 |
+
"engines": {
|
| 326 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 327 |
+
}
|
| 328 |
+
},
|
| 329 |
+
"node_modules/@oxlint/binding-linux-riscv64-gnu": {
|
| 330 |
+
"version": "1.72.0",
|
| 331 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.72.0.tgz",
|
| 332 |
+
"integrity": "sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==",
|
| 333 |
+
"cpu": [
|
| 334 |
+
"riscv64"
|
| 335 |
+
],
|
| 336 |
+
"dev": true,
|
| 337 |
+
"license": "MIT",
|
| 338 |
+
"optional": true,
|
| 339 |
+
"os": [
|
| 340 |
+
"linux"
|
| 341 |
+
],
|
| 342 |
+
"engines": {
|
| 343 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 344 |
+
}
|
| 345 |
+
},
|
| 346 |
+
"node_modules/@oxlint/binding-linux-riscv64-musl": {
|
| 347 |
+
"version": "1.72.0",
|
| 348 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.72.0.tgz",
|
| 349 |
+
"integrity": "sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==",
|
| 350 |
+
"cpu": [
|
| 351 |
+
"riscv64"
|
| 352 |
+
],
|
| 353 |
+
"dev": true,
|
| 354 |
+
"license": "MIT",
|
| 355 |
+
"optional": true,
|
| 356 |
+
"os": [
|
| 357 |
+
"linux"
|
| 358 |
+
],
|
| 359 |
+
"engines": {
|
| 360 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 361 |
+
}
|
| 362 |
+
},
|
| 363 |
+
"node_modules/@oxlint/binding-linux-s390x-gnu": {
|
| 364 |
+
"version": "1.72.0",
|
| 365 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.72.0.tgz",
|
| 366 |
+
"integrity": "sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==",
|
| 367 |
+
"cpu": [
|
| 368 |
+
"s390x"
|
| 369 |
+
],
|
| 370 |
+
"dev": true,
|
| 371 |
+
"license": "MIT",
|
| 372 |
+
"optional": true,
|
| 373 |
+
"os": [
|
| 374 |
+
"linux"
|
| 375 |
+
],
|
| 376 |
+
"engines": {
|
| 377 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 378 |
+
}
|
| 379 |
+
},
|
| 380 |
+
"node_modules/@oxlint/binding-linux-x64-gnu": {
|
| 381 |
+
"version": "1.72.0",
|
| 382 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.72.0.tgz",
|
| 383 |
+
"integrity": "sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==",
|
| 384 |
+
"cpu": [
|
| 385 |
+
"x64"
|
| 386 |
+
],
|
| 387 |
+
"dev": true,
|
| 388 |
+
"license": "MIT",
|
| 389 |
+
"optional": true,
|
| 390 |
+
"os": [
|
| 391 |
+
"linux"
|
| 392 |
+
],
|
| 393 |
+
"engines": {
|
| 394 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 395 |
+
}
|
| 396 |
+
},
|
| 397 |
+
"node_modules/@oxlint/binding-linux-x64-musl": {
|
| 398 |
+
"version": "1.72.0",
|
| 399 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.72.0.tgz",
|
| 400 |
+
"integrity": "sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==",
|
| 401 |
+
"cpu": [
|
| 402 |
+
"x64"
|
| 403 |
+
],
|
| 404 |
+
"dev": true,
|
| 405 |
+
"license": "MIT",
|
| 406 |
+
"optional": true,
|
| 407 |
+
"os": [
|
| 408 |
+
"linux"
|
| 409 |
+
],
|
| 410 |
+
"engines": {
|
| 411 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 412 |
+
}
|
| 413 |
+
},
|
| 414 |
+
"node_modules/@oxlint/binding-openharmony-arm64": {
|
| 415 |
+
"version": "1.72.0",
|
| 416 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.72.0.tgz",
|
| 417 |
+
"integrity": "sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==",
|
| 418 |
+
"cpu": [
|
| 419 |
+
"arm64"
|
| 420 |
+
],
|
| 421 |
+
"dev": true,
|
| 422 |
+
"license": "MIT",
|
| 423 |
+
"optional": true,
|
| 424 |
+
"os": [
|
| 425 |
+
"openharmony"
|
| 426 |
+
],
|
| 427 |
+
"engines": {
|
| 428 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 429 |
+
}
|
| 430 |
+
},
|
| 431 |
+
"node_modules/@oxlint/binding-win32-arm64-msvc": {
|
| 432 |
+
"version": "1.72.0",
|
| 433 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.72.0.tgz",
|
| 434 |
+
"integrity": "sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==",
|
| 435 |
+
"cpu": [
|
| 436 |
+
"arm64"
|
| 437 |
+
],
|
| 438 |
+
"dev": true,
|
| 439 |
+
"license": "MIT",
|
| 440 |
+
"optional": true,
|
| 441 |
+
"os": [
|
| 442 |
+
"win32"
|
| 443 |
+
],
|
| 444 |
+
"engines": {
|
| 445 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 446 |
+
}
|
| 447 |
+
},
|
| 448 |
+
"node_modules/@oxlint/binding-win32-ia32-msvc": {
|
| 449 |
+
"version": "1.72.0",
|
| 450 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.72.0.tgz",
|
| 451 |
+
"integrity": "sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==",
|
| 452 |
+
"cpu": [
|
| 453 |
+
"ia32"
|
| 454 |
+
],
|
| 455 |
+
"dev": true,
|
| 456 |
+
"license": "MIT",
|
| 457 |
+
"optional": true,
|
| 458 |
+
"os": [
|
| 459 |
+
"win32"
|
| 460 |
+
],
|
| 461 |
+
"engines": {
|
| 462 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 463 |
+
}
|
| 464 |
+
},
|
| 465 |
+
"node_modules/@oxlint/binding-win32-x64-msvc": {
|
| 466 |
+
"version": "1.72.0",
|
| 467 |
+
"resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.72.0.tgz",
|
| 468 |
+
"integrity": "sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==",
|
| 469 |
+
"cpu": [
|
| 470 |
+
"x64"
|
| 471 |
+
],
|
| 472 |
+
"dev": true,
|
| 473 |
+
"license": "MIT",
|
| 474 |
+
"optional": true,
|
| 475 |
+
"os": [
|
| 476 |
+
"win32"
|
| 477 |
+
],
|
| 478 |
+
"engines": {
|
| 479 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 480 |
+
}
|
| 481 |
+
},
|
| 482 |
+
"node_modules/@rolldown/binding-android-arm64": {
|
| 483 |
+
"version": "1.1.4",
|
| 484 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz",
|
| 485 |
+
"integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==",
|
| 486 |
+
"cpu": [
|
| 487 |
+
"arm64"
|
| 488 |
+
],
|
| 489 |
+
"license": "MIT",
|
| 490 |
+
"optional": true,
|
| 491 |
+
"os": [
|
| 492 |
+
"android"
|
| 493 |
+
],
|
| 494 |
+
"engines": {
|
| 495 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 496 |
+
}
|
| 497 |
+
},
|
| 498 |
+
"node_modules/@rolldown/binding-darwin-arm64": {
|
| 499 |
+
"version": "1.1.4",
|
| 500 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz",
|
| 501 |
+
"integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==",
|
| 502 |
+
"cpu": [
|
| 503 |
+
"arm64"
|
| 504 |
+
],
|
| 505 |
+
"license": "MIT",
|
| 506 |
+
"optional": true,
|
| 507 |
+
"os": [
|
| 508 |
+
"darwin"
|
| 509 |
+
],
|
| 510 |
+
"engines": {
|
| 511 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 512 |
+
}
|
| 513 |
+
},
|
| 514 |
+
"node_modules/@rolldown/binding-darwin-x64": {
|
| 515 |
+
"version": "1.1.4",
|
| 516 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz",
|
| 517 |
+
"integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==",
|
| 518 |
+
"cpu": [
|
| 519 |
+
"x64"
|
| 520 |
+
],
|
| 521 |
+
"license": "MIT",
|
| 522 |
+
"optional": true,
|
| 523 |
+
"os": [
|
| 524 |
+
"darwin"
|
| 525 |
+
],
|
| 526 |
+
"engines": {
|
| 527 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 528 |
+
}
|
| 529 |
+
},
|
| 530 |
+
"node_modules/@rolldown/binding-freebsd-x64": {
|
| 531 |
+
"version": "1.1.4",
|
| 532 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz",
|
| 533 |
+
"integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==",
|
| 534 |
+
"cpu": [
|
| 535 |
+
"x64"
|
| 536 |
+
],
|
| 537 |
+
"license": "MIT",
|
| 538 |
+
"optional": true,
|
| 539 |
+
"os": [
|
| 540 |
+
"freebsd"
|
| 541 |
+
],
|
| 542 |
+
"engines": {
|
| 543 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 544 |
+
}
|
| 545 |
+
},
|
| 546 |
+
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
| 547 |
+
"version": "1.1.4",
|
| 548 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz",
|
| 549 |
+
"integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==",
|
| 550 |
+
"cpu": [
|
| 551 |
+
"arm"
|
| 552 |
+
],
|
| 553 |
+
"license": "MIT",
|
| 554 |
+
"optional": true,
|
| 555 |
+
"os": [
|
| 556 |
+
"linux"
|
| 557 |
+
],
|
| 558 |
+
"engines": {
|
| 559 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 560 |
+
}
|
| 561 |
+
},
|
| 562 |
+
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
| 563 |
+
"version": "1.1.4",
|
| 564 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz",
|
| 565 |
+
"integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==",
|
| 566 |
+
"cpu": [
|
| 567 |
+
"arm64"
|
| 568 |
+
],
|
| 569 |
+
"license": "MIT",
|
| 570 |
+
"optional": true,
|
| 571 |
+
"os": [
|
| 572 |
+
"linux"
|
| 573 |
+
],
|
| 574 |
+
"engines": {
|
| 575 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 576 |
+
}
|
| 577 |
+
},
|
| 578 |
+
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
| 579 |
+
"version": "1.1.4",
|
| 580 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz",
|
| 581 |
+
"integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==",
|
| 582 |
+
"cpu": [
|
| 583 |
+
"arm64"
|
| 584 |
+
],
|
| 585 |
+
"license": "MIT",
|
| 586 |
+
"optional": true,
|
| 587 |
+
"os": [
|
| 588 |
+
"linux"
|
| 589 |
+
],
|
| 590 |
+
"engines": {
|
| 591 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 592 |
+
}
|
| 593 |
+
},
|
| 594 |
+
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
| 595 |
+
"version": "1.1.4",
|
| 596 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz",
|
| 597 |
+
"integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==",
|
| 598 |
+
"cpu": [
|
| 599 |
+
"ppc64"
|
| 600 |
+
],
|
| 601 |
+
"license": "MIT",
|
| 602 |
+
"optional": true,
|
| 603 |
+
"os": [
|
| 604 |
+
"linux"
|
| 605 |
+
],
|
| 606 |
+
"engines": {
|
| 607 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 608 |
+
}
|
| 609 |
+
},
|
| 610 |
+
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
| 611 |
+
"version": "1.1.4",
|
| 612 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz",
|
| 613 |
+
"integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==",
|
| 614 |
+
"cpu": [
|
| 615 |
+
"s390x"
|
| 616 |
+
],
|
| 617 |
+
"license": "MIT",
|
| 618 |
+
"optional": true,
|
| 619 |
+
"os": [
|
| 620 |
+
"linux"
|
| 621 |
+
],
|
| 622 |
+
"engines": {
|
| 623 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 624 |
+
}
|
| 625 |
+
},
|
| 626 |
+
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
| 627 |
+
"version": "1.1.4",
|
| 628 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz",
|
| 629 |
+
"integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==",
|
| 630 |
+
"cpu": [
|
| 631 |
+
"x64"
|
| 632 |
+
],
|
| 633 |
+
"license": "MIT",
|
| 634 |
+
"optional": true,
|
| 635 |
+
"os": [
|
| 636 |
+
"linux"
|
| 637 |
+
],
|
| 638 |
+
"engines": {
|
| 639 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 640 |
+
}
|
| 641 |
+
},
|
| 642 |
+
"node_modules/@rolldown/binding-linux-x64-musl": {
|
| 643 |
+
"version": "1.1.4",
|
| 644 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz",
|
| 645 |
+
"integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==",
|
| 646 |
+
"cpu": [
|
| 647 |
+
"x64"
|
| 648 |
+
],
|
| 649 |
+
"license": "MIT",
|
| 650 |
+
"optional": true,
|
| 651 |
+
"os": [
|
| 652 |
+
"linux"
|
| 653 |
+
],
|
| 654 |
+
"engines": {
|
| 655 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 656 |
+
}
|
| 657 |
+
},
|
| 658 |
+
"node_modules/@rolldown/binding-openharmony-arm64": {
|
| 659 |
+
"version": "1.1.4",
|
| 660 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz",
|
| 661 |
+
"integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==",
|
| 662 |
+
"cpu": [
|
| 663 |
+
"arm64"
|
| 664 |
+
],
|
| 665 |
+
"license": "MIT",
|
| 666 |
+
"optional": true,
|
| 667 |
+
"os": [
|
| 668 |
+
"openharmony"
|
| 669 |
+
],
|
| 670 |
+
"engines": {
|
| 671 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 672 |
+
}
|
| 673 |
+
},
|
| 674 |
+
"node_modules/@rolldown/binding-wasm32-wasi": {
|
| 675 |
+
"version": "1.1.4",
|
| 676 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz",
|
| 677 |
+
"integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==",
|
| 678 |
+
"cpu": [
|
| 679 |
+
"wasm32"
|
| 680 |
+
],
|
| 681 |
+
"license": "MIT",
|
| 682 |
+
"optional": true,
|
| 683 |
+
"dependencies": {
|
| 684 |
+
"@emnapi/core": "1.11.1",
|
| 685 |
+
"@emnapi/runtime": "1.11.1",
|
| 686 |
+
"@napi-rs/wasm-runtime": "^1.1.6"
|
| 687 |
+
},
|
| 688 |
+
"engines": {
|
| 689 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 690 |
+
}
|
| 691 |
+
},
|
| 692 |
+
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
| 693 |
+
"version": "1.1.4",
|
| 694 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz",
|
| 695 |
+
"integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==",
|
| 696 |
+
"cpu": [
|
| 697 |
+
"arm64"
|
| 698 |
+
],
|
| 699 |
+
"license": "MIT",
|
| 700 |
+
"optional": true,
|
| 701 |
+
"os": [
|
| 702 |
+
"win32"
|
| 703 |
+
],
|
| 704 |
+
"engines": {
|
| 705 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 706 |
+
}
|
| 707 |
+
},
|
| 708 |
+
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
| 709 |
+
"version": "1.1.4",
|
| 710 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz",
|
| 711 |
+
"integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==",
|
| 712 |
+
"cpu": [
|
| 713 |
+
"x64"
|
| 714 |
+
],
|
| 715 |
+
"license": "MIT",
|
| 716 |
+
"optional": true,
|
| 717 |
+
"os": [
|
| 718 |
+
"win32"
|
| 719 |
+
],
|
| 720 |
+
"engines": {
|
| 721 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 722 |
+
}
|
| 723 |
+
},
|
| 724 |
+
"node_modules/@rolldown/pluginutils": {
|
| 725 |
+
"version": "1.0.1",
|
| 726 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
| 727 |
+
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
| 728 |
+
"license": "MIT"
|
| 729 |
+
},
|
| 730 |
+
"node_modules/@tailwindcss/node": {
|
| 731 |
+
"version": "4.3.2",
|
| 732 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz",
|
| 733 |
+
"integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==",
|
| 734 |
+
"license": "MIT",
|
| 735 |
+
"dependencies": {
|
| 736 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 737 |
+
"enhanced-resolve": "5.21.6",
|
| 738 |
+
"jiti": "^2.7.0",
|
| 739 |
+
"lightningcss": "1.32.0",
|
| 740 |
+
"magic-string": "^0.30.21",
|
| 741 |
+
"source-map-js": "^1.2.1",
|
| 742 |
+
"tailwindcss": "4.3.2"
|
| 743 |
+
}
|
| 744 |
+
},
|
| 745 |
+
"node_modules/@tailwindcss/oxide": {
|
| 746 |
+
"version": "4.3.2",
|
| 747 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz",
|
| 748 |
+
"integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==",
|
| 749 |
+
"license": "MIT",
|
| 750 |
+
"engines": {
|
| 751 |
+
"node": ">= 20"
|
| 752 |
+
},
|
| 753 |
+
"optionalDependencies": {
|
| 754 |
+
"@tailwindcss/oxide-android-arm64": "4.3.2",
|
| 755 |
+
"@tailwindcss/oxide-darwin-arm64": "4.3.2",
|
| 756 |
+
"@tailwindcss/oxide-darwin-x64": "4.3.2",
|
| 757 |
+
"@tailwindcss/oxide-freebsd-x64": "4.3.2",
|
| 758 |
+
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2",
|
| 759 |
+
"@tailwindcss/oxide-linux-arm64-gnu": "4.3.2",
|
| 760 |
+
"@tailwindcss/oxide-linux-arm64-musl": "4.3.2",
|
| 761 |
+
"@tailwindcss/oxide-linux-x64-gnu": "4.3.2",
|
| 762 |
+
"@tailwindcss/oxide-linux-x64-musl": "4.3.2",
|
| 763 |
+
"@tailwindcss/oxide-wasm32-wasi": "4.3.2",
|
| 764 |
+
"@tailwindcss/oxide-win32-arm64-msvc": "4.3.2",
|
| 765 |
+
"@tailwindcss/oxide-win32-x64-msvc": "4.3.2"
|
| 766 |
+
}
|
| 767 |
+
},
|
| 768 |
+
"node_modules/@tailwindcss/oxide-android-arm64": {
|
| 769 |
+
"version": "4.3.2",
|
| 770 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz",
|
| 771 |
+
"integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==",
|
| 772 |
+
"cpu": [
|
| 773 |
+
"arm64"
|
| 774 |
+
],
|
| 775 |
+
"license": "MIT",
|
| 776 |
+
"optional": true,
|
| 777 |
+
"os": [
|
| 778 |
+
"android"
|
| 779 |
+
],
|
| 780 |
+
"engines": {
|
| 781 |
+
"node": ">= 20"
|
| 782 |
+
}
|
| 783 |
+
},
|
| 784 |
+
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
| 785 |
+
"version": "4.3.2",
|
| 786 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz",
|
| 787 |
+
"integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==",
|
| 788 |
+
"cpu": [
|
| 789 |
+
"arm64"
|
| 790 |
+
],
|
| 791 |
+
"license": "MIT",
|
| 792 |
+
"optional": true,
|
| 793 |
+
"os": [
|
| 794 |
+
"darwin"
|
| 795 |
+
],
|
| 796 |
+
"engines": {
|
| 797 |
+
"node": ">= 20"
|
| 798 |
+
}
|
| 799 |
+
},
|
| 800 |
+
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
| 801 |
+
"version": "4.3.2",
|
| 802 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz",
|
| 803 |
+
"integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==",
|
| 804 |
+
"cpu": [
|
| 805 |
+
"x64"
|
| 806 |
+
],
|
| 807 |
+
"license": "MIT",
|
| 808 |
+
"optional": true,
|
| 809 |
+
"os": [
|
| 810 |
+
"darwin"
|
| 811 |
+
],
|
| 812 |
+
"engines": {
|
| 813 |
+
"node": ">= 20"
|
| 814 |
+
}
|
| 815 |
+
},
|
| 816 |
+
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
| 817 |
+
"version": "4.3.2",
|
| 818 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz",
|
| 819 |
+
"integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==",
|
| 820 |
+
"cpu": [
|
| 821 |
+
"x64"
|
| 822 |
+
],
|
| 823 |
+
"license": "MIT",
|
| 824 |
+
"optional": true,
|
| 825 |
+
"os": [
|
| 826 |
+
"freebsd"
|
| 827 |
+
],
|
| 828 |
+
"engines": {
|
| 829 |
+
"node": ">= 20"
|
| 830 |
+
}
|
| 831 |
+
},
|
| 832 |
+
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
| 833 |
+
"version": "4.3.2",
|
| 834 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz",
|
| 835 |
+
"integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==",
|
| 836 |
+
"cpu": [
|
| 837 |
+
"arm"
|
| 838 |
+
],
|
| 839 |
+
"license": "MIT",
|
| 840 |
+
"optional": true,
|
| 841 |
+
"os": [
|
| 842 |
+
"linux"
|
| 843 |
+
],
|
| 844 |
+
"engines": {
|
| 845 |
+
"node": ">= 20"
|
| 846 |
+
}
|
| 847 |
+
},
|
| 848 |
+
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
| 849 |
+
"version": "4.3.2",
|
| 850 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz",
|
| 851 |
+
"integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==",
|
| 852 |
+
"cpu": [
|
| 853 |
+
"arm64"
|
| 854 |
+
],
|
| 855 |
+
"license": "MIT",
|
| 856 |
+
"optional": true,
|
| 857 |
+
"os": [
|
| 858 |
+
"linux"
|
| 859 |
+
],
|
| 860 |
+
"engines": {
|
| 861 |
+
"node": ">= 20"
|
| 862 |
+
}
|
| 863 |
+
},
|
| 864 |
+
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
| 865 |
+
"version": "4.3.2",
|
| 866 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz",
|
| 867 |
+
"integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==",
|
| 868 |
+
"cpu": [
|
| 869 |
+
"arm64"
|
| 870 |
+
],
|
| 871 |
+
"license": "MIT",
|
| 872 |
+
"optional": true,
|
| 873 |
+
"os": [
|
| 874 |
+
"linux"
|
| 875 |
+
],
|
| 876 |
+
"engines": {
|
| 877 |
+
"node": ">= 20"
|
| 878 |
+
}
|
| 879 |
+
},
|
| 880 |
+
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
| 881 |
+
"version": "4.3.2",
|
| 882 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz",
|
| 883 |
+
"integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==",
|
| 884 |
+
"cpu": [
|
| 885 |
+
"x64"
|
| 886 |
+
],
|
| 887 |
+
"license": "MIT",
|
| 888 |
+
"optional": true,
|
| 889 |
+
"os": [
|
| 890 |
+
"linux"
|
| 891 |
+
],
|
| 892 |
+
"engines": {
|
| 893 |
+
"node": ">= 20"
|
| 894 |
+
}
|
| 895 |
+
},
|
| 896 |
+
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
| 897 |
+
"version": "4.3.2",
|
| 898 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz",
|
| 899 |
+
"integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==",
|
| 900 |
+
"cpu": [
|
| 901 |
+
"x64"
|
| 902 |
+
],
|
| 903 |
+
"license": "MIT",
|
| 904 |
+
"optional": true,
|
| 905 |
+
"os": [
|
| 906 |
+
"linux"
|
| 907 |
+
],
|
| 908 |
+
"engines": {
|
| 909 |
+
"node": ">= 20"
|
| 910 |
+
}
|
| 911 |
+
},
|
| 912 |
+
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
| 913 |
+
"version": "4.3.2",
|
| 914 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz",
|
| 915 |
+
"integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==",
|
| 916 |
+
"bundleDependencies": [
|
| 917 |
+
"@napi-rs/wasm-runtime",
|
| 918 |
+
"@emnapi/core",
|
| 919 |
+
"@emnapi/runtime",
|
| 920 |
+
"@tybys/wasm-util",
|
| 921 |
+
"@emnapi/wasi-threads",
|
| 922 |
+
"tslib"
|
| 923 |
+
],
|
| 924 |
+
"cpu": [
|
| 925 |
+
"wasm32"
|
| 926 |
+
],
|
| 927 |
+
"license": "MIT",
|
| 928 |
+
"optional": true,
|
| 929 |
+
"dependencies": {
|
| 930 |
+
"@emnapi/core": "^1.11.1",
|
| 931 |
+
"@emnapi/runtime": "^1.11.1",
|
| 932 |
+
"@emnapi/wasi-threads": "^1.2.2",
|
| 933 |
+
"@napi-rs/wasm-runtime": "^1.1.4",
|
| 934 |
+
"@tybys/wasm-util": "^0.10.2",
|
| 935 |
+
"tslib": "^2.8.1"
|
| 936 |
+
},
|
| 937 |
+
"engines": {
|
| 938 |
+
"node": ">=14.0.0"
|
| 939 |
+
}
|
| 940 |
+
},
|
| 941 |
+
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
| 942 |
+
"version": "4.3.2",
|
| 943 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz",
|
| 944 |
+
"integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==",
|
| 945 |
+
"cpu": [
|
| 946 |
+
"arm64"
|
| 947 |
+
],
|
| 948 |
+
"license": "MIT",
|
| 949 |
+
"optional": true,
|
| 950 |
+
"os": [
|
| 951 |
+
"win32"
|
| 952 |
+
],
|
| 953 |
+
"engines": {
|
| 954 |
+
"node": ">= 20"
|
| 955 |
+
}
|
| 956 |
+
},
|
| 957 |
+
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
| 958 |
+
"version": "4.3.2",
|
| 959 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz",
|
| 960 |
+
"integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==",
|
| 961 |
+
"cpu": [
|
| 962 |
+
"x64"
|
| 963 |
+
],
|
| 964 |
+
"license": "MIT",
|
| 965 |
+
"optional": true,
|
| 966 |
+
"os": [
|
| 967 |
+
"win32"
|
| 968 |
+
],
|
| 969 |
+
"engines": {
|
| 970 |
+
"node": ">= 20"
|
| 971 |
+
}
|
| 972 |
+
},
|
| 973 |
+
"node_modules/@tailwindcss/vite": {
|
| 974 |
+
"version": "4.3.2",
|
| 975 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz",
|
| 976 |
+
"integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==",
|
| 977 |
+
"license": "MIT",
|
| 978 |
+
"dependencies": {
|
| 979 |
+
"@tailwindcss/node": "4.3.2",
|
| 980 |
+
"@tailwindcss/oxide": "4.3.2",
|
| 981 |
+
"tailwindcss": "4.3.2"
|
| 982 |
+
},
|
| 983 |
+
"peerDependencies": {
|
| 984 |
+
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
| 985 |
+
}
|
| 986 |
+
},
|
| 987 |
+
"node_modules/@tanstack/query-core": {
|
| 988 |
+
"version": "5.101.2",
|
| 989 |
+
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.2.tgz",
|
| 990 |
+
"integrity": "sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw==",
|
| 991 |
+
"license": "MIT",
|
| 992 |
+
"funding": {
|
| 993 |
+
"type": "github",
|
| 994 |
+
"url": "https://github.com/sponsors/tannerlinsley"
|
| 995 |
+
}
|
| 996 |
+
},
|
| 997 |
+
"node_modules/@tanstack/react-query": {
|
| 998 |
+
"version": "5.101.2",
|
| 999 |
+
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.2.tgz",
|
| 1000 |
+
"integrity": "sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg==",
|
| 1001 |
+
"license": "MIT",
|
| 1002 |
+
"dependencies": {
|
| 1003 |
+
"@tanstack/query-core": "5.101.2"
|
| 1004 |
+
},
|
| 1005 |
+
"funding": {
|
| 1006 |
+
"type": "github",
|
| 1007 |
+
"url": "https://github.com/sponsors/tannerlinsley"
|
| 1008 |
+
},
|
| 1009 |
+
"peerDependencies": {
|
| 1010 |
+
"react": "^18 || ^19"
|
| 1011 |
+
}
|
| 1012 |
+
},
|
| 1013 |
+
"node_modules/@tybys/wasm-util": {
|
| 1014 |
+
"version": "0.10.3",
|
| 1015 |
+
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
|
| 1016 |
+
"integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
|
| 1017 |
+
"license": "MIT",
|
| 1018 |
+
"optional": true,
|
| 1019 |
+
"dependencies": {
|
| 1020 |
+
"tslib": "^2.4.0"
|
| 1021 |
+
}
|
| 1022 |
+
},
|
| 1023 |
+
"node_modules/@types/node": {
|
| 1024 |
+
"version": "24.13.2",
|
| 1025 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
| 1026 |
+
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
|
| 1027 |
+
"devOptional": true,
|
| 1028 |
+
"license": "MIT",
|
| 1029 |
+
"dependencies": {
|
| 1030 |
+
"undici-types": "~7.18.0"
|
| 1031 |
+
}
|
| 1032 |
+
},
|
| 1033 |
+
"node_modules/@types/react": {
|
| 1034 |
+
"version": "19.2.17",
|
| 1035 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
|
| 1036 |
+
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
|
| 1037 |
+
"dev": true,
|
| 1038 |
+
"license": "MIT",
|
| 1039 |
+
"dependencies": {
|
| 1040 |
+
"csstype": "^3.2.2"
|
| 1041 |
+
}
|
| 1042 |
+
},
|
| 1043 |
+
"node_modules/@types/react-dom": {
|
| 1044 |
+
"version": "19.2.3",
|
| 1045 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
| 1046 |
+
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
| 1047 |
+
"dev": true,
|
| 1048 |
+
"license": "MIT",
|
| 1049 |
+
"peerDependencies": {
|
| 1050 |
+
"@types/react": "^19.2.0"
|
| 1051 |
+
}
|
| 1052 |
+
},
|
| 1053 |
+
"node_modules/@types/trusted-types": {
|
| 1054 |
+
"version": "2.0.7",
|
| 1055 |
+
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
| 1056 |
+
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
| 1057 |
+
"license": "MIT",
|
| 1058 |
+
"optional": true,
|
| 1059 |
+
"peer": true
|
| 1060 |
+
},
|
| 1061 |
+
"node_modules/@vitejs/plugin-react": {
|
| 1062 |
+
"version": "6.0.3",
|
| 1063 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz",
|
| 1064 |
+
"integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==",
|
| 1065 |
+
"dev": true,
|
| 1066 |
+
"license": "MIT",
|
| 1067 |
+
"dependencies": {
|
| 1068 |
+
"@rolldown/pluginutils": "^1.0.1"
|
| 1069 |
+
},
|
| 1070 |
+
"engines": {
|
| 1071 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1072 |
+
},
|
| 1073 |
+
"peerDependencies": {
|
| 1074 |
+
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
|
| 1075 |
+
"babel-plugin-react-compiler": "^1.0.0",
|
| 1076 |
+
"vite": "^8.0.0"
|
| 1077 |
+
},
|
| 1078 |
+
"peerDependenciesMeta": {
|
| 1079 |
+
"@rolldown/plugin-babel": {
|
| 1080 |
+
"optional": true
|
| 1081 |
+
},
|
| 1082 |
+
"babel-plugin-react-compiler": {
|
| 1083 |
+
"optional": true
|
| 1084 |
+
}
|
| 1085 |
+
}
|
| 1086 |
+
},
|
| 1087 |
+
"node_modules/clsx": {
|
| 1088 |
+
"version": "2.1.1",
|
| 1089 |
+
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
| 1090 |
+
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
| 1091 |
+
"license": "MIT",
|
| 1092 |
+
"engines": {
|
| 1093 |
+
"node": ">=6"
|
| 1094 |
+
}
|
| 1095 |
+
},
|
| 1096 |
+
"node_modules/cookie": {
|
| 1097 |
+
"version": "1.1.1",
|
| 1098 |
+
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
| 1099 |
+
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
| 1100 |
+
"license": "MIT",
|
| 1101 |
+
"engines": {
|
| 1102 |
+
"node": ">=18"
|
| 1103 |
+
},
|
| 1104 |
+
"funding": {
|
| 1105 |
+
"type": "opencollective",
|
| 1106 |
+
"url": "https://opencollective.com/express"
|
| 1107 |
+
}
|
| 1108 |
+
},
|
| 1109 |
+
"node_modules/csstype": {
|
| 1110 |
+
"version": "3.2.3",
|
| 1111 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1112 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1113 |
+
"dev": true,
|
| 1114 |
+
"license": "MIT"
|
| 1115 |
+
},
|
| 1116 |
+
"node_modules/detect-libc": {
|
| 1117 |
+
"version": "2.1.2",
|
| 1118 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 1119 |
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 1120 |
+
"license": "Apache-2.0",
|
| 1121 |
+
"engines": {
|
| 1122 |
+
"node": ">=8"
|
| 1123 |
+
}
|
| 1124 |
+
},
|
| 1125 |
+
"node_modules/dompurify": {
|
| 1126 |
+
"version": "3.2.7",
|
| 1127 |
+
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
|
| 1128 |
+
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
|
| 1129 |
+
"license": "(MPL-2.0 OR Apache-2.0)",
|
| 1130 |
+
"peer": true,
|
| 1131 |
+
"optionalDependencies": {
|
| 1132 |
+
"@types/trusted-types": "^2.0.7"
|
| 1133 |
+
}
|
| 1134 |
+
},
|
| 1135 |
+
"node_modules/enhanced-resolve": {
|
| 1136 |
+
"version": "5.21.6",
|
| 1137 |
+
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz",
|
| 1138 |
+
"integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==",
|
| 1139 |
+
"license": "MIT",
|
| 1140 |
+
"dependencies": {
|
| 1141 |
+
"graceful-fs": "^4.2.4",
|
| 1142 |
+
"tapable": "^2.3.3"
|
| 1143 |
+
},
|
| 1144 |
+
"engines": {
|
| 1145 |
+
"node": ">=10.13.0"
|
| 1146 |
+
}
|
| 1147 |
+
},
|
| 1148 |
+
"node_modules/fdir": {
|
| 1149 |
+
"version": "6.5.0",
|
| 1150 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1151 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1152 |
+
"license": "MIT",
|
| 1153 |
+
"engines": {
|
| 1154 |
+
"node": ">=12.0.0"
|
| 1155 |
+
},
|
| 1156 |
+
"peerDependencies": {
|
| 1157 |
+
"picomatch": "^3 || ^4"
|
| 1158 |
+
},
|
| 1159 |
+
"peerDependenciesMeta": {
|
| 1160 |
+
"picomatch": {
|
| 1161 |
+
"optional": true
|
| 1162 |
+
}
|
| 1163 |
+
}
|
| 1164 |
+
},
|
| 1165 |
+
"node_modules/framer-motion": {
|
| 1166 |
+
"version": "12.42.2",
|
| 1167 |
+
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz",
|
| 1168 |
+
"integrity": "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==",
|
| 1169 |
+
"license": "MIT",
|
| 1170 |
+
"dependencies": {
|
| 1171 |
+
"motion-dom": "^12.42.2",
|
| 1172 |
+
"motion-utils": "^12.39.0",
|
| 1173 |
+
"tslib": "^2.4.0"
|
| 1174 |
+
},
|
| 1175 |
+
"peerDependencies": {
|
| 1176 |
+
"@emotion/is-prop-valid": "*",
|
| 1177 |
+
"react": "^18.0.0 || ^19.0.0",
|
| 1178 |
+
"react-dom": "^18.0.0 || ^19.0.0"
|
| 1179 |
+
},
|
| 1180 |
+
"peerDependenciesMeta": {
|
| 1181 |
+
"@emotion/is-prop-valid": {
|
| 1182 |
+
"optional": true
|
| 1183 |
+
},
|
| 1184 |
+
"react": {
|
| 1185 |
+
"optional": true
|
| 1186 |
+
},
|
| 1187 |
+
"react-dom": {
|
| 1188 |
+
"optional": true
|
| 1189 |
+
}
|
| 1190 |
+
}
|
| 1191 |
+
},
|
| 1192 |
+
"node_modules/fsevents": {
|
| 1193 |
+
"version": "2.3.3",
|
| 1194 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1195 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1196 |
+
"hasInstallScript": true,
|
| 1197 |
+
"license": "MIT",
|
| 1198 |
+
"optional": true,
|
| 1199 |
+
"os": [
|
| 1200 |
+
"darwin"
|
| 1201 |
+
],
|
| 1202 |
+
"engines": {
|
| 1203 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1204 |
+
}
|
| 1205 |
+
},
|
| 1206 |
+
"node_modules/graceful-fs": {
|
| 1207 |
+
"version": "4.2.11",
|
| 1208 |
+
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
| 1209 |
+
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
| 1210 |
+
"license": "ISC"
|
| 1211 |
+
},
|
| 1212 |
+
"node_modules/jiti": {
|
| 1213 |
+
"version": "2.7.0",
|
| 1214 |
+
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
| 1215 |
+
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
| 1216 |
+
"license": "MIT",
|
| 1217 |
+
"bin": {
|
| 1218 |
+
"jiti": "lib/jiti-cli.mjs"
|
| 1219 |
+
}
|
| 1220 |
+
},
|
| 1221 |
+
"node_modules/lightningcss": {
|
| 1222 |
+
"version": "1.32.0",
|
| 1223 |
+
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
| 1224 |
+
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
| 1225 |
+
"license": "MPL-2.0",
|
| 1226 |
+
"dependencies": {
|
| 1227 |
+
"detect-libc": "^2.0.3"
|
| 1228 |
+
},
|
| 1229 |
+
"engines": {
|
| 1230 |
+
"node": ">= 12.0.0"
|
| 1231 |
+
},
|
| 1232 |
+
"funding": {
|
| 1233 |
+
"type": "opencollective",
|
| 1234 |
+
"url": "https://opencollective.com/parcel"
|
| 1235 |
+
},
|
| 1236 |
+
"optionalDependencies": {
|
| 1237 |
+
"lightningcss-android-arm64": "1.32.0",
|
| 1238 |
+
"lightningcss-darwin-arm64": "1.32.0",
|
| 1239 |
+
"lightningcss-darwin-x64": "1.32.0",
|
| 1240 |
+
"lightningcss-freebsd-x64": "1.32.0",
|
| 1241 |
+
"lightningcss-linux-arm-gnueabihf": "1.32.0",
|
| 1242 |
+
"lightningcss-linux-arm64-gnu": "1.32.0",
|
| 1243 |
+
"lightningcss-linux-arm64-musl": "1.32.0",
|
| 1244 |
+
"lightningcss-linux-x64-gnu": "1.32.0",
|
| 1245 |
+
"lightningcss-linux-x64-musl": "1.32.0",
|
| 1246 |
+
"lightningcss-win32-arm64-msvc": "1.32.0",
|
| 1247 |
+
"lightningcss-win32-x64-msvc": "1.32.0"
|
| 1248 |
+
}
|
| 1249 |
+
},
|
| 1250 |
+
"node_modules/lightningcss-android-arm64": {
|
| 1251 |
+
"version": "1.32.0",
|
| 1252 |
+
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
|
| 1253 |
+
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
|
| 1254 |
+
"cpu": [
|
| 1255 |
+
"arm64"
|
| 1256 |
+
],
|
| 1257 |
+
"license": "MPL-2.0",
|
| 1258 |
+
"optional": true,
|
| 1259 |
+
"os": [
|
| 1260 |
+
"android"
|
| 1261 |
+
],
|
| 1262 |
+
"engines": {
|
| 1263 |
+
"node": ">= 12.0.0"
|
| 1264 |
+
},
|
| 1265 |
+
"funding": {
|
| 1266 |
+
"type": "opencollective",
|
| 1267 |
+
"url": "https://opencollective.com/parcel"
|
| 1268 |
+
}
|
| 1269 |
+
},
|
| 1270 |
+
"node_modules/lightningcss-darwin-arm64": {
|
| 1271 |
+
"version": "1.32.0",
|
| 1272 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
|
| 1273 |
+
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
|
| 1274 |
+
"cpu": [
|
| 1275 |
+
"arm64"
|
| 1276 |
+
],
|
| 1277 |
+
"license": "MPL-2.0",
|
| 1278 |
+
"optional": true,
|
| 1279 |
+
"os": [
|
| 1280 |
+
"darwin"
|
| 1281 |
+
],
|
| 1282 |
+
"engines": {
|
| 1283 |
+
"node": ">= 12.0.0"
|
| 1284 |
+
},
|
| 1285 |
+
"funding": {
|
| 1286 |
+
"type": "opencollective",
|
| 1287 |
+
"url": "https://opencollective.com/parcel"
|
| 1288 |
+
}
|
| 1289 |
+
},
|
| 1290 |
+
"node_modules/lightningcss-darwin-x64": {
|
| 1291 |
+
"version": "1.32.0",
|
| 1292 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
|
| 1293 |
+
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
|
| 1294 |
+
"cpu": [
|
| 1295 |
+
"x64"
|
| 1296 |
+
],
|
| 1297 |
+
"license": "MPL-2.0",
|
| 1298 |
+
"optional": true,
|
| 1299 |
+
"os": [
|
| 1300 |
+
"darwin"
|
| 1301 |
+
],
|
| 1302 |
+
"engines": {
|
| 1303 |
+
"node": ">= 12.0.0"
|
| 1304 |
+
},
|
| 1305 |
+
"funding": {
|
| 1306 |
+
"type": "opencollective",
|
| 1307 |
+
"url": "https://opencollective.com/parcel"
|
| 1308 |
+
}
|
| 1309 |
+
},
|
| 1310 |
+
"node_modules/lightningcss-freebsd-x64": {
|
| 1311 |
+
"version": "1.32.0",
|
| 1312 |
+
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
|
| 1313 |
+
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
|
| 1314 |
+
"cpu": [
|
| 1315 |
+
"x64"
|
| 1316 |
+
],
|
| 1317 |
+
"license": "MPL-2.0",
|
| 1318 |
+
"optional": true,
|
| 1319 |
+
"os": [
|
| 1320 |
+
"freebsd"
|
| 1321 |
+
],
|
| 1322 |
+
"engines": {
|
| 1323 |
+
"node": ">= 12.0.0"
|
| 1324 |
+
},
|
| 1325 |
+
"funding": {
|
| 1326 |
+
"type": "opencollective",
|
| 1327 |
+
"url": "https://opencollective.com/parcel"
|
| 1328 |
+
}
|
| 1329 |
+
},
|
| 1330 |
+
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
| 1331 |
+
"version": "1.32.0",
|
| 1332 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
|
| 1333 |
+
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
|
| 1334 |
+
"cpu": [
|
| 1335 |
+
"arm"
|
| 1336 |
+
],
|
| 1337 |
+
"license": "MPL-2.0",
|
| 1338 |
+
"optional": true,
|
| 1339 |
+
"os": [
|
| 1340 |
+
"linux"
|
| 1341 |
+
],
|
| 1342 |
+
"engines": {
|
| 1343 |
+
"node": ">= 12.0.0"
|
| 1344 |
+
},
|
| 1345 |
+
"funding": {
|
| 1346 |
+
"type": "opencollective",
|
| 1347 |
+
"url": "https://opencollective.com/parcel"
|
| 1348 |
+
}
|
| 1349 |
+
},
|
| 1350 |
+
"node_modules/lightningcss-linux-arm64-gnu": {
|
| 1351 |
+
"version": "1.32.0",
|
| 1352 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
|
| 1353 |
+
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
|
| 1354 |
+
"cpu": [
|
| 1355 |
+
"arm64"
|
| 1356 |
+
],
|
| 1357 |
+
"license": "MPL-2.0",
|
| 1358 |
+
"optional": true,
|
| 1359 |
+
"os": [
|
| 1360 |
+
"linux"
|
| 1361 |
+
],
|
| 1362 |
+
"engines": {
|
| 1363 |
+
"node": ">= 12.0.0"
|
| 1364 |
+
},
|
| 1365 |
+
"funding": {
|
| 1366 |
+
"type": "opencollective",
|
| 1367 |
+
"url": "https://opencollective.com/parcel"
|
| 1368 |
+
}
|
| 1369 |
+
},
|
| 1370 |
+
"node_modules/lightningcss-linux-arm64-musl": {
|
| 1371 |
+
"version": "1.32.0",
|
| 1372 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
|
| 1373 |
+
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
|
| 1374 |
+
"cpu": [
|
| 1375 |
+
"arm64"
|
| 1376 |
+
],
|
| 1377 |
+
"license": "MPL-2.0",
|
| 1378 |
+
"optional": true,
|
| 1379 |
+
"os": [
|
| 1380 |
+
"linux"
|
| 1381 |
+
],
|
| 1382 |
+
"engines": {
|
| 1383 |
+
"node": ">= 12.0.0"
|
| 1384 |
+
},
|
| 1385 |
+
"funding": {
|
| 1386 |
+
"type": "opencollective",
|
| 1387 |
+
"url": "https://opencollective.com/parcel"
|
| 1388 |
+
}
|
| 1389 |
+
},
|
| 1390 |
+
"node_modules/lightningcss-linux-x64-gnu": {
|
| 1391 |
+
"version": "1.32.0",
|
| 1392 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
|
| 1393 |
+
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
|
| 1394 |
+
"cpu": [
|
| 1395 |
+
"x64"
|
| 1396 |
+
],
|
| 1397 |
+
"license": "MPL-2.0",
|
| 1398 |
+
"optional": true,
|
| 1399 |
+
"os": [
|
| 1400 |
+
"linux"
|
| 1401 |
+
],
|
| 1402 |
+
"engines": {
|
| 1403 |
+
"node": ">= 12.0.0"
|
| 1404 |
+
},
|
| 1405 |
+
"funding": {
|
| 1406 |
+
"type": "opencollective",
|
| 1407 |
+
"url": "https://opencollective.com/parcel"
|
| 1408 |
+
}
|
| 1409 |
+
},
|
| 1410 |
+
"node_modules/lightningcss-linux-x64-musl": {
|
| 1411 |
+
"version": "1.32.0",
|
| 1412 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
|
| 1413 |
+
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
|
| 1414 |
+
"cpu": [
|
| 1415 |
+
"x64"
|
| 1416 |
+
],
|
| 1417 |
+
"license": "MPL-2.0",
|
| 1418 |
+
"optional": true,
|
| 1419 |
+
"os": [
|
| 1420 |
+
"linux"
|
| 1421 |
+
],
|
| 1422 |
+
"engines": {
|
| 1423 |
+
"node": ">= 12.0.0"
|
| 1424 |
+
},
|
| 1425 |
+
"funding": {
|
| 1426 |
+
"type": "opencollective",
|
| 1427 |
+
"url": "https://opencollective.com/parcel"
|
| 1428 |
+
}
|
| 1429 |
+
},
|
| 1430 |
+
"node_modules/lightningcss-win32-arm64-msvc": {
|
| 1431 |
+
"version": "1.32.0",
|
| 1432 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
|
| 1433 |
+
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
|
| 1434 |
+
"cpu": [
|
| 1435 |
+
"arm64"
|
| 1436 |
+
],
|
| 1437 |
+
"license": "MPL-2.0",
|
| 1438 |
+
"optional": true,
|
| 1439 |
+
"os": [
|
| 1440 |
+
"win32"
|
| 1441 |
+
],
|
| 1442 |
+
"engines": {
|
| 1443 |
+
"node": ">= 12.0.0"
|
| 1444 |
+
},
|
| 1445 |
+
"funding": {
|
| 1446 |
+
"type": "opencollective",
|
| 1447 |
+
"url": "https://opencollective.com/parcel"
|
| 1448 |
+
}
|
| 1449 |
+
},
|
| 1450 |
+
"node_modules/lightningcss-win32-x64-msvc": {
|
| 1451 |
+
"version": "1.32.0",
|
| 1452 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
|
| 1453 |
+
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
|
| 1454 |
+
"cpu": [
|
| 1455 |
+
"x64"
|
| 1456 |
+
],
|
| 1457 |
+
"license": "MPL-2.0",
|
| 1458 |
+
"optional": true,
|
| 1459 |
+
"os": [
|
| 1460 |
+
"win32"
|
| 1461 |
+
],
|
| 1462 |
+
"engines": {
|
| 1463 |
+
"node": ">= 12.0.0"
|
| 1464 |
+
},
|
| 1465 |
+
"funding": {
|
| 1466 |
+
"type": "opencollective",
|
| 1467 |
+
"url": "https://opencollective.com/parcel"
|
| 1468 |
+
}
|
| 1469 |
+
},
|
| 1470 |
+
"node_modules/lucide-react": {
|
| 1471 |
+
"version": "1.23.0",
|
| 1472 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.23.0.tgz",
|
| 1473 |
+
"integrity": "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw==",
|
| 1474 |
+
"license": "ISC",
|
| 1475 |
+
"peerDependencies": {
|
| 1476 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1477 |
+
}
|
| 1478 |
+
},
|
| 1479 |
+
"node_modules/magic-string": {
|
| 1480 |
+
"version": "0.30.21",
|
| 1481 |
+
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
| 1482 |
+
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
| 1483 |
+
"license": "MIT",
|
| 1484 |
+
"dependencies": {
|
| 1485 |
+
"@jridgewell/sourcemap-codec": "^1.5.5"
|
| 1486 |
+
}
|
| 1487 |
+
},
|
| 1488 |
+
"node_modules/marked": {
|
| 1489 |
+
"version": "14.0.0",
|
| 1490 |
+
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
|
| 1491 |
+
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
|
| 1492 |
+
"license": "MIT",
|
| 1493 |
+
"peer": true,
|
| 1494 |
+
"bin": {
|
| 1495 |
+
"marked": "bin/marked.js"
|
| 1496 |
+
},
|
| 1497 |
+
"engines": {
|
| 1498 |
+
"node": ">= 18"
|
| 1499 |
+
}
|
| 1500 |
+
},
|
| 1501 |
+
"node_modules/monaco-editor": {
|
| 1502 |
+
"version": "0.55.1",
|
| 1503 |
+
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
|
| 1504 |
+
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
|
| 1505 |
+
"license": "MIT",
|
| 1506 |
+
"peer": true,
|
| 1507 |
+
"dependencies": {
|
| 1508 |
+
"dompurify": "3.2.7",
|
| 1509 |
+
"marked": "14.0.0"
|
| 1510 |
+
}
|
| 1511 |
+
},
|
| 1512 |
+
"node_modules/motion-dom": {
|
| 1513 |
+
"version": "12.42.2",
|
| 1514 |
+
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.2.tgz",
|
| 1515 |
+
"integrity": "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==",
|
| 1516 |
+
"license": "MIT",
|
| 1517 |
+
"dependencies": {
|
| 1518 |
+
"motion-utils": "^12.39.0"
|
| 1519 |
+
}
|
| 1520 |
+
},
|
| 1521 |
+
"node_modules/motion-utils": {
|
| 1522 |
+
"version": "12.39.0",
|
| 1523 |
+
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
|
| 1524 |
+
"integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
|
| 1525 |
+
"license": "MIT"
|
| 1526 |
+
},
|
| 1527 |
+
"node_modules/nanoid": {
|
| 1528 |
+
"version": "3.3.15",
|
| 1529 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
|
| 1530 |
+
"integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
|
| 1531 |
+
"funding": [
|
| 1532 |
+
{
|
| 1533 |
+
"type": "github",
|
| 1534 |
+
"url": "https://github.com/sponsors/ai"
|
| 1535 |
+
}
|
| 1536 |
+
],
|
| 1537 |
+
"license": "MIT",
|
| 1538 |
+
"bin": {
|
| 1539 |
+
"nanoid": "bin/nanoid.cjs"
|
| 1540 |
+
},
|
| 1541 |
+
"engines": {
|
| 1542 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 1543 |
+
}
|
| 1544 |
+
},
|
| 1545 |
+
"node_modules/oxlint": {
|
| 1546 |
+
"version": "1.72.0",
|
| 1547 |
+
"resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.72.0.tgz",
|
| 1548 |
+
"integrity": "sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==",
|
| 1549 |
+
"dev": true,
|
| 1550 |
+
"license": "MIT",
|
| 1551 |
+
"bin": {
|
| 1552 |
+
"oxlint": "bin/oxlint"
|
| 1553 |
+
},
|
| 1554 |
+
"engines": {
|
| 1555 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1556 |
+
},
|
| 1557 |
+
"funding": {
|
| 1558 |
+
"url": "https://github.com/sponsors/Boshen"
|
| 1559 |
+
},
|
| 1560 |
+
"optionalDependencies": {
|
| 1561 |
+
"@oxlint/binding-android-arm-eabi": "1.72.0",
|
| 1562 |
+
"@oxlint/binding-android-arm64": "1.72.0",
|
| 1563 |
+
"@oxlint/binding-darwin-arm64": "1.72.0",
|
| 1564 |
+
"@oxlint/binding-darwin-x64": "1.72.0",
|
| 1565 |
+
"@oxlint/binding-freebsd-x64": "1.72.0",
|
| 1566 |
+
"@oxlint/binding-linux-arm-gnueabihf": "1.72.0",
|
| 1567 |
+
"@oxlint/binding-linux-arm-musleabihf": "1.72.0",
|
| 1568 |
+
"@oxlint/binding-linux-arm64-gnu": "1.72.0",
|
| 1569 |
+
"@oxlint/binding-linux-arm64-musl": "1.72.0",
|
| 1570 |
+
"@oxlint/binding-linux-ppc64-gnu": "1.72.0",
|
| 1571 |
+
"@oxlint/binding-linux-riscv64-gnu": "1.72.0",
|
| 1572 |
+
"@oxlint/binding-linux-riscv64-musl": "1.72.0",
|
| 1573 |
+
"@oxlint/binding-linux-s390x-gnu": "1.72.0",
|
| 1574 |
+
"@oxlint/binding-linux-x64-gnu": "1.72.0",
|
| 1575 |
+
"@oxlint/binding-linux-x64-musl": "1.72.0",
|
| 1576 |
+
"@oxlint/binding-openharmony-arm64": "1.72.0",
|
| 1577 |
+
"@oxlint/binding-win32-arm64-msvc": "1.72.0",
|
| 1578 |
+
"@oxlint/binding-win32-ia32-msvc": "1.72.0",
|
| 1579 |
+
"@oxlint/binding-win32-x64-msvc": "1.72.0"
|
| 1580 |
+
},
|
| 1581 |
+
"peerDependencies": {
|
| 1582 |
+
"oxlint-tsgolint": ">=0.22.1",
|
| 1583 |
+
"vite-plus": "*"
|
| 1584 |
+
},
|
| 1585 |
+
"peerDependenciesMeta": {
|
| 1586 |
+
"oxlint-tsgolint": {
|
| 1587 |
+
"optional": true
|
| 1588 |
+
},
|
| 1589 |
+
"vite-plus": {
|
| 1590 |
+
"optional": true
|
| 1591 |
+
}
|
| 1592 |
+
}
|
| 1593 |
+
},
|
| 1594 |
+
"node_modules/picocolors": {
|
| 1595 |
+
"version": "1.1.1",
|
| 1596 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1597 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1598 |
+
"license": "ISC"
|
| 1599 |
+
},
|
| 1600 |
+
"node_modules/picomatch": {
|
| 1601 |
+
"version": "4.0.4",
|
| 1602 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 1603 |
+
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 1604 |
+
"license": "MIT",
|
| 1605 |
+
"engines": {
|
| 1606 |
+
"node": ">=12"
|
| 1607 |
+
},
|
| 1608 |
+
"funding": {
|
| 1609 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 1610 |
+
}
|
| 1611 |
+
},
|
| 1612 |
+
"node_modules/postcss": {
|
| 1613 |
+
"version": "8.5.16",
|
| 1614 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
|
| 1615 |
+
"integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
|
| 1616 |
+
"funding": [
|
| 1617 |
+
{
|
| 1618 |
+
"type": "opencollective",
|
| 1619 |
+
"url": "https://opencollective.com/postcss/"
|
| 1620 |
+
},
|
| 1621 |
+
{
|
| 1622 |
+
"type": "tidelift",
|
| 1623 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1624 |
+
},
|
| 1625 |
+
{
|
| 1626 |
+
"type": "github",
|
| 1627 |
+
"url": "https://github.com/sponsors/ai"
|
| 1628 |
+
}
|
| 1629 |
+
],
|
| 1630 |
+
"license": "MIT",
|
| 1631 |
+
"dependencies": {
|
| 1632 |
+
"nanoid": "^3.3.12",
|
| 1633 |
+
"picocolors": "^1.1.1",
|
| 1634 |
+
"source-map-js": "^1.2.1"
|
| 1635 |
+
},
|
| 1636 |
+
"engines": {
|
| 1637 |
+
"node": "^10 || ^12 || >=14"
|
| 1638 |
+
}
|
| 1639 |
+
},
|
| 1640 |
+
"node_modules/react": {
|
| 1641 |
+
"version": "19.2.7",
|
| 1642 |
+
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
| 1643 |
+
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
| 1644 |
+
"license": "MIT",
|
| 1645 |
+
"engines": {
|
| 1646 |
+
"node": ">=0.10.0"
|
| 1647 |
+
}
|
| 1648 |
+
},
|
| 1649 |
+
"node_modules/react-dom": {
|
| 1650 |
+
"version": "19.2.7",
|
| 1651 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
|
| 1652 |
+
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
|
| 1653 |
+
"license": "MIT",
|
| 1654 |
+
"dependencies": {
|
| 1655 |
+
"scheduler": "^0.27.0"
|
| 1656 |
+
},
|
| 1657 |
+
"peerDependencies": {
|
| 1658 |
+
"react": "^19.2.7"
|
| 1659 |
+
}
|
| 1660 |
+
},
|
| 1661 |
+
"node_modules/react-router": {
|
| 1662 |
+
"version": "7.18.1",
|
| 1663 |
+
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz",
|
| 1664 |
+
"integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==",
|
| 1665 |
+
"license": "MIT",
|
| 1666 |
+
"dependencies": {
|
| 1667 |
+
"cookie": "^1.0.1",
|
| 1668 |
+
"set-cookie-parser": "^2.6.0"
|
| 1669 |
+
},
|
| 1670 |
+
"engines": {
|
| 1671 |
+
"node": ">=20.0.0"
|
| 1672 |
+
},
|
| 1673 |
+
"peerDependencies": {
|
| 1674 |
+
"react": ">=18",
|
| 1675 |
+
"react-dom": ">=18"
|
| 1676 |
+
},
|
| 1677 |
+
"peerDependenciesMeta": {
|
| 1678 |
+
"react-dom": {
|
| 1679 |
+
"optional": true
|
| 1680 |
+
}
|
| 1681 |
+
}
|
| 1682 |
+
},
|
| 1683 |
+
"node_modules/react-router-dom": {
|
| 1684 |
+
"version": "7.18.1",
|
| 1685 |
+
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz",
|
| 1686 |
+
"integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==",
|
| 1687 |
+
"license": "MIT",
|
| 1688 |
+
"dependencies": {
|
| 1689 |
+
"react-router": "7.18.1"
|
| 1690 |
+
},
|
| 1691 |
+
"engines": {
|
| 1692 |
+
"node": ">=20.0.0"
|
| 1693 |
+
},
|
| 1694 |
+
"peerDependencies": {
|
| 1695 |
+
"react": ">=18",
|
| 1696 |
+
"react-dom": ">=18"
|
| 1697 |
+
}
|
| 1698 |
+
},
|
| 1699 |
+
"node_modules/rolldown": {
|
| 1700 |
+
"version": "1.1.4",
|
| 1701 |
+
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz",
|
| 1702 |
+
"integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==",
|
| 1703 |
+
"license": "MIT",
|
| 1704 |
+
"dependencies": {
|
| 1705 |
+
"@oxc-project/types": "=0.138.0",
|
| 1706 |
+
"@rolldown/pluginutils": "^1.0.0"
|
| 1707 |
+
},
|
| 1708 |
+
"bin": {
|
| 1709 |
+
"rolldown": "bin/cli.mjs"
|
| 1710 |
+
},
|
| 1711 |
+
"engines": {
|
| 1712 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1713 |
+
},
|
| 1714 |
+
"optionalDependencies": {
|
| 1715 |
+
"@rolldown/binding-android-arm64": "1.1.4",
|
| 1716 |
+
"@rolldown/binding-darwin-arm64": "1.1.4",
|
| 1717 |
+
"@rolldown/binding-darwin-x64": "1.1.4",
|
| 1718 |
+
"@rolldown/binding-freebsd-x64": "1.1.4",
|
| 1719 |
+
"@rolldown/binding-linux-arm-gnueabihf": "1.1.4",
|
| 1720 |
+
"@rolldown/binding-linux-arm64-gnu": "1.1.4",
|
| 1721 |
+
"@rolldown/binding-linux-arm64-musl": "1.1.4",
|
| 1722 |
+
"@rolldown/binding-linux-ppc64-gnu": "1.1.4",
|
| 1723 |
+
"@rolldown/binding-linux-s390x-gnu": "1.1.4",
|
| 1724 |
+
"@rolldown/binding-linux-x64-gnu": "1.1.4",
|
| 1725 |
+
"@rolldown/binding-linux-x64-musl": "1.1.4",
|
| 1726 |
+
"@rolldown/binding-openharmony-arm64": "1.1.4",
|
| 1727 |
+
"@rolldown/binding-wasm32-wasi": "1.1.4",
|
| 1728 |
+
"@rolldown/binding-win32-arm64-msvc": "1.1.4",
|
| 1729 |
+
"@rolldown/binding-win32-x64-msvc": "1.1.4"
|
| 1730 |
+
}
|
| 1731 |
+
},
|
| 1732 |
+
"node_modules/scheduler": {
|
| 1733 |
+
"version": "0.27.0",
|
| 1734 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
| 1735 |
+
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
| 1736 |
+
"license": "MIT"
|
| 1737 |
+
},
|
| 1738 |
+
"node_modules/set-cookie-parser": {
|
| 1739 |
+
"version": "2.7.2",
|
| 1740 |
+
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
| 1741 |
+
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
| 1742 |
+
"license": "MIT"
|
| 1743 |
+
},
|
| 1744 |
+
"node_modules/source-map-js": {
|
| 1745 |
+
"version": "1.2.1",
|
| 1746 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 1747 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 1748 |
+
"license": "BSD-3-Clause",
|
| 1749 |
+
"engines": {
|
| 1750 |
+
"node": ">=0.10.0"
|
| 1751 |
+
}
|
| 1752 |
+
},
|
| 1753 |
+
"node_modules/state-local": {
|
| 1754 |
+
"version": "1.0.7",
|
| 1755 |
+
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
|
| 1756 |
+
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
|
| 1757 |
+
"license": "MIT"
|
| 1758 |
+
},
|
| 1759 |
+
"node_modules/tailwind-merge": {
|
| 1760 |
+
"version": "3.6.0",
|
| 1761 |
+
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz",
|
| 1762 |
+
"integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==",
|
| 1763 |
+
"license": "MIT",
|
| 1764 |
+
"funding": {
|
| 1765 |
+
"type": "github",
|
| 1766 |
+
"url": "https://github.com/sponsors/dcastil"
|
| 1767 |
+
}
|
| 1768 |
+
},
|
| 1769 |
+
"node_modules/tailwindcss": {
|
| 1770 |
+
"version": "4.3.2",
|
| 1771 |
+
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz",
|
| 1772 |
+
"integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==",
|
| 1773 |
+
"license": "MIT"
|
| 1774 |
+
},
|
| 1775 |
+
"node_modules/tapable": {
|
| 1776 |
+
"version": "2.3.3",
|
| 1777 |
+
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
|
| 1778 |
+
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
|
| 1779 |
+
"license": "MIT",
|
| 1780 |
+
"engines": {
|
| 1781 |
+
"node": ">=6"
|
| 1782 |
+
},
|
| 1783 |
+
"funding": {
|
| 1784 |
+
"type": "opencollective",
|
| 1785 |
+
"url": "https://opencollective.com/webpack"
|
| 1786 |
+
}
|
| 1787 |
+
},
|
| 1788 |
+
"node_modules/tinyglobby": {
|
| 1789 |
+
"version": "0.2.17",
|
| 1790 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
| 1791 |
+
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
| 1792 |
+
"license": "MIT",
|
| 1793 |
+
"dependencies": {
|
| 1794 |
+
"fdir": "^6.5.0",
|
| 1795 |
+
"picomatch": "^4.0.4"
|
| 1796 |
+
},
|
| 1797 |
+
"engines": {
|
| 1798 |
+
"node": ">=12.0.0"
|
| 1799 |
+
},
|
| 1800 |
+
"funding": {
|
| 1801 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 1802 |
+
}
|
| 1803 |
+
},
|
| 1804 |
+
"node_modules/tslib": {
|
| 1805 |
+
"version": "2.8.1",
|
| 1806 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 1807 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 1808 |
+
"license": "0BSD"
|
| 1809 |
+
},
|
| 1810 |
+
"node_modules/typescript": {
|
| 1811 |
+
"version": "6.0.3",
|
| 1812 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
| 1813 |
+
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
| 1814 |
+
"dev": true,
|
| 1815 |
+
"license": "Apache-2.0",
|
| 1816 |
+
"bin": {
|
| 1817 |
+
"tsc": "bin/tsc",
|
| 1818 |
+
"tsserver": "bin/tsserver"
|
| 1819 |
+
},
|
| 1820 |
+
"engines": {
|
| 1821 |
+
"node": ">=14.17"
|
| 1822 |
+
}
|
| 1823 |
+
},
|
| 1824 |
+
"node_modules/undici-types": {
|
| 1825 |
+
"version": "7.18.2",
|
| 1826 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
| 1827 |
+
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
| 1828 |
+
"devOptional": true,
|
| 1829 |
+
"license": "MIT"
|
| 1830 |
+
},
|
| 1831 |
+
"node_modules/vite": {
|
| 1832 |
+
"version": "8.1.3",
|
| 1833 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz",
|
| 1834 |
+
"integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
|
| 1835 |
+
"license": "MIT",
|
| 1836 |
+
"dependencies": {
|
| 1837 |
+
"lightningcss": "^1.32.0",
|
| 1838 |
+
"picomatch": "^4.0.4",
|
| 1839 |
+
"postcss": "^8.5.16",
|
| 1840 |
+
"rolldown": "~1.1.3",
|
| 1841 |
+
"tinyglobby": "^0.2.17"
|
| 1842 |
+
},
|
| 1843 |
+
"bin": {
|
| 1844 |
+
"vite": "bin/vite.js"
|
| 1845 |
+
},
|
| 1846 |
+
"engines": {
|
| 1847 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1848 |
+
},
|
| 1849 |
+
"funding": {
|
| 1850 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 1851 |
+
},
|
| 1852 |
+
"optionalDependencies": {
|
| 1853 |
+
"fsevents": "~2.3.3"
|
| 1854 |
+
},
|
| 1855 |
+
"peerDependencies": {
|
| 1856 |
+
"@types/node": "^20.19.0 || >=22.12.0",
|
| 1857 |
+
"@vitejs/devtools": "^0.3.0",
|
| 1858 |
+
"esbuild": "^0.27.0 || ^0.28.0",
|
| 1859 |
+
"jiti": ">=1.21.0",
|
| 1860 |
+
"less": "^4.0.0",
|
| 1861 |
+
"sass": "^1.70.0",
|
| 1862 |
+
"sass-embedded": "^1.70.0",
|
| 1863 |
+
"stylus": ">=0.54.8",
|
| 1864 |
+
"sugarss": "^5.0.0",
|
| 1865 |
+
"terser": "^5.16.0",
|
| 1866 |
+
"tsx": "^4.8.1",
|
| 1867 |
+
"yaml": "^2.4.2"
|
| 1868 |
+
},
|
| 1869 |
+
"peerDependenciesMeta": {
|
| 1870 |
+
"@types/node": {
|
| 1871 |
+
"optional": true
|
| 1872 |
+
},
|
| 1873 |
+
"@vitejs/devtools": {
|
| 1874 |
+
"optional": true
|
| 1875 |
+
},
|
| 1876 |
+
"esbuild": {
|
| 1877 |
+
"optional": true
|
| 1878 |
+
},
|
| 1879 |
+
"jiti": {
|
| 1880 |
+
"optional": true
|
| 1881 |
+
},
|
| 1882 |
+
"less": {
|
| 1883 |
+
"optional": true
|
| 1884 |
+
},
|
| 1885 |
+
"sass": {
|
| 1886 |
+
"optional": true
|
| 1887 |
+
},
|
| 1888 |
+
"sass-embedded": {
|
| 1889 |
+
"optional": true
|
| 1890 |
+
},
|
| 1891 |
+
"stylus": {
|
| 1892 |
+
"optional": true
|
| 1893 |
+
},
|
| 1894 |
+
"sugarss": {
|
| 1895 |
+
"optional": true
|
| 1896 |
+
},
|
| 1897 |
+
"terser": {
|
| 1898 |
+
"optional": true
|
| 1899 |
+
},
|
| 1900 |
+
"tsx": {
|
| 1901 |
+
"optional": true
|
| 1902 |
+
},
|
| 1903 |
+
"yaml": {
|
| 1904 |
+
"optional": true
|
| 1905 |
+
}
|
| 1906 |
+
}
|
| 1907 |
+
}
|
| 1908 |
+
}
|
| 1909 |
+
}
|
frontend/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "tsc -b && vite build",
|
| 9 |
+
"lint": "oxlint",
|
| 10 |
+
"preview": "vite preview"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"@monaco-editor/react": "^4.7.0",
|
| 14 |
+
"@tailwindcss/vite": "^4.3.2",
|
| 15 |
+
"@tanstack/react-query": "^5.101.2",
|
| 16 |
+
"clsx": "^2.1.1",
|
| 17 |
+
"framer-motion": "^12.42.2",
|
| 18 |
+
"lucide-react": "^1.23.0",
|
| 19 |
+
"react": "^19.2.7",
|
| 20 |
+
"react-dom": "^19.2.7",
|
| 21 |
+
"react-router-dom": "^7.18.1",
|
| 22 |
+
"tailwind-merge": "^3.6.0",
|
| 23 |
+
"tailwindcss": "^4.3.2"
|
| 24 |
+
},
|
| 25 |
+
"devDependencies": {
|
| 26 |
+
"@types/node": "^24.13.2",
|
| 27 |
+
"@types/react": "^19.2.17",
|
| 28 |
+
"@types/react-dom": "^19.2.3",
|
| 29 |
+
"@vitejs/plugin-react": "^6.0.3",
|
| 30 |
+
"oxlint": "^1.71.0",
|
| 31 |
+
"typescript": "~6.0.2",
|
| 32 |
+
"vite": "^8.1.1"
|
| 33 |
+
}
|
| 34 |
+
}
|
frontend/public/favicon.svg
ADDED
|
|
frontend/public/icons.svg
ADDED
|
|
frontend/src/App.css
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.counter {
|
| 2 |
+
font-size: 16px;
|
| 3 |
+
padding: 5px 10px;
|
| 4 |
+
border-radius: 5px;
|
| 5 |
+
color: var(--accent);
|
| 6 |
+
background: var(--accent-bg);
|
| 7 |
+
border: 2px solid transparent;
|
| 8 |
+
transition: border-color 0.3s;
|
| 9 |
+
margin-bottom: 24px;
|
| 10 |
+
|
| 11 |
+
&:hover {
|
| 12 |
+
border-color: var(--accent-border);
|
| 13 |
+
}
|
| 14 |
+
&:focus-visible {
|
| 15 |
+
outline: 2px solid var(--accent);
|
| 16 |
+
outline-offset: 2px;
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.hero {
|
| 21 |
+
position: relative;
|
| 22 |
+
|
| 23 |
+
.base,
|
| 24 |
+
.framework,
|
| 25 |
+
.vite {
|
| 26 |
+
inset-inline: 0;
|
| 27 |
+
margin: 0 auto;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.base {
|
| 31 |
+
width: 170px;
|
| 32 |
+
position: relative;
|
| 33 |
+
z-index: 0;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.framework,
|
| 37 |
+
.vite {
|
| 38 |
+
position: absolute;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.framework {
|
| 42 |
+
z-index: 1;
|
| 43 |
+
top: 34px;
|
| 44 |
+
height: 28px;
|
| 45 |
+
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
| 46 |
+
scale(1.4);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.vite {
|
| 50 |
+
z-index: 0;
|
| 51 |
+
top: 107px;
|
| 52 |
+
height: 26px;
|
| 53 |
+
width: auto;
|
| 54 |
+
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
| 55 |
+
scale(0.8);
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
#center {
|
| 60 |
+
display: flex;
|
| 61 |
+
flex-direction: column;
|
| 62 |
+
gap: 25px;
|
| 63 |
+
place-content: center;
|
| 64 |
+
place-items: center;
|
| 65 |
+
flex-grow: 1;
|
| 66 |
+
|
| 67 |
+
@media (max-width: 1024px) {
|
| 68 |
+
padding: 32px 20px 24px;
|
| 69 |
+
gap: 18px;
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
#next-steps {
|
| 74 |
+
display: flex;
|
| 75 |
+
border-top: 1px solid var(--border);
|
| 76 |
+
text-align: left;
|
| 77 |
+
|
| 78 |
+
& > div {
|
| 79 |
+
flex: 1 1 0;
|
| 80 |
+
padding: 32px;
|
| 81 |
+
@media (max-width: 1024px) {
|
| 82 |
+
padding: 24px 20px;
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.icon {
|
| 87 |
+
margin-bottom: 16px;
|
| 88 |
+
width: 22px;
|
| 89 |
+
height: 22px;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
@media (max-width: 1024px) {
|
| 93 |
+
flex-direction: column;
|
| 94 |
+
text-align: center;
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
#docs {
|
| 99 |
+
border-right: 1px solid var(--border);
|
| 100 |
+
|
| 101 |
+
@media (max-width: 1024px) {
|
| 102 |
+
border-right: none;
|
| 103 |
+
border-bottom: 1px solid var(--border);
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
#next-steps ul {
|
| 108 |
+
list-style: none;
|
| 109 |
+
padding: 0;
|
| 110 |
+
display: flex;
|
| 111 |
+
gap: 8px;
|
| 112 |
+
margin: 32px 0 0;
|
| 113 |
+
|
| 114 |
+
.logo {
|
| 115 |
+
height: 18px;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
a {
|
| 119 |
+
color: var(--text-h);
|
| 120 |
+
font-size: 16px;
|
| 121 |
+
border-radius: 6px;
|
| 122 |
+
background: var(--social-bg);
|
| 123 |
+
display: flex;
|
| 124 |
+
padding: 6px 12px;
|
| 125 |
+
align-items: center;
|
| 126 |
+
gap: 8px;
|
| 127 |
+
text-decoration: none;
|
| 128 |
+
transition: box-shadow 0.3s;
|
| 129 |
+
|
| 130 |
+
&:hover {
|
| 131 |
+
box-shadow: var(--shadow);
|
| 132 |
+
}
|
| 133 |
+
.button-icon {
|
| 134 |
+
height: 18px;
|
| 135 |
+
width: 18px;
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
@media (max-width: 1024px) {
|
| 140 |
+
margin-top: 20px;
|
| 141 |
+
flex-wrap: wrap;
|
| 142 |
+
justify-content: center;
|
| 143 |
+
|
| 144 |
+
li {
|
| 145 |
+
flex: 1 1 calc(50% - 8px);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
a {
|
| 149 |
+
width: 100%;
|
| 150 |
+
justify-content: center;
|
| 151 |
+
box-sizing: border-box;
|
| 152 |
+
}
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
#spacer {
|
| 157 |
+
height: 88px;
|
| 158 |
+
border-top: 1px solid var(--border);
|
| 159 |
+
@media (max-width: 1024px) {
|
| 160 |
+
height: 48px;
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.ticks {
|
| 165 |
+
position: relative;
|
| 166 |
+
width: 100%;
|
| 167 |
+
|
| 168 |
+
&::before,
|
| 169 |
+
&::after {
|
| 170 |
+
content: '';
|
| 171 |
+
position: absolute;
|
| 172 |
+
top: -4.5px;
|
| 173 |
+
border: 5px solid transparent;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
&::before {
|
| 177 |
+
left: 0;
|
| 178 |
+
border-left-color: var(--border);
|
| 179 |
+
}
|
| 180 |
+
&::after {
|
| 181 |
+
right: 0;
|
| 182 |
+
border-right-color: var(--border);
|
| 183 |
+
}
|
| 184 |
+
}
|
frontend/src/App.tsx
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { AnimatePresence, motion } from 'framer-motion';
|
| 3 |
+
import { useChat } from './hooks/useChat';
|
| 4 |
+
import { Sidebar } from './components/Sidebar';
|
| 5 |
+
import { Navbar } from './components/Navbar';
|
| 6 |
+
import { Dashboard } from './components/Dashboard';
|
| 7 |
+
import { ChatInterface } from './components/ChatInterface';
|
| 8 |
+
import { CodePlayground } from './components/CodePlayground';
|
| 9 |
+
import { Settings } from './components/Settings';
|
| 10 |
+
import { AboutPage } from './components/AboutPage';
|
| 11 |
+
|
| 12 |
+
export const App: React.FC = () => {
|
| 13 |
+
const {
|
| 14 |
+
conversations,
|
| 15 |
+
activeConversationId,
|
| 16 |
+
setActiveConversationId,
|
| 17 |
+
activeConversation,
|
| 18 |
+
settings,
|
| 19 |
+
modelInfo,
|
| 20 |
+
metrics,
|
| 21 |
+
isLoading,
|
| 22 |
+
error,
|
| 23 |
+
updateSettings,
|
| 24 |
+
createNewConversation,
|
| 25 |
+
deleteConversation,
|
| 26 |
+
sendMessage,
|
| 27 |
+
executeCodeAction,
|
| 28 |
+
clearHistory,
|
| 29 |
+
} = useChat();
|
| 30 |
+
|
| 31 |
+
const [activeTab, setActiveTab] = useState<string>('dashboard');
|
| 32 |
+
const [sidebarCollapsed, setSidebarCollapsed] = useState<boolean>(false);
|
| 33 |
+
const [mobileSidebarOpen, setMobileSidebarOpen] = useState<boolean>(false);
|
| 34 |
+
|
| 35 |
+
const selectConversationFromSidebar = (id: string) => {
|
| 36 |
+
setActiveConversationId(id);
|
| 37 |
+
setActiveTab('chat');
|
| 38 |
+
setMobileSidebarOpen(false);
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
const selectTabFromNav = (tab: string) => {
|
| 42 |
+
setActiveTab(tab);
|
| 43 |
+
setMobileSidebarOpen(false);
|
| 44 |
+
};
|
| 45 |
+
|
| 46 |
+
return (
|
| 47 |
+
<div className="flex h-screen w-screen overflow-hidden bg-background text-foreground font-sans">
|
| 48 |
+
|
| 49 |
+
{/* 1. Sidebar Navigation (Desktop only, hidden on lg screens down) */}
|
| 50 |
+
<div className="hidden lg:block h-full">
|
| 51 |
+
<Sidebar
|
| 52 |
+
conversations={conversations}
|
| 53 |
+
activeConversationId={activeConversationId}
|
| 54 |
+
setActiveConversationId={selectConversationFromSidebar}
|
| 55 |
+
createNewConversation={createNewConversation}
|
| 56 |
+
deleteConversation={deleteConversation}
|
| 57 |
+
activeTab={activeTab}
|
| 58 |
+
setActiveTab={selectTabFromNav}
|
| 59 |
+
modelInfo={modelInfo}
|
| 60 |
+
collapsed={sidebarCollapsed}
|
| 61 |
+
setCollapsed={setSidebarCollapsed}
|
| 62 |
+
/>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
{/* 2. Responsive Overlay Drawer Sidebar (Mobile / Tablet) */}
|
| 66 |
+
<AnimatePresence>
|
| 67 |
+
{mobileSidebarOpen && (
|
| 68 |
+
<motion.div
|
| 69 |
+
initial={{ opacity: 0 }}
|
| 70 |
+
animate={{ opacity: 1 }}
|
| 71 |
+
exit={{ opacity: 0 }}
|
| 72 |
+
className="fixed inset-0 z-50 flex lg:hidden bg-background/80 backdrop-blur-sm"
|
| 73 |
+
>
|
| 74 |
+
<motion.div
|
| 75 |
+
initial={{ x: '-100%' }}
|
| 76 |
+
animate={{ x: 0 }}
|
| 77 |
+
exit={{ x: '-100%' }}
|
| 78 |
+
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
| 79 |
+
className="relative"
|
| 80 |
+
>
|
| 81 |
+
<Sidebar
|
| 82 |
+
conversations={conversations}
|
| 83 |
+
activeConversationId={activeConversationId}
|
| 84 |
+
setActiveConversationId={selectConversationFromSidebar}
|
| 85 |
+
createNewConversation={createNewConversation}
|
| 86 |
+
deleteConversation={deleteConversation}
|
| 87 |
+
activeTab={activeTab}
|
| 88 |
+
setActiveTab={selectTabFromNav}
|
| 89 |
+
modelInfo={modelInfo}
|
| 90 |
+
collapsed={false}
|
| 91 |
+
setCollapsed={() => {}}
|
| 92 |
+
/>
|
| 93 |
+
{/* Close trigger overlay inside the sidebar */}
|
| 94 |
+
<button
|
| 95 |
+
onClick={() => setMobileSidebarOpen(false)}
|
| 96 |
+
className="absolute top-4 right-4 p-1.5 bg-muted/50 hover:bg-muted text-muted-foreground hover:text-foreground rounded-lg transition-colors cursor-pointer"
|
| 97 |
+
>
|
| 98 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" x2="6" y1="6" y2="18"></line><line x1="6" x2="18" y1="6" y2="18"></line></svg>
|
| 99 |
+
</button>
|
| 100 |
+
</motion.div>
|
| 101 |
+
{/* Backdrop shadow space clicking closer */}
|
| 102 |
+
<div className="flex-1 h-full" onClick={() => setMobileSidebarOpen(false)}></div>
|
| 103 |
+
</motion.div>
|
| 104 |
+
)}
|
| 105 |
+
</AnimatePresence>
|
| 106 |
+
|
| 107 |
+
{/* 3. Main content frame (Contains sticky header and tabs) */}
|
| 108 |
+
<main className="flex-1 flex flex-col min-w-0 overflow-hidden relative">
|
| 109 |
+
<Navbar
|
| 110 |
+
activeTab={activeTab}
|
| 111 |
+
modelInfo={modelInfo}
|
| 112 |
+
settings={settings}
|
| 113 |
+
updateSettings={updateSettings}
|
| 114 |
+
onOpenMobileSidebar={() => setMobileSidebarOpen(true)}
|
| 115 |
+
/>
|
| 116 |
+
|
| 117 |
+
<div className="flex-1 flex flex-col min-h-0 overflow-hidden relative">
|
| 118 |
+
<AnimatePresence mode="wait">
|
| 119 |
+
<motion.div
|
| 120 |
+
key={activeTab}
|
| 121 |
+
initial={{ opacity: 0, y: 6 }}
|
| 122 |
+
animate={{ opacity: 1, y: 0 }}
|
| 123 |
+
exit={{ opacity: 0, y: -6 }}
|
| 124 |
+
transition={{ duration: 0.2 }}
|
| 125 |
+
className="flex-1 flex flex-col min-h-0 overflow-hidden"
|
| 126 |
+
>
|
| 127 |
+
{activeTab === 'dashboard' && (
|
| 128 |
+
<Dashboard
|
| 129 |
+
modelInfo={modelInfo}
|
| 130 |
+
metrics={metrics}
|
| 131 |
+
setActiveTab={selectTabFromNav}
|
| 132 |
+
createNewConversation={createNewConversation}
|
| 133 |
+
sendMessage={sendMessage}
|
| 134 |
+
/>
|
| 135 |
+
)}
|
| 136 |
+
|
| 137 |
+
{activeTab === 'chat' && (
|
| 138 |
+
<ChatInterface
|
| 139 |
+
activeConversation={activeConversation}
|
| 140 |
+
sendMessage={sendMessage}
|
| 141 |
+
isLoading={isLoading}
|
| 142 |
+
error={error}
|
| 143 |
+
modelInfo={modelInfo}
|
| 144 |
+
/>
|
| 145 |
+
)}
|
| 146 |
+
|
| 147 |
+
{activeTab === 'playground' && (
|
| 148 |
+
<CodePlayground
|
| 149 |
+
executeCodeAction={executeCodeAction}
|
| 150 |
+
isLoading={isLoading}
|
| 151 |
+
/>
|
| 152 |
+
)}
|
| 153 |
+
|
| 154 |
+
{activeTab === 'settings' && (
|
| 155 |
+
<Settings
|
| 156 |
+
settings={settings}
|
| 157 |
+
updateSettings={updateSettings}
|
| 158 |
+
clearHistory={clearHistory}
|
| 159 |
+
modelInfo={modelInfo}
|
| 160 |
+
/>
|
| 161 |
+
)}
|
| 162 |
+
|
| 163 |
+
{activeTab === 'about' && (
|
| 164 |
+
<AboutPage />
|
| 165 |
+
)}
|
| 166 |
+
|
| 167 |
+
{/* Placeholder tabs for nav items not yet implemented */}
|
| 168 |
+
{['history', 'documents', 'snippets', 'models'].includes(activeTab) && (
|
| 169 |
+
<div className="flex-1 flex flex-col items-center justify-center bg-[#090810] text-center p-8 space-y-4 select-none">
|
| 170 |
+
<div className="p-4 bg-primary/10 rounded-2xl text-primary">
|
| 171 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" x2="8" y1="13" y2="13"></line><line x1="16" x2="8" y1="17" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
| 172 |
+
</div>
|
| 173 |
+
<div className="space-y-1.5">
|
| 174 |
+
<h2 className="text-base font-bold text-white capitalize">{activeTab}</h2>
|
| 175 |
+
<p className="text-xs text-[#9d99b3] max-w-xs">This section is coming soon. Core chat and playground features are fully available.</p>
|
| 176 |
+
</div>
|
| 177 |
+
<button
|
| 178 |
+
onClick={() => selectTabFromNav('dashboard')}
|
| 179 |
+
className="px-4 py-2 bg-primary/10 hover:bg-primary/20 border border-primary/30 text-primary text-xs font-bold rounded-xl cursor-pointer transition-all"
|
| 180 |
+
>
|
| 181 |
+
Back to Dashboard
|
| 182 |
+
</button>
|
| 183 |
+
</div>
|
| 184 |
+
)}
|
| 185 |
+
</motion.div>
|
| 186 |
+
</AnimatePresence>
|
| 187 |
+
</div>
|
| 188 |
+
</main>
|
| 189 |
+
</div>
|
| 190 |
+
);
|
| 191 |
+
};
|
| 192 |
+
|
| 193 |
+
export default App;
|
frontend/src/assets/hero.png
ADDED
|
frontend/src/assets/react.svg
ADDED
|
|
frontend/src/assets/vite.svg
ADDED
|
|
frontend/src/components/AboutPage.tsx
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Info, Cpu, Code, BookOpen, HardDrive, Share2 } from 'lucide-react';
|
| 3 |
+
|
| 4 |
+
export const AboutPage: React.FC = () => {
|
| 5 |
+
return (
|
| 6 |
+
<div className="flex-1 overflow-y-auto bg-background p-6 lg:p-8 space-y-8 select-none">
|
| 7 |
+
<div className="flex items-center gap-2 border-b border-border pb-4">
|
| 8 |
+
<span className="p-2 bg-primary/10 rounded-xl text-primary">
|
| 9 |
+
<Info size={20} />
|
| 10 |
+
</span>
|
| 11 |
+
<div>
|
| 12 |
+
<h1 className="text-2xl font-bold tracking-tight">About Antigravity AI Coder</h1>
|
| 13 |
+
<p className="text-xs text-muted-foreground">Learn about the architecture and technologies running under the hood.</p>
|
| 14 |
+
</div>
|
| 15 |
+
</div>
|
| 16 |
+
|
| 17 |
+
<div className="max-w-3xl space-y-6">
|
| 18 |
+
<div className="bg-card border border-border p-6 rounded-2xl space-y-4">
|
| 19 |
+
<h2 className="text-base font-bold flex items-center gap-2">
|
| 20 |
+
<Cpu size={18} className="text-primary" />
|
| 21 |
+
Hybrid Local/Cloud Architecture
|
| 22 |
+
</h2>
|
| 23 |
+
<p className="text-sm text-muted-foreground leading-relaxed">
|
| 24 |
+
Antigravity Coder is built on a cascading inference pipeline. It is configured to run the state-of-the-art <strong>Qwen2.5-Coder-0.5B-Instruct</strong> model locally in GGUF format using <code>llama-cpp-python</code>.
|
| 25 |
+
</p>
|
| 26 |
+
<p className="text-sm text-muted-foreground leading-relaxed">
|
| 27 |
+
If the application is deployed on cloud hosts with restricted memory boundaries (e.g. Render Free, Railway Starter), the backend automatically and transparently switches to the <strong>Hugging Face Serverless Inference API</strong>, guaranteeing high-performance execution without needing a local GPU or extensive memory budgets.
|
| 28 |
+
</p>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 32 |
+
<div className="bg-card border border-border p-5 rounded-2xl space-y-2">
|
| 33 |
+
<h3 className="text-sm font-bold flex items-center gap-1.5">
|
| 34 |
+
<Code size={16} className="text-primary" />
|
| 35 |
+
Qwen2.5-Coder Engine
|
| 36 |
+
</h3>
|
| 37 |
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
| 38 |
+
Leverages the Qwen2.5-Coder 0.5B Instruct model, specifically fine-tuned for code generation, mathematical logic, bug fixing, and language formatting in dozens of programming languages.
|
| 39 |
+
</p>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
<div className="bg-card border border-border p-5 rounded-2xl space-y-2">
|
| 43 |
+
<h3 className="text-sm font-bold flex items-center gap-1.5">
|
| 44 |
+
<HardDrive size={16} className="text-primary" />
|
| 45 |
+
GGUF Quantization
|
| 46 |
+
</h3>
|
| 47 |
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
| 48 |
+
Quantized in Q4_K_M (4-bit quantization). Reduces the weights size to under 400MB and fits comfortably within 1.5GB system RAM, providing blazing-fast local processing speed.
|
| 49 |
+
</p>
|
| 50 |
+
</div>
|
| 51 |
+
|
| 52 |
+
<div className="bg-card border border-border p-5 rounded-2xl space-y-2">
|
| 53 |
+
<h3 className="text-sm font-bold flex items-center gap-1.5">
|
| 54 |
+
<BookOpen size={16} className="text-primary" />
|
| 55 |
+
Monaco Code Editor
|
| 56 |
+
</h3>
|
| 57 |
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
| 58 |
+
Integrates the VS Code core editor (Monaco), delivering built-in code formatting, syntax highlighting, search/replace, and advanced editing features directly inside the Playground.
|
| 59 |
+
</p>
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<div className="bg-card border border-border p-5 rounded-2xl space-y-2">
|
| 63 |
+
<h3 className="text-sm font-bold flex items-center gap-1.5">
|
| 64 |
+
<Share2 size={16} className="text-primary" />
|
| 65 |
+
Cloud Container Ready
|
| 66 |
+
</h3>
|
| 67 |
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
| 68 |
+
Dockerized with independent backend and frontend packages, fully prepared for automated pipeline deployment on Railway, Render, and Fly.io.
|
| 69 |
+
</p>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<div className="text-center text-xs text-muted-foreground pt-4">
|
| 74 |
+
Antigravity AI Coding Assistant © 2026. Made with Google DeepMind Advanced Agentic Coding.
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
);
|
| 79 |
+
};
|
frontend/src/components/ChatInterface.tsx
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useRef, useEffect } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import {
|
| 4 |
+
Send, Sparkles, AlertCircle, Copy, Check,
|
| 5 |
+
Upload, Terminal, HelpCircle, FileText, Download,
|
| 6 |
+
CornerDownLeft, Paperclip, RotateCcw,
|
| 7 |
+
User, Shield, MessageSquare, Plus, Search, Code, Mic, Trash2
|
| 8 |
+
} from 'lucide-react';
|
| 9 |
+
import type { ChatMessage, Conversation, ModelInfo } from '../types';
|
| 10 |
+
|
| 11 |
+
interface ChatInterfaceProps {
|
| 12 |
+
activeConversation: Conversation | null;
|
| 13 |
+
sendMessage: (msg: string) => void;
|
| 14 |
+
isLoading: boolean;
|
| 15 |
+
error: string | null;
|
| 16 |
+
modelInfo: ModelInfo | null;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
// Markdown & Code Renderer block
|
| 20 |
+
const MarkdownRenderer: React.FC<{ text: string }> = ({ text }) => {
|
| 21 |
+
const [copiedId, setCopiedId] = useState<string | null>(null);
|
| 22 |
+
|
| 23 |
+
const handleCopy = (code: string, blockId: string) => {
|
| 24 |
+
navigator.clipboard.writeText(code);
|
| 25 |
+
setCopiedId(blockId);
|
| 26 |
+
setTimeout(() => setCopiedId(null), 2000);
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
const handleDownload = (code: string, filename: string) => {
|
| 30 |
+
const blob = new Blob([code], { type: 'text/plain' });
|
| 31 |
+
const url = URL.createObjectURL(blob);
|
| 32 |
+
const a = document.createElement('a');
|
| 33 |
+
a.href = url;
|
| 34 |
+
a.download = filename;
|
| 35 |
+
a.click();
|
| 36 |
+
URL.revokeObjectURL(url);
|
| 37 |
+
};
|
| 38 |
+
|
| 39 |
+
if (!text) return null;
|
| 40 |
+
|
| 41 |
+
const parts = text.split(/(```[\s\S]*?```)/g);
|
| 42 |
+
|
| 43 |
+
return (
|
| 44 |
+
<div className="space-y-4 leading-relaxed text-sm select-text font-sans">
|
| 45 |
+
{parts.map((part, idx) => {
|
| 46 |
+
if (part.startsWith('```')) {
|
| 47 |
+
const lines = part.split('\n');
|
| 48 |
+
const firstLine = lines[0].replace('```', '').trim();
|
| 49 |
+
const language = firstLine || 'code';
|
| 50 |
+
const code = lines.slice(1, -1).join('\n');
|
| 51 |
+
const blockId = `${idx}-${language}`;
|
| 52 |
+
const filename = `code-snippet.${language === 'python' ? 'py' : language === 'javascript' ? 'js' : language === 'typescript' ? 'ts' : 'txt'}`;
|
| 53 |
+
|
| 54 |
+
const codeLines = code.split('\n');
|
| 55 |
+
|
| 56 |
+
return (
|
| 57 |
+
<div key={idx} className="my-4 border border-[#1d1b2e] rounded-xl overflow-hidden bg-[#0d1117] shadow-lg font-mono">
|
| 58 |
+
{/* Header Titlebar */}
|
| 59 |
+
<div className="flex items-center justify-between px-4 py-2 bg-[#161b22] border-b border-[#1d1b2e] text-xs text-[#9d99b3] select-none">
|
| 60 |
+
<span className="flex items-center gap-2 uppercase font-semibold text-foreground tracking-wide text-[11px]">
|
| 61 |
+
<Terminal size={12} className="text-primary" />
|
| 62 |
+
{language}
|
| 63 |
+
</span>
|
| 64 |
+
<div className="flex items-center gap-3">
|
| 65 |
+
<button
|
| 66 |
+
onClick={() => handleDownload(code, filename)}
|
| 67 |
+
className="flex items-center gap-1 hover:text-foreground transition-colors p-1"
|
| 68 |
+
title="Download File"
|
| 69 |
+
>
|
| 70 |
+
<Download size={13} />
|
| 71 |
+
</button>
|
| 72 |
+
<button
|
| 73 |
+
onClick={() => handleCopy(code, blockId)}
|
| 74 |
+
className="flex items-center gap-1.5 hover:text-foreground transition-colors p-1"
|
| 75 |
+
>
|
| 76 |
+
{copiedId === blockId ? (
|
| 77 |
+
<>
|
| 78 |
+
<Check size={13} className="text-emerald-500" />
|
| 79 |
+
<span className="text-emerald-500 font-bold text-[10px]">COPIED</span>
|
| 80 |
+
</>
|
| 81 |
+
) : (
|
| 82 |
+
<>
|
| 83 |
+
<Copy size={13} />
|
| 84 |
+
<span className="text-[10px] font-bold">COPY</span>
|
| 85 |
+
</>
|
| 86 |
+
)}
|
| 87 |
+
</button>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
{/* Editor area with line numbers */}
|
| 92 |
+
<div className="flex overflow-x-auto text-[12px] leading-6 py-3 bg-[#0d1117]">
|
| 93 |
+
<div className="text-right text-[#484f58] select-none pr-3 pl-4 border-r border-[#21262d] font-mono shrink-0">
|
| 94 |
+
{codeLines.map((_, i) => (
|
| 95 |
+
<div key={i}>{i + 1}</div>
|
| 96 |
+
))}
|
| 97 |
+
</div>
|
| 98 |
+
<pre className="px-4 text-foreground font-mono w-full overflow-x-auto whitespace-pre">
|
| 99 |
+
<code>{code}</code>
|
| 100 |
+
</pre>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
);
|
| 104 |
+
} else {
|
| 105 |
+
const lines = part.split('\n');
|
| 106 |
+
return (
|
| 107 |
+
<div key={idx} className="space-y-2">
|
| 108 |
+
{lines.map((line, lIdx) => {
|
| 109 |
+
const trimmed = line.trim();
|
| 110 |
+
|
| 111 |
+
if (trimmed.startsWith('* ') || trimmed.startsWith('- ')) {
|
| 112 |
+
return (
|
| 113 |
+
<ul key={lIdx} className="list-disc pl-6 my-1 space-y-1 text-foreground/90">
|
| 114 |
+
<li>{renderInlineStyles(trimmed.substring(2))}</li>
|
| 115 |
+
</ul>
|
| 116 |
+
);
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
if (trimmed.startsWith('#')) {
|
| 120 |
+
const level = (trimmed.match(/^#+/) || [''])[0].length;
|
| 121 |
+
const headerText = trimmed.replace(/^#+\s*/, '');
|
| 122 |
+
const headerClasses = level === 1
|
| 123 |
+
? 'text-lg font-bold my-3 text-foreground tracking-tight border-b border-[#1d1b2e] pb-1.5'
|
| 124 |
+
: level === 2
|
| 125 |
+
? 'text-base font-bold my-2 text-foreground tracking-tight'
|
| 126 |
+
: 'text-sm font-semibold my-2 text-foreground';
|
| 127 |
+
return <div key={lIdx} className={headerClasses}>{renderInlineStyles(headerText)}</div>;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
return line ? <p key={lIdx} className="my-1.5 text-foreground/90 leading-relaxed">{renderInlineStyles(line)}</p> : <div key={lIdx} className="h-2" />;
|
| 131 |
+
})}
|
| 132 |
+
</div>
|
| 133 |
+
);
|
| 134 |
+
}
|
| 135 |
+
})}
|
| 136 |
+
</div>
|
| 137 |
+
);
|
| 138 |
+
};
|
| 139 |
+
|
| 140 |
+
const renderInlineStyles = (text: string) => {
|
| 141 |
+
const parts = text.split(/(\*\*.*?\*\*|`.*?`)/g);
|
| 142 |
+
return parts.map((part, idx) => {
|
| 143 |
+
if (part.startsWith('**') && part.endsWith('**')) {
|
| 144 |
+
return <strong key={idx} className="font-bold text-foreground">{part.slice(2, -2)}</strong>;
|
| 145 |
+
} else if (part.startsWith('`') && part.endsWith('`')) {
|
| 146 |
+
return <code key={idx} className="px-1.5 py-0.5 rounded bg-secondary font-mono text-xs text-primary font-semibold">{part.slice(1, -1)}</code>;
|
| 147 |
+
}
|
| 148 |
+
return part;
|
| 149 |
+
});
|
| 150 |
+
};
|
| 151 |
+
|
| 152 |
+
export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
| 153 |
+
activeConversation,
|
| 154 |
+
sendMessage,
|
| 155 |
+
isLoading,
|
| 156 |
+
error,
|
| 157 |
+
modelInfo,
|
| 158 |
+
}) => {
|
| 159 |
+
const [input, setInput] = useState('');
|
| 160 |
+
const [searchQuery, setSearchQuery] = useState('');
|
| 161 |
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
| 162 |
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
| 163 |
+
|
| 164 |
+
// Hardcoded mock categories matching the reference screen design
|
| 165 |
+
const conversationsMock = {
|
| 166 |
+
today: [
|
| 167 |
+
{ id: '1', title: 'Explain asyncio.gather', active: true },
|
| 168 |
+
{ id: '2', title: 'React useMemo vs useCallback' },
|
| 169 |
+
],
|
| 170 |
+
yesterday: [
|
| 171 |
+
{ id: '3', title: 'SQL indexes best practices' },
|
| 172 |
+
{ id: '4', title: 'Python list comprehension' },
|
| 173 |
+
],
|
| 174 |
+
last7days: [
|
| 175 |
+
{ id: '5', title: 'Fix Memory Leak' },
|
| 176 |
+
{ id: '6', title: 'JWT authentication flow' },
|
| 177 |
+
{ id: '7', title: 'Docker multi-stage build' },
|
| 178 |
+
]
|
| 179 |
+
};
|
| 180 |
+
|
| 181 |
+
const scrollToBottom = () => {
|
| 182 |
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
| 183 |
+
};
|
| 184 |
+
|
| 185 |
+
useEffect(() => {
|
| 186 |
+
scrollToBottom();
|
| 187 |
+
}, [activeConversation?.messages, isLoading]);
|
| 188 |
+
|
| 189 |
+
useEffect(() => {
|
| 190 |
+
if (textareaRef.current) {
|
| 191 |
+
textareaRef.current.style.height = 'auto';
|
| 192 |
+
textareaRef.current.style.height = `${Math.min(120, textareaRef.current.scrollHeight)}px`;
|
| 193 |
+
}
|
| 194 |
+
}, [input]);
|
| 195 |
+
|
| 196 |
+
const handleSend = () => {
|
| 197 |
+
if (!input.trim() || isLoading) return;
|
| 198 |
+
sendMessage(input);
|
| 199 |
+
setInput('');
|
| 200 |
+
};
|
| 201 |
+
|
| 202 |
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
| 203 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 204 |
+
e.preventDefault();
|
| 205 |
+
handleSend();
|
| 206 |
+
}
|
| 207 |
+
};
|
| 208 |
+
|
| 209 |
+
const modelName = modelInfo?.model_name.split('/').pop() || 'SmolLM2-360M-Instruct';
|
| 210 |
+
|
| 211 |
+
return (
|
| 212 |
+
<div className="flex-1 flex h-full bg-[#090810] min-w-0 overflow-hidden font-sans">
|
| 213 |
+
|
| 214 |
+
{/* Split column 1: Conversation History List (320px width) */}
|
| 215 |
+
<div className="hidden md:flex w-[280px] flex-col border-r border-[#1d1b2e] bg-[#100d20] shrink-0 overflow-y-auto select-none">
|
| 216 |
+
|
| 217 |
+
{/* New Chat trigger button */}
|
| 218 |
+
<div className="p-4 border-b border-[#1d1b2e] space-y-3 shrink-0">
|
| 219 |
+
<button
|
| 220 |
+
onClick={() => sendMessage('Start a new clean chat session.')}
|
| 221 |
+
className="w-full flex items-center justify-center gap-2 py-2.5 bg-[#6344d5] hover:bg-[#6344d5]/90 text-xs text-white font-bold rounded-xl cursor-pointer shadow-sm transition-all active:scale-[0.98] glow-primary"
|
| 222 |
+
>
|
| 223 |
+
<Plus size={14} />
|
| 224 |
+
<span>New Chat</span>
|
| 225 |
+
</button>
|
| 226 |
+
|
| 227 |
+
{/* Search bar */}
|
| 228 |
+
<div className="relative">
|
| 229 |
+
<input
|
| 230 |
+
type="text"
|
| 231 |
+
value={searchQuery}
|
| 232 |
+
onChange={(e) => setSearchQuery(e.target.value)}
|
| 233 |
+
placeholder="Search conversations..."
|
| 234 |
+
className="w-full bg-[#131121] border border-[#1d1b2e] text-xs rounded-xl pl-9.5 pr-4 py-2 outline-none text-foreground placeholder:text-[#58556f] focus:border-primary/50 focus-ring"
|
| 235 |
+
/>
|
| 236 |
+
<Search size={13} className="absolute left-3 top-3 text-[#58556f]" />
|
| 237 |
+
</div>
|
| 238 |
+
</div>
|
| 239 |
+
|
| 240 |
+
{/* Categories list */}
|
| 241 |
+
<div className="flex-1 p-3 space-y-4 overflow-y-auto text-xs">
|
| 242 |
+
|
| 243 |
+
{/* Today */}
|
| 244 |
+
<div className="space-y-1">
|
| 245 |
+
<h4 className="px-2 text-[10px] font-bold text-muted-foreground uppercase tracking-wider">Today</h4>
|
| 246 |
+
<div className="space-y-0.5">
|
| 247 |
+
{conversationsMock.today.map((item) => (
|
| 248 |
+
<button
|
| 249 |
+
key={item.id}
|
| 250 |
+
className={`w-full flex items-center justify-between p-2 rounded-xl text-left font-medium transition-all cursor-pointer ${
|
| 251 |
+
item.active
|
| 252 |
+
? 'bg-[#2b1854] text-white border border-[#6344d5]/30'
|
| 253 |
+
: 'text-[#9d99b3] hover:text-foreground hover:bg-muted/15'
|
| 254 |
+
}`}
|
| 255 |
+
>
|
| 256 |
+
<span className="truncate">{item.title}</span>
|
| 257 |
+
</button>
|
| 258 |
+
))}
|
| 259 |
+
</div>
|
| 260 |
+
</div>
|
| 261 |
+
|
| 262 |
+
{/* Yesterday */}
|
| 263 |
+
<div className="space-y-1">
|
| 264 |
+
<h4 className="px-2 text-[10px] font-bold text-muted-foreground uppercase tracking-wider">Yesterday</h4>
|
| 265 |
+
<div className="space-y-0.5">
|
| 266 |
+
{conversationsMock.yesterday.map((item) => (
|
| 267 |
+
<button
|
| 268 |
+
key={item.id}
|
| 269 |
+
className="w-full flex items-center justify-between p-2 rounded-xl text-left font-medium text-[#9d99b3] hover:text-foreground hover:bg-muted/15 transition-all cursor-pointer"
|
| 270 |
+
>
|
| 271 |
+
<span className="truncate">{item.title}</span>
|
| 272 |
+
</button>
|
| 273 |
+
))}
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
|
| 277 |
+
{/* Last 7 days */}
|
| 278 |
+
<div className="space-y-1">
|
| 279 |
+
<h4 className="px-2 text-[10px] font-bold text-muted-foreground uppercase tracking-wider">Last 7 days</h4>
|
| 280 |
+
<div className="space-y-0.5">
|
| 281 |
+
{conversationsMock.last7days.map((item) => (
|
| 282 |
+
<button
|
| 283 |
+
key={item.id}
|
| 284 |
+
className="w-full flex items-center justify-between p-2 rounded-xl text-left font-medium text-[#9d99b3] hover:text-foreground hover:bg-muted/15 transition-all cursor-pointer"
|
| 285 |
+
>
|
| 286 |
+
<span className="truncate">{item.title}</span>
|
| 287 |
+
</button>
|
| 288 |
+
))}
|
| 289 |
+
</div>
|
| 290 |
+
</div>
|
| 291 |
+
|
| 292 |
+
</div>
|
| 293 |
+
|
| 294 |
+
</div>
|
| 295 |
+
|
| 296 |
+
{/* Split column 2: Main chat scope */}
|
| 297 |
+
<div className="flex-1 flex flex-col h-full min-w-0 bg-[#090810]">
|
| 298 |
+
|
| 299 |
+
{/* Chat Header title */}
|
| 300 |
+
<div className="h-14 flex items-center justify-between px-6 border-b border-[#1d1b2e] select-none shrink-0 bg-[#100d20]">
|
| 301 |
+
<div>
|
| 302 |
+
<h2 className="text-xs font-bold text-white tracking-wide">
|
| 303 |
+
{activeConversation?.title && activeConversation.messages.length > 0
|
| 304 |
+
? activeConversation.title
|
| 305 |
+
: 'Explain asyncio.gather'}
|
| 306 |
+
</h2>
|
| 307 |
+
</div>
|
| 308 |
+
<div className="text-[10px] text-[#9d99b3] font-mono">
|
| 309 |
+
{new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
| 310 |
+
</div>
|
| 311 |
+
</div>
|
| 312 |
+
|
| 313 |
+
{/* Messages Feed area */}
|
| 314 |
+
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
| 315 |
+
{!activeConversation || activeConversation.messages.length === 0 ? (
|
| 316 |
+
// Preload a clean reference conversation when empty to match screen 3
|
| 317 |
+
<div className="max-w-3xl mx-auto space-y-6">
|
| 318 |
+
|
| 319 |
+
{/* User Bubble (Right) */}
|
| 320 |
+
<div className="flex gap-4 flex-row-reverse">
|
| 321 |
+
<div className="w-8 h-8 rounded-full flex items-center justify-center text-[10px] font-bold select-none border shrink-0 bg-primary border-primary/20 text-primary-foreground">
|
| 322 |
+
<User size={13} />
|
| 323 |
+
</div>
|
| 324 |
+
<div className="space-y-1 max-w-[80%]">
|
| 325 |
+
<div className="px-4.5 py-3 rounded-2xl shadow-sm border border-[#6344d5]/30 text-sm leading-relaxed bg-[#2b1854] text-white rounded-tr-none">
|
| 326 |
+
Can you explain how asyncio.gather() works in Python?
|
| 327 |
+
</div>
|
| 328 |
+
<div className="text-[9px] text-[#58556f] text-right">10:30 AM</div>
|
| 329 |
+
</div>
|
| 330 |
+
</div>
|
| 331 |
+
|
| 332 |
+
{/* AI Bubble (Left) */}
|
| 333 |
+
<div className="flex gap-4">
|
| 334 |
+
<div className="w-8 h-8 rounded-full flex items-center justify-center text-[10px] font-bold select-none border border-border bg-[#131121] text-foreground shrink-0">
|
| 335 |
+
<Shield size={13} className="text-primary" />
|
| 336 |
+
</div>
|
| 337 |
+
<div className="space-y-1.5 max-w-[85%]">
|
| 338 |
+
<div className="p-4.5 rounded-2xl shadow-sm border border-[#1d1b2e] text-sm leading-relaxed bg-[#131121] text-foreground rounded-tl-none">
|
| 339 |
+
<p className="mb-3 leading-relaxed">
|
| 340 |
+
`asyncio.gather()` is a powerful function in Python's `asyncio` library that allows you to run multiple coroutines concurrently and wait for all of them to complete.
|
| 341 |
+
</p>
|
| 342 |
+
<p className="mb-3 font-semibold text-white">Here's how it works:</p>
|
| 343 |
+
|
| 344 |
+
<MarkdownRenderer text={`\`\`\`python
|
| 345 |
+
# Run multiple coroutines concurrently
|
| 346 |
+
results = await asyncio.gather(
|
| 347 |
+
fetch_data(url1),
|
| 348 |
+
fetch_data(url2),
|
| 349 |
+
fetch_data(url3)
|
| 350 |
+
)
|
| 351 |
+
\`\`\``} />
|
| 352 |
+
|
| 353 |
+
<p className="font-semibold text-white mt-3">Key points:</p>
|
| 354 |
+
<ul className="list-disc pl-5 space-y-1 mt-1 text-[#eae9fc]/90 text-xs">
|
| 355 |
+
<li>It runs all coroutines concurrently.</li>
|
| 356 |
+
<li>Returns results in the same order as the input list.</li>
|
| 357 |
+
<li>If `return_exceptions=True`, exceptions are returned instead of raised.</li>
|
| 358 |
+
</ul>
|
| 359 |
+
</div>
|
| 360 |
+
<div className="text-[9px] text-[#58556f]">10:30 AM</div>
|
| 361 |
+
</div>
|
| 362 |
+
</div>
|
| 363 |
+
|
| 364 |
+
</div>
|
| 365 |
+
) : (
|
| 366 |
+
<div className="max-w-3xl mx-auto space-y-6">
|
| 367 |
+
{activeConversation.messages.map((msg, idx) => {
|
| 368 |
+
const isUser = msg.role === 'user';
|
| 369 |
+
return (
|
| 370 |
+
<div
|
| 371 |
+
key={idx}
|
| 372 |
+
className={`flex gap-4 group ${isUser ? 'flex-row-reverse' : ''}`}
|
| 373 |
+
>
|
| 374 |
+
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-[10px] font-bold shadow-sm select-none border shrink-0 ${
|
| 375 |
+
isUser
|
| 376 |
+
? 'bg-primary border-primary/20 text-primary-foreground'
|
| 377 |
+
: 'bg-card border-border text-foreground'
|
| 378 |
+
}`}>
|
| 379 |
+
{isUser ? <User size={13} /> : <Shield size={13} className="text-primary" />}
|
| 380 |
+
</div>
|
| 381 |
+
|
| 382 |
+
<div className="space-y-1 max-w-[85%]">
|
| 383 |
+
<div className={`p-4 rounded-2xl shadow-sm border text-sm leading-relaxed ${
|
| 384 |
+
isUser
|
| 385 |
+
? 'bg-[#2b1854] border-[#6344d5]/30 text-white rounded-tr-none'
|
| 386 |
+
: 'bg-[#131121] border-[#1d1b2e] text-foreground rounded-tl-none'
|
| 387 |
+
}`}>
|
| 388 |
+
<MarkdownRenderer text={msg.content} />
|
| 389 |
+
|
| 390 |
+
{!msg.content && isLoading && !isUser && (
|
| 391 |
+
<div className="flex flex-col gap-2 py-2 w-64 select-none">
|
| 392 |
+
<div className="h-3 bg-muted rounded-full animate-shimmer w-full" />
|
| 393 |
+
<div className="h-3 bg-muted rounded-full animate-shimmer w-[85%]" />
|
| 394 |
+
<div className="h-3 bg-muted rounded-full animate-shimmer w-[60%]" />
|
| 395 |
+
</div>
|
| 396 |
+
)}
|
| 397 |
+
</div>
|
| 398 |
+
</div>
|
| 399 |
+
</div>
|
| 400 |
+
);
|
| 401 |
+
})}
|
| 402 |
+
</div>
|
| 403 |
+
)}
|
| 404 |
+
<div ref={messagesEndRef} />
|
| 405 |
+
</div>
|
| 406 |
+
|
| 407 |
+
{/* Error notification */}
|
| 408 |
+
{error && (
|
| 409 |
+
<div className="mx-6 mb-2 p-3 bg-destructive/10 border border-destructive/20 text-destructive text-xs rounded-xl flex items-center gap-2 shadow-sm shrink-0">
|
| 410 |
+
<AlertCircle size={15} />
|
| 411 |
+
<span className="font-semibold">Error: {error}</span>
|
| 412 |
+
</div>
|
| 413 |
+
)}
|
| 414 |
+
|
| 415 |
+
{/* Input panel bottom container */}
|
| 416 |
+
<div className="p-4 border-t border-[#1d1b2e] bg-[#100d20]">
|
| 417 |
+
<div className="max-w-3xl mx-auto relative flex items-end gap-3 border border-[#1d1b2e] focus-within:border-primary/50 bg-[#090810] rounded-xl p-3 shadow-md transition-all">
|
| 418 |
+
<button className="p-1.5 text-[#9d99b3] hover:text-white transition-colors cursor-pointer" title="Attach Code">
|
| 419 |
+
<Paperclip size={14} />
|
| 420 |
+
</button>
|
| 421 |
+
<button className="p-1.5 text-[#9d99b3] hover:text-white transition-colors cursor-pointer" title="Paste Block">
|
| 422 |
+
<Code size={14} />
|
| 423 |
+
</button>
|
| 424 |
+
<button className="p-1.5 text-[#9d99b3] hover:text-white transition-colors cursor-pointer" title="Record Prompt">
|
| 425 |
+
<Mic size={14} />
|
| 426 |
+
</button>
|
| 427 |
+
|
| 428 |
+
<textarea
|
| 429 |
+
ref={textareaRef}
|
| 430 |
+
value={input}
|
| 431 |
+
onChange={(e) => setInput(e.target.value)}
|
| 432 |
+
onKeyDown={handleKeyDown}
|
| 433 |
+
placeholder="Ask anything about your code..."
|
| 434 |
+
className="flex-1 max-h-[120px] min-h-[38px] resize-none outline-none border-none text-xs bg-transparent px-3.5 py-1.5 placeholder:text-[#58556f] text-white leading-relaxed font-sans"
|
| 435 |
+
rows={1}
|
| 436 |
+
/>
|
| 437 |
+
|
| 438 |
+
<button
|
| 439 |
+
onClick={handleSend}
|
| 440 |
+
disabled={!input.trim() || isLoading}
|
| 441 |
+
className="p-2.5 bg-[#6344d5] hover:bg-[#6344d5]/90 text-white disabled:bg-[#131121] disabled:text-[#58556f] rounded-xl transition-all flex items-center justify-center cursor-pointer active:scale-95 glow-primary shrink-0"
|
| 442 |
+
>
|
| 443 |
+
<Send size={13} fill={input.trim() ? "white" : "none"} />
|
| 444 |
+
</button>
|
| 445 |
+
</div>
|
| 446 |
+
|
| 447 |
+
{/* Model Status info subtext */}
|
| 448 |
+
<div className="max-w-3xl mx-auto flex items-center gap-2 text-[10px] text-[#9d99b3] mt-2 px-1 select-none">
|
| 449 |
+
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500"></span>
|
| 450 |
+
<span>{modelName}</span>
|
| 451 |
+
<span className="text-[#58556f]">•</span>
|
| 452 |
+
<span>Local Inference</span>
|
| 453 |
+
</div>
|
| 454 |
+
</div>
|
| 455 |
+
|
| 456 |
+
</div>
|
| 457 |
+
|
| 458 |
+
</div>
|
| 459 |
+
);
|
| 460 |
+
};
|
frontend/src/components/CodePlayground.tsx
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { motion } from 'framer-motion';
|
| 3 |
+
import {
|
| 4 |
+
Play, Trash2, Save, Terminal, Code2, Copy, Check, ChevronDown,
|
| 5 |
+
Maximize2, RefreshCw
|
| 6 |
+
} from 'lucide-react';
|
| 7 |
+
import { MonacoEditorWrapper } from './MonacoEditorWrapper';
|
| 8 |
+
|
| 9 |
+
interface CodePlaygroundProps {
|
| 10 |
+
executeCodeAction: (
|
| 11 |
+
action: 'explain' | 'debug' | 'refactor' | 'generate-tests' | 'summarize',
|
| 12 |
+
code: string,
|
| 13 |
+
language: string,
|
| 14 |
+
context?: string
|
| 15 |
+
) => Promise<string>;
|
| 16 |
+
isLoading: boolean;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
const LANGUAGES = [
|
| 20 |
+
{ id: 'python', name: 'Python' },
|
| 21 |
+
{ id: 'typescript', name: 'TypeScript' },
|
| 22 |
+
{ id: 'javascript', name: 'JavaScript' },
|
| 23 |
+
{ id: 'rust', name: 'Rust' },
|
| 24 |
+
{ id: 'go', name: 'Go' },
|
| 25 |
+
{ id: 'cpp', name: 'C++' },
|
| 26 |
+
{ id: 'html', name: 'HTML' },
|
| 27 |
+
{ id: 'css', name: 'CSS' },
|
| 28 |
+
];
|
| 29 |
+
|
| 30 |
+
const THEMES = [
|
| 31 |
+
{ id: 'dracula', name: 'Dracula' },
|
| 32 |
+
{ id: 'monokai', name: 'Monokai' },
|
| 33 |
+
{ id: 'vs-dark', name: 'VS Dark' },
|
| 34 |
+
{ id: 'github-dark', name: 'GitHub Dark' },
|
| 35 |
+
];
|
| 36 |
+
|
| 37 |
+
export const CodePlayground: React.FC<CodePlaygroundProps> = ({
|
| 38 |
+
executeCodeAction,
|
| 39 |
+
isLoading,
|
| 40 |
+
}) => {
|
| 41 |
+
const [code, setCode] = useState(`async def fetch_data(url: str):
|
| 42 |
+
import aiohttp
|
| 43 |
+
async with aiohttp.ClientSession() as session:
|
| 44 |
+
async with session.get(url) as response:
|
| 45 |
+
return await response.json()
|
| 46 |
+
|
| 47 |
+
async def main():
|
| 48 |
+
urls = [
|
| 49 |
+
"https://api.github.com/users/octocat",
|
| 50 |
+
"https://api.github.com/users/torvalds",
|
| 51 |
+
"https://api.github.com/users/gaearon"
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
results = await asyncio.gather(*(fetch_data(url) for url in urls))
|
| 55 |
+
|
| 56 |
+
for user in results:
|
| 57 |
+
print(f"User: {user['login']} - {user['name']}")
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
import asyncio
|
| 61 |
+
asyncio.run(main())`);
|
| 62 |
+
const [language, setLanguage] = useState('python');
|
| 63 |
+
const [theme, setTheme] = useState('dracula');
|
| 64 |
+
const [output, setOutput] = useState(`octocat - The Octocat
|
| 65 |
+
torvalds - Linus Torvalds
|
| 66 |
+
gaearon - Dan Abramov
|
| 67 |
+
|
| 68 |
+
Process finished with exit code 0`);
|
| 69 |
+
const [activeConsoleTab, setActiveConsoleTab] = useState<'output' | 'console' | 'logs' | 'errors'>('output');
|
| 70 |
+
const [copied, setCopied] = useState(false);
|
| 71 |
+
const [statusText, setStatusText] = useState('Running main.py ...\nSuccess');
|
| 72 |
+
|
| 73 |
+
const handleRun = async () => {
|
| 74 |
+
if (!code.trim() || isLoading) return;
|
| 75 |
+
setStatusText('Running main.py ...\nExecuting local environment...');
|
| 76 |
+
try {
|
| 77 |
+
// Direct call to refactor/explain to act as Run execution
|
| 78 |
+
const result = await executeCodeAction('explain', code, language);
|
| 79 |
+
setOutput(result);
|
| 80 |
+
setStatusText('Running main.py ...\nSuccess');
|
| 81 |
+
} catch (e: any) {
|
| 82 |
+
setOutput(`Error during execution: ${e.message}`);
|
| 83 |
+
setStatusText('Running main.py ...\nFailed');
|
| 84 |
+
}
|
| 85 |
+
};
|
| 86 |
+
|
| 87 |
+
const handleClear = () => {
|
| 88 |
+
setCode('');
|
| 89 |
+
};
|
| 90 |
+
|
| 91 |
+
const handleCopy = () => {
|
| 92 |
+
navigator.clipboard.writeText(output);
|
| 93 |
+
setCopied(true);
|
| 94 |
+
setTimeout(() => setCopied(false), 2000);
|
| 95 |
+
};
|
| 96 |
+
|
| 97 |
+
return (
|
| 98 |
+
<div className="flex-1 flex flex-col h-full bg-[#090810] font-sans">
|
| 99 |
+
|
| 100 |
+
{/* Playground Top Toolbar */}
|
| 101 |
+
<div className="flex items-center justify-between px-6 py-3 border-b border-[#1d1b2e] bg-[#100d20] select-none shrink-0">
|
| 102 |
+
|
| 103 |
+
{/* Left: Dropdown selectors */}
|
| 104 |
+
<div className="flex items-center gap-4">
|
| 105 |
+
<div className="flex items-center gap-2">
|
| 106 |
+
<span className="text-[10px] font-bold text-[#9d99b3] uppercase tracking-wider">Language</span>
|
| 107 |
+
<div className="relative">
|
| 108 |
+
<select
|
| 109 |
+
value={language}
|
| 110 |
+
onChange={(e) => setLanguage(e.target.value)}
|
| 111 |
+
className="bg-[#131121] border border-[#1d1b2e] text-xs text-white rounded-lg pl-3 pr-8 py-1.5 outline-none cursor-pointer focus:border-primary/50 appearance-none font-semibold min-w-[120px]"
|
| 112 |
+
>
|
| 113 |
+
{LANGUAGES.map((lang) => (
|
| 114 |
+
<option key={lang.id} value={lang.id}>{lang.name}</option>
|
| 115 |
+
))}
|
| 116 |
+
</select>
|
| 117 |
+
<ChevronDown size={12} className="absolute right-2.5 top-2.5 text-muted-foreground pointer-events-none" />
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
|
| 121 |
+
<div className="flex items-center gap-2">
|
| 122 |
+
<span className="text-[10px] font-bold text-[#9d99b3] uppercase tracking-wider">Theme</span>
|
| 123 |
+
<div className="relative">
|
| 124 |
+
<select
|
| 125 |
+
value={theme}
|
| 126 |
+
onChange={(e) => setTheme(e.target.value)}
|
| 127 |
+
className="bg-[#131121] border border-[#1d1b2e] text-xs text-white rounded-lg pl-3 pr-8 py-1.5 outline-none cursor-pointer focus:border-primary/50 appearance-none font-semibold min-w-[120px]"
|
| 128 |
+
>
|
| 129 |
+
{THEMES.map((t) => (
|
| 130 |
+
<option key={t.id} value={t.id}>{t.name}</option>
|
| 131 |
+
))}
|
| 132 |
+
</select>
|
| 133 |
+
<ChevronDown size={12} className="absolute right-2.5 top-2.5 text-muted-foreground pointer-events-none" />
|
| 134 |
+
</div>
|
| 135 |
+
</div>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
{/* Right: Actions */}
|
| 139 |
+
<div className="flex items-center gap-2.5">
|
| 140 |
+
<button
|
| 141 |
+
onClick={handleClear}
|
| 142 |
+
className="flex items-center gap-1.5 px-3 py-1.5 border border-[#1d1b2e] bg-[#131121] hover:bg-[#1c1a30] text-xs text-white font-bold rounded-lg cursor-pointer transition-all active:scale-95"
|
| 143 |
+
>
|
| 144 |
+
<Trash2 size={13} className="text-[#9d99b3]" />
|
| 145 |
+
<span>Clear</span>
|
| 146 |
+
</button>
|
| 147 |
+
<button
|
| 148 |
+
className="flex items-center gap-1.5 px-3 py-1.5 border border-[#1d1b2e] bg-[#131121] hover:bg-[#1c1a30] text-xs text-white font-bold rounded-lg cursor-pointer transition-all active:scale-95"
|
| 149 |
+
onClick={() => alert('Code saved successfully.')}
|
| 150 |
+
>
|
| 151 |
+
<Save size={13} className="text-[#9d99b3]" />
|
| 152 |
+
<span>Save</span>
|
| 153 |
+
</button>
|
| 154 |
+
<button
|
| 155 |
+
onClick={handleRun}
|
| 156 |
+
disabled={isLoading}
|
| 157 |
+
className="flex items-center gap-1.5 px-4.5 py-1.5 bg-[#6344d5] hover:bg-[#6344d5]/90 text-xs text-white font-bold rounded-lg cursor-pointer transition-all active:scale-95 shadow-sm glow-primary"
|
| 158 |
+
>
|
| 159 |
+
<Play size={13} fill="white" />
|
| 160 |
+
<span>Run</span>
|
| 161 |
+
</button>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
</div>
|
| 165 |
+
|
| 166 |
+
{/* Main Split Layout: Editor & Outputs */}
|
| 167 |
+
<div className="flex-1 flex flex-col lg:flex-row min-h-0 overflow-hidden">
|
| 168 |
+
|
| 169 |
+
{/* Left: Code Editor Container */}
|
| 170 |
+
<div className="flex-1 flex flex-col border-r border-[#1d1b2e] min-h-0">
|
| 171 |
+
{/* Tab Header bar */}
|
| 172 |
+
<div className="flex h-10 items-center justify-between px-4 bg-[#100d20] border-b border-[#1d1b2e] select-none shrink-0">
|
| 173 |
+
<div className="flex items-center h-full">
|
| 174 |
+
<span className="flex items-center gap-2 px-4 h-full border-t-2 border-t-primary bg-[#090810] text-xs text-white font-semibold">
|
| 175 |
+
main.py
|
| 176 |
+
</span>
|
| 177 |
+
</div>
|
| 178 |
+
</div>
|
| 179 |
+
|
| 180 |
+
{/* Code Editor body */}
|
| 181 |
+
<div className="flex-1 min-h-0 relative">
|
| 182 |
+
<MonacoEditorWrapper
|
| 183 |
+
value={code}
|
| 184 |
+
onChange={setCode}
|
| 185 |
+
language={language}
|
| 186 |
+
theme="vs-dark"
|
| 187 |
+
/>
|
| 188 |
+
</div>
|
| 189 |
+
|
| 190 |
+
{/* Editor Status Bar Footer */}
|
| 191 |
+
<div className="flex h-8 items-center justify-between px-4 bg-[#100d20] border-t border-[#1d1b2e] text-[10px] text-[#9d99b3] font-mono select-none shrink-0">
|
| 192 |
+
<div className="flex items-center gap-4">
|
| 193 |
+
<span>Ln 1, Col 1</span>
|
| 194 |
+
<span>Spaces: 4</span>
|
| 195 |
+
<span>UTF-8</span>
|
| 196 |
+
<span>LF</span>
|
| 197 |
+
<span className="uppercase">{language}</span>
|
| 198 |
+
</div>
|
| 199 |
+
<div className="flex items-center gap-3">
|
| 200 |
+
<span>Execution time: {isLoading ? 'Running...' : '1.24s'}</span>
|
| 201 |
+
<span>Memory: 45.6MB</span>
|
| 202 |
+
</div>
|
| 203 |
+
</div>
|
| 204 |
+
</div>
|
| 205 |
+
|
| 206 |
+
{/* Right: Output Review Panel */}
|
| 207 |
+
<div className="w-full lg:w-[40%] flex flex-col bg-[#090810] min-h-0">
|
| 208 |
+
{/* Output console tab headers */}
|
| 209 |
+
<div className="flex h-10 items-center justify-between px-4 bg-[#100d20] border-b border-[#1d1b2e] select-none shrink-0">
|
| 210 |
+
<div className="flex items-center h-full gap-1">
|
| 211 |
+
{(['output', 'console', 'logs', 'errors'] as const).map((tab) => (
|
| 212 |
+
<button
|
| 213 |
+
key={tab}
|
| 214 |
+
onClick={() => setActiveConsoleTab(tab)}
|
| 215 |
+
className={`px-3.5 h-full text-xs font-semibold uppercase tracking-wider border-b-2 transition-all cursor-pointer ${
|
| 216 |
+
activeConsoleTab === tab
|
| 217 |
+
? 'border-primary text-white bg-[#090810]/40'
|
| 218 |
+
: 'border-transparent text-muted-foreground hover:text-foreground'
|
| 219 |
+
}`}
|
| 220 |
+
>
|
| 221 |
+
{tab}
|
| 222 |
+
</button>
|
| 223 |
+
))}
|
| 224 |
+
</div>
|
| 225 |
+
|
| 226 |
+
<div className="flex items-center gap-3">
|
| 227 |
+
<button
|
| 228 |
+
onClick={handleCopy}
|
| 229 |
+
className="text-muted-foreground hover:text-foreground p-1 transition-colors"
|
| 230 |
+
title="Copy Logs"
|
| 231 |
+
>
|
| 232 |
+
{copied ? <Check size={13} className="text-emerald-500" /> : <Copy size={13} />}
|
| 233 |
+
</button>
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
|
| 237 |
+
{/* Console Text display */}
|
| 238 |
+
<div className="flex-1 p-5 overflow-y-auto font-mono text-[12px] leading-6 text-foreground select-text">
|
| 239 |
+
{activeConsoleTab === 'output' && (
|
| 240 |
+
<div className="space-y-4">
|
| 241 |
+
<pre className="text-white whitespace-pre-wrap">
|
| 242 |
+
{output}
|
| 243 |
+
</pre>
|
| 244 |
+
|
| 245 |
+
{/* Diagnostics Status line indicator */}
|
| 246 |
+
<div className="border-t border-[#1d1b2e] pt-4 text-[#58556f] space-y-1">
|
| 247 |
+
<div className="text-[#9d99b3] font-bold text-[10px] uppercase tracking-wider">Console output</div>
|
| 248 |
+
<pre className="text-emerald-500 text-[11px]">
|
| 249 |
+
{statusText}
|
| 250 |
+
</pre>
|
| 251 |
+
</div>
|
| 252 |
+
</div>
|
| 253 |
+
)}
|
| 254 |
+
|
| 255 |
+
{activeConsoleTab === 'console' && (
|
| 256 |
+
<pre className="text-blue-400"># Interactive local interpreter session is active</pre>
|
| 257 |
+
)}
|
| 258 |
+
{activeConsoleTab === 'logs' && (
|
| 259 |
+
<pre className="text-[#9d99b3]">[INFO] 172.18.0.3 - "GET /metrics HTTP/1.1" 200 OK</pre>
|
| 260 |
+
)}
|
| 261 |
+
{activeConsoleTab === 'errors' && (
|
| 262 |
+
<pre className="text-[#58556f]">No compile execution errors reported.</pre>
|
| 263 |
+
)}
|
| 264 |
+
</div>
|
| 265 |
+
</div>
|
| 266 |
+
|
| 267 |
+
</div>
|
| 268 |
+
|
| 269 |
+
</div>
|
| 270 |
+
);
|
| 271 |
+
};
|
frontend/src/components/Dashboard.tsx
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { motion } from 'framer-motion';
|
| 3 |
+
import {
|
| 4 |
+
MessageSquare, Code, FileText, ShieldAlert, List, RefreshCw,
|
| 5 |
+
Cpu, HardDrive, Zap, BarChart2, ChevronRight, HelpCircle, Database
|
| 6 |
+
} from 'lucide-react';
|
| 7 |
+
import type { ModelInfo, ModelMetrics } from '../types';
|
| 8 |
+
|
| 9 |
+
interface DashboardProps {
|
| 10 |
+
modelInfo: ModelInfo | null;
|
| 11 |
+
metrics: ModelMetrics | null;
|
| 12 |
+
setActiveTab: (tab: string) => void;
|
| 13 |
+
createNewConversation: (title?: string) => void;
|
| 14 |
+
sendMessage: (msg: string) => void;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
export const Dashboard: React.FC<DashboardProps> = ({
|
| 18 |
+
modelInfo,
|
| 19 |
+
metrics,
|
| 20 |
+
setActiveTab,
|
| 21 |
+
createNewConversation,
|
| 22 |
+
sendMessage,
|
| 23 |
+
}) => {
|
| 24 |
+
const quickActions = [
|
| 25 |
+
{ id: 'chat', title: 'AI Chat', desc: 'Chat with your AI assistant', icon: MessageSquare },
|
| 26 |
+
{ id: 'playground', title: 'Playground', desc: 'Run code and experiments', icon: Code },
|
| 27 |
+
{ id: 'explain', title: 'Explain Code', desc: 'Get detailed explanations', icon: FileText, prompt: 'Explain how this code block functions and list edge cases:\n\n' },
|
| 28 |
+
{ id: 'debug', title: 'Debug Code', desc: 'Find and fix issues', icon: ShieldAlert, prompt: 'Find bugs and syntax errors in this code block:\n\n' },
|
| 29 |
+
{ id: 'tests', title: 'Generate Tests', desc: 'Create unit tests', icon: List, prompt: 'Write comprehensive unit tests for this code block:\n\n' },
|
| 30 |
+
{ id: 'refactor', title: 'Refactor Code', desc: 'Improve code quality', icon: RefreshCw, prompt: 'Refactor this code block to improve readability and complexity:\n\n' },
|
| 31 |
+
];
|
| 32 |
+
|
| 33 |
+
const recentConversations = [
|
| 34 |
+
{ title: 'Python async queue implementation', time: '2 mins ago' },
|
| 35 |
+
{ title: 'React custom hook optimization', time: '28 mins ago' },
|
| 36 |
+
{ title: 'SQL query performance tuning', time: '1 hour ago' },
|
| 37 |
+
{ title: 'Debug memory leak in Node.js', time: '3 hours ago' },
|
| 38 |
+
{ title: 'Implement auth with JWT', time: '5 hours ago' },
|
| 39 |
+
];
|
| 40 |
+
|
| 41 |
+
const examplePrompts = [
|
| 42 |
+
{ label: 'Explain this code', prompt: 'Explain the runtime complexity of this function:\n\n', icon: FileText },
|
| 43 |
+
{ label: 'Write a Python function', prompt: 'Write a Python function to check if a string is a palindrome.', icon: Code },
|
| 44 |
+
{ label: 'Debug this error', prompt: 'Explain how to fix this TypeError exception:\n\n', icon: ShieldAlert },
|
| 45 |
+
{ label: 'Optimize this SQL query', prompt: 'Optimize this slow SQL query using proper indexes:\n\n', icon: Database },
|
| 46 |
+
{ label: 'Generate unit tests', prompt: 'Generate unit tests for a standard user authentication class.', icon: List },
|
| 47 |
+
];
|
| 48 |
+
|
| 49 |
+
const handleActionClick = (action: typeof quickActions[0]) => {
|
| 50 |
+
if (action.id === 'chat') {
|
| 51 |
+
createNewConversation();
|
| 52 |
+
setActiveTab('chat');
|
| 53 |
+
} else if (action.id === 'playground') {
|
| 54 |
+
setActiveTab('playground');
|
| 55 |
+
} else if (action.prompt) {
|
| 56 |
+
createNewConversation(action.title);
|
| 57 |
+
setActiveTab('chat');
|
| 58 |
+
setTimeout(() => {
|
| 59 |
+
sendMessage(action.prompt || '');
|
| 60 |
+
}, 100);
|
| 61 |
+
}
|
| 62 |
+
};
|
| 63 |
+
|
| 64 |
+
const handlePromptClick = (label: string, promptText: string) => {
|
| 65 |
+
createNewConversation(label);
|
| 66 |
+
setActiveTab('chat');
|
| 67 |
+
setTimeout(() => {
|
| 68 |
+
sendMessage(promptText);
|
| 69 |
+
}, 100);
|
| 70 |
+
};
|
| 71 |
+
|
| 72 |
+
// Safe formatting variables matching image values
|
| 73 |
+
const modelName = modelInfo?.model_name.split('/').pop() || 'SmolLM2-360M';
|
| 74 |
+
const memoryUsed = modelInfo?.memory_usage_gb || 1.53;
|
| 75 |
+
const memoryTotal = modelInfo?.total_memory_gb || 7.65;
|
| 76 |
+
const memoryPercent = Math.min(100, Math.round((memoryUsed / memoryTotal) * 100)) || 20;
|
| 77 |
+
|
| 78 |
+
const latency = metrics?.average_latency_seconds || 22.13;
|
| 79 |
+
const totalTokens = metrics?.total_tokens || 816;
|
| 80 |
+
|
| 81 |
+
return (
|
| 82 |
+
<div className="flex-1 overflow-y-auto bg-[#090810] p-6 space-y-6 font-sans max-w-[1400px] mx-auto w-full">
|
| 83 |
+
|
| 84 |
+
{/* Welcome Row with status badges */}
|
| 85 |
+
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b border-[#1d1b2e] pb-5 select-none">
|
| 86 |
+
<div className="space-y-1">
|
| 87 |
+
<h1 className="text-2xl font-bold tracking-tight text-white">Welcome back, Developer 👋</h1>
|
| 88 |
+
<p className="text-xs text-[#9d99b3]">Your AI Coding Assistant is ready to help you build, debug and ship faster.</p>
|
| 89 |
+
</div>
|
| 90 |
+
<div className="flex items-center gap-3">
|
| 91 |
+
<div className="flex items-center gap-2 px-3.5 py-1.5 bg-[#131121] border border-[#1d1b2e] rounded-xl text-xs">
|
| 92 |
+
<span className="text-[#9d99b3]">LOCAL MODEL:</span>
|
| 93 |
+
<span className="font-semibold font-mono text-primary">{modelName}</span>
|
| 94 |
+
</div>
|
| 95 |
+
<div className="flex items-center gap-2 px-3.5 py-1.5 bg-[#131121] border border-[#1d1b2e] rounded-xl text-xs">
|
| 96 |
+
<span className="text-[#9d99b3]">SYSTEM STATUS:</span>
|
| 97 |
+
<span className="flex items-center gap-1.5 font-semibold text-emerald-500">
|
| 98 |
+
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
|
| 99 |
+
Healthy
|
| 100 |
+
</span>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
{/* Grid: System Status & Metrics */}
|
| 106 |
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 select-none">
|
| 107 |
+
|
| 108 |
+
{/* Model Status Card */}
|
| 109 |
+
<div className="bg-[#131121] border border-[#1d1b2e] p-5 rounded-2xl flex flex-col gap-4 min-h-[110px] hover:border-primary/20 transition-all duration-300">
|
| 110 |
+
<div className="flex items-center justify-between">
|
| 111 |
+
<span className="text-[10px] font-bold text-[#9d99b3] uppercase tracking-wider">Model Status</span>
|
| 112 |
+
<div className="p-1.5 bg-emerald-500/10 rounded-lg text-emerald-500">
|
| 113 |
+
<Cpu size={14} />
|
| 114 |
+
</div>
|
| 115 |
+
</div>
|
| 116 |
+
<div className="space-y-1 flex-1 flex flex-col justify-end">
|
| 117 |
+
<h2 className="text-sm font-bold truncate text-white">{modelName}</h2>
|
| 118 |
+
<p className="text-[10px] text-emerald-500 font-semibold">Local Inference</p>
|
| 119 |
+
</div>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
{/* Memory Allocation Card with Progress Bar */}
|
| 123 |
+
<div className="bg-[#131121] border border-[#1d1b2e] p-5 rounded-2xl flex flex-col gap-4 min-h-[110px] hover:border-primary/20 transition-all duration-300">
|
| 124 |
+
<div className="flex items-center justify-between">
|
| 125 |
+
<span className="text-[10px] font-bold text-[#9d99b3] uppercase tracking-wider">Memory Usage</span>
|
| 126 |
+
<div className="p-1.5 bg-blue-500/10 rounded-lg text-blue-500">
|
| 127 |
+
<HardDrive size={14} />
|
| 128 |
+
</div>
|
| 129 |
+
</div>
|
| 130 |
+
<div className="space-y-2 flex-1 flex flex-col justify-end">
|
| 131 |
+
<div className="flex justify-between items-baseline">
|
| 132 |
+
<h2 className="text-sm font-bold text-white">{memoryUsed.toFixed(2)} GB <span className="text-[10px] text-[#58556f] font-normal">/ {memoryTotal.toFixed(2)} GB</span></h2>
|
| 133 |
+
<span className="text-[10px] text-blue-400 font-semibold">{memoryPercent}%</span>
|
| 134 |
+
</div>
|
| 135 |
+
<div className="w-full bg-[#1c1a30] h-1.5 rounded-full overflow-hidden">
|
| 136 |
+
<div className="bg-blue-500 h-full rounded-full" style={{ width: `${memoryPercent}%` }}></div>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
</div>
|
| 140 |
+
|
| 141 |
+
{/* Latency Card */}
|
| 142 |
+
<div className="bg-[#131121] border border-[#1d1b2e] p-5 rounded-2xl flex flex-col gap-4 min-h-[110px] hover:border-primary/20 transition-all duration-300">
|
| 143 |
+
<div className="flex items-center justify-between">
|
| 144 |
+
<span className="text-[10px] font-bold text-[#9d99b3] uppercase tracking-wider">Avg. Latency</span>
|
| 145 |
+
<div className="p-1.5 bg-primary/10 rounded-lg text-primary">
|
| 146 |
+
<Zap size={14} />
|
| 147 |
+
</div>
|
| 148 |
+
</div>
|
| 149 |
+
<div className="space-y-1 flex-1 flex flex-col justify-end">
|
| 150 |
+
<h2 className="text-xl font-bold text-white">{latency.toFixed(2)} s</h2>
|
| 151 |
+
<p className="text-[10px] text-[#58556f]">Per request</p>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
{/* Tokens Card */}
|
| 156 |
+
<div className="bg-[#131121] border border-[#1d1b2e] p-5 rounded-2xl flex flex-col gap-4 min-h-[110px] hover:border-primary/20 transition-all duration-300">
|
| 157 |
+
<div className="flex items-center justify-between">
|
| 158 |
+
<span className="text-[10px] font-bold text-[#9d99b3] uppercase tracking-wider">Tokens Used</span>
|
| 159 |
+
<div className="p-1.5 bg-red-500/10 rounded-lg text-red-500">
|
| 160 |
+
<BarChart2 size={14} />
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
<div className="space-y-1 flex-1 flex flex-col justify-end">
|
| 164 |
+
<h2 className="text-xl font-bold text-white">{totalTokens.toLocaleString()}</h2>
|
| 165 |
+
<p className="text-[10px] text-[#58556f]">Total (Input + Output)</p>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
|
| 169 |
+
</div>
|
| 170 |
+
|
| 171 |
+
{/* Main Layout Split */}
|
| 172 |
+
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6">
|
| 173 |
+
|
| 174 |
+
{/* Left: Quick Actions Grid */}
|
| 175 |
+
<div className="lg:col-span-3 space-y-4">
|
| 176 |
+
<div className="select-none">
|
| 177 |
+
<h3 className="text-sm font-bold text-white">Quick Actions</h3>
|
| 178 |
+
<p className="text-[10px] text-[#9d99b3] mt-0.5">Choose a tool to get started</p>
|
| 179 |
+
</div>
|
| 180 |
+
|
| 181 |
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
| 182 |
+
{quickActions.map((action, idx) => {
|
| 183 |
+
const Icon = action.icon;
|
| 184 |
+
return (
|
| 185 |
+
<button
|
| 186 |
+
key={idx}
|
| 187 |
+
onClick={() => handleActionClick(action)}
|
| 188 |
+
className="flex items-center gap-3.5 p-4 bg-[#131121] hover:bg-[#1c1a30] border border-[#1d1b2e] hover:border-primary/40 rounded-xl text-left transition-all duration-200 cursor-pointer shadow-sm group"
|
| 189 |
+
>
|
| 190 |
+
<div className="p-2 bg-primary/10 rounded-lg text-primary glow-primary shrink-0 group-hover:scale-105 transition-transform">
|
| 191 |
+
<Icon size={15} />
|
| 192 |
+
</div>
|
| 193 |
+
<div className="space-y-0.5 min-w-0">
|
| 194 |
+
<h4 className="font-bold text-xs text-white group-hover:text-primary transition-colors">{action.title}</h4>
|
| 195 |
+
<p className="text-[10px] text-[#9d99b3] leading-relaxed truncate">{action.desc}</p>
|
| 196 |
+
</div>
|
| 197 |
+
</button>
|
| 198 |
+
);
|
| 199 |
+
})}
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
|
| 203 |
+
{/* Right: Recent Conversations */}
|
| 204 |
+
<div className="lg:col-span-2 space-y-4 flex flex-col">
|
| 205 |
+
<div className="flex items-center justify-between select-none">
|
| 206 |
+
<h3 className="text-sm font-bold text-white">Recent Conversations</h3>
|
| 207 |
+
<button
|
| 208 |
+
onClick={() => setActiveTab('chat')}
|
| 209 |
+
className="text-[10px] font-bold text-primary hover:underline cursor-pointer"
|
| 210 |
+
>
|
| 211 |
+
View all
|
| 212 |
+
</button>
|
| 213 |
+
</div>
|
| 214 |
+
|
| 215 |
+
<div className="flex-1 bg-[#131121] border border-[#1d1b2e] rounded-2xl overflow-hidden select-none">
|
| 216 |
+
{recentConversations.map((conv, idx) => (
|
| 217 |
+
<div
|
| 218 |
+
key={idx}
|
| 219 |
+
className="flex items-center justify-between px-4 py-3 border-b border-[#1d1b2e] last:border-0 hover:bg-[#1c1a30] cursor-pointer transition-colors gap-4"
|
| 220 |
+
onClick={() => { createNewConversation(conv.title); setActiveTab('chat'); }}
|
| 221 |
+
>
|
| 222 |
+
<span className="text-xs font-medium text-[#eae9fc] truncate min-w-0 flex-1">{conv.title}</span>
|
| 223 |
+
<span className="text-[10px] text-[#58556f] font-mono shrink-0 whitespace-nowrap">{conv.time}</span>
|
| 224 |
+
</div>
|
| 225 |
+
))}
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
|
| 229 |
+
</div>
|
| 230 |
+
|
| 231 |
+
{/* Bottom: Example Prompts */}
|
| 232 |
+
<div className="space-y-4 pt-2">
|
| 233 |
+
<div className="select-none">
|
| 234 |
+
<h3 className="text-sm font-bold text-white">Example Prompts</h3>
|
| 235 |
+
<p className="text-[10px] text-[#9d99b3] mt-0.5">Try these examples to get started</p>
|
| 236 |
+
</div>
|
| 237 |
+
|
| 238 |
+
<div className="flex flex-wrap gap-3">
|
| 239 |
+
{examplePrompts.map((ep, idx) => {
|
| 240 |
+
const Icon = ep.icon;
|
| 241 |
+
return (
|
| 242 |
+
<button
|
| 243 |
+
key={idx}
|
| 244 |
+
onClick={() => handlePromptClick(ep.label, ep.prompt)}
|
| 245 |
+
className="flex items-center gap-2 px-3.5 py-2.5 bg-[#131121] hover:bg-[#1c1a30] border border-[#1d1b2e] hover:border-primary/40 text-xs text-[#eae9fc] font-medium rounded-xl transition-all cursor-pointer shadow-sm"
|
| 246 |
+
>
|
| 247 |
+
<Icon size={12} className="text-[#9d99b3]" />
|
| 248 |
+
<span>{ep.label}</span>
|
| 249 |
+
</button>
|
| 250 |
+
);
|
| 251 |
+
})}
|
| 252 |
+
</div>
|
| 253 |
+
</div>
|
| 254 |
+
|
| 255 |
+
</div>
|
| 256 |
+
);
|
| 257 |
+
};
|
frontend/src/components/MonacoEditorWrapper.tsx
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useRef } from 'react';
|
| 2 |
+
import Editor from '@monaco-editor/react';
|
| 3 |
+
import type { Monaco } from '@monaco-editor/react';
|
| 4 |
+
|
| 5 |
+
interface MonacoEditorWrapperProps {
|
| 6 |
+
value: string;
|
| 7 |
+
onChange: (val: string) => void;
|
| 8 |
+
language: string;
|
| 9 |
+
theme?: 'vs-dark' | 'light';
|
| 10 |
+
height?: string;
|
| 11 |
+
readOnly?: boolean;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export const MonacoEditorWrapper: React.FC<MonacoEditorWrapperProps> = ({
|
| 15 |
+
value,
|
| 16 |
+
onChange,
|
| 17 |
+
language,
|
| 18 |
+
theme = 'vs-dark',
|
| 19 |
+
height = '100%',
|
| 20 |
+
readOnly = false,
|
| 21 |
+
}) => {
|
| 22 |
+
const editorRef = useRef<any>(null);
|
| 23 |
+
|
| 24 |
+
const handleEditorDidMount = (editor: any, monaco: Monaco) => {
|
| 25 |
+
editorRef.current = editor;
|
| 26 |
+
|
| 27 |
+
monaco.editor.defineTheme('antigravity-dark', {
|
| 28 |
+
base: 'vs-dark',
|
| 29 |
+
inherit: true,
|
| 30 |
+
rules: [
|
| 31 |
+
{ token: 'comment', foreground: '6272a4', fontStyle: 'italic' },
|
| 32 |
+
{ token: 'keyword', foreground: 'ff79c6' },
|
| 33 |
+
{ token: 'string', foreground: 'f1fa8c' },
|
| 34 |
+
{ token: 'number', foreground: 'bd93f9' },
|
| 35 |
+
{ token: 'regexp', foreground: 'ffb86c' },
|
| 36 |
+
{ token: 'type', foreground: '8be9fd' },
|
| 37 |
+
],
|
| 38 |
+
colors: {
|
| 39 |
+
'editor.background': '#0b0f19',
|
| 40 |
+
'editor.foreground': '#f8f8f2',
|
| 41 |
+
'editorLineNumber.foreground': '#4b5563',
|
| 42 |
+
'editorLineNumber.activeForeground': '#3b82f6',
|
| 43 |
+
'editor.lineHighlightBackground': '#1e293b50',
|
| 44 |
+
'editorCursor.foreground': '#3b82f6',
|
| 45 |
+
},
|
| 46 |
+
});
|
| 47 |
+
|
| 48 |
+
monaco.editor.setTheme(theme === 'vs-dark' ? 'antigravity-dark' : 'light');
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
const handleEditorChange = (value: string | undefined) => {
|
| 52 |
+
if (value !== undefined) {
|
| 53 |
+
onChange(value);
|
| 54 |
+
}
|
| 55 |
+
};
|
| 56 |
+
|
| 57 |
+
return (
|
| 58 |
+
<div className="w-full h-full min-h-[300px] border border-border rounded-xl overflow-hidden shadow-sm">
|
| 59 |
+
<Editor
|
| 60 |
+
height={height}
|
| 61 |
+
language={language}
|
| 62 |
+
value={value}
|
| 63 |
+
onChange={handleEditorChange}
|
| 64 |
+
onMount={handleEditorDidMount}
|
| 65 |
+
loading={
|
| 66 |
+
<div className="flex items-center justify-center h-full bg-card text-muted-foreground text-xs gap-2">
|
| 67 |
+
<span className="w-4 h-4 border-2 border-primary border-t-transparent rounded-full animate-spin"></span>
|
| 68 |
+
Loading editor assets...
|
| 69 |
+
</div>
|
| 70 |
+
}
|
| 71 |
+
options={{
|
| 72 |
+
readOnly,
|
| 73 |
+
minimap: { enabled: true },
|
| 74 |
+
fontSize: 14,
|
| 75 |
+
fontFamily: "'Fira Code', 'Courier New', monospace",
|
| 76 |
+
fontLigatures: true,
|
| 77 |
+
lineNumbers: 'on',
|
| 78 |
+
roundedSelection: true,
|
| 79 |
+
scrollBeyondLastLine: false,
|
| 80 |
+
automaticLayout: true,
|
| 81 |
+
tabSize: 4,
|
| 82 |
+
padding: { top: 12, bottom: 12 },
|
| 83 |
+
wordWrap: 'on',
|
| 84 |
+
}}
|
| 85 |
+
/>
|
| 86 |
+
</div>
|
| 87 |
+
);
|
| 88 |
+
};
|
frontend/src/components/Navbar.tsx
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { motion } from 'framer-motion';
|
| 3 |
+
import {
|
| 4 |
+
Bell, Search, Sun, Moon, Cpu, Globe,
|
| 5 |
+
CheckCircle2, AlertCircle, HelpCircle
|
| 6 |
+
} from 'lucide-react';
|
| 7 |
+
import type { ModelInfo, AppSettings } from '../types';
|
| 8 |
+
|
| 9 |
+
interface NavbarProps {
|
| 10 |
+
activeTab: string;
|
| 11 |
+
modelInfo: ModelInfo | null;
|
| 12 |
+
settings: AppSettings;
|
| 13 |
+
updateSettings: (newSettings: Partial<AppSettings>) => void;
|
| 14 |
+
onSearchClick?: () => void;
|
| 15 |
+
onOpenMobileSidebar?: () => void;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
export const Navbar: React.FC<NavbarProps> = ({
|
| 19 |
+
activeTab,
|
| 20 |
+
modelInfo,
|
| 21 |
+
settings,
|
| 22 |
+
updateSettings,
|
| 23 |
+
onSearchClick,
|
| 24 |
+
onOpenMobileSidebar,
|
| 25 |
+
}) => {
|
| 26 |
+
const getTabTitle = () => {
|
| 27 |
+
switch (activeTab) {
|
| 28 |
+
case 'dashboard': return 'Dashboard';
|
| 29 |
+
case 'chat': return 'AI Chat Console';
|
| 30 |
+
case 'playground': return 'Code Playground';
|
| 31 |
+
case 'history': return 'History';
|
| 32 |
+
case 'documents': return 'Documents';
|
| 33 |
+
case 'snippets': return 'Snippets';
|
| 34 |
+
case 'models': return 'Models';
|
| 35 |
+
case 'settings': return 'System Settings';
|
| 36 |
+
case 'about': return 'About';
|
| 37 |
+
default: return 'Dashboard';
|
| 38 |
+
}
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
const toggleTheme = () => {
|
| 42 |
+
updateSettings({ theme: settings.theme === 'dark' ? 'light' : 'dark' });
|
| 43 |
+
};
|
| 44 |
+
|
| 45 |
+
return (
|
| 46 |
+
<motion.header
|
| 47 |
+
initial={{ opacity: 0, y: -8 }}
|
| 48 |
+
animate={{ opacity: 1, y: 0 }}
|
| 49 |
+
transition={{ duration: 0.3 }}
|
| 50 |
+
className="sticky top-0 z-40 w-full border-b border-border bg-card/60 backdrop-blur-md select-none shrink-0"
|
| 51 |
+
>
|
| 52 |
+
<div className="flex h-16 items-center justify-between px-6 gap-4">
|
| 53 |
+
|
| 54 |
+
{/* Left Section: Breadcrumb/Page Title */}
|
| 55 |
+
<div className="flex items-center gap-3">
|
| 56 |
+
{onOpenMobileSidebar && (
|
| 57 |
+
<button
|
| 58 |
+
onClick={onOpenMobileSidebar}
|
| 59 |
+
className="lg:hidden p-2 text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded-lg transition-colors cursor-pointer"
|
| 60 |
+
>
|
| 61 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" x2="20" y1="12" y2="12"></line><line x1="4" x2="20" y1="6" y2="6"></line><line x1="4" x2="20" y1="18" y2="18"></line></svg>
|
| 62 |
+
</button>
|
| 63 |
+
)}
|
| 64 |
+
<span className="text-[#9d99b3] text-xs">‹</span>
|
| 65 |
+
<h1 className="text-sm font-bold tracking-tight text-foreground">{getTabTitle()}</h1>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
{/* Middle Section: Mock Search Bar */}
|
| 69 |
+
<div className="hidden md:flex flex-1 max-w-md mx-auto">
|
| 70 |
+
<div
|
| 71 |
+
onClick={onSearchClick}
|
| 72 |
+
className="w-full flex items-center justify-between px-3 py-2 bg-background hover:bg-muted/30 border border-border focus-within:border-primary/50 rounded-xl text-xs text-muted-foreground transition-all cursor-pointer group"
|
| 73 |
+
>
|
| 74 |
+
<div className="flex items-center gap-2">
|
| 75 |
+
<Search size={14} className="text-muted-foreground group-hover:text-foreground transition-colors" />
|
| 76 |
+
<span>Quick search prompts...</span>
|
| 77 |
+
</div>
|
| 78 |
+
<kbd className="inline-flex h-5 select-none items-center gap-0.5 rounded border border-border bg-muted/50 px-1.5 font-mono text-[9px] font-medium text-muted-foreground opacity-100">
|
| 79 |
+
<span className="text-[10px]">⌘</span>K
|
| 80 |
+
</kbd>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
|
| 84 |
+
{/* Right Section: Diagnostics Status Controls */}
|
| 85 |
+
<div className="flex items-center gap-3">
|
| 86 |
+
|
| 87 |
+
{/* Connection badge */}
|
| 88 |
+
<div className="hidden sm:flex items-center gap-2 px-3 py-1.5 bg-secondary border border-border rounded-full text-[10px] font-semibold text-muted-foreground">
|
| 89 |
+
{modelInfo?.status === 'loaded' ? (
|
| 90 |
+
<>
|
| 91 |
+
<CheckCircle2 size={12} className="text-emerald-500" />
|
| 92 |
+
<span className="text-emerald-500 font-bold uppercase tracking-wider">Online</span>
|
| 93 |
+
</>
|
| 94 |
+
) : modelInfo?.status === 'loading' ? (
|
| 95 |
+
<>
|
| 96 |
+
<div className="h-1.5 w-1.5 rounded-full bg-amber-500 animate-pulse" />
|
| 97 |
+
<span className="text-amber-500 font-bold uppercase tracking-wider">Syncing</span>
|
| 98 |
+
</>
|
| 99 |
+
) : (
|
| 100 |
+
<>
|
| 101 |
+
<AlertCircle size={12} className="text-muted-foreground" />
|
| 102 |
+
<span className="font-bold uppercase tracking-wider">Offline</span>
|
| 103 |
+
</>
|
| 104 |
+
)}
|
| 105 |
+
</div>
|
| 106 |
+
|
| 107 |
+
{/* Model info indicator */}
|
| 108 |
+
<div className="hidden lg:flex items-center gap-2 px-3 py-1.5 bg-secondary border border-border rounded-full text-xs font-semibold text-foreground">
|
| 109 |
+
<Cpu size={13} className="text-primary animate-pulse" />
|
| 110 |
+
<span className="truncate max-w-[120px] font-mono text-[11px]">
|
| 111 |
+
{modelInfo?.model_name.split('/').pop() || 'SmolLM2-360M'}
|
| 112 |
+
</span>
|
| 113 |
+
</div>
|
| 114 |
+
|
| 115 |
+
<div className="h-4 w-px bg-border hidden sm:block"></div>
|
| 116 |
+
|
| 117 |
+
{/* Notifications Trigger */}
|
| 118 |
+
<button className="p-2 text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded-xl transition-colors cursor-pointer relative">
|
| 119 |
+
<Bell size={16} />
|
| 120 |
+
<span className="absolute top-2 right-2 h-1.5 w-1.5 rounded-full bg-primary animate-pulse-glow" />
|
| 121 |
+
</button>
|
| 122 |
+
|
| 123 |
+
{/* Theme Selector */}
|
| 124 |
+
<button
|
| 125 |
+
onClick={toggleTheme}
|
| 126 |
+
className="p-2 text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded-xl transition-colors cursor-pointer"
|
| 127 |
+
title={settings.theme === 'dark' ? 'Light Theme' : 'Dark Theme'}
|
| 128 |
+
>
|
| 129 |
+
{settings.theme === 'dark' ? <Sun size={16} /> : <Moon size={16} />}
|
| 130 |
+
</button>
|
| 131 |
+
|
| 132 |
+
{/* User Profile avatar */}
|
| 133 |
+
<div className="h-8 w-8 rounded-full border border-border bg-gradient-to-tr from-primary to-accent text-primary-foreground flex items-center justify-center font-bold text-xs shadow-sm cursor-pointer hover:border-primary/50 transition-colors select-none">
|
| 134 |
+
AC
|
| 135 |
+
</div>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
</div>
|
| 139 |
+
</motion.header>
|
| 140 |
+
);
|
| 141 |
+
};
|
frontend/src/components/Settings.tsx
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import {
|
| 3 |
+
Cpu, Sliders, AlertTriangle, ShieldCheck, Save, CheckCircle2,
|
| 4 |
+
HardDrive, Info, Activity, RefreshCw
|
| 5 |
+
} from 'lucide-react';
|
| 6 |
+
import type { AppSettings, ModelInfo } from '../types';
|
| 7 |
+
|
| 8 |
+
interface SettingsProps {
|
| 9 |
+
settings: AppSettings;
|
| 10 |
+
updateSettings: (newSettings: Partial<AppSettings>) => void;
|
| 11 |
+
clearHistory: () => void;
|
| 12 |
+
modelInfo: ModelInfo | null;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
export const Settings: React.FC<SettingsProps> = ({
|
| 16 |
+
settings,
|
| 17 |
+
updateSettings,
|
| 18 |
+
clearHistory,
|
| 19 |
+
modelInfo,
|
| 20 |
+
}) => {
|
| 21 |
+
const [saveSuccess, setSaveSuccess] = useState(false);
|
| 22 |
+
const [resetting, setResetting] = useState(false);
|
| 23 |
+
const [confirmClear, setConfirmClear] = useState(false);
|
| 24 |
+
|
| 25 |
+
const handleSave = () => {
|
| 26 |
+
setSaveSuccess(true);
|
| 27 |
+
setTimeout(() => setSaveSuccess(false), 2000);
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
const handleClearHistory = () => {
|
| 31 |
+
if (confirmClear) {
|
| 32 |
+
setResetting(true);
|
| 33 |
+
setTimeout(() => {
|
| 34 |
+
clearHistory();
|
| 35 |
+
setConfirmClear(false);
|
| 36 |
+
setResetting(false);
|
| 37 |
+
alert('Local database log history has been successfully reset.');
|
| 38 |
+
}, 800);
|
| 39 |
+
} else {
|
| 40 |
+
setConfirmClear(true);
|
| 41 |
+
}
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
// Safe formatting variables matching image specifications
|
| 45 |
+
const memoryUsed = modelInfo?.memory_usage_gb || 1.53;
|
| 46 |
+
const memoryTotal = modelInfo?.total_memory_gb || 7.65;
|
| 47 |
+
const memoryPercent = Math.min(100, Math.round((memoryUsed / memoryTotal) * 100)) || 20;
|
| 48 |
+
|
| 49 |
+
return (
|
| 50 |
+
<div className="flex-1 overflow-y-auto bg-[#090810] p-6 lg:p-8 font-sans select-none">
|
| 51 |
+
|
| 52 |
+
{/* Settings Split Layout grid */}
|
| 53 |
+
<div className="max-w-6xl grid grid-cols-1 lg:grid-cols-5 gap-8 items-start">
|
| 54 |
+
|
| 55 |
+
{/* Left Column Form (3/5 width) */}
|
| 56 |
+
<div className="lg:col-span-3 space-y-5 bg-[#131121] border border-[#1d1b2e] p-6 rounded-2xl">
|
| 57 |
+
|
| 58 |
+
<div className="border-b border-[#1d1b2e] pb-4">
|
| 59 |
+
<h2 className="text-md font-bold text-white tracking-tight">Model & Inference</h2>
|
| 60 |
+
<p className="text-[10px] text-[#9d99b3] mt-0.5">Configure how the AI model runs and generates responses.</p>
|
| 61 |
+
</div>
|
| 62 |
+
|
| 63 |
+
<div className="space-y-5 pt-1">
|
| 64 |
+
|
| 65 |
+
{/* Inference Mode selector */}
|
| 66 |
+
<div className="space-y-1.5">
|
| 67 |
+
<label className="text-[10px] font-bold text-[#9d99b3] uppercase tracking-wider">Inference Mode</label>
|
| 68 |
+
<select
|
| 69 |
+
className="w-full bg-[#090810] border border-[#1d1b2e] text-xs text-white rounded-xl px-3 py-2.5 outline-none font-semibold cursor-pointer"
|
| 70 |
+
disabled
|
| 71 |
+
>
|
| 72 |
+
<option>Local Inference</option>
|
| 73 |
+
</select>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
{/* Target model selector */}
|
| 77 |
+
<div className="space-y-1.5">
|
| 78 |
+
<div className="flex justify-between items-center select-none">
|
| 79 |
+
<label className="text-[10px] font-bold text-[#9d99b3] uppercase tracking-wider">Model</label>
|
| 80 |
+
<span className="flex items-center gap-1 text-[10px] text-emerald-500 font-bold uppercase tracking-wider bg-emerald-500/10 px-2 py-0.5 rounded">
|
| 81 |
+
<CheckCircle2 size={10} />
|
| 82 |
+
Loaded
|
| 83 |
+
</span>
|
| 84 |
+
</div>
|
| 85 |
+
<select
|
| 86 |
+
className="w-full bg-[#090810] border border-[#1d1b2e] text-xs text-white rounded-xl px-3 py-2.5 outline-none font-mono cursor-default"
|
| 87 |
+
disabled
|
| 88 |
+
>
|
| 89 |
+
<option>{modelInfo?.local_model_path.split('/').pop() || 'SmolLM2-360M-Instruct-Q4_K_M.gguf'}</option>
|
| 90 |
+
</select>
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
<div className="h-px bg-[#1d1b2e] my-2"></div>
|
| 94 |
+
|
| 95 |
+
{/* Context length slider */}
|
| 96 |
+
<div className="space-y-2">
|
| 97 |
+
<div className="flex justify-between text-xs font-semibold">
|
| 98 |
+
<span className="text-white">Context Length</span>
|
| 99 |
+
<span className="text-primary font-mono text-[11px] bg-primary/10 px-2 py-0.5 rounded">{settings.contextLength}</span>
|
| 100 |
+
</div>
|
| 101 |
+
<input
|
| 102 |
+
type="range"
|
| 103 |
+
min="256"
|
| 104 |
+
max="4096"
|
| 105 |
+
step="256"
|
| 106 |
+
value={settings.contextLength}
|
| 107 |
+
onChange={(e) => updateSettings({ contextLength: parseInt(e.target.value) })}
|
| 108 |
+
className="w-full accent-primary h-1 bg-[#090810] rounded-lg appearance-none cursor-pointer focus:outline-none"
|
| 109 |
+
/>
|
| 110 |
+
<p className="text-[9px] text-[#9d99b3]">Maximum context length constraints for the model.</p>
|
| 111 |
+
</div>
|
| 112 |
+
|
| 113 |
+
{/* Temperature Slider */}
|
| 114 |
+
<div className="space-y-2">
|
| 115 |
+
<div className="flex justify-between text-xs font-semibold">
|
| 116 |
+
<span className="text-white">Temperature</span>
|
| 117 |
+
<span className="text-primary font-mono text-[11px] bg-primary/10 px-2 py-0.5 rounded">{settings.temperature}</span>
|
| 118 |
+
</div>
|
| 119 |
+
<input
|
| 120 |
+
type="range"
|
| 121 |
+
min="0.1"
|
| 122 |
+
max="1.5"
|
| 123 |
+
step="0.1"
|
| 124 |
+
value={settings.temperature}
|
| 125 |
+
onChange={(e) => updateSettings({ temperature: parseFloat(e.target.value) })}
|
| 126 |
+
className="w-full accent-primary h-1 bg-[#090810] rounded-lg appearance-none cursor-pointer focus:outline-none"
|
| 127 |
+
/>
|
| 128 |
+
<p className="text-[9px] text-[#9d99b3]">Controls randomness in responses.</p>
|
| 129 |
+
</div>
|
| 130 |
+
|
| 131 |
+
{/* Max Output Tokens Slider */}
|
| 132 |
+
<div className="space-y-2">
|
| 133 |
+
<div className="flex justify-between text-xs font-semibold">
|
| 134 |
+
<span className="text-white">Max Tokens</span>
|
| 135 |
+
<span className="text-primary font-mono text-[11px] bg-primary/10 px-2 py-0.5 rounded">{settings.maxTokens}</span>
|
| 136 |
+
</div>
|
| 137 |
+
<input
|
| 138 |
+
type="range"
|
| 139 |
+
min="64"
|
| 140 |
+
max="2048"
|
| 141 |
+
step="64"
|
| 142 |
+
value={settings.maxTokens}
|
| 143 |
+
onChange={(e) => updateSettings({ maxTokens: parseInt(e.target.value) })}
|
| 144 |
+
className="w-full accent-primary h-1 bg-[#090810] rounded-lg appearance-none cursor-pointer focus:outline-none"
|
| 145 |
+
/>
|
| 146 |
+
<p className="text-[9px] text-[#9d99b3]">Maximum tokens in the response.</p>
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
{/* Top P Slider */}
|
| 150 |
+
<div className="space-y-2">
|
| 151 |
+
<div className="flex justify-between text-xs font-semibold">
|
| 152 |
+
<span className="text-white">Top P</span>
|
| 153 |
+
<span className="text-primary font-mono text-[11px] bg-primary/10 px-2 py-0.5 rounded">{settings.topP}</span>
|
| 154 |
+
</div>
|
| 155 |
+
<input
|
| 156 |
+
type="range"
|
| 157 |
+
min="0.1"
|
| 158 |
+
max="1.0"
|
| 159 |
+
step="0.05"
|
| 160 |
+
value={settings.topP}
|
| 161 |
+
onChange={(e) => updateSettings({ topP: parseFloat(e.target.value) })}
|
| 162 |
+
className="w-full accent-primary h-1 bg-[#090810] rounded-lg appearance-none cursor-pointer focus:outline-none"
|
| 163 |
+
/>
|
| 164 |
+
<p className="text-[9px] text-[#9d99b3]">Nucleus sampling probability parameter.</p>
|
| 165 |
+
</div>
|
| 166 |
+
|
| 167 |
+
<div className="h-px bg-[#1d1b2e] my-2"></div>
|
| 168 |
+
|
| 169 |
+
{/* Stream response switch */}
|
| 170 |
+
<div className="flex items-center justify-between py-1.5">
|
| 171 |
+
<div className="space-y-0.5">
|
| 172 |
+
<label className="text-xs font-bold text-white cursor-pointer" htmlFor="stream-switch">
|
| 173 |
+
Stream Response
|
| 174 |
+
</label>
|
| 175 |
+
<p className="text-[10px] text-[#9d99b3]">Stream tokens as they are generated.</p>
|
| 176 |
+
</div>
|
| 177 |
+
<input
|
| 178 |
+
type="checkbox"
|
| 179 |
+
id="stream-switch"
|
| 180 |
+
checked={settings.streaming}
|
| 181 |
+
onChange={(e) => updateSettings({ streaming: e.target.checked })}
|
| 182 |
+
className="w-4.5 h-4.5 rounded text-primary focus:ring-primary accent-primary bg-background border-border cursor-pointer focus-ring"
|
| 183 |
+
/>
|
| 184 |
+
</div>
|
| 185 |
+
|
| 186 |
+
{/* Keep history switch */}
|
| 187 |
+
<div className="flex items-center justify-between py-1.5">
|
| 188 |
+
<div className="space-y-0.5">
|
| 189 |
+
<label className="text-xs font-bold text-white cursor-pointer" htmlFor="history-switch">
|
| 190 |
+
Keep Conversation History
|
| 191 |
+
</label>
|
| 192 |
+
<p className="text-[10px] text-[#9d99b3]">Maintain conversation history log records in memory.</p>
|
| 193 |
+
</div>
|
| 194 |
+
<input
|
| 195 |
+
type="checkbox"
|
| 196 |
+
id="history-switch"
|
| 197 |
+
checked={true}
|
| 198 |
+
readOnly
|
| 199 |
+
className="w-4.5 h-4.5 rounded text-primary focus:ring-primary accent-primary bg-background border-border cursor-pointer focus-ring"
|
| 200 |
+
/>
|
| 201 |
+
</div>
|
| 202 |
+
|
| 203 |
+
{/* Save Button */}
|
| 204 |
+
<div className="flex items-center justify-end pt-3">
|
| 205 |
+
<button
|
| 206 |
+
onClick={handleSave}
|
| 207 |
+
className="flex items-center justify-center gap-2 px-5 py-2.5 bg-[#6344d5] hover:bg-[#6344d5]/95 text-white font-bold text-xs rounded-xl shadow-md transition-all active:scale-[0.98] cursor-pointer glow-primary"
|
| 208 |
+
>
|
| 209 |
+
{saveSuccess ? (
|
| 210 |
+
<>
|
| 211 |
+
<CheckCircle2 size={13} />
|
| 212 |
+
<span>Settings Saved</span>
|
| 213 |
+
</>
|
| 214 |
+
) : (
|
| 215 |
+
<>
|
| 216 |
+
<Save size={13} />
|
| 217 |
+
<span>Save Settings</span>
|
| 218 |
+
</>
|
| 219 |
+
)}
|
| 220 |
+
</button>
|
| 221 |
+
</div>
|
| 222 |
+
|
| 223 |
+
</div>
|
| 224 |
+
</div>
|
| 225 |
+
|
| 226 |
+
{/* Right Column Stats (2/5 width) */}
|
| 227 |
+
<div className="lg:col-span-2 space-y-6">
|
| 228 |
+
|
| 229 |
+
{/* Model Information metadata */}
|
| 230 |
+
<div className="bg-[#131121] border border-[#1d1b2e] p-5 rounded-2xl space-y-4">
|
| 231 |
+
<h3 className="text-xs font-bold text-white uppercase tracking-wider flex items-center gap-2 select-none border-b border-[#1d1b2e] pb-3">
|
| 232 |
+
<Info size={13} className="text-primary" />
|
| 233 |
+
Model Information
|
| 234 |
+
</h3>
|
| 235 |
+
<div className="space-y-3.5 text-xs select-none">
|
| 236 |
+
<div className="flex justify-between">
|
| 237 |
+
<span className="text-[#9d99b3]">Model Name</span>
|
| 238 |
+
<span className="font-semibold text-white font-mono text-[11px] truncate max-w-[140px]">{modelInfo?.model_name.split('/').pop() || 'SmolLM2-360M-Instruct'}</span>
|
| 239 |
+
</div>
|
| 240 |
+
<div className="flex justify-between">
|
| 241 |
+
<span className="text-[#9d99b3]">Quantization</span>
|
| 242 |
+
<span className="font-semibold text-white font-mono text-[11px]">Q4_K_M</span>
|
| 243 |
+
</div>
|
| 244 |
+
<div className="flex justify-between">
|
| 245 |
+
<span className="text-[#9d99b3]">Model Size</span>
|
| 246 |
+
<span className="font-semibold text-white font-mono text-[11px]">1.6 GB</span>
|
| 247 |
+
</div>
|
| 248 |
+
<div className="flex justify-between">
|
| 249 |
+
<span className="text-[#9d99b3]">Context Length</span>
|
| 250 |
+
<span className="font-semibold text-white font-mono text-[11px]">4096</span>
|
| 251 |
+
</div>
|
| 252 |
+
<div className="flex justify-between">
|
| 253 |
+
<span className="text-[#9d99b3]">Vocabulary Size</span>
|
| 254 |
+
<span className="font-semibold text-white font-mono text-[11px]">151,936</span>
|
| 255 |
+
</div>
|
| 256 |
+
<div className="flex justify-between">
|
| 257 |
+
<span className="text-[#9d99b3]">Architecture</span>
|
| 258 |
+
<span className="font-semibold text-white font-mono text-[11px]">Transformers</span>
|
| 259 |
+
</div>
|
| 260 |
+
</div>
|
| 261 |
+
</div>
|
| 262 |
+
|
| 263 |
+
{/* System resource bars */}
|
| 264 |
+
<div className="bg-[#131121] border border-[#1d1b2e] p-5 rounded-2xl space-y-5 select-none">
|
| 265 |
+
<h3 className="text-xs font-bold text-white uppercase tracking-wider flex items-center gap-2 border-b border-[#1d1b2e] pb-3">
|
| 266 |
+
<Activity size={13} className="text-primary" />
|
| 267 |
+
System Resources
|
| 268 |
+
</h3>
|
| 269 |
+
|
| 270 |
+
{/* RAM Usage progress bar */}
|
| 271 |
+
<div className="space-y-2">
|
| 272 |
+
<div className="flex justify-between text-xs font-semibold">
|
| 273 |
+
<span className="text-[#9d99b3]">RAM Usage</span>
|
| 274 |
+
<span className="text-white font-mono">{memoryPercent}%</span>
|
| 275 |
+
</div>
|
| 276 |
+
<div className="w-full bg-[#090810] h-1.5 rounded-full overflow-hidden">
|
| 277 |
+
<div className="bg-emerald-500 h-full" style={{ width: `${memoryPercent}%` }}></div>
|
| 278 |
+
</div>
|
| 279 |
+
<div className="text-[10px] text-[#58556f]">{memoryUsed.toFixed(2)} GB / {memoryTotal.toFixed(2)} GB</div>
|
| 280 |
+
</div>
|
| 281 |
+
|
| 282 |
+
{/* CPU Usage progress bar */}
|
| 283 |
+
<div className="space-y-2">
|
| 284 |
+
<div className="flex justify-between text-xs font-semibold">
|
| 285 |
+
<span className="text-[#9d99b3]">CPU Usage</span>
|
| 286 |
+
<span className="text-white font-mono">12%</span>
|
| 287 |
+
</div>
|
| 288 |
+
<div className="w-full bg-[#090810] h-1.5 rounded-full overflow-hidden">
|
| 289 |
+
<div className="bg-[#6344d5] h-full" style={{ width: '12%' }}></div>
|
| 290 |
+
</div>
|
| 291 |
+
</div>
|
| 292 |
+
</div>
|
| 293 |
+
|
| 294 |
+
{/* Danger zone log clear button */}
|
| 295 |
+
<div className="bg-[#131121] border border-[#1d1b2e] p-5 rounded-2xl space-y-3 select-none">
|
| 296 |
+
<h3 className="text-xs font-bold text-destructive uppercase tracking-wider flex items-center gap-1.5">
|
| 297 |
+
<AlertTriangle size={14} />
|
| 298 |
+
Reset Cache
|
| 299 |
+
</h3>
|
| 300 |
+
|
| 301 |
+
<button
|
| 302 |
+
onClick={handleClearHistory}
|
| 303 |
+
disabled={resetting}
|
| 304 |
+
className={`w-full flex items-center justify-center gap-2 px-3 py-2.5 border rounded-xl font-bold text-xs transition-all cursor-pointer ${
|
| 305 |
+
confirmClear
|
| 306 |
+
? 'bg-destructive hover:bg-destructive/90 text-destructive-foreground border-transparent animate-pulse'
|
| 307 |
+
: 'bg-destructive/10 hover:bg-destructive/20 border-destructive/20 text-destructive'
|
| 308 |
+
}`}
|
| 309 |
+
>
|
| 310 |
+
{resetting ? (
|
| 311 |
+
<>
|
| 312 |
+
<RefreshCw size={13} className="animate-spin" />
|
| 313 |
+
<span>Reseting...</span>
|
| 314 |
+
</>
|
| 315 |
+
) : (
|
| 316 |
+
<>
|
| 317 |
+
<span>{confirmClear ? 'Confirm Reset' : 'Reset Database Records'}</span>
|
| 318 |
+
</>
|
| 319 |
+
)}
|
| 320 |
+
</button>
|
| 321 |
+
{confirmClear && !resetting && (
|
| 322 |
+
<button
|
| 323 |
+
onClick={() => setConfirmClear(false)}
|
| 324 |
+
className="w-full text-center text-[10px] hover:underline text-muted-foreground block py-1 cursor-pointer"
|
| 325 |
+
>
|
| 326 |
+
Cancel
|
| 327 |
+
</button>
|
| 328 |
+
)}
|
| 329 |
+
</div>
|
| 330 |
+
|
| 331 |
+
</div>
|
| 332 |
+
|
| 333 |
+
</div>
|
| 334 |
+
|
| 335 |
+
</div>
|
| 336 |
+
);
|
| 337 |
+
};
|
frontend/src/components/Sidebar.tsx
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import {
|
| 4 |
+
MessageSquare, Code2, Settings2, Info, Plus,
|
| 5 |
+
Trash2, ChevronLeft, ChevronRight, LayoutDashboard, Cpu,
|
| 6 |
+
User, Database, Sparkles, HelpCircle, History, FileText,
|
| 7 |
+
Terminal, ShieldCheck, Settings
|
| 8 |
+
} from 'lucide-react';
|
| 9 |
+
import type { Conversation, ModelInfo } from '../types';
|
| 10 |
+
|
| 11 |
+
interface SidebarProps {
|
| 12 |
+
conversations: Conversation[];
|
| 13 |
+
activeConversationId: string | null;
|
| 14 |
+
setActiveConversationId: (id: string) => void;
|
| 15 |
+
createNewConversation: () => void;
|
| 16 |
+
deleteConversation: (id: string) => void;
|
| 17 |
+
activeTab: string;
|
| 18 |
+
setActiveTab: (tab: string) => void;
|
| 19 |
+
modelInfo: ModelInfo | null;
|
| 20 |
+
collapsed: boolean;
|
| 21 |
+
setCollapsed: (c: boolean) => void;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
export const Sidebar: React.FC<SidebarProps> = ({
|
| 25 |
+
conversations,
|
| 26 |
+
activeConversationId,
|
| 27 |
+
setActiveConversationId,
|
| 28 |
+
createNewConversation,
|
| 29 |
+
deleteConversation,
|
| 30 |
+
activeTab,
|
| 31 |
+
setActiveTab,
|
| 32 |
+
modelInfo,
|
| 33 |
+
collapsed,
|
| 34 |
+
setCollapsed,
|
| 35 |
+
}) => {
|
| 36 |
+
const navItems = [
|
| 37 |
+
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
|
| 38 |
+
{ id: 'chat', label: 'AI Chat', icon: MessageSquare },
|
| 39 |
+
{ id: 'playground', label: 'Playground', icon: Code2 },
|
| 40 |
+
{ id: 'history', label: 'History', icon: History },
|
| 41 |
+
{ id: 'documents', label: 'Documents', icon: FileText },
|
| 42 |
+
{ id: 'snippets', label: 'Snippets', icon: Terminal },
|
| 43 |
+
{ id: 'models', label: 'Models', icon: Cpu },
|
| 44 |
+
{ id: 'settings', label: 'Settings', icon: Settings2 },
|
| 45 |
+
];
|
| 46 |
+
|
| 47 |
+
return (
|
| 48 |
+
<motion.aside
|
| 49 |
+
animate={{ width: collapsed ? 64 : 256 }}
|
| 50 |
+
transition={{ duration: 0.3, cubicBezier: [0.16, 1, 0.3, 1] }}
|
| 51 |
+
className="bg-[#100d20] border-r border-[#1d1b2e] flex flex-col h-screen relative select-none overflow-hidden shrink-0"
|
| 52 |
+
>
|
| 53 |
+
{/* Brand Header */}
|
| 54 |
+
<div className="p-4 border-b border-[#1d1b2e] flex items-center justify-between overflow-hidden shrink-0 h-16">
|
| 55 |
+
<div className="flex items-center gap-2.5 overflow-hidden">
|
| 56 |
+
<div className="p-1.5 bg-primary/10 rounded-lg text-primary glow-primary shrink-0">
|
| 57 |
+
<Cpu size={16} className="animate-pulse" />
|
| 58 |
+
</div>
|
| 59 |
+
<AnimatePresence>
|
| 60 |
+
{!collapsed && (
|
| 61 |
+
<motion.div
|
| 62 |
+
initial={{ opacity: 0, x: -8 }}
|
| 63 |
+
animate={{ opacity: 1, x: 0 }}
|
| 64 |
+
exit={{ opacity: 0, x: -8 }}
|
| 65 |
+
transition={{ duration: 0.2 }}
|
| 66 |
+
className="flex flex-col whitespace-nowrap"
|
| 67 |
+
>
|
| 68 |
+
<span className="font-bold text-sm tracking-tight text-foreground">
|
| 69 |
+
Antigravity
|
| 70 |
+
</span>
|
| 71 |
+
<span className="text-[10px] font-medium text-muted-foreground -mt-1">
|
| 72 |
+
Coder
|
| 73 |
+
</span>
|
| 74 |
+
</motion.div>
|
| 75 |
+
)}
|
| 76 |
+
</AnimatePresence>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
{!collapsed && (
|
| 80 |
+
<button
|
| 81 |
+
onClick={() => setCollapsed(true)}
|
| 82 |
+
className="text-muted-foreground hover:text-foreground p-1 hover:bg-muted/50 rounded-lg transition-colors cursor-pointer shrink-0"
|
| 83 |
+
title="Collapse Sidebar"
|
| 84 |
+
>
|
| 85 |
+
<ChevronLeft size={15} />
|
| 86 |
+
</button>
|
| 87 |
+
)}
|
| 88 |
+
</div>
|
| 89 |
+
|
| 90 |
+
{/* Navigation Links */}
|
| 91 |
+
<nav className="p-4 space-y-1.5 shrink-0 flex-1 overflow-y-auto">
|
| 92 |
+
{navItems.map((item) => {
|
| 93 |
+
const Icon = item.icon;
|
| 94 |
+
const isActive = activeTab === item.id;
|
| 95 |
+
return (
|
| 96 |
+
<button
|
| 97 |
+
key={item.id}
|
| 98 |
+
onClick={() => setActiveTab(item.id)}
|
| 99 |
+
className={`w-full flex items-center gap-3 p-2.5 rounded-xl text-xs font-semibold transition-all group relative cursor-pointer ${
|
| 100 |
+
isActive
|
| 101 |
+
? 'bg-[#2b1854] text-white border border-[#6344d5]/30'
|
| 102 |
+
: 'text-muted-foreground hover:text-foreground hover:bg-muted/20'
|
| 103 |
+
}`}
|
| 104 |
+
>
|
| 105 |
+
<Icon size={16} className={isActive ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground'} />
|
| 106 |
+
|
| 107 |
+
<AnimatePresence>
|
| 108 |
+
{!collapsed && (
|
| 109 |
+
<motion.span
|
| 110 |
+
initial={{ opacity: 0 }}
|
| 111 |
+
animate={{ opacity: 1 }}
|
| 112 |
+
exit={{ opacity: 0 }}
|
| 113 |
+
transition={{ duration: 0.15 }}
|
| 114 |
+
className="truncate whitespace-nowrap"
|
| 115 |
+
>
|
| 116 |
+
{item.label}
|
| 117 |
+
</motion.span>
|
| 118 |
+
)}
|
| 119 |
+
</AnimatePresence>
|
| 120 |
+
|
| 121 |
+
{collapsed && (
|
| 122 |
+
<div className="absolute left-18 bg-popover border border-border text-popover-foreground text-[10px] font-bold px-3 py-2 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap z-50 pointer-events-none shadow-md">
|
| 123 |
+
{item.label}
|
| 124 |
+
</div>
|
| 125 |
+
)}
|
| 126 |
+
</button>
|
| 127 |
+
);
|
| 128 |
+
})}
|
| 129 |
+
</nav>
|
| 130 |
+
|
| 131 |
+
{/* Expand trigger when collapsed */}
|
| 132 |
+
{collapsed && (
|
| 133 |
+
<div className="p-4 flex justify-center shrink-0 border-t border-[#1d1b2e] mt-2">
|
| 134 |
+
<button
|
| 135 |
+
onClick={() => setCollapsed(false)}
|
| 136 |
+
className="text-muted-foreground hover:text-foreground p-2 hover:bg-muted/50 rounded-xl transition-colors cursor-pointer"
|
| 137 |
+
title="Expand Sidebar"
|
| 138 |
+
>
|
| 139 |
+
<ChevronRight size={15} />
|
| 140 |
+
</button>
|
| 141 |
+
</div>
|
| 142 |
+
)}
|
| 143 |
+
|
| 144 |
+
{/* Mock User Profile Area */}
|
| 145 |
+
<div className="border-t border-[#1d1b2e] bg-[#0f0c1d] shrink-0">
|
| 146 |
+
<AnimatePresence>
|
| 147 |
+
{!collapsed && (
|
| 148 |
+
<motion.div
|
| 149 |
+
initial={{ opacity: 0, height: 0 }}
|
| 150 |
+
animate={{ opacity: 1, height: 'auto' }}
|
| 151 |
+
exit={{ opacity: 0, height: 0 }}
|
| 152 |
+
className="p-4"
|
| 153 |
+
>
|
| 154 |
+
<div className="flex items-center justify-between p-2 bg-[#131121] border border-[#1d1b2e] rounded-xl shadow-sm hover:border-primary/20 transition-all group">
|
| 155 |
+
<div className="flex items-center gap-2.5 min-w-0">
|
| 156 |
+
<div className="h-8 w-8 rounded-lg bg-gradient-to-tr from-primary to-[#2b1854] text-white flex items-center justify-center font-bold text-xs select-none shrink-0">
|
| 157 |
+
D
|
| 158 |
+
</div>
|
| 159 |
+
<div className="flex flex-col text-[11px] truncate min-w-0">
|
| 160 |
+
<span className="font-semibold text-foreground truncate">Developer</span>
|
| 161 |
+
<span className="text-[9px] text-[#9d99b3]">Pro Plan</span>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
<button
|
| 165 |
+
onClick={() => setActiveTab('settings')}
|
| 166 |
+
className="text-muted-foreground hover:text-foreground cursor-pointer p-1 rounded-md hover:bg-muted/40 shrink-0"
|
| 167 |
+
title="Settings"
|
| 168 |
+
>
|
| 169 |
+
<Settings2 size={14} />
|
| 170 |
+
</button>
|
| 171 |
+
</div>
|
| 172 |
+
</motion.div>
|
| 173 |
+
)}
|
| 174 |
+
</AnimatePresence>
|
| 175 |
+
</div>
|
| 176 |
+
</motion.aside>
|
| 177 |
+
);
|
| 178 |
+
};
|