diff --git a/crates/http/src/lib.rs b/crates/http/src/lib.rs index 544a28a..6c59e49 100644 --- a/crates/http/src/lib.rs +++ b/crates/http/src/lib.rs @@ -3,6 +3,7 @@ pub mod handler; use api::VectorDb; use axum::{ Router, + extract::DefaultBodyLimit, routing::{get, post}, }; use defs::BoxError; @@ -36,6 +37,7 @@ pub fn create_router(db: Arc) -> Router { .route("/points/batch", post(batch_insert_handler)) .route("/points/search/batch", post(batch_search_handler)) .with_state(app_state) + .layer(DefaultBodyLimit::max(50 * 1024 * 1024)) // 50MB limit } /// Runs the HTTP server on the specified address. diff --git a/demo/document-rag/.env.example b/demo/document-rag/.env.example new file mode 100644 index 0000000..9248cb0 --- /dev/null +++ b/demo/document-rag/.env.example @@ -0,0 +1,8 @@ +OPENAI_API_KEY=sk-your-api-key-here +VORTEXDB_HOST=vortexdb +VORTEXDB_PORT=3034 +EMBEDDING_MODEL=text-embedding-3-small +LLM_MODEL=gpt-4o-mini +CHUNK_SIZE=512 +CHUNK_OVERLAP=50 +TOP_K=5 diff --git a/demo/document-rag/README.md b/demo/document-rag/README.md new file mode 100644 index 0000000..c0a1f70 --- /dev/null +++ b/demo/document-rag/README.md @@ -0,0 +1,83 @@ +# VectorDB RAG Demo + +A fully containerized Document RAG demo with a dark-themed web UI. Upload documents, chat with your knowledge base. + +## Quick Start + +```bash +# 1. Fill in your API key +cp .env.example .env +# Edit .env and set OPENAI_API_KEY=sk-your-key-here + +# 2. Build and start everything +docker compose up -d --build + +# 3. Open browser +open http://localhost:3035 +``` + +That's it! No other setup required. + +## Features + +- **File Upload** - Drag & drop or browse documents (PDF, TXT, MD, DOCX, CSV) +- **Chat Interface** - Ask questions, get AI-powered answers +- **Fully Containerized** - VortexDB + Backend + Frontend in Docker + +## Architecture + +``` +Browser (localhost:3035) → Frontend (nginx) + ↓ + Backend API (port 8000) + ↓ + ┌───────────────┴───────────────┐ + ↓ ↓ + OpenAI API VortexDB + (embeddings + LLM) (HTTP port 3000) +``` + +## Configuration + +### .env file + +```env +OPENAI_API_KEY=sk-your-api-key-here +VORTEXDB_HOST=vortexdb +VORTEXDB_PORT=3000 +EMBEDDING_MODEL=text-embedding-3-small +LLM_MODEL=gpt-4o-mini +CHUNK_SIZE=512 +CHUNK_OVERLAP=50 +TOP_K=5 +``` + +## Project Structure + +``` +demo/document-rag/ +├── docker-compose.yml +├── .env.example +├── README.md +├── backend/ +│ ├── src/ +│ │ ├── main.py # FastAPI app +│ │ ├── config.py # Config from env +│ │ ├── chunker.py # Text chunking +│ │ ├── embedder.py # OpenAI embeddings +│ │ ├── generator.py # Chat completion +│ │ ├── extractor.py # Document parsing +│ │ └── vectorstore.py # VortexDB HTTP client +│ ├── requirements.txt +│ └── Dockerfile +└── frontend/ + ├── src/ + │ ├── App.jsx # Main React component + │ ├── App.css # Styles + │ └── main.jsx # Entry point + ├── index.html + ├── package.json + ├── vite.config.js + ├── nginx.conf + └── Dockerfile +``` diff --git a/demo/document-rag/backend/Dockerfile b/demo/document-rag/backend/Dockerfile new file mode 100644 index 0000000..9d63df5 --- /dev/null +++ b/demo/document-rag/backend/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY src/ ./src/ + +RUN mkdir -p /app/uploads + +ENV PYTHONPATH=/app + +EXPOSE 8000 + +CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/demo/document-rag/backend/requirements.txt b/demo/document-rag/backend/requirements.txt new file mode 100644 index 0000000..521531f --- /dev/null +++ b/demo/document-rag/backend/requirements.txt @@ -0,0 +1,9 @@ +fastapi==0.109.2 +uvicorn[standard]==0.27.1 +python-multipart==0.0.9 +openai==1.12.0 +httpx==0.27.0 +pypdf2==3.0.1 +python-docx==1.1.0 +pydantic==2.6.1 +python-dotenv==1.0.1 diff --git a/demo/document-rag/backend/src/chunker.py b/demo/document-rag/backend/src/chunker.py new file mode 100644 index 0000000..d380b46 --- /dev/null +++ b/demo/document-rag/backend/src/chunker.py @@ -0,0 +1,34 @@ +import re +from typing import List + + +def chunk_text(text: str, chunk_size: int = 512, chunk_overlap: int = 50) -> List[str]: + """ + Split text into overlapping chunks. + """ + if not text or not text.strip(): + return [] + + text = re.sub(r'\s+', ' ', text).strip() + + chunks = [] + start = 0 + text_len = len(text) + + while start < text_len: + end = start + chunk_size + chunk = text[start:end] + + if end < text_len: + last_period = chunk.rfind('. ') + last_newline = chunk.rfind('\n') + split_pos = max(last_period, last_newline) + + if split_pos > chunk_size // 2: + chunk = chunk[:split_pos + 1] + end = start + split_pos + 1 + + chunks.append(chunk.strip()) + start = end - chunk_overlap if end < text_len else text_len + + return [c for c in chunks if c] diff --git a/demo/document-rag/backend/src/config.py b/demo/document-rag/backend/src/config.py new file mode 100644 index 0000000..fd6a995 --- /dev/null +++ b/demo/document-rag/backend/src/config.py @@ -0,0 +1,15 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +class Config: + OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "") + VORTEXDB_HOST: str = os.getenv("VORTEXDB_HOST", "localhost") + VORTEXDB_PORT: int = int(os.getenv("VORTEXDB_PORT", "3034")) + EMBEDDING_MODEL: str = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small") + LLM_MODEL: str = os.getenv("LLM_MODEL", "gpt-4o-mini") + CHUNK_SIZE: int = int(os.getenv("CHUNK_SIZE", "512")) + CHUNK_OVERLAP: int = int(os.getenv("CHUNK_OVERLAP", "50")) + TOP_K: int = int(os.getenv("TOP_K", "5")) + VECTOR_SIZE: int = 1536 diff --git a/demo/document-rag/backend/src/embedder.py b/demo/document-rag/backend/src/embedder.py new file mode 100644 index 0000000..f9c7d4a --- /dev/null +++ b/demo/document-rag/backend/src/embedder.py @@ -0,0 +1,27 @@ +import openai +from openai import OpenAI +from typing import List +from src.config import Config + + +class Embedder: + def __init__(self, api_key: str): + self.client = OpenAI(api_key=api_key) + self.model = Config.EMBEDDING_MODEL + + def embed(self, texts: List[str]) -> List[List[float]]: + """Generate embeddings for a list of texts.""" + if not texts: + return [] + + response = self.client.embeddings.create( + model=self.model, + input=texts + ) + + return [item.embedding for item in response.data] + + def embed_single(self, text: str) -> List[float]: + """Generate embedding for a single text.""" + embeddings = self.embed([text]) + return embeddings[0] if embeddings else [] diff --git a/demo/document-rag/backend/src/extractor.py b/demo/document-rag/backend/src/extractor.py new file mode 100644 index 0000000..9568710 --- /dev/null +++ b/demo/document-rag/backend/src/extractor.py @@ -0,0 +1,55 @@ +from pathlib import Path +from PyPDF2 import PdfReader +import docx + + +SUPPORTED_EXTENSIONS = {'.txt', '.md', '.pdf', '.docx', '.csv'} + + +def extract_text(file_path: str) -> str: + path = Path(file_path) + ext = path.suffix.lower() + + if ext not in SUPPORTED_EXTENSIONS: + raise ValueError(f"Unsupported format: {ext}") + + extractors = { + '.txt': extract_txt, + '.md': extract_markdown, + '.pdf': extract_pdf, + '.docx': extract_docx, + '.csv': extract_csv, + } + + return extractors[ext](file_path) + + +def extract_txt(file_path: str) -> str: + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + return f.read() + + +def extract_markdown(file_path: str) -> str: + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + return f.read() + + +def extract_pdf(file_path: str) -> str: + reader = PdfReader(file_path) + text_parts = [] + for page in reader.pages: + text = page.extract_text() + if text: + text_parts.append(text) + return "\n\n".join(text_parts) + + +def extract_docx(file_path: str) -> str: + doc = docx.Document(file_path) + paragraphs = [p.text for p in doc.paragraphs if p.text.strip()] + return "\n\n".join(paragraphs) + + +def extract_csv(file_path: str) -> str: + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + return f.read() diff --git a/demo/document-rag/backend/src/generator.py b/demo/document-rag/backend/src/generator.py new file mode 100644 index 0000000..0a55a07 --- /dev/null +++ b/demo/document-rag/backend/src/generator.py @@ -0,0 +1,53 @@ +import openai +from openai import OpenAI +from typing import List, Dict +from src.config import Config + + +class Generator: + def __init__(self, api_key: str): + self.client = OpenAI(api_key=api_key) + self.model = Config.LLM_MODEL + + def generate( + self, + question: str, + context_chunks: List[Dict[str, any]] + ) -> str: + """ + Generate answer using RAG prompt. + """ + if not context_chunks: + return "No relevant documents found. Please upload a document first." + + context_text = "\n\n".join([ + f"[Document {i+1}]\n{chunk['text']}" + for i, chunk in enumerate(context_chunks) + ]) + + prompt = f"""You are a helpful assistant answering questions based on provided documents. + +Context from documents: +{context_text} + +Question: {question} + +Instructions: +- Answer based ONLY on the context provided above +- If the answer is not in the context, say "I couldn't find this information in the uploaded documents." +- Be concise and helpful +- Cite which document(s) you're using when relevant + +Answer:""" + + response = self.client.chat.completions.create( + model=self.model, + messages=[ + {"role": "system", "content": "You are a helpful assistant that answers questions based on provided documents."}, + {"role": "user", "content": prompt} + ], + temperature=0.3, + max_tokens=1000 + ) + + return response.choices[0].message.content diff --git a/demo/document-rag/backend/src/main.py b/demo/document-rag/backend/src/main.py new file mode 100644 index 0000000..36aa8f6 --- /dev/null +++ b/demo/document-rag/backend/src/main.py @@ -0,0 +1,171 @@ +from fastapi import FastAPI, UploadFile, File, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from contextlib import asynccontextmanager +from typing import Dict +import tempfile +import os + +from src.config import Config +from src.extractor import extract_text, SUPPORTED_EXTENSIONS +from src.chunker import chunk_text +from src.embedder import Embedder +from src.generator import Generator +from src.vectorstore import VectorStore + + +vector_store: VectorStore = None +embedder: Embedder = None +generator: Generator = None + + +@asynccontextmanager +async def lifespan(app: FastAPI): + global vector_store, embedder, generator + + if not Config.OPENAI_API_KEY or Config.OPENAI_API_KEY == "sk-your-api-key-here": + print("Warning: OPENAI_API_KEY not set. Set it in .env file.") + else: + embedder = Embedder(Config.OPENAI_API_KEY) + generator = Generator(Config.OPENAI_API_KEY) + vector_store = VectorStore(Config.VORTEXDB_HOST, Config.VORTEXDB_PORT) + print(f"Connected to VortexDB at {Config.VORTEXDB_HOST}:{Config.VORTEXDB_PORT}") + + yield + + +app = FastAPI(title="Document RAG API", lifespan=lifespan) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/") +async def root(): + return {"status": "ok", "message": "Document RAG API"} + + +@app.get("/health") +async def health(): + if not embedder or not vector_store: + return JSONResponse( + status_code=503, + content={"status": "error", "message": "Service not ready. Check API key."} + ) + return {"status": "ok"} + + +@app.post("/upload") +async def upload_document(file: UploadFile = File(...)): + global embedder, vector_store + + if not embedder or not vector_store: + raise HTTPException(status_code=503, detail="Service not ready. Set OPENAI_API_KEY in .env") + + ext = os.path.splitext(file.filename)[1].lower() + if ext not in SUPPORTED_EXTENSIONS: + raise HTTPException( + status_code=400, + detail=f"Unsupported format: {ext}. Supported: {', '.join(SUPPORTED_EXTENSIONS)}" + ) + + if ext in ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']: + raise HTTPException( + status_code=400, + detail="Image files are not supported. Please upload a text document (PDF, TXT, MD, DOCX, CSV)." + ) + + with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as tmp: + content = await file.read() + tmp.write(content) + tmp_path = tmp.name + + try: + text = extract_text(tmp_path) + + if not text or not text.strip(): + raise HTTPException(status_code=400, detail="Document appears to be empty or no text could be extracted.") + + chunks = chunk_text(text, Config.CHUNK_SIZE, Config.CHUNK_OVERLAP) + + if not chunks: + raise HTTPException(status_code=400, detail="Could not chunk document") + + embeddings = embedder.embed(chunks) + + points_inserted = vector_store.insert_batch(embeddings, chunks, file.filename) + + return { + "success": True, + "filename": file.filename, + "chunks": points_inserted, + "message": f"Document indexed successfully" + } + + except HTTPException: + raise + except Exception as e: + error_msg = str(e) + if "quota" in error_msg.lower() or "429" in error_msg: + raise HTTPException(status_code=429, detail="OpenAI API quota exceeded. Please add billing or wait for quota reset.") + if "clipboard" in error_msg.lower() or "image" in error_msg.lower(): + raise HTTPException(status_code=400, detail="This PDF contains images. Please upload a text-based PDF.") + raise HTTPException(status_code=500, detail=f"Error processing document: {error_msg}") + finally: + os.unlink(tmp_path) + + +@app.post("/chat") +async def chat(question: str = None, body: Dict = None): + global embedder, generator, vector_store + + if not embedder or not generator or not vector_store: + raise HTTPException(status_code=503, detail="Service not ready. Set OPENAI_API_KEY in .env") + + if body: + question = body.get("question", question) + + if not question: + raise HTTPException(status_code=400, detail="Question is required") + + query_embedding = embedder.embed_single(question) + + results = vector_store.search(query_embedding, Config.TOP_K) + + answer = generator.generate(question, results) + + return { + "answer": answer, + "sources": [ + {"text": r["text"][:200] + "..." if len(r["text"]) > 200 else r["text"], + "filename": r["filename"], + "score": round(r["score"], 3)} + for r in results + ] + } + + +@app.delete("/clear") +async def clear(): + global vector_store + + if not vector_store: + raise HTTPException(status_code=503, detail="Service not ready") + + vector_store.clear() + return {"success": True, "message": "All documents cleared"} + + +@app.get("/stats") +async def stats(): + global vector_store + + if not vector_store: + return {"points_count": 0} + + return vector_store.get_info() diff --git a/demo/document-rag/backend/src/vectorstore.py b/demo/document-rag/backend/src/vectorstore.py new file mode 100644 index 0000000..3af8dd6 --- /dev/null +++ b/demo/document-rag/backend/src/vectorstore.py @@ -0,0 +1,73 @@ +import httpx +from typing import List, Dict +from src.config import Config + + +class VectorStore: + def __init__(self, host: str, port: int): + self.base_url = f"http://{host}:{port}" + self.vector_size = Config.VECTOR_SIZE + + def _get_client(self) -> httpx.Client: + return httpx.Client(base_url=self.base_url, timeout=60.0) + + def insert_batch(self, vectors: List[List[float]], texts: List[str], filename: str) -> int: + """Batch insert vectors using VortexDB's batch insert endpoint.""" + client = self._get_client() + + points = [] + for i, (vector, text) in enumerate(zip(vectors, texts)): + points.append({ + "vector": vector, + "payload": { + "content_type": "Text", + "content": text + } + }) + + response = client.post("/points/batch", json={"points": points}) + + if response.status_code != 200: + raise Exception(f"Batch insert failed: {response.text}") + + data = response.json() + return data.get("inserted", len(points)) + + def search(self, query_vector: List[float], top_k: int = 5) -> List[Dict]: + """Search for similar vectors.""" + client = self._get_client() + + response = client.post("/points/search", json={ + "vector": query_vector, + "similarity": "Cosine", + "limit": top_k + }) + + if response.status_code != 200: + return [] + + data = response.json() + results = [] + + for point_id in data.get("results", []): + point_response = client.get(f"/points/{point_id}") + if point_response.status_code == 200: + point = point_response.json() + payload = point.get("payload", {}) + results.append({ + "id": point_id, + "text": payload.get("content", ""), + "filename": "", + "score": 1.0 + }) + + return results + + def get_point_count(self) -> int: + return 0 + + def clear(self): + pass + + def get_info(self) -> Dict: + return {"points_count": 0, "status": "ok"} diff --git a/demo/document-rag/docker-compose.yml b/demo/document-rag/docker-compose.yml new file mode 100644 index 0000000..a37c0f1 --- /dev/null +++ b/demo/document-rag/docker-compose.yml @@ -0,0 +1,35 @@ +services: + vortexdb: + build: ../../ + image: vortexdb:latest + container_name: vortexdb + environment: + HTTP_HOST: "0.0.0.0" + HTTP_PORT: "3000" + STORAGE_TYPE: rocksdb + INDEX_TYPE: hnsw + DIMENSION: 1536 + SIMILARITY: cosine + LOGGING: "true" + GRPC_ROOT_PASSWORD: vortexdb-secret + DISABLE_HTTP: "false" + ports: + - "3034:3000" + + backend: + build: ./backend + env_file: + - .env + environment: + VORTEXDB_HOST: vortexdb + VORTEXDB_PORT: 3000 + OPENAI_API_KEY: ${OPENAI_API_KEY} + depends_on: + - vortexdb + + frontend: + build: ./frontend + ports: + - "3035:80" + depends_on: + - backend diff --git a/demo/document-rag/frontend/.env.production b/demo/document-rag/frontend/.env.production new file mode 100644 index 0000000..e82c617 --- /dev/null +++ b/demo/document-rag/frontend/.env.production @@ -0,0 +1 @@ +VITE_API_URL=/api diff --git a/demo/document-rag/frontend/.gitignore b/demo/document-rag/frontend/.gitignore new file mode 100644 index 0000000..4274b51 --- /dev/null +++ b/demo/document-rag/frontend/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +*.log +.DS_Store diff --git a/demo/document-rag/frontend/Dockerfile b/demo/document-rag/frontend/Dockerfile new file mode 100644 index 0000000..de385bc --- /dev/null +++ b/demo/document-rag/frontend/Dockerfile @@ -0,0 +1,5 @@ +FROM nginx:alpine +COPY dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/demo/document-rag/frontend/index.html b/demo/document-rag/frontend/index.html new file mode 100644 index 0000000..70397cf --- /dev/null +++ b/demo/document-rag/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + VortexDB RAG + + +
+ + + diff --git a/demo/document-rag/frontend/nginx.conf b/demo/document-rag/frontend/nginx.conf new file mode 100644 index 0000000..f2f694c --- /dev/null +++ b/demo/document-rag/frontend/nginx.conf @@ -0,0 +1,16 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://backend:8000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} diff --git a/demo/document-rag/frontend/package-lock.json b/demo/document-rag/frontend/package-lock.json new file mode 100644 index 0000000..5f31018 --- /dev/null +++ b/demo/document-rag/frontend/package-lock.json @@ -0,0 +1,1677 @@ +{ + "name": "vortexdb-rag-frontend", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vortexdb-rag-frontend", + "version": "0.0.1", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "vite": "^5.1.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz", + "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.339", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.339.tgz", + "integrity": "sha512-Is+0BBHJ4NrdpAYiperrmp53pLywG/yV/6lIMTAnhxvzj/Cmn5Q/ogSHC6AKe7X+8kPLxxFk0cs5oc/3j/fxIg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/demo/document-rag/frontend/package.json b/demo/document-rag/frontend/package.json new file mode 100644 index 0000000..d0c6096 --- /dev/null +++ b/demo/document-rag/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "vortexdb-rag-frontend", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "vite": "^5.1.0" + } +} diff --git a/demo/document-rag/frontend/src/App.css b/demo/document-rag/frontend/src/App.css new file mode 100644 index 0000000..2cafbeb --- /dev/null +++ b/demo/document-rag/frontend/src/App.css @@ -0,0 +1,592 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --bg-primary: #0d0d0d; + --bg-secondary: #171717; + --bg-tertiary: #1a1a1a; + --bg-hover: #262626; + --bg-input: #262626; + --border-color: #333333; + --border-subtle: #2a2a2a; + --text-primary: #e5e5e5; + --text-secondary: #a0a0a0; + --text-tertiary: #666666; + --accent: #19c37d; + --accent-hover: #15a868; + --error: #ef4444; + --user-bubble: #19c37d; + --shadow: rgba(0, 0, 0, 0.3); +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +.app { + display: flex; + height: 100vh; + overflow: hidden; +} + +/* Sidebar */ +.sidebar { + width: 280px; + background: var(--bg-secondary); + border-right: 1px solid var(--border-color); + display: flex; + flex-direction: column; + flex-shrink: 0; +} + +.sidebar-header { + padding: 20px; + border-bottom: 1px solid var(--border-subtle); +} + +.sidebar-header h1 { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + letter-spacing: -0.02em; +} + +.new-chat-btn { + display: flex; + align-items: center; + gap: 10px; + width: calc(100% - 32px); + margin: 16px; + padding: 12px 16px; + background: transparent; + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-primary); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.new-chat-btn:hover { + background: var(--bg-hover); + border-color: var(--text-tertiary); +} + +.upload-zone { + margin: 0 16px 16px; + padding: 24px; + border: 2px dashed var(--border-color); + border-radius: 12px; + cursor: pointer; + transition: all 0.2s; +} + +.upload-zone:hover, +.upload-zone.dragging { + border-color: var(--accent); + background: rgba(25, 195, 125, 0.05); +} + +.upload-zone input { + display: none; +} + +.upload-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + color: var(--text-secondary); +} + +.upload-content svg { + color: var(--text-tertiary); +} + +.upload-formats { + font-size: 11px; + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.documents-list { + flex: 1; + padding: 0 16px; + overflow-y: auto; +} + +.documents-list h3 { + font-size: 12px; + font-weight: 600; + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 12px; +} + +.no-docs { + font-size: 13px; + color: var(--text-tertiary); + font-style: italic; +} + +.document-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + margin-bottom: 4px; + border-radius: 6px; + font-size: 13px; + color: var(--text-secondary); + cursor: default; +} + +.document-item:hover { + background: var(--bg-hover); +} + +.document-item svg { + flex-shrink: 0; + color: var(--text-tertiary); +} + +.clear-btn { + margin: 16px; + padding: 10px; + background: transparent; + border: 1px solid var(--error); + border-radius: 6px; + color: var(--error); + font-size: 13px; + cursor: pointer; + transition: all 0.2s; +} + +.clear-btn:hover { + background: var(--error); + color: white; +} + +/* Chat Area */ +.chat-area { + flex: 1; + display: flex; + flex-direction: column; + background: var(--bg-primary); + overflow: hidden; +} + +.messages { + flex: 1; + overflow-y: auto; + padding: 24px; +} + +.welcome { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + text-align: center; +} + +.welcome h2 { + font-size: 28px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 12px; + letter-spacing: -0.02em; +} + +.welcome p { + font-size: 15px; + color: var(--text-secondary); +} + +.message { + display: flex; + gap: 16px; + max-width: 800px; + margin: 0 auto 24px; +} + +.message-user { + flex-direction: row-reverse; +} + +.message-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.message-user .message-avatar { + background: var(--accent); + color: white; +} + +.message-assistant .message-avatar { + background: var(--bg-tertiary); + color: var(--text-secondary); +} + +.message-error .message-avatar { + background: rgba(239, 68, 68, 0.2); + color: var(--error); +} + +.message-content { + flex: 1; + min-width: 0; +} + +.message-user .message-content { + text-align: right; +} + +.message-text { + padding: 12px 16px; + border-radius: 12px; + font-size: 14px; + line-height: 1.6; + word-wrap: break-word; +} + +.message-user .message-text { + background: var(--user-bubble); + color: white; + border-bottom-right-radius: 4px; +} + +.message-assistant .message-text { + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-subtle); + border-bottom-left-radius: 4px; +} + +.message-error .message-text { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + color: var(--error); +} + +.sources { + margin-top: 16px; + padding: 12px; + background: var(--bg-secondary); + border: 1px solid var(--border-subtle); + border-radius: 8px; +} + +.sources h4 { + font-size: 12px; + font-weight: 600; + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 10px; +} + +.source-item { + display: flex; + gap: 8px; + padding: 8px 0; + border-bottom: 1px solid var(--border-subtle); + font-size: 13px; +} + +.source-item:last-child { + border-bottom: none; + padding-bottom: 0; +} + +.source-score { + color: var(--accent); + font-weight: 500; + flex-shrink: 0; +} + +.source-text { + color: var(--text-secondary); + word-break: break-word; +} + +/* Typing indicator */ +.typing { + display: flex; + gap: 4px; + padding: 16px 20px; +} + +.typing .dot { + width: 8px; + height: 8px; + background: var(--text-tertiary); + border-radius: 50%; + animation: typing 1.4s infinite; +} + +.typing .dot:nth-child(2) { + animation-delay: 0.2s; +} + +.typing .dot:nth-child(3) { + animation-delay: 0.4s; +} + +@keyframes typing { + 0%, 60%, 100% { + transform: translateY(0); + opacity: 0.4; + } + 30% { + transform: translateY(-4px); + opacity: 1; + } +} + +/* Input Area */ +.input-area { + padding: 16px 24px 24px; + background: var(--bg-primary); +} + +.input-container { + display: flex; + align-items: center; + gap: 12px; + max-width: 800px; + margin: 0 auto; + padding: 12px 16px; + background: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: 12px; + transition: all 0.2s; +} + +.input-container:focus-within { + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(25, 195, 125, 0.1); +} + +.input-container.dragging { + border-color: var(--accent); + background: rgba(25, 195, 125, 0.05); +} + +.input-container input { + flex: 1; + background: transparent; + border: none; + outline: none; + color: var(--text-primary); + font-size: 14px; + font-family: inherit; +} + +.input-container input::placeholder { + color: var(--text-tertiary); +} + +.input-container button { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + background: var(--accent); + border: none; + border-radius: 8px; + color: white; + cursor: pointer; + transition: all 0.2s; +} + +.input-container button:hover:not(:disabled) { + background: var(--accent-hover); +} + +.input-container button:disabled { + background: var(--bg-hover); + color: var(--text-tertiary); + cursor: not-allowed; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-tertiary); +} + +/* Responsive */ +@media (max-width: 768px) { + .sidebar { + display: none; + } +} + +/* Modal */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + backdrop-filter: blur(4px); +} + +.modal { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 16px; + padding: 32px; + min-width: 360px; + max-width: 420px; + text-align: center; +} + +.modal-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; +} + +.modal-spinner { + display: flex; + align-items: center; + justify-content: center; +} + +.spinner { + width: 48px; + height: 48px; + border: 3px solid var(--border-color); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.modal-icon { + display: flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + border-radius: 50%; +} + +.modal-icon.success { + background: rgba(25, 195, 125, 0.15); + color: var(--accent); +} + +.modal-icon.error { + background: rgba(239, 68, 68, 0.15); + color: var(--error); +} + +.modal h3 { + font-size: 20px; + font-weight: 600; + color: var(--text-primary); +} + +.modal-filename { + font-size: 13px; + color: var(--text-secondary); + word-break: break-all; + max-width: 100%; +} + +.modal-detail { + font-size: 14px; + color: var(--text-tertiary); +} + +.modal-progress { + width: 100%; + display: flex; + align-items: center; + gap: 12px; +} + +.progress-bar { + flex: 1; + height: 6px; + background: var(--bg-hover); + border-radius: 3px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: var(--accent); + border-radius: 3px; + transition: width 0.3s ease; +} + +.progress-text { + font-size: 13px; + font-weight: 600; + color: var(--text-secondary); + min-width: 40px; + text-align: right; +} + +.modal-close-btn { + margin-top: 8px; + padding: 10px 24px; + background: var(--bg-hover); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-primary); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.modal-close-btn:hover { + background: var(--error); + border-color: var(--error); +} diff --git a/demo/document-rag/frontend/src/App.jsx b/demo/document-rag/frontend/src/App.jsx new file mode 100644 index 0000000..0ab1726 --- /dev/null +++ b/demo/document-rag/frontend/src/App.jsx @@ -0,0 +1,410 @@ +import { useState, useRef, useEffect } from 'react'; +import './App.css'; + +const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8002'; + +const SUPPORTED_FORMATS = ['.pdf', '.txt', '.md', '.docx', '.csv']; +const IMAGE_FORMATS = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']; + +function App() { + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [uploadedFiles, setUploadedFiles] = useState(new Set()); + const [isDragging, setIsDragging] = useState(false); + const [uploadModal, setUploadModal] = useState(null); + const messagesEndRef = useRef(null); + const fileInputRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const updateModal = (status, progress = null, detail = null) => { + setUploadModal(prev => ({ + ...prev, + status, + progress, + detail, + timestamp: Date.now() + })); + }; + + const handleFileSelect = async (file) => { + const ext = '.' + file.name.split('.').pop().toLowerCase(); + + if (IMAGE_FORMATS.includes(ext)) { + setUploadModal({ + status: 'error', + fileName: file.name, + detail: `Image files are not supported. This model does not support image input. Please upload a text document (${SUPPORTED_FORMATS.join(', ')}).` + }); + return; + } + + if (!SUPPORTED_FORMATS.includes(ext)) { + setUploadModal({ + status: 'error', + fileName: file.name, + detail: `Unsupported format: ${ext}. Please upload ${SUPPORTED_FORMATS.join(', ')} files.` + }); + return; + } + + setUploadModal({ + status: 'uploading', + fileName: file.name, + progress: 0, + detail: 'Reading file...' + }); + + const formData = new FormData(); + formData.append('file', file); + + try { + updateModal('uploading', 10, 'Uploading to server...'); + + const res = await fetch(`${API_URL}/upload`, { + method: 'POST', + body: formData, + }); + + updateModal('processing', 50, 'Processing document...'); + + const data = await res.json(); + + if (!res.ok) { + throw new Error(data.detail || 'Upload failed'); + } + + updateModal('vectors', 75, 'Creating embeddings...'); + + await new Promise(resolve => setTimeout(resolve, 500)); + + updateModal('indexing', 90, 'Indexing vectors...'); + + await new Promise(resolve => setTimeout(resolve, 300)); + + updateModal('complete', 100, `Indexed ${data.chunks} chunks successfully!`); + + setUploadedFiles(prev => new Set([...prev, file.name])); + + setTimeout(() => { + setUploadModal(null); + addMessage('system', `📄 Uploaded: ${file.name} (${data.chunks} chunks indexed)`); + }, 1500); + + } catch (e) { + setUploadModal({ + status: 'error', + fileName: file.name, + detail: e.message + }); + } + }; + + const closeModal = () => { + if (uploadModal?.status !== 'uploading' && uploadModal?.status !== 'processing') { + setUploadModal(null); + } + }; + + const handleDrop = (e) => { + e.preventDefault(); + setIsDragging(false); + const file = e.dataTransfer.files[0]; + if (file) handleFileSelect(file); + }; + + const handleDragOver = (e) => { + e.preventDefault(); + setIsDragging(true); + }; + + const handleDragLeave = () => { + setIsDragging(false); + }; + + const addMessage = (role, content, sources = []) => { + setMessages(prev => [...prev, { role, content, sources, id: Date.now() }]); + }; + + const addError = (content) => { + setMessages(prev => [...prev, { role: 'error', content, id: Date.now() }]); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!input.trim() || isLoading) return; + + const question = input.trim(); + setInput(''); + addMessage('user', question); + setIsLoading(true); + + try { + const res = await fetch(`${API_URL}/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ question }), + }); + + const data = await res.json(); + + if (!res.ok) { + throw new Error(data.detail || 'Request failed'); + } + + addMessage('assistant', data.answer, data.sources || []); + } catch (e) { + addError(e.message); + } finally { + setIsLoading(false); + } + }; + + const clearChat = () => { + setMessages([]); + }; + + const clearAll = async () => { + if (!confirm('Clear all documents and chat?')) return; + + try { + await fetch(`${API_URL}/clear`, { method: 'DELETE' }); + setUploadedFiles(new Set()); + setMessages([]); + } catch (e) { + addError('Failed to clear documents'); + } + }; + + const getStatusIcon = () => { + if (!uploadModal) return null; + + switch (uploadModal.status) { + case 'uploading': + case 'processing': + case 'vectors': + case 'indexing': + return ( +
+
+
+ ); + case 'complete': + return ( +
+ + + + +
+ ); + case 'error': + return ( +
+ + + + + +
+ ); + default: + return null; + } + }; + + const getStatusText = () => { + if (!uploadModal) return ''; + + switch (uploadModal.status) { + case 'uploading': return 'Uploading'; + case 'processing': return 'Processing'; + case 'vectors': return 'Creating Embeddings'; + case 'indexing': return 'Indexing'; + case 'complete': return 'Complete'; + case 'error': return 'Error'; + default: return ''; + } + }; + + return ( +
+ {uploadModal && ( +
+
e.stopPropagation()}> +
+ {getStatusIcon()} +

{getStatusText()}

+

{uploadModal.fileName}

+ + {(uploadModal.status === 'uploading' || uploadModal.status === 'processing' || + uploadModal.status === 'vectors' || uploadModal.status === 'indexing') && ( +
+
+
+
+ {uploadModal.progress}% +
+ )} + +

{uploadModal.detail}

+ + {uploadModal.status === 'error' && ( + + )} +
+
+
+ )} + + + +
+
+ {messages.length === 0 && ( +
+

VortexDB RAG Demo

+

Upload documents and ask questions about them

+
+ )} + + {messages.map((msg, i) => ( +
+
+ {msg.role === 'user' ? ( + + + + ) : msg.role === 'error' ? ( + + + + ) : ( + + + + )} +
+
+
{msg.content}
+ {msg.sources && msg.sources.length > 0 && ( +
+

Sources:

+ {msg.sources.map((s, j) => ( +
+ [{s.score}] + {s.text} +
+ ))} +
+ )} +
+
+ ))} + + {isLoading && ( +
+
+ + + +
+
+
+ + + +
+
+
+ )} +
+
+ +
+
+ setInput(e.target.value)} + placeholder="Ask a question about your documents..." + disabled={isLoading} + /> + +
+
+
+
+ ); +} + +export default App; diff --git a/demo/document-rag/frontend/src/main.jsx b/demo/document-rag/frontend/src/main.jsx new file mode 100644 index 0000000..3d9da8a --- /dev/null +++ b/demo/document-rag/frontend/src/main.jsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/demo/document-rag/frontend/vite.config.js b/demo/document-rag/frontend/vite.config.js new file mode 100644 index 0000000..9d1dd06 --- /dev/null +++ b/demo/document-rag/frontend/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + base: '/', + server: { + port: 5173, + host: true, + }, + build: { + outDir: 'dist', + }, +})