diff --git a/multimodal/README.md b/multimodal/README.md
new file mode 100644
index 00000000..80459daf
--- /dev/null
+++ b/multimodal/README.md
@@ -0,0 +1,13 @@
+# Multimodal ExecuTorch Examples
+
+This directory contains examples demonstrating multimodal AI inference using ExecuTorch with various backends (XNNPACK, Metal).
+
+## Projects
+
+| Directory | Description | Model |
+|-----------|-------------|-------|
+| [ask-anything-app](./ask-anything-app) | Web app with camera + chat interface | Gemma3 Vision + Whisper |
+| [text-runtime](./text-runtime) | Text generation | Qwen3-0.6B |
+| [text-image-runtime](./text-image-runtime) | Vision-language inference | Gemma3 4B |
+| [voice-runtime](./voice-runtime) | Speech-to-text | Whisper Tiny |
+| [object-detection-runtime](./object-detection-runtime) | Object detection | YOLO26m |
diff --git a/multimodal/ask-anything-app/.gitignore b/multimodal/ask-anything-app/.gitignore
new file mode 100644
index 00000000..68aa5fc7
--- /dev/null
+++ b/multimodal/ask-anything-app/.gitignore
@@ -0,0 +1,40 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+.venv/
+venv/
+env/
+
+# Model files
+*.pte
+*.bin
+*.onnx
+*.pt
+*.pth
+*.safetensors
diff --git a/multimodal/ask-anything-app/README.md b/multimodal/ask-anything-app/README.md
new file mode 100644
index 00000000..c8cef48f
--- /dev/null
+++ b/multimodal/ask-anything-app/README.md
@@ -0,0 +1,95 @@
+# Ask Anything - Multimodal Web Dashboard
+
+A two-column web dashboard with real-time camera streaming and Facebook-style chat interface, powered by **Gemma3** (vision-language) and **Whisper** (speech-to-text) ExecuTorch runtimes.
+
+## Features
+
+- Real-time camera streaming with frame capture
+- Facebook-style chat interface (blue user bubbles, gray AI bubbles)
+- Vision-language understanding via Gemma3 4B
+- Speech-to-text transcription via Whisper (optional)
+- Models loaded at startup for fast inference
+
+## Quick Start
+
+### 1. Start the Backend
+
+```bash
+# From the ask-anything-app directory
+cd backend
+
+# Install Python dependencies (if not already)
+pip install -r ../requirements.txt
+
+# Start the FastAPI server
+python -m uvicorn main:app --reload --port 8000
+```
+
+The backend will load the Gemma3 and Whisper models at startup.
+
+### 2. Start the Frontend
+
+```bash
+# From the ask-anything-app directory
+npm install # Install dependencies (first time only)
+npm run dev # Start the dev server
+```
+
+### 3. Open the App
+
+Navigate to http://localhost:5173 in your browser.
+
+- Allow camera access when prompted
+- Type a question and press Enter
+- The current camera frame will be sent to Gemma3 for analysis
+
+## Project Structure
+
+```
+ask-anything-app/
+├── backend/ # FastAPI backend
+│ ├── main.py # App entry point
+│ ├── config.py # Model paths
+│ ├── modules/ # Inference modules
+│ │ ├── base.py # BaseModule interface
+│ │ ├── multimodal/ # Gemma3 module
+│ │ └── voice/ # Whisper module
+│ └── routers/ # API endpoints
+│ ├── health.py # Health check
+│ ├── vision.py # Vision inference
+│ └── speech.py # Speech transcription
+├── src/ # React frontend
+│ ├── components/ # UI components
+│ │ ├── layout/ # SplitLayout
+│ │ ├── camera/ # CameraStream
+│ │ └── chat/ # ChatInterface
+│ ├── contexts/ # Zustand store
+│ ├── hooks/ # Custom hooks
+│ ├── services/ # API client
+│ └── types/ # TypeScript types
+├── package.json
+└── requirements.txt
+```
+
+## API Endpoints
+
+| Endpoint | Method | Description |
+|----------|--------|-------------|
+| `/api/health` | GET | Health check |
+| `/api/status` | GET | Model status |
+| `/api/vision/infer` | POST | Vision-language inference |
+| `/api/speech/transcribe` | POST | Speech-to-text |
+
+## Configuration
+
+Model paths are configured in `backend/config.py`:
+
+- **Gemma3**: `../text-image-runtime/gemma3/GEMMA3_4B_XNNPACK_INT8_INT4.pte`
+- **Whisper**: `../voice-runtime/models/whisper-tiny-ExecuTorch-XNNPACK/`
+
+## Tech Stack
+
+- **Frontend**: React 19 + TypeScript + Vite + Tailwind CSS
+- **State**: Zustand
+- **Backend**: FastAPI + Uvicorn
+- **ML Runtime**: ExecuTorch
diff --git a/multimodal/ask-anything-app/backend/__init__.py b/multimodal/ask-anything-app/backend/__init__.py
new file mode 100644
index 00000000..27923379
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/__init__.py
@@ -0,0 +1 @@
+# Ask Anything Backend
diff --git a/multimodal/ask-anything-app/backend/config.py b/multimodal/ask-anything-app/backend/config.py
new file mode 100644
index 00000000..83a1380b
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/config.py
@@ -0,0 +1,26 @@
+"""Configuration for Ask Anything backend."""
+from pathlib import Path
+
+# Base paths
+APP_DIR = Path(__file__).parent.parent
+MULTIMODAL_DIR = APP_DIR.parent
+
+# Model paths
+GEMMA3_MODEL_PATH = str(
+ MULTIMODAL_DIR / "text-image-runtime" / "gemma3" / "GEMMA3_4B_XNNPACK_INT8_INT4.pte"
+)
+GEMMA3_PROCESSOR_PATH = str(MULTIMODAL_DIR / "text-image-runtime" / "gemma3")
+GEMMA3_HF_MODEL_ID = "google/gemma-3-4b-it"
+
+WHISPER_MODEL_DIR = str(
+ MULTIMODAL_DIR / "voice-runtime" / "models" / "whisper-tiny-ExecuTorch-XNNPACK"
+)
+
+# Server config
+HOST = "0.0.0.0"
+PORT = 8000
+CORS_ORIGINS = [
+ "http://localhost:5173",
+ "http://127.0.0.1:5173",
+ "http://localhost:3000",
+]
diff --git a/multimodal/ask-anything-app/backend/main.py b/multimodal/ask-anything-app/backend/main.py
new file mode 100644
index 00000000..f1578f2c
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/main.py
@@ -0,0 +1,122 @@
+"""
+Ask Anything Backend - FastAPI server for multimodal inference.
+
+This server loads Gemma3 (vision-language) and Whisper (speech-to-text) models
+at startup and provides REST API endpoints for inference.
+
+Usage:
+ From ask-anything-app directory:
+ python -m uvicorn backend.main:app --reload --port 8000
+
+ Or from backend directory:
+ python -m uvicorn main:app --reload --port 8000
+"""
+from contextlib import asynccontextmanager
+
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+import uvicorn
+
+# Use try/except to handle both relative and absolute imports
+try:
+ from .config import HOST, PORT, CORS_ORIGINS
+ from .modules.multimodal import Gemma3Module
+ from .modules.voice import WhisperModule
+ from .routers import health, vision, speech
+except ImportError:
+ from config import HOST, PORT, CORS_ORIGINS
+ from modules.multimodal import Gemma3Module
+ from modules.voice import WhisperModule
+ from routers import health, vision, speech
+
+# Global module instances (loaded at startup)
+gemma3_module = Gemma3Module()
+whisper_module = WhisperModule()
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ """Load models at startup, cleanup on shutdown."""
+ print("=" * 60)
+ print("Ask Anything Backend - Starting up...")
+ print("=" * 60)
+
+ # Load Gemma3 (slower - 3.5GB model)
+ print("\n[1/2] Loading Gemma3 vision-language model...")
+ try:
+ gemma3_module.load()
+ print(" ✓ Gemma3 loaded successfully")
+ except Exception as e:
+ import traceback
+ print(f" ✗ Failed to load Gemma3: {e}")
+ traceback.print_exc()
+
+ # Load Whisper (faster - 231MB model)
+ print("\n[2/2] Loading Whisper speech-to-text model...")
+ try:
+ whisper_module.load()
+ print(" ✓ Whisper loaded successfully")
+ except Exception as e:
+ import traceback
+ print(f" ✗ Failed to load Whisper: {e}")
+ traceback.print_exc()
+
+ print("\n" + "=" * 60)
+ print("Server ready!")
+ print(f" Gemma3: {'✓ Loaded' if gemma3_module.is_loaded else '✗ Not loaded'}")
+ print(f" Whisper: {'✓ Loaded' if whisper_module.is_loaded else '✗ Not loaded'}")
+ print("=" * 60)
+
+ yield
+
+ # Cleanup on shutdown
+ print("\nShutting down...")
+ gemma3_module.unload()
+ whisper_module.unload()
+ print("Goodbye!")
+
+
+# Create FastAPI app
+app = FastAPI(
+ title="Ask Anything API",
+ description="Multimodal inference API for vision-language and speech-to-text using ExecuTorch",
+ version="1.0.0",
+ lifespan=lifespan,
+)
+
+# Configure CORS for React dev server
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=CORS_ORIGINS,
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+# Store module references in app state for access in routes
+app.state.gemma3 = gemma3_module
+app.state.whisper = whisper_module
+
+# Include routers
+app.include_router(health.router, prefix="/api", tags=["Health"])
+app.include_router(vision.router, prefix="/api/vision", tags=["Vision"])
+app.include_router(speech.router, prefix="/api/speech", tags=["Speech"])
+
+
+@app.get("/")
+async def root():
+ """Root endpoint with API information."""
+ return {
+ "name": "Ask Anything API",
+ "version": "1.0.0",
+ "endpoints": {
+ "health": "/api/health",
+ "status": "/api/status",
+ "vision": "/api/vision/infer",
+ "speech": "/api/speech/transcribe",
+ },
+ }
+
+
+if __name__ == "__main__":
+ uvicorn.run(app, host=HOST, port=PORT)
diff --git a/multimodal/ask-anything-app/backend/modules/__init__.py b/multimodal/ask-anything-app/backend/modules/__init__.py
new file mode 100644
index 00000000..20b3dc27
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/modules/__init__.py
@@ -0,0 +1,7 @@
+"""Inference modules for Ask Anything."""
+try:
+ from .base import BaseModule
+except ImportError:
+ from base import BaseModule
+
+__all__ = ["BaseModule"]
diff --git a/multimodal/ask-anything-app/backend/modules/base.py b/multimodal/ask-anything-app/backend/modules/base.py
new file mode 100644
index 00000000..fa839d9a
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/modules/base.py
@@ -0,0 +1,53 @@
+"""Abstract base class for inference modules."""
+from abc import ABC, abstractmethod
+from typing import Any, Dict
+
+
+class BaseModule(ABC):
+ """Abstract base class for all inference modules.
+
+ All modules must implement load(), unload(), and infer() methods
+ to provide a consistent interface.
+ """
+
+ def __init__(self):
+ self._loaded = False
+ self._model = None
+
+ @abstractmethod
+ def load(self, **kwargs) -> None:
+ """Load the model into memory.
+
+ Args:
+ **kwargs: Model-specific configuration (paths, etc.)
+ """
+ pass
+
+ @abstractmethod
+ def unload(self) -> None:
+ """Unload the model and free resources."""
+ pass
+
+ @abstractmethod
+ def infer(self, **kwargs) -> Any:
+ """Run inference on input data.
+
+ Args:
+ **kwargs: Model-specific input parameters
+
+ Returns:
+ Model-specific output
+ """
+ pass
+
+ @property
+ def is_loaded(self) -> bool:
+ """Check if the model is loaded."""
+ return self._loaded
+
+ def get_status(self) -> Dict[str, Any]:
+ """Get module status information."""
+ return {
+ "loaded": self._loaded,
+ "model_type": self.__class__.__name__,
+ }
diff --git a/multimodal/ask-anything-app/backend/modules/multimodal/__init__.py b/multimodal/ask-anything-app/backend/modules/multimodal/__init__.py
new file mode 100644
index 00000000..aeb99880
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/modules/multimodal/__init__.py
@@ -0,0 +1,7 @@
+"""Multimodal (vision-language) modules."""
+try:
+ from .gemma3_module import Gemma3Module
+except ImportError:
+ from gemma3_module import Gemma3Module
+
+__all__ = ["Gemma3Module"]
diff --git a/multimodal/ask-anything-app/backend/modules/multimodal/gemma3_module.py b/multimodal/ask-anything-app/backend/modules/multimodal/gemma3_module.py
new file mode 100644
index 00000000..ac905c4f
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/modules/multimodal/gemma3_module.py
@@ -0,0 +1,125 @@
+"""Gemma3 vision-language module using ExecutorTorch runtime."""
+import base64
+import io
+import sys
+import time
+from pathlib import Path
+from typing import Any, Dict, Optional
+
+from PIL import Image
+
+# Handle both relative and absolute imports
+try:
+ from ..base import BaseModule
+ from ...config import GEMMA3_MODEL_PATH, GEMMA3_PROCESSOR_PATH, GEMMA3_HF_MODEL_ID
+except ImportError:
+ from modules.base import BaseModule
+ from config import GEMMA3_MODEL_PATH, GEMMA3_PROCESSOR_PATH, GEMMA3_HF_MODEL_ID
+
+# Add the runtime directories to path for imports
+# Path: gemma3_module.py -> multimodal -> modules -> backend -> ask-anything-app -> multimodal (project)
+MULTIMODAL_DIR = Path(__file__).parent.parent.parent.parent.parent
+sys.path.insert(0, str(MULTIMODAL_DIR / "text-image-runtime"))
+
+
+class Gemma3Module(BaseModule):
+ """Vision-language module using Gemma3 4B model."""
+
+ def __init__(self):
+ super().__init__()
+ self._runner = None
+
+ def load(
+ self,
+ model_path: Optional[str] = None,
+ processor_path: Optional[str] = None,
+ hf_model_id: Optional[str] = None,
+ ) -> None:
+ """Load Gemma3 model.
+
+ Args:
+ model_path: Path to the .pte model file
+ processor_path: Path to processor/tokenizer directory
+ hf_model_id: HuggingFace model ID for tokenizer fallback
+ """
+ if self._loaded:
+ print("Gemma3 model already loaded")
+ return
+
+ # Use defaults if not provided
+ model_path = model_path or GEMMA3_MODEL_PATH
+ processor_path = processor_path or GEMMA3_PROCESSOR_PATH
+ hf_model_id = hf_model_id or GEMMA3_HF_MODEL_ID
+
+ print(f"Loading Gemma3 model from {model_path}...")
+ load_start = time.time()
+
+ # Import the runtime module
+ from runtime_inference import Gemma3RuntimeRunner
+
+ self._runner = Gemma3RuntimeRunner(
+ model_path=model_path,
+ processor_path=processor_path,
+ hf_model_id=hf_model_id,
+ )
+
+ load_time = time.time() - load_start
+ print(f"Gemma3 model loaded in {load_time:.2f}s")
+ self._loaded = True
+
+ def unload(self) -> None:
+ """Unload the model and free resources."""
+ self._runner = None
+ self._loaded = False
+ print("Gemma3 model unloaded")
+
+ def infer(
+ self,
+ prompt: str,
+ image: Optional[Image.Image] = None,
+ image_base64: Optional[str] = None,
+ max_new_tokens: int = 256,
+ temperature: float = 0.7,
+ ) -> str:
+ """Run vision-language inference.
+
+ Args:
+ prompt: Text prompt for the model
+ image: Optional PIL Image
+ image_base64: Optional base64-encoded image string
+ max_new_tokens: Maximum tokens to generate
+ temperature: Sampling temperature (0 = greedy)
+
+ Returns:
+ Generated text response
+ """
+ if not self._loaded or self._runner is None:
+ raise RuntimeError("Gemma3 model not loaded. Call load() first.")
+
+ # Decode base64 image if provided
+ if image_base64 and image is None:
+ try:
+ image_data = base64.b64decode(image_base64)
+ image = Image.open(io.BytesIO(image_data))
+ except Exception as e:
+ print(f"Warning: Failed to decode base64 image: {e}")
+ image = None
+
+ # Run inference
+ response = self._runner.generate(
+ prompt=prompt,
+ image=image,
+ max_new_tokens=max_new_tokens,
+ temperature=temperature,
+ echo=False, # Disable console output in server mode
+ )
+
+ return response
+
+ def get_status(self) -> Dict[str, Any]:
+ """Get detailed module status."""
+ status = super().get_status()
+ if self._runner:
+ status["max_seq_len"] = getattr(self._runner, "max_seq_len", None)
+ status["vision_token_id"] = getattr(self._runner, "vision_token_id", None)
+ return status
diff --git a/multimodal/ask-anything-app/backend/modules/voice/__init__.py b/multimodal/ask-anything-app/backend/modules/voice/__init__.py
new file mode 100644
index 00000000..d61e6224
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/modules/voice/__init__.py
@@ -0,0 +1,7 @@
+"""Voice (speech-to-text) modules."""
+try:
+ from .whisper_module import WhisperModule
+except ImportError:
+ from whisper_module import WhisperModule
+
+__all__ = ["WhisperModule"]
diff --git a/multimodal/ask-anything-app/backend/modules/voice/whisper_module.py b/multimodal/ask-anything-app/backend/modules/voice/whisper_module.py
new file mode 100644
index 00000000..e76fbd33
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/modules/voice/whisper_module.py
@@ -0,0 +1,116 @@
+"""Whisper speech-to-text module using ExecutorTorch runtime."""
+import sys
+import tempfile
+import time
+from pathlib import Path
+from typing import Any, Dict, Optional
+
+import torch
+
+# Handle both relative and absolute imports
+try:
+ from ..base import BaseModule
+ from ...config import WHISPER_MODEL_DIR
+except ImportError:
+ from modules.base import BaseModule
+ from config import WHISPER_MODEL_DIR
+
+# Add the runtime directories to path for imports
+# Path: whisper_module.py -> voice -> modules -> backend -> ask-anything-app -> multimodal (project)
+MULTIMODAL_DIR = Path(__file__).parent.parent.parent.parent.parent
+sys.path.insert(0, str(MULTIMODAL_DIR / "voice-runtime"))
+
+
+class WhisperModule(BaseModule):
+ """Speech-to-text module using Whisper model."""
+
+ def __init__(self):
+ super().__init__()
+ self._runner = None
+ self._load_audio = None
+
+ def load(
+ self,
+ model_dir: Optional[str] = None,
+ ) -> None:
+ """Load Whisper model.
+
+ Args:
+ model_dir: Path to the model directory containing model.pte and tokenizer
+ """
+ if self._loaded:
+ print("Whisper model already loaded")
+ return
+
+ model_dir = Path(model_dir or WHISPER_MODEL_DIR)
+
+ print(f"Loading Whisper model from {model_dir}...")
+ load_start = time.time()
+
+ # Import the runtime module
+ from whisper_runtime_inference import WhisperRuntimeRunner, load_audio
+
+ self._runner = WhisperRuntimeRunner(
+ model_path=str(model_dir / "model.pte"),
+ preprocessor_path=str(model_dir / "whisper_preprocessor.pte"),
+ tokenizer_path=str(model_dir),
+ )
+ self._load_audio = load_audio
+
+ load_time = time.time() - load_start
+ print(f"Whisper model loaded in {load_time:.2f}s")
+ self._loaded = True
+
+ def unload(self) -> None:
+ """Unload the model and free resources."""
+ self._runner = None
+ self._load_audio = None
+ self._loaded = False
+ print("Whisper model unloaded")
+
+ def infer(
+ self,
+ audio_bytes: Optional[bytes] = None,
+ audio_tensor: Optional[torch.Tensor] = None,
+ max_new_tokens: int = 448,
+ ) -> str:
+ """Transcribe audio to text.
+
+ Args:
+ audio_bytes: Raw audio bytes (WAV format)
+ audio_tensor: Pre-loaded audio tensor
+ max_new_tokens: Maximum tokens to generate
+
+ Returns:
+ Transcribed text
+ """
+ if not self._loaded or self._runner is None:
+ raise RuntimeError("Whisper model not loaded. Call load() first.")
+
+ # Convert bytes to tensor if needed
+ if audio_bytes is not None and audio_tensor is None:
+ # Write to temp file and load with proper resampling
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=True) as f:
+ f.write(audio_bytes)
+ f.flush()
+ audio_tensor = self._load_audio(f.name)
+
+ if audio_tensor is None:
+ raise ValueError("Either audio_bytes or audio_tensor must be provided")
+
+ # Run transcription
+ transcription = self._runner.transcribe(
+ audio=audio_tensor,
+ max_new_tokens=max_new_tokens,
+ echo=False, # Disable console output in server mode
+ )
+
+ return transcription
+
+ def get_status(self) -> Dict[str, Any]:
+ """Get detailed module status."""
+ status = super().get_status()
+ if self._runner:
+ status["max_seq_len"] = getattr(self._runner, "max_seq_len", None)
+ status["eos_token_id"] = getattr(self._runner, "eos_token_id", None)
+ return status
diff --git a/multimodal/ask-anything-app/backend/routers/__init__.py b/multimodal/ask-anything-app/backend/routers/__init__.py
new file mode 100644
index 00000000..548d9a6f
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/routers/__init__.py
@@ -0,0 +1 @@
+"""API routers for Ask Anything."""
diff --git a/multimodal/ask-anything-app/backend/routers/health.py b/multimodal/ask-anything-app/backend/routers/health.py
new file mode 100644
index 00000000..7488bb92
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/routers/health.py
@@ -0,0 +1,19 @@
+"""Health check and status endpoints."""
+from fastapi import APIRouter, Request
+
+router = APIRouter()
+
+
+@router.get("/health")
+async def health_check():
+ """Check if the server is running."""
+ return {"status": "healthy"}
+
+
+@router.get("/status")
+async def model_status(request: Request):
+ """Get status of all loaded models."""
+ return {
+ "gemma3": request.app.state.gemma3.get_status(),
+ "whisper": request.app.state.whisper.get_status(),
+ }
diff --git a/multimodal/ask-anything-app/backend/routers/speech.py b/multimodal/ask-anything-app/backend/routers/speech.py
new file mode 100644
index 00000000..f6ff8575
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/routers/speech.py
@@ -0,0 +1,36 @@
+"""Speech-to-text transcription endpoint."""
+from fastapi import APIRouter, Request, UploadFile, File, HTTPException
+from pydantic import BaseModel
+
+router = APIRouter()
+
+
+class TranscriptionResponse(BaseModel):
+ """Response from speech transcription."""
+
+ transcription: str
+
+
+@router.post("/transcribe", response_model=TranscriptionResponse)
+async def transcribe_audio(request: Request, audio: UploadFile = File(...)):
+ """Transcribe audio file to text.
+
+ Args:
+ audio: Audio file upload (WAV, MP3, etc.)
+
+ Returns:
+ Transcribed text from Whisper
+ """
+ whisper = request.app.state.whisper
+
+ if not whisper.is_loaded:
+ raise HTTPException(status_code=503, detail="Whisper model not loaded")
+
+ try:
+ audio_bytes = await audio.read()
+ transcription = whisper.infer(audio_bytes=audio_bytes)
+
+ return TranscriptionResponse(transcription=transcription)
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Transcription failed: {str(e)}")
diff --git a/multimodal/ask-anything-app/backend/routers/vision.py b/multimodal/ask-anything-app/backend/routers/vision.py
new file mode 100644
index 00000000..ecc65fee
--- /dev/null
+++ b/multimodal/ask-anything-app/backend/routers/vision.py
@@ -0,0 +1,54 @@
+"""Vision-language inference endpoint."""
+from fastapi import APIRouter, Request, HTTPException
+from pydantic import BaseModel
+from typing import Optional
+
+router = APIRouter()
+
+
+class VisionRequest(BaseModel):
+ """Request body for vision inference."""
+
+ prompt: str
+ image_base64: Optional[str] = None
+ max_new_tokens: int = 256
+ temperature: float = 0.7
+
+
+class VisionResponse(BaseModel):
+ """Response from vision inference."""
+
+ response: str
+ tokens_generated: int
+
+
+@router.post("/infer", response_model=VisionResponse)
+async def vision_infer(request: Request, body: VisionRequest):
+ """Run vision-language inference on an image with a text prompt.
+
+ Args:
+ body: Request with prompt and optional base64 image
+
+ Returns:
+ Generated text response from Gemma3
+ """
+ gemma3 = request.app.state.gemma3
+
+ if not gemma3.is_loaded:
+ raise HTTPException(status_code=503, detail="Gemma3 model not loaded")
+
+ try:
+ response = gemma3.infer(
+ prompt=body.prompt,
+ image_base64=body.image_base64,
+ max_new_tokens=body.max_new_tokens,
+ temperature=body.temperature,
+ )
+
+ # Approximate token count from response length
+ tokens_generated = len(response.split())
+
+ return VisionResponse(response=response, tokens_generated=tokens_generated)
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Inference failed: {str(e)}")
diff --git a/multimodal/ask-anything-app/eslint.config.js b/multimodal/ask-anything-app/eslint.config.js
new file mode 100644
index 00000000..5e6b472f
--- /dev/null
+++ b/multimodal/ask-anything-app/eslint.config.js
@@ -0,0 +1,23 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/multimodal/ask-anything-app/index.html b/multimodal/ask-anything-app/index.html
new file mode 100644
index 00000000..48540a7e
--- /dev/null
+++ b/multimodal/ask-anything-app/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Ask Anything - Multimodal Dashboard
+
+
+
+
+
+
diff --git a/multimodal/ask-anything-app/package-lock.json b/multimodal/ask-anything-app/package-lock.json
new file mode 100644
index 00000000..3f95abba
--- /dev/null
+++ b/multimodal/ask-anything-app/package-lock.json
@@ -0,0 +1,4188 @@
+{
+ "name": "ask-anything-app",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ask-anything-app",
+ "version": "1.0.0",
+ "dependencies": {
+ "axios": "^1.7.0",
+ "liquid-glass-react": "^1.1.1",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "zustand": "^5.0.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@tailwindcss/vite": "^4.0.0",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "tailwindcss": "^4.0.0",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.48.0",
+ "vite": "^7.3.1"
+ }
+ },
+ "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.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "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-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+ "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
+ "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
+ "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-x64": "4.1.18",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.18",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
+ "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
+ "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
+ "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
+ "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
+ "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
+ "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
+ "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
+ "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
+ "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
+ "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.0",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
+ "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
+ "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz",
+ "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.18",
+ "@tailwindcss/oxide": "4.1.18",
+ "tailwindcss": "4.1.18"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.10.13",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
+ "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
+ "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.55.0",
+ "@typescript-eslint/type-utils": "8.55.0",
+ "@typescript-eslint/utils": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.55.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz",
+ "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.55.0",
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz",
+ "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.55.0",
+ "@typescript-eslint/types": "^8.55.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz",
+ "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz",
+ "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz",
+ "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0",
+ "@typescript-eslint/utils": "8.55.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz",
+ "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz",
+ "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.55.0",
+ "@typescript-eslint/tsconfig-utils": "8.55.0",
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0",
+ "debug": "^4.4.3",
+ "minimatch": "^9.0.5",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz",
+ "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.55.0",
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz",
+ "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.55.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
+ "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
+ "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.19",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001769",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
+ "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
+ "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/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": 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/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.286",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.19.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
+ "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "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/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.2",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.26",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
+ "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "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/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.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/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/liquid-glass-react": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/liquid-glass-react/-/liquid-glass-react-1.1.1.tgz",
+ "integrity": "sha512-pKzaktaMAEztd93wpWcz2Z5Z9qdLJUNJdMX+n00Ca4XsnrLTQ5xJzm/+GQXZUeuFXe/PQ8ziVMZO6531PyaFJw==",
+ "license": "MIT",
+ "workspaces": [
+ "liquid-glass"
+ ],
+ "peerDependencies": {
+ "react": ">=19",
+ "react-dom": ">=19"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
+ "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz",
+ "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.55.0",
+ "@typescript-eslint/parser": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0",
+ "@typescript-eslint/utils": "8.55.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.11",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz",
+ "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/multimodal/ask-anything-app/package.json b/multimodal/ask-anything-app/package.json
new file mode 100644
index 00000000..ae6a387b
--- /dev/null
+++ b/multimodal/ask-anything-app/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "ask-anything-app",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "axios": "^1.7.0",
+ "liquid-glass-react": "^1.1.1",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "zustand": "^5.0.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@tailwindcss/vite": "^4.0.0",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "tailwindcss": "^4.0.0",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.48.0",
+ "vite": "^7.3.1"
+ }
+}
diff --git a/multimodal/ask-anything-app/public/vite.svg b/multimodal/ask-anything-app/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/multimodal/ask-anything-app/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/multimodal/ask-anything-app/requirements.txt b/multimodal/ask-anything-app/requirements.txt
new file mode 100644
index 00000000..dcf351dc
--- /dev/null
+++ b/multimodal/ask-anything-app/requirements.txt
@@ -0,0 +1,10 @@
+# Ask Anything Backend Dependencies
+fastapi>=0.109.0
+uvicorn[standard]>=0.27.0
+python-multipart>=0.0.6
+pillow>=10.0.0
+pydantic>=2.0.0
+
+# ExecuTorch dependencies (assumed installed in environment)
+# torch
+# transformers
diff --git a/multimodal/ask-anything-app/src/App.tsx b/multimodal/ask-anything-app/src/App.tsx
new file mode 100644
index 00000000..48708097
--- /dev/null
+++ b/multimodal/ask-anything-app/src/App.tsx
@@ -0,0 +1,14 @@
+/**
+ * Ask Anything - Multimodal Web Dashboard
+ *
+ * A full-screen glassmorphic app with:
+ * - Full-screen camera background
+ * - Floating glass chat panel powered by Gemma 3 Vision
+ */
+import { GlassLayout } from "./components/layout/GlassLayout";
+
+function App() {
+ return ;
+}
+
+export default App;
diff --git a/multimodal/ask-anything-app/src/assets/react.svg b/multimodal/ask-anything-app/src/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/multimodal/ask-anything-app/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/multimodal/ask-anything-app/src/components/camera/CameraStream.tsx b/multimodal/ask-anything-app/src/components/camera/CameraStream.tsx
new file mode 100644
index 00000000..84c2d93e
--- /dev/null
+++ b/multimodal/ask-anything-app/src/components/camera/CameraStream.tsx
@@ -0,0 +1,203 @@
+/**
+ * Camera stream component with real-time video and frame capture.
+ * Supports fullscreen mode for glass layout.
+ */
+import { useEffect, useRef, useCallback, useState } from "react";
+import { useAppStore } from "../../contexts/AppContext";
+
+interface CameraStreamProps {
+ fullscreen?: boolean;
+}
+
+export function CameraStream({ fullscreen = false }: CameraStreamProps) {
+ const videoRef = useRef(null);
+ const canvasRef = useRef(null);
+ const { camera, setCameraStream, setCameraError, frozenFrame } = useAppStore();
+ const [isInitializing, setIsInitializing] = useState(true);
+
+ const startCamera = useCallback(async () => {
+ try {
+ setIsInitializing(true);
+ const stream = await navigator.mediaDevices.getUserMedia({
+ video: { width: 1280, height: 720, facingMode: "user" },
+ });
+ if (videoRef.current) {
+ videoRef.current.srcObject = stream;
+ }
+ setCameraStream(stream);
+ setIsInitializing(false);
+ } catch (error) {
+ setCameraError("Failed to access camera. Please allow camera access.");
+ setIsInitializing(false);
+ }
+ }, [setCameraStream, setCameraError]);
+
+ const stopCamera = useCallback(() => {
+ if (camera.stream) {
+ camera.stream.getTracks().forEach((track) => track.stop());
+ }
+ setCameraStream(null);
+ }, [camera.stream, setCameraStream]);
+
+ useEffect(() => {
+ startCamera();
+ return () => stopCamera();
+ }, []);
+
+ // Fullscreen mode - minimal UI, glass layout handles overlays
+ if (fullscreen) {
+ return (
+
+ {/* Video */}
+
+
+ {/* Frozen frame overlay while processing */}
+ {frozenFrame && (
+

+ )}
+
+ {/* Hidden canvas for frame capture */}
+
+
+ {/* Loading state */}
+ {isInitializing && !camera.error && (
+
+
+
+
Starting camera...
+
+ Please allow camera access when prompted
+
+
+
+ )}
+
+ );
+ }
+
+ // Standard mode with full UI (legacy)
+ return (
+
+ {/* Header overlay */}
+
+
+
+
+
+ {camera.isActive ? "Live Camera" : "Camera Inactive"}
+
+
+ {camera.isActive && (
+
+ Frame captured on send
+
+ )}
+
+
+
+ {/* Video */}
+
+
+ {/* Hidden canvas for frame capture */}
+
+
+ {/* Error state */}
+ {camera.error && (
+
+
+
+
+ Camera Unavailable
+
+
+ {camera.error}
+
+
+
+
+ )}
+
+ {/* Loading state */}
+ {isInitializing && !camera.error && (
+
+
+
+
Starting camera...
+
+ Please allow camera access when prompted
+
+
+
+ )}
+
+ {/* Bottom gradient for visual depth */}
+
+
+ );
+}
+
+/**
+ * Hook to capture the current camera frame.
+ */
+export function useCameraCapture() {
+ const captureFrame = useCallback((): string | null => {
+ // Find video element in the DOM
+ const video = document.querySelector("video");
+ const canvas = document.createElement("canvas");
+
+ if (!video || video.videoWidth === 0) return null;
+
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return null;
+
+ ctx.drawImage(video, 0, 0);
+ return canvas.toDataURL("image/jpeg", 0.8).split(",")[1]; // Base64 only
+ }, []);
+
+ return { captureFrame };
+}
diff --git a/multimodal/ask-anything-app/src/components/chat/ChatInput.tsx b/multimodal/ask-anything-app/src/components/chat/ChatInput.tsx
new file mode 100644
index 00000000..02c3bb2e
--- /dev/null
+++ b/multimodal/ask-anything-app/src/components/chat/ChatInput.tsx
@@ -0,0 +1,141 @@
+/**
+ * Chat input with Enter key handling, send button, and voice recording button.
+ * Glass-styled controls using CSS glassmorphism.
+ */
+import { useState, useRef, type KeyboardEvent } from "react";
+
+interface ChatInputProps {
+ onSend: (message: string) => void;
+ onVoiceResult?: (transcription: string) => void;
+ disabled?: boolean;
+}
+
+export function ChatInput({ onSend, onVoiceResult, disabled }: ChatInputProps) {
+ const [input, setInput] = useState("");
+ const [isRecording, setIsRecording] = useState(false);
+ const mediaRecorderRef = useRef(null);
+ const chunksRef = useRef([]);
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === "Enter" && !e.shiftKey && input.trim()) {
+ e.preventDefault();
+ onSend(input.trim());
+ setInput("");
+ }
+ };
+
+ const handleSend = () => {
+ if (input.trim()) {
+ onSend(input.trim());
+ setInput("");
+ }
+ };
+
+ const startRecording = async () => {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ const mediaRecorder = new MediaRecorder(stream);
+ mediaRecorderRef.current = mediaRecorder;
+ chunksRef.current = [];
+
+ mediaRecorder.ondataavailable = (e) => {
+ if (e.data.size > 0) {
+ chunksRef.current.push(e.data);
+ }
+ };
+
+ mediaRecorder.onstop = async () => {
+ const audioBlob = new Blob(chunksRef.current, { type: "audio/wav" });
+ stream.getTracks().forEach((track) => track.stop());
+
+ // Call the voice result callback if provided
+ if (onVoiceResult) {
+ try {
+ // Import api here to avoid circular deps
+ const { api } = await import("../../services/api");
+ const result = await api.transcribe(audioBlob);
+ if (result.transcription) {
+ onVoiceResult(result.transcription);
+ setInput(result.transcription);
+ }
+ } catch (error) {
+ console.error("Transcription failed:", error);
+ }
+ }
+ };
+
+ mediaRecorder.start();
+ setIsRecording(true);
+ } catch (error) {
+ console.error("Failed to start recording:", error);
+ }
+ };
+
+ const stopRecording = () => {
+ if (mediaRecorderRef.current && isRecording) {
+ mediaRecorderRef.current.stop();
+ setIsRecording(false);
+ }
+ };
+
+ const toggleRecording = () => {
+ if (isRecording) {
+ stopRecording();
+ } else {
+ startRecording();
+ }
+ };
+
+ return (
+
+
+ {/* Voice recording button */}
+
+
+ {/* Input field with Send button inside */}
+
+ setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ disabled={disabled || isRecording}
+ placeholder={
+ isRecording ? "Recording..." : "Ask anything about what you see..."
+ }
+ className="flex-1 py-2 bg-transparent border-0 text-white placeholder:text-white/40 focus:outline-none text-[14px]"
+ />
+
+ {/* Send button inside input */}
+
+
+
+
+ {/* Recording indicator */}
+ {isRecording && (
+
+ Recording... Click "Stop" to finish
+
+ )}
+
+ );
+}
diff --git a/multimodal/ask-anything-app/src/components/chat/ChatInterface.tsx b/multimodal/ask-anything-app/src/components/chat/ChatInterface.tsx
new file mode 100644
index 00000000..72a7afd1
--- /dev/null
+++ b/multimodal/ask-anything-app/src/components/chat/ChatInterface.tsx
@@ -0,0 +1,125 @@
+/**
+ * Main chat interface with messages and input.
+ * Designed for glass layout with transparent backgrounds.
+ */
+import { useRef, useEffect } from "react";
+import { useAppStore } from "../../contexts/AppContext";
+import { useCameraCapture } from "../camera/CameraStream";
+import { api } from "../../services/api";
+import { ChatMessage } from "./ChatMessage";
+import { ChatInput } from "./ChatInput";
+import { ThinkingIndicator } from "./ThinkingIndicator";
+
+export function ChatInterface() {
+ const { messages, isThinking, addMessage, setIsThinking, setFrozenFrame } = useAppStore();
+ const { captureFrame } = useCameraCapture();
+ const messagesEndRef = useRef(null);
+
+ // Auto-scroll to bottom on new messages
+ useEffect(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ }, [messages, isThinking]);
+
+ const handleSend = async (content: string) => {
+ // Capture current frame when user sends message
+ const imageBase64 = captureFrame();
+
+ // Freeze the camera on this frame while processing
+ if (imageBase64) {
+ setFrozenFrame(imageBase64);
+ }
+
+ // Add user message with captured image
+ addMessage({
+ role: "user",
+ content,
+ imageBase64: imageBase64 || undefined,
+ });
+
+ // Set thinking state
+ setIsThinking(true);
+
+ try {
+ // Call vision API
+ const response = await api.visionInfer({
+ prompt: content,
+ image_base64: imageBase64 || undefined,
+ max_new_tokens: 256,
+ temperature: 0.7,
+ });
+
+ // Add assistant response
+ addMessage({ role: "assistant", content: response.response });
+ } catch (error) {
+ console.error("Vision inference failed:", error);
+ addMessage({
+ role: "assistant",
+ content:
+ "Sorry, I encountered an error processing your request. Please make sure the backend server is running.",
+ });
+ } finally {
+ setIsThinking(false);
+ // Unfreeze camera when done
+ setFrozenFrame(null);
+ }
+ };
+
+ const handleVoiceResult = (transcription: string) => {
+ console.log("Voice transcription:", transcription);
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
Ask Anything
+
Powered by Gemma 3 Vision
+
+
+
+ {/* Messages */}
+
+ {messages.length === 0 && (
+
+
+ Welcome!
+
+
+ Ask any question about what you see in the camera. I'll analyze
+ the current frame and provide an answer.
+
+
+
+ "What do you see?"
+
+
+ "How many people?"
+
+
+ "Describe this"
+
+
+
+ )}
+
+ {messages.map((message) => (
+
+ ))}
+
+ {isThinking &&
}
+
+
+
+
+ {/* Input */}
+
+
+
+
+ );
+}
diff --git a/multimodal/ask-anything-app/src/components/chat/ChatMessage.tsx b/multimodal/ask-anything-app/src/components/chat/ChatMessage.tsx
new file mode 100644
index 00000000..e4edb84c
--- /dev/null
+++ b/multimodal/ask-anything-app/src/components/chat/ChatMessage.tsx
@@ -0,0 +1,61 @@
+/**
+ * Individual chat message bubble with glass styling.
+ * Shows captured image for user messages when available.
+ */
+import type { Message } from "../../types";
+
+interface ChatMessageProps {
+ message: Message;
+}
+
+export function ChatMessage({ message }: ChatMessageProps) {
+ const isUser = message.role === "user";
+
+ return (
+
+
+ {/* Show captured image for user messages */}
+ {isUser && message.imageBase64 && (
+
+

+
+ )}
+
+ {/* Message bubble with glass effect */}
+
+
+ {message.content}
+
+
+
+ {/* Timestamp */}
+
+ {message.timestamp.toLocaleTimeString([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+
+
+ );
+}
diff --git a/multimodal/ask-anything-app/src/components/chat/ThinkingIndicator.tsx b/multimodal/ask-anything-app/src/components/chat/ThinkingIndicator.tsx
new file mode 100644
index 00000000..b9fc0f01
--- /dev/null
+++ b/multimodal/ask-anything-app/src/components/chat/ThinkingIndicator.tsx
@@ -0,0 +1,28 @@
+/**
+ * Thinking indicator with animated dots and glass effect.
+ */
+export function ThinkingIndicator() {
+ return (
+
+
+
+
+ Gemma is thinking...
+
+
+
+ );
+}
diff --git a/multimodal/ask-anything-app/src/components/layout/GlassLayout.tsx b/multimodal/ask-anything-app/src/components/layout/GlassLayout.tsx
new file mode 100644
index 00000000..f5b7a297
--- /dev/null
+++ b/multimodal/ask-anything-app/src/components/layout/GlassLayout.tsx
@@ -0,0 +1,73 @@
+/**
+ * Glass layout with full-screen camera background and floating glass panels.
+ * Uses CSS glassmorphism for compatibility with video backgrounds.
+ */
+import { CameraStream } from "../camera/CameraStream";
+import { ChatInterface } from "../chat/ChatInterface";
+import { useAppStore } from "../../contexts/AppContext";
+
+export function GlassLayout() {
+ const { camera, frozenFrame } = useAppStore();
+
+ return (
+
+ {/* Full-screen camera background */}
+
+
+
+
+ {/* Glass header bar */}
+
+
+
+
+
+ {frozenFrame
+ ? "Processing..."
+ : camera.isActive
+ ? "Live Camera"
+ : "Camera Inactive"}
+
+
+ {camera.isActive && !frozenFrame && (
+
+ Frame captured on send
+
+ )}
+ {frozenFrame && (
+
+ Analyzing frame...
+
+ )}
+
+
+
+ {/* Glass chat panel - right side */}
+
+
+
+
+ {/* Camera error overlay */}
+ {camera.error && (
+
+
+
+ Camera Unavailable
+
+
+ {camera.error}
+
+
+
+ )}
+
+ );
+}
diff --git a/multimodal/ask-anything-app/src/components/layout/SplitLayout.tsx b/multimodal/ask-anything-app/src/components/layout/SplitLayout.tsx
new file mode 100644
index 00000000..49d29981
--- /dev/null
+++ b/multimodal/ask-anything-app/src/components/layout/SplitLayout.tsx
@@ -0,0 +1,18 @@
+/**
+ * Two-column split layout component.
+ */
+import type { ReactNode } from "react";
+
+interface SplitLayoutProps {
+ left: ReactNode;
+ right: ReactNode;
+}
+
+export function SplitLayout({ left, right }: SplitLayoutProps) {
+ return (
+
+ );
+}
diff --git a/multimodal/ask-anything-app/src/contexts/AppContext.tsx b/multimodal/ask-anything-app/src/contexts/AppContext.tsx
new file mode 100644
index 00000000..f7dfb18c
--- /dev/null
+++ b/multimodal/ask-anything-app/src/contexts/AppContext.tsx
@@ -0,0 +1,77 @@
+/**
+ * Global state management using Zustand.
+ */
+import { create } from "zustand";
+import type { Message, AppStatus, CameraState } from "../types";
+
+interface AppState {
+ // Messages
+ messages: Message[];
+ addMessage: (message: Omit) => void;
+ clearMessages: () => void;
+
+ // Loading states
+ isThinking: boolean;
+ setIsThinking: (thinking: boolean) => void;
+
+ // Frozen frame (shown while thinking)
+ frozenFrame: string | null;
+ setFrozenFrame: (frame: string | null) => void;
+
+ // Camera
+ camera: CameraState;
+ setCameraStream: (stream: MediaStream | null) => void;
+ setCameraError: (error: string | null) => void;
+
+ // Model status
+ status: AppStatus | null;
+ setStatus: (status: AppStatus) => void;
+
+ // Settings
+ isSettingsOpen: boolean;
+ setSettingsOpen: (open: boolean) => void;
+}
+
+export const useAppStore = create((set) => ({
+ // Messages
+ messages: [],
+ addMessage: (message) =>
+ set((state) => ({
+ messages: [
+ ...state.messages,
+ {
+ ...message,
+ id: crypto.randomUUID(),
+ timestamp: new Date(),
+ },
+ ],
+ })),
+ clearMessages: () => set({ messages: [] }),
+
+ // Loading
+ isThinking: false,
+ setIsThinking: (thinking) => set({ isThinking: thinking }),
+
+ // Frozen frame
+ frozenFrame: null,
+ setFrozenFrame: (frame) => set({ frozenFrame: frame }),
+
+ // Camera
+ camera: { isActive: false, stream: null, error: null },
+ setCameraStream: (stream) =>
+ set({
+ camera: { isActive: !!stream, stream, error: null },
+ }),
+ setCameraError: (error) =>
+ set({
+ camera: { isActive: false, stream: null, error },
+ }),
+
+ // Status
+ status: null,
+ setStatus: (status) => set({ status }),
+
+ // Settings
+ isSettingsOpen: false,
+ setSettingsOpen: (open) => set({ isSettingsOpen: open }),
+}));
diff --git a/multimodal/ask-anything-app/src/hooks/useCamera.ts b/multimodal/ask-anything-app/src/hooks/useCamera.ts
new file mode 100644
index 00000000..65b93910
--- /dev/null
+++ b/multimodal/ask-anything-app/src/hooks/useCamera.ts
@@ -0,0 +1,53 @@
+/**
+ * Custom hook for camera operations.
+ */
+import { useCallback } from "react";
+import { useAppStore } from "../contexts/AppContext";
+
+export function useCamera() {
+ const { camera, setCameraStream, setCameraError } = useAppStore();
+
+ const startCamera = useCallback(async () => {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({
+ video: { width: 1280, height: 720, facingMode: "user" },
+ });
+ setCameraStream(stream);
+ return stream;
+ } catch (error) {
+ setCameraError("Failed to access camera");
+ return null;
+ }
+ }, [setCameraStream, setCameraError]);
+
+ const stopCamera = useCallback(() => {
+ if (camera.stream) {
+ camera.stream.getTracks().forEach((track) => track.stop());
+ }
+ setCameraStream(null);
+ }, [camera.stream, setCameraStream]);
+
+ const captureFrame = useCallback((): string | null => {
+ // Find video element in the DOM
+ const video = document.querySelector("video");
+ if (!video || video.videoWidth === 0) return null;
+
+ const canvas = document.createElement("canvas");
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return null;
+
+ ctx.drawImage(video, 0, 0);
+ return canvas.toDataURL("image/jpeg", 0.8).split(",")[1]; // Base64 only
+ }, []);
+
+ return {
+ isActive: camera.isActive,
+ error: camera.error,
+ startCamera,
+ stopCamera,
+ captureFrame,
+ };
+}
diff --git a/multimodal/ask-anything-app/src/hooks/useChat.ts b/multimodal/ask-anything-app/src/hooks/useChat.ts
new file mode 100644
index 00000000..15d2ec6b
--- /dev/null
+++ b/multimodal/ask-anything-app/src/hooks/useChat.ts
@@ -0,0 +1,54 @@
+/**
+ * Custom hook for chat operations.
+ */
+import { useCallback } from "react";
+import { useAppStore } from "../contexts/AppContext";
+import { api } from "../services/api";
+import { useCamera } from "./useCamera";
+
+export function useChat() {
+ const { messages, isThinking, addMessage, setIsThinking } = useAppStore();
+ const { captureFrame } = useCamera();
+
+ const sendMessage = useCallback(
+ async (content: string, imageBase64?: string) => {
+ // Capture frame if not provided
+ const image = imageBase64 || captureFrame();
+
+ // Add user message
+ addMessage({ role: "user", content, imageBase64: image || undefined });
+
+ // Set thinking state
+ setIsThinking(true);
+
+ try {
+ // Call vision API
+ const response = await api.visionInfer({
+ prompt: content,
+ image_base64: image || undefined,
+ max_new_tokens: 256,
+ temperature: 0.7,
+ });
+
+ // Add assistant response
+ addMessage({ role: "assistant", content: response.response });
+ } catch (error) {
+ console.error("Vision inference failed:", error);
+ addMessage({
+ role: "assistant",
+ content:
+ "Sorry, I encountered an error processing your request. Please make sure the backend server is running on http://localhost:8000",
+ });
+ } finally {
+ setIsThinking(false);
+ }
+ },
+ [addMessage, setIsThinking, captureFrame]
+ );
+
+ return {
+ messages,
+ isThinking,
+ sendMessage,
+ };
+}
diff --git a/multimodal/ask-anything-app/src/index.css b/multimodal/ask-anything-app/src/index.css
new file mode 100644
index 00000000..91ac9bfd
--- /dev/null
+++ b/multimodal/ask-anything-app/src/index.css
@@ -0,0 +1,86 @@
+@import "tailwindcss";
+
+html,
+body,
+#root {
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ margin: 0;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
+ Arial, sans-serif;
+ background-color: #000000;
+ color: #ffffff;
+}
+
+/* Glass scrollbar styling */
+::-webkit-scrollbar {
+ width: 6px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.3);
+}
+
+/* Glass panel - main glassmorphism effect */
+.glass-panel {
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(20px) saturate(180%);
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+ box-shadow:
+ 0 8px 32px rgba(0, 0, 0, 0.3),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+/* Glass bubble for user messages */
+.glass-bubble-user {
+ background: rgba(59, 130, 246, 0.35);
+ backdrop-filter: blur(12px) saturate(150%);
+ -webkit-backdrop-filter: blur(12px) saturate(150%);
+ border: 1px solid rgba(59, 130, 246, 0.3);
+ box-shadow: 0 4px 16px rgba(59, 130, 246, 0.2);
+}
+
+/* Glass bubble for AI messages */
+.glass-bubble-ai {
+ background: rgba(255, 255, 255, 0.12);
+ backdrop-filter: blur(12px) saturate(150%);
+ -webkit-backdrop-filter: blur(12px) saturate(150%);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+}
+
+/* Glass input area */
+.glass-input {
+ background: rgba(255, 255, 255, 0.08);
+ backdrop-filter: blur(16px) saturate(150%);
+ -webkit-backdrop-filter: blur(16px) saturate(150%);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+}
+
+/* Glass button */
+.glass-button {
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ transition: all 0.2s ease;
+}
+
+.glass-button:hover {
+ background: rgba(255, 255, 255, 0.18);
+ border-color: rgba(255, 255, 255, 0.2);
+}
diff --git a/multimodal/ask-anything-app/src/main.tsx b/multimodal/ask-anything-app/src/main.tsx
new file mode 100644
index 00000000..bef5202a
--- /dev/null
+++ b/multimodal/ask-anything-app/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/multimodal/ask-anything-app/src/services/api.ts b/multimodal/ask-anything-app/src/services/api.ts
new file mode 100644
index 00000000..16c5fe67
--- /dev/null
+++ b/multimodal/ask-anything-app/src/services/api.ts
@@ -0,0 +1,55 @@
+/**
+ * API client for communicating with the FastAPI backend.
+ */
+import axios from "axios";
+import type {
+ VisionRequest,
+ VisionResponse,
+ TranscriptionResponse,
+ AppStatus,
+} from "../types";
+
+const API_BASE = "http://localhost:8000/api";
+
+const client = axios.create({
+ baseURL: API_BASE,
+ timeout: 120000, // 2 minutes for slow inference
+});
+
+export const api = {
+ /**
+ * Check if the server is healthy.
+ */
+ async getHealth(): Promise<{ status: string }> {
+ const { data } = await client.get("/health");
+ return data;
+ },
+
+ /**
+ * Get status of all loaded models.
+ */
+ async getStatus(): Promise {
+ const { data } = await client.get("/status");
+ return data;
+ },
+
+ /**
+ * Run vision-language inference.
+ */
+ async visionInfer(request: VisionRequest): Promise {
+ const { data } = await client.post("/vision/infer", request);
+ return data;
+ },
+
+ /**
+ * Transcribe audio to text.
+ */
+ async transcribe(audioBlob: Blob): Promise {
+ const formData = new FormData();
+ formData.append("audio", audioBlob, "audio.wav");
+ const { data } = await client.post("/speech/transcribe", formData, {
+ headers: { "Content-Type": "multipart/form-data" },
+ });
+ return data;
+ },
+};
diff --git a/multimodal/ask-anything-app/src/types/index.ts b/multimodal/ask-anything-app/src/types/index.ts
new file mode 100644
index 00000000..fccfe780
--- /dev/null
+++ b/multimodal/ask-anything-app/src/types/index.ts
@@ -0,0 +1,53 @@
+/**
+ * Type definitions for Ask Anything app.
+ */
+
+/** Chat message in the conversation */
+export interface Message {
+ id: string;
+ role: "user" | "assistant";
+ content: string;
+ timestamp: Date;
+ imageBase64?: string; // Captured frame for context
+}
+
+/** Status of a loaded model */
+export interface ModelStatus {
+ loaded: boolean;
+ model_type: string;
+ max_seq_len?: number;
+ vision_token_id?: number;
+ eos_token_id?: number;
+}
+
+/** Status of all models */
+export interface AppStatus {
+ gemma3: ModelStatus;
+ whisper: ModelStatus;
+}
+
+/** Request for vision-language inference */
+export interface VisionRequest {
+ prompt: string;
+ image_base64?: string;
+ max_new_tokens?: number;
+ temperature?: number;
+}
+
+/** Response from vision-language inference */
+export interface VisionResponse {
+ response: string;
+ tokens_generated: number;
+}
+
+/** Response from speech transcription */
+export interface TranscriptionResponse {
+ transcription: string;
+}
+
+/** Camera stream state */
+export interface CameraState {
+ isActive: boolean;
+ stream: MediaStream | null;
+ error: string | null;
+}
diff --git a/multimodal/ask-anything-app/tsconfig.app.json b/multimodal/ask-anything-app/tsconfig.app.json
new file mode 100644
index 00000000..a9b5a59c
--- /dev/null
+++ b/multimodal/ask-anything-app/tsconfig.app.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/multimodal/ask-anything-app/tsconfig.json b/multimodal/ask-anything-app/tsconfig.json
new file mode 100644
index 00000000..1ffef600
--- /dev/null
+++ b/multimodal/ask-anything-app/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/multimodal/ask-anything-app/tsconfig.node.json b/multimodal/ask-anything-app/tsconfig.node.json
new file mode 100644
index 00000000..8a67f62f
--- /dev/null
+++ b/multimodal/ask-anything-app/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/multimodal/ask-anything-app/vite.config.ts b/multimodal/ask-anything-app/vite.config.ts
new file mode 100644
index 00000000..c4069b77
--- /dev/null
+++ b/multimodal/ask-anything-app/vite.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+})
diff --git a/multimodal/object-detection-runtime/.gitignore b/multimodal/object-detection-runtime/.gitignore
new file mode 100644
index 00000000..b6ca54c0
--- /dev/null
+++ b/multimodal/object-detection-runtime/.gitignore
@@ -0,0 +1,23 @@
+# Model files
+models/
+*.pte
+*.bin
+*.onnx
+*.pt
+*.pth
+*.safetensors
+
+# Output files
+output.jpg
+output*.jpg
+
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+.venv/
+venv/
+env/
+
+# OS
+.DS_Store
diff --git a/multimodal/object-detection-runtime/README.md b/multimodal/object-detection-runtime/README.md
new file mode 100644
index 00000000..e828da63
--- /dev/null
+++ b/multimodal/object-detection-runtime/README.md
@@ -0,0 +1,143 @@
+# YOLO Object Detection with ExecuTorch
+
+This example demonstrates YOLO object detection inference using ExecuTorch with the XNNPACK backend.
+
+## Prerequisites
+
+- Python 3.10+
+- ExecuTorch runtime
+- Ultralytics library
+
+## Installation
+
+```bash
+# Install dependencies
+pip install ultralytics opencv-python pillow
+
+# Install ExecuTorch (if not already installed)
+pip install executorch
+```
+
+[huggingface-cli]: https://huggingface.co/docs/huggingface_hub/en/guides/cli
+
+## Download Model
+
+Download the YOLO26m ExecuTorch model from Hugging Face:
+
+```bash
+hf download larryliu0820/yolo26m-ExecuTorch-XNNPACK \
+ --local-dir models/yolo26m-ExecuTorch-XNNPACK
+```
+
+## Usage
+
+### Basic Usage
+
+```bash
+python yolo_test.py
+```
+
+### With Custom Options
+
+```bash
+# Specify image, model, and confidence threshold
+python yolo_test.py --image bus.jpg --conf 0.5
+
+# Use a different model
+python yolo_test.py --model path/to/model.pte --image my_image.jpg
+
+# Full options
+python yolo_test.py \
+ --image example.jpg \
+ --model models/yolo26m-ExecuTorch-XNNPACK/yolo26m_xnnpack.pte \
+ --output result.jpg \
+ --conf 0.25
+```
+
+### Command Line Arguments
+
+| Argument | Default | Description |
+|----------|---------|-------------|
+| `--image` | `example.jpg` | Input image path |
+| `--model` | `models/yolo26m-ExecuTorch-XNNPACK/yolo26m_xnnpack.pte` | ExecuTorch model path |
+| `--output` | `output.jpg` | Output image path |
+| `--conf` | `0.25` | Confidence threshold (0-1) |
+
+## Expected Output
+
+```
+Model: models/yolo26m-ExecuTorch-XNNPACK/yolo26m_xnnpack.pte
+Image: bus.jpg
+Confidence threshold: 0.25
+
+Output shape: torch.Size([1, 300, 6])
+Confidence range: [0.0010, 0.9280]
+
+Detections: 5
+ 1. person: 0.928 @ (589, 267) 260x267
+ 2. person: 0.876 @ (53, 212) 106x374
+ 3. bus: 0.831 @ (653, 212) 133x341
+ 4. person: 0.782 @ (257, 171) 110x215
+ 5. person: 0.732 @ (374, 257) 206x286
+
+Saved: output.jpg
+```
+
+## Model Output Format
+
+The YOLO26 end-to-end model outputs a tensor of shape `[1, 300, 6]`:
+
+| Index | Field | Description |
+|-------|-------|-------------|
+| 0 | x_center | Box center X coordinate (pixels) |
+| 1 | y_center | Box center Y coordinate (pixels) |
+| 2 | width | Box width (pixels) |
+| 3 | height | Box height (pixels) |
+| 4 | confidence | Detection confidence (0-1) |
+| 5 | class_id | COCO class ID (0-79) |
+
+## COCO Classes
+
+The model detects 80 COCO classes:
+
+```
+0: person, 1: bicycle, 2: car, 3: motorcycle, 4: airplane,
+5: bus, 6: train, 7: truck, 8: boat, 9: traffic light,
+10: fire hydrant, 11: stop sign, 12: parking meter, 13: bench,
+14: bird, 15: cat, 16: dog, 17: horse, 18: sheep, 19: cow,
+...
+```
+
+## Troubleshooting
+
+### Low confidence scores
+
+If all detections have very low confidence (< 25%), the model file may be corrupted or incorrectly exported. Try re-downloading the model:
+
+```bash
+rm -rf models/yolo26m-ExecuTorch-XNNPACK
+huggingface-cli download larryliu0820/yolo26m-ExecuTorch-XNNPACK \
+ --local-dir models/yolo26m-ExecuTorch-XNNPACK
+```
+
+### Import errors
+
+Ensure all dependencies are installed:
+
+```bash
+pip install ultralytics opencv-python pillow torch executorch
+```
+
+### Model not found
+
+Verify the model path exists:
+
+```bash
+ls -la models/yolo26m-ExecuTorch-XNNPACK/
+```
+
+## References
+
+- [Ultralytics YOLO](https://github.com/ultralytics/ultralytics)
+- [ExecuTorch](https://github.com/pytorch/executorch)
+- [XNNPACK Backend](https://github.com/google/XNNPACK)
diff --git a/multimodal/object-detection-runtime/example.jpg b/multimodal/object-detection-runtime/example.jpg
new file mode 100644
index 00000000..05d0887e
Binary files /dev/null and b/multimodal/object-detection-runtime/example.jpg differ
diff --git a/multimodal/object-detection-runtime/yolo_test.py b/multimodal/object-detection-runtime/yolo_test.py
new file mode 100644
index 00000000..0a6a334b
--- /dev/null
+++ b/multimodal/object-detection-runtime/yolo_test.py
@@ -0,0 +1,286 @@
+"""
+YOLO Object Detection with ExecuTorch Runtime
+
+Demonstrates YOLO inference using ExecuTorch with XNNPACK backend.
+Uses Ultralytics library for image preprocessing.
+
+Usage:
+ python yolo_test.py [--image PATH] [--model PATH] [--conf THRESHOLD]
+"""
+
+import argparse
+from pathlib import Path
+
+import cv2
+import numpy as np
+import torch
+from executorch.runtime import Runtime
+from PIL import Image, ImageDraw
+from ultralytics.data.augment import LetterBox
+
+
+# COCO class names (80 classes)
+COCO_CLASSES = [
+ "person",
+ "bicycle",
+ "car",
+ "motorcycle",
+ "airplane",
+ "bus",
+ "train",
+ "truck",
+ "boat",
+ "traffic light",
+ "fire hydrant",
+ "stop sign",
+ "parking meter",
+ "bench",
+ "bird",
+ "cat",
+ "dog",
+ "horse",
+ "sheep",
+ "cow",
+ "elephant",
+ "bear",
+ "zebra",
+ "giraffe",
+ "backpack",
+ "umbrella",
+ "handbag",
+ "tie",
+ "suitcase",
+ "frisbee",
+ "skis",
+ "snowboard",
+ "sports ball",
+ "kite",
+ "baseball bat",
+ "baseball glove",
+ "skateboard",
+ "surfboard",
+ "tennis racket",
+ "bottle",
+ "wine glass",
+ "cup",
+ "fork",
+ "knife",
+ "spoon",
+ "bowl",
+ "banana",
+ "apple",
+ "sandwich",
+ "orange",
+ "broccoli",
+ "carrot",
+ "hot dog",
+ "pizza",
+ "donut",
+ "cake",
+ "chair",
+ "couch",
+ "potted plant",
+ "bed",
+ "dining table",
+ "toilet",
+ "tv",
+ "laptop",
+ "mouse",
+ "remote",
+ "keyboard",
+ "cell phone",
+ "microwave",
+ "oven",
+ "toaster",
+ "sink",
+ "refrigerator",
+ "book",
+ "clock",
+ "vase",
+ "scissors",
+ "teddy bear",
+ "hair drier",
+ "toothbrush",
+]
+
+# Random colors for visualization
+np.random.seed(42)
+CLASS_COLORS = [
+ (int(r), int(g), int(b)) for r, g, b in np.random.randint(0, 255, size=(80, 3))
+]
+
+
+def preprocess(image_path: str, imgsz: int = 640):
+ """
+ Preprocess image using Ultralytics LetterBox transform.
+
+ Returns:
+ input_tensor: Preprocessed image tensor [1, 3, H, W]
+ scale: Scale factor applied during resize
+ padding: (pad_w, pad_h) padding applied
+ orig_shape: Original image shape (height, width)
+ """
+ img = cv2.imread(str(image_path))
+ orig_shape = img.shape[:2] # (H, W)
+
+ # Apply Ultralytics LetterBox
+ letterbox = LetterBox(new_shape=(imgsz, imgsz), auto=False, stride=32)
+ img_lb = letterbox(image=img)
+
+ # Calculate transform parameters
+ h, w = orig_shape
+ scale = min(imgsz / h, imgsz / w)
+ pad_h = (imgsz - int(h * scale)) // 2
+ pad_w = (imgsz - int(w * scale)) // 2
+
+ # Convert BGR->RGB, normalize, to tensor
+ img_rgb = cv2.cvtColor(img_lb, cv2.COLOR_BGR2RGB)
+ img_norm = img_rgb.astype(np.float32) / 255.0
+ tensor = torch.from_numpy(img_norm).permute(2, 0, 1).contiguous().unsqueeze(0)
+
+ return tensor, scale, (pad_w, pad_h), orig_shape
+
+
+def postprocess(output, conf_thresh: float, scale: float, padding: tuple):
+ """
+ Post-process YOLO end-to-end output [batch, 300, 6].
+
+ Output format: [x1, y1, x2, y2, confidence, class_id] (xyxy corner format)
+
+ Returns:
+ List of detections as dicts with keys: x1, y1, x2, y2, conf, cls
+ """
+ preds = output[0] if len(output.shape) == 3 else output
+ pad_w, pad_h = padding
+
+ # Filter by confidence
+ mask = preds[:, 4] > conf_thresh
+
+ detections = []
+ for i in range(len(preds)):
+ if mask[i]:
+ # Convert from letterbox space to original image space
+ x1 = (preds[i, 0].item() - pad_w) / scale
+ y1 = (preds[i, 1].item() - pad_h) / scale
+ x2 = (preds[i, 2].item() - pad_w) / scale
+ y2 = (preds[i, 3].item() - pad_h) / scale
+
+ detections.append(
+ {
+ "x1": x1,
+ "y1": y1,
+ "x2": x2,
+ "y2": y2,
+ "conf": preds[i, 4].item(),
+ "cls": int(preds[i, 5].item()),
+ }
+ )
+
+ return sorted(detections, key=lambda d: d["conf"], reverse=True)
+
+
+def draw_boxes(image_path: str, detections: list, output_path: str):
+ """Draw bounding boxes on image and save."""
+ img = Image.open(image_path).convert("RGB")
+ draw = ImageDraw.Draw(img)
+ W, H = img.size
+
+ for det in detections:
+ x1, y1, x2, y2 = det["x1"], det["y1"], det["x2"], det["y2"]
+ cls, conf = det["cls"], det["conf"]
+
+ # Clip to image bounds
+ x1, y1 = max(0, x1), max(0, y1)
+ x2, y2 = min(W, x2), min(H, y2)
+
+ if x2 <= x1 or y2 <= y1:
+ continue
+
+ color = CLASS_COLORS[cls % 80]
+ label = f"{COCO_CLASSES[cls]}: {conf:.2f}"
+
+ # Draw box
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=2)
+
+ # Draw label
+ bbox = draw.textbbox((0, 0), label)
+ tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
+ lx, ly = max(0, x1), max(0, y1 - th - 4)
+ draw.rectangle([lx, ly, lx + tw + 4, ly + th + 4], fill=color)
+ draw.text((lx + 2, ly + 2), label, fill=(255, 255, 255))
+
+ img.save(output_path, quality=95)
+ return len(detections)
+
+
+def run_inference(
+ model_path: str, image_path: str, output_path: str, conf_thresh: float = 0.25
+):
+ """Run YOLO inference with ExecuTorch."""
+
+ # Load model
+ runtime = Runtime.get()
+ program = runtime.load_program(model_path)
+ method = program.load_method("forward")
+
+ # Preprocess
+ input_tensor, scale, padding, orig_shape = preprocess(image_path)
+
+ # Inference
+ outputs = method.execute([input_tensor])
+
+ # Post-process (use first output - end-to-end format [1, 300, 6])
+ detections = postprocess(outputs[0], conf_thresh, scale, padding)
+
+ # Draw and save
+ num_drawn = draw_boxes(image_path, detections, output_path)
+
+ return detections, outputs[0]
+
+
+def main():
+ parser = argparse.ArgumentParser(description="YOLO ExecuTorch Inference")
+ parser.add_argument("--image", default="example.jpg", help="Input image path")
+ parser.add_argument(
+ "--model",
+ default="models/yolo26m-ExecuTorch-XNNPACK/yolo26m_xnnpack.pte",
+ help="ExecuTorch model path",
+ )
+ parser.add_argument("--output", default="output.jpg", help="Output image path")
+ parser.add_argument("--conf", type=float, default=0.25, help="Confidence threshold")
+ args = parser.parse_args()
+
+ script_dir = Path(__file__).parent
+ image_path = str(script_dir / args.image)
+ model_path = str(script_dir / args.model)
+ output_path = str(script_dir / args.output)
+
+ print(f"Model: {model_path}")
+ print(f"Image: {image_path}")
+ print(f"Confidence threshold: {args.conf}")
+
+ detections, raw_output = run_inference(
+ model_path, image_path, output_path, args.conf
+ )
+
+ print(f"\nOutput shape: {raw_output.shape}")
+ print(
+ f"Confidence range: [{raw_output[0, :, 4].min():.4f}, {raw_output[0, :, 4].max():.4f}]"
+ )
+ print(f"\nDetections: {len(detections)}")
+
+ for i, det in enumerate(detections[:10]):
+ cls_name = COCO_CLASSES[det["cls"]] if det["cls"] < 80 else f"cls_{det['cls']}"
+ w = det["x2"] - det["x1"]
+ h = det["y2"] - det["y1"]
+ cx = (det["x1"] + det["x2"]) / 2
+ cy = (det["y1"] + det["y2"]) / 2
+ print(
+ f" {i+1}. {cls_name}: {det['conf']:.3f} @ ({cx:.0f}, {cy:.0f}) {w:.0f}x{h:.0f}"
+ )
+
+ print(f"\nSaved: {output_path}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/multimodal/text-image-runtime/.gitignore b/multimodal/text-image-runtime/.gitignore
new file mode 100644
index 00000000..74a19cfa
--- /dev/null
+++ b/multimodal/text-image-runtime/.gitignore
@@ -0,0 +1,26 @@
+# Model directories (download with huggingface-cli)
+# hf download lucylq/gemma3 --local-dir models/gemma3
+gemma3/
+models/
+
+# Model files
+*.pte
+*.bin
+*.onnx
+*.pt
+*.pth
+*.safetensors
+
+# Metal runtime (work in progress)
+runtime_inference_metal.py
+
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+.venv/
+venv/
+env/
+
+# OS
+.DS_Store
diff --git a/multimodal/text-image-runtime/README.md b/multimodal/text-image-runtime/README.md
new file mode 100644
index 00000000..c7ec41f9
--- /dev/null
+++ b/multimodal/text-image-runtime/README.md
@@ -0,0 +1,80 @@
+# Gemma3 Vision-Language Runtime
+
+ExecuTorch-based inference for Gemma3 4B multimodal model with XNNPACK backend.
+
+## Prerequisites
+
+- Python 3.10+
+- ExecuTorch with Python bindings
+- transformers
+- Pillow
+
+## Installation
+
+```bash
+pip install executorch transformers pillow torch
+```
+
+## Download Model
+
+Download the Gemma3 4B quantized model from Hugging Face:
+
+```bash
+hf download lucylq/gemma3 --local-dir models/gemma3
+```
+
+## Usage
+
+### Using runtime_inference.py (Recommended)
+
+The full-featured inference script with direct ExecuTorch portable_lib API:
+
+```bash
+# Basic usage
+python runtime_inference.py \
+ --image_path example.jpg \
+ --prompt "What is in this image?"
+
+# With custom model path
+python runtime_inference.py \
+ --model_path models/gemma3/GEMMA3_4B_XNNPACK_INT8_INT4.pte \
+ --image_path example.jpg \
+ --prompt "How many people are in this image?"
+
+# Adjust generation parameters
+python runtime_inference.py \
+ --image_path example.jpg \
+ --prompt "Describe this scene" \
+ --max_new_tokens 256 \
+ --temperature 0.7
+```
+
+### Using run.py (Simple CLI)
+
+Simplified interface for quick testing:
+
+```bash
+python run.py \
+ --model_path models/gemma3/GEMMA3_4B_XNNPACK_INT8_INT4.pte \
+ --image_path example.jpg \
+ --prompt "What do you see?"
+```
+
+## Command Line Arguments
+
+### runtime_inference.py
+
+| Argument | Default | Description |
+|----------|---------|-------------|
+| `--model_path` | `gemma3/GEMMA3_4B_XNNPACK_INT8_INT4.pte` | Path to .pte model |
+| `--image_path` | Required | Input image path |
+| `--prompt` | Required | Text prompt for the model |
+| `--max_new_tokens` | `128` | Maximum tokens to generate |
+| `--temperature` | `0.8` | Sampling temperature |
+
+## Model Details
+
+- **Model**: Gemma3 4B Vision-Language
+- **Quantization**: INT8/INT4 mixed precision
+- **Backend**: XNNPACK (CPU optimized)
+- **Size**: ~3.5GB
diff --git a/multimodal/text-image-runtime/example.jpg b/multimodal/text-image-runtime/example.jpg
new file mode 100644
index 00000000..05d0887e
Binary files /dev/null and b/multimodal/text-image-runtime/example.jpg differ
diff --git a/multimodal/text-image-runtime/run.py b/multimodal/text-image-runtime/run.py
new file mode 100644
index 00000000..782a147e
--- /dev/null
+++ b/multimodal/text-image-runtime/run.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python3
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the BSD-style license found in the
+# LICENSE file in the root directory of this source tree.
+
+"""
+Gemma 3 Multimodal Python Binding Example
+
+This script demonstrates how to run Gemma 3 multimodal inference using
+ExecuTorch Python bindings. It loads a .pte model and processes both
+text and image inputs.
+
+Example usage:
+ python pybinding_run.py \
+ --model_path /path/to/model.pte \
+ --tokenizer_path /path/to/tokenizer.json \
+ --image_path /path/to/image.png \
+ --prompt "What is in this image?"
+
+Requirements:
+ - ExecuTorch with Python bindings installed
+ - PIL (Pillow) for image loading
+ - numpy for array operations
+"""
+
+import argparse
+import sys
+
+import numpy as np
+import torch
+from PIL import Image
+from transformers import AutoProcessor
+
+# Load required operator libraries for quantized and custom ops
+# These must be imported BEFORE creating the runner to register the operators
+try:
+ import executorch.kernels.quantized # noqa: F401
+except Exception as e:
+ print(f"Warning: Failed to load quantized kernels: {e}")
+ print("The model may fail if it uses quantized operators.")
+ print("To fix this, reinstall ExecuTorch with: pip install executorch")
+
+try:
+ from executorch.extension.llm.custom_ops import custom_ops # noqa: F401
+except Exception as e:
+ print(f"Warning: Failed to load custom ops: {e}")
+ print("The model may fail if it uses custom operators like custom_sdpa.")
+
+from executorch.extension.llm.runner import (
+ GenerationConfig,
+ make_image_input,
+ make_text_input,
+ MultimodalRunner,
+)
+
+
+def load_image(image_path: str, target_size: int = 896) -> torch.Tensor:
+ """
+ Load and preprocess an image for Gemma 3 vision encoder.
+
+ The image is:
+ 1. Loaded and converted to RGB
+ 2. Resized to target_size x target_size (default 896x896)
+ 3. Converted from HWC to CHW format
+ 4. Normalized from uint8 [0, 255] to float32 [0.0, 1.0]
+
+ Args:
+ image_path: Path to the image file (.jpg, .png, .bmp, etc.)
+ target_size: Target size for resizing (default 896 for Gemma 3)
+
+ Returns:
+ torch.Tensor: Preprocessed image tensor of shape (3, target_size, target_size)
+ """
+ pil_image = Image.open(image_path).convert("RGB")
+ pil_image = pil_image.resize((target_size, target_size))
+
+ # Convert to tensor: HWC -> CHW, uint8 -> float32 [0, 1]
+ image_tensor = (
+ torch.from_numpy(np.array(pil_image))
+ .permute(2, 0, 1)
+ .contiguous()
+ .float()
+ / 255.0
+ )
+
+ return image_tensor
+
+
+def build_multimodal_inputs(
+ prompt: str, image_tensor: torch.Tensor, processor: AutoProcessor
+) -> list:
+ """
+ Build the multimodal input sequence for Gemma 3 using the processor's chat template.
+
+ Args:
+ prompt: The text prompt/question about the image
+ image_tensor: Preprocessed image tensor from load_image()
+ processor: The AutoProcessor instance for applying the chat template
+
+ Returns:
+ list: List of MultimodalInput objects for the runner
+ """
+ messages = [
+ {
+ "role": "user",
+ "content": [
+ {"type": "image"},
+ {"type": "text", "text": prompt},
+ ],
+ }
+ ]
+ formatted_prompt = processor.apply_chat_template(
+ messages, add_generation_prompt=True, tokenize=False
+ )
+
+ # Split the formatted prompt around the image placeholder
+ # The processor inserts or similar for the image position
+ # We need to find where the image should be inserted
+ image_token = ""
+ if image_token in formatted_prompt:
+ before_image, after_image = formatted_prompt.split(image_token, 1)
+ inputs = []
+ inputs.append(make_text_input(before_image + image_token))
+ inputs.append(make_image_input(image_tensor))
+ inputs.append(make_text_input(after_image))
+ else:
+ # Fallback: put image at the beginning of user content
+ inputs = []
+ inputs.append(make_text_input(formatted_prompt))
+
+ return inputs
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Run Gemma 3 multimodal inference with ExecuTorch Python bindings",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+Examples:
+ # Basic usage
+ python run.py --model_path model.pte --tokenizer_path tokenizer.json \\
+ --image_path image.png --prompt "What is in this image?"
+
+ # With custom generation settings and model ID
+ python run.py --model_path model.pte --tokenizer_path tokenizer.json \\
+ --image_path image.png --prompt "Describe this image in detail" \\
+ --max_new_tokens 200 --temperature 0.7 --model_id google/gemma-3-4b-it
+ """,
+ )
+ parser.add_argument(
+ "--model_path",
+ type=str,
+ required=True,
+ help="Path to the .pte model file",
+ )
+ parser.add_argument(
+ "--tokenizer_path",
+ type=str,
+ required=True,
+ help="Path to the tokenizer.json file",
+ )
+ parser.add_argument(
+ "--image_path",
+ type=str,
+ required=True,
+ help="Path to the input image file",
+ )
+ parser.add_argument(
+ "--prompt",
+ type=str,
+ default="What is in this image?",
+ help="Text prompt for the model (default: 'What is in this image?')",
+ )
+ parser.add_argument(
+ "--max_new_tokens",
+ type=int,
+ default=100,
+ help="Maximum number of tokens to generate (default: 100)",
+ )
+ parser.add_argument(
+ "--temperature",
+ type=float,
+ default=0.0,
+ help="Sampling temperature. 0.0 for greedy decoding (default: 0.0)",
+ )
+ parser.add_argument(
+ "--model_id",
+ type=str,
+ default="google/gemma-3-4b-it",
+ help="HuggingFace model ID for loading the processor (default: google/gemma-3-4b-it)",
+ )
+
+ args = parser.parse_args()
+
+ print(f"Loading model from: {args.model_path}")
+ print(f"Loading tokenizer from: {args.tokenizer_path}")
+ print(f"Loading processor from: {args.model_id}")
+
+ # Load the processor for chat template formatting
+ processor = AutoProcessor.from_pretrained(args.model_id)
+
+ # Create the multimodal runner
+ runner = MultimodalRunner(args.model_path, args.tokenizer_path)
+
+ # Load and preprocess the image
+ print(f"Loading image from: {args.image_path}")
+ image_tensor = load_image(args.image_path)
+ print(f"Image tensor shape: {image_tensor.shape}")
+
+ # Build multimodal inputs using the processor's chat template
+ inputs = build_multimodal_inputs(args.prompt, image_tensor, processor)
+
+ # Configure generation settings
+ config = GenerationConfig(
+ max_new_tokens=args.max_new_tokens,
+ temperature=args.temperature,
+ echo=False,
+ )
+
+ print(f"\nPrompt: {args.prompt}")
+ print("-" * 50)
+ print("Response: ", end="", flush=True)
+
+ # Collect generated tokens
+ # Note: The C++ MultimodalRunner already prints tokens via safe_printf(),
+ # so we don't print in the callback to avoid duplication
+ generated_tokens = []
+ stop_generation = False
+
+ def token_callback(token: str):
+ nonlocal stop_generation
+ # Stop collecting after first token
+ if stop_generation:
+ return
+ if "" in token:
+ # Add any text before the end token
+ before_end = token.split("")[0]
+ if before_end:
+ generated_tokens.append(before_end)
+ stop_generation = True
+ return
+ generated_tokens.append(token)
+
+ def stats_callback(stats):
+ # Print the complete response (since C++ prints token by token)
+ print() # Newline after streaming output
+ print("-" * 50)
+ print(f"Prompt tokens: {stats.num_prompt_tokens}")
+ print(f"Generated tokens: {stats.num_generated_tokens}")
+ # Calculate time to first token
+ time_to_first_token_s = (stats.first_token_ms - stats.inference_start_ms) / 1000.0
+ print(f"Time to first token: {time_to_first_token_s:.3f} s")
+ # Calculate generation rate
+ generation_time_s = (stats.inference_end_ms - stats.first_token_ms) / 1000.0
+ if generation_time_s > 0:
+ tokens_per_sec = stats.num_generated_tokens / generation_time_s
+ print(f"Generation rate: {tokens_per_sec:.2f} tokens/sec")
+
+ # Run generation
+ runner.generate(inputs, config, token_callback, stats_callback)
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/multimodal/text-image-runtime/runtime_inference.py b/multimodal/text-image-runtime/runtime_inference.py
new file mode 100644
index 00000000..a774da6b
--- /dev/null
+++ b/multimodal/text-image-runtime/runtime_inference.py
@@ -0,0 +1,566 @@
+#!/usr/bin/env python3
+"""
+Gemma3 Multimodal (Vision-Language) inference using ExecutorTorch Runtime directly.
+
+This script demonstrates how to run Gemma3 multimodal inference using the low-level
+ExecutorTorch portable_lib API for vision-language tasks.
+
+Example usage:
+ python runtime_inference.py --image_path example.jpg --prompt "What is in this image?"
+ python runtime_inference.py --image_path example.jpg --prompt "How many people are in this image?"
+
+Requirements:
+ - ExecuTorch with Python bindings installed
+ - transformers
+ - Pillow for image loading
+"""
+
+import argparse
+import sys
+import time
+from pathlib import Path
+from typing import List, Optional
+
+import torch
+from PIL import Image
+from transformers import AutoProcessor, AutoTokenizer
+
+# Load required operator libraries for quantized ops
+# These must be imported BEFORE loading the model to register the operators.
+# IMPORTANT: Import order matters! We follow the same order as optimum-executorch:
+# 1. torch.ao.quantization decomposed lib
+# 2. portable_lib (loads ExecuTorch runtime)
+# 3. executorch.kernels.quantized
+# 4. LLM custom ops (custom_sdpa specifically)
+print("Loading operator libraries...")
+
+try:
+ # This registers the quantized decomposed ops
+ from torch.ao.quantization.fx._decomposed import quantized_decomposed_lib # noqa: F401
+
+ print(" ✓ Loaded torch quantized decomposed lib")
+except Exception as e:
+ print(f" ✗ Failed to load torch quantized decomposed lib: {e}")
+
+# Import portable_lib BEFORE custom_ops (same order as optimum-executorch)
+from executorch.extension.pybindings.portable_lib import _load_for_executorch
+
+print(" ✓ Loaded portable_lib")
+
+try:
+ from executorch.kernels import quantized # noqa: F401
+
+ print(" ✓ Loaded executorch quantized kernels")
+except Exception as e:
+ print(f" ✗ Failed to load executorch quantized kernels: {e}")
+
+try:
+ # This loads libcustom_ops_aot_lib.dylib which registers llama::update_cache and llama::custom_sdpa
+ # IMPORTANT: Import custom_sdpa directly (not just the module) to ensure the library is loaded
+ from executorch.extension.llm.custom_ops.custom_ops import custom_sdpa # noqa: F401
+
+ print(" ✓ Loaded LLM custom ops (custom_sdpa, update_cache)")
+except Exception as e:
+ print(f" ⚠ LLM custom ops not loaded (may not be needed): {e}")
+
+
+def preprocess_image_manual(
+ image: Image.Image,
+ target_size: int = 896,
+ image_mean: List[float] = [0.5, 0.5, 0.5],
+ image_std: List[float] = [0.5, 0.5, 0.5],
+) -> torch.Tensor:
+ """Preprocess image manually without using transformers processor.
+
+ Args:
+ image: PIL Image
+ target_size: Target size for resizing (896 for Gemma3)
+ image_mean: Mean values for normalization
+ image_std: Std values for normalization
+
+ Returns:
+ Preprocessed image tensor of shape [1, 3, H, W]
+ """
+ # Resize to target size
+ image = image.convert("RGB")
+ image = image.resize((target_size, target_size), Image.Resampling.BILINEAR)
+
+ # Convert to tensor and normalize
+ import numpy as np
+
+ img_array = np.array(image).astype(np.float32) / 255.0 # Scale to [0, 1]
+
+ # Normalize: (x - mean) / std
+ for c in range(3):
+ img_array[:, :, c] = (img_array[:, :, c] - image_mean[c]) / image_std[c]
+
+ # Convert from HWC to CHW and add batch dimension
+ img_tensor = torch.from_numpy(img_array).permute(2, 0, 1).unsqueeze(0)
+
+ return img_tensor
+
+
+class Gemma3RuntimeRunner:
+ """Run Gemma3 multimodal inference using ExecutorTorch portable_lib directly."""
+
+ # Default HuggingFace model ID for tokenizer/processor
+ DEFAULT_HF_MODEL_ID = "google/gemma-3-4b-it"
+
+ def __init__(self, model_path: str, processor_path: str, hf_model_id: Optional[str] = None):
+ # Load model using _load_for_executorch first to get metadata
+ print(f"Loading model from {model_path}...")
+ load_start = time.time()
+ self.model = _load_for_executorch(model_path)
+ load_time = time.time() - load_start
+ print(f" Model loaded in {load_time:.2f}s")
+
+ # Load metadata first (needed for vision_token_id)
+ self._load_metadata()
+
+ # Load processor/tokenizer
+ # Try local path first, then fallback to HuggingFace model ID
+ hf_model_id = hf_model_id or self.DEFAULT_HF_MODEL_ID
+ print(f"Loading processor...")
+
+ try:
+ self.processor = AutoProcessor.from_pretrained(processor_path, trust_remote_code=True)
+ self.tokenizer = self.processor.tokenizer
+ print(f" ✓ Loaded processor from {processor_path}")
+ except Exception as e1:
+ print(f" ⚠ Local processor not found: {e1}")
+ try:
+ self.processor = AutoProcessor.from_pretrained(hf_model_id, trust_remote_code=True)
+ self.tokenizer = self.processor.tokenizer
+ print(f" ✓ Loaded processor from HuggingFace: {hf_model_id}")
+ except Exception as e2:
+ print(f" ⚠ HuggingFace processor not found: {e2}")
+ # Final fallback to just tokenizer
+ try:
+ self.tokenizer = AutoTokenizer.from_pretrained(hf_model_id, trust_remote_code=True)
+ self.processor = None
+ print(f" ✓ Loaded tokenizer from HuggingFace: {hf_model_id}")
+ except Exception as e3:
+ raise RuntimeError(
+ f"Could not load processor or tokenizer. Tried:\n"
+ f" 1. {processor_path}: {e1}\n"
+ f" 2. {hf_model_id}: {e2}\n"
+ f" 3. Tokenizer only from {hf_model_id}: {e3}"
+ )
+
+ def _load_metadata(self):
+ """Load model metadata from auxiliary methods."""
+ method_names = self.model.method_names()
+ print(f"Available methods: {method_names}")
+
+ # Get key metadata
+ self.use_kv_cache = self._get_metadata("use_kv_cache", True)
+ self.max_seq_len = self._get_metadata("get_max_seq_len", 2048)
+ self.eos_token_id = self._get_metadata("get_eos_id", 1)
+ self.bos_token_id = self._get_metadata("get_bos_id", 2)
+ self.vision_token_id = self._get_metadata("vision_token_id", 262144)
+ self.image_seq_length = self._get_metadata("image_seq_length", 256)
+
+ # Image preprocessing metadata
+ self.image_size = self._get_metadata("size", [896, 896])
+ self.image_mean = self._get_metadata("image_mean", [0.5, 0.5, 0.5])
+ self.image_std = self._get_metadata("image_std", [0.5, 0.5, 0.5])
+
+ # Stop tokens - include both EOS and for chat format
+ # token ID is 106 for Gemma3
+ self.stop_token_ids = {self.eos_token_id, 106}
+
+ print(f"Model metadata:")
+ print(f" max_seq_len: {self.max_seq_len}")
+ print(f" eos_token_id: {self.eos_token_id}")
+ print(f" bos_token_id: {self.bos_token_id}")
+ print(f" vision_token_id: {self.vision_token_id}")
+ print(f" image_seq_length: {self.image_seq_length}")
+ print(f" image_size: {self.image_size}")
+ print(f" stop_token_ids: {self.stop_token_ids}")
+
+ def _get_metadata(self, method_name: str, default):
+ """Get metadata from model method."""
+ if method_name in self.model.method_names():
+ try:
+ result = self.model.run_method(method_name)
+ return result[0] if len(result) == 1 else result
+ except Exception:
+ return default
+ return default
+
+ def preprocess_image(self, image: Image.Image) -> torch.Tensor:
+ """Preprocess image for vision encoder.
+
+ Args:
+ image: PIL Image
+
+ Returns:
+ Preprocessed image tensor
+ """
+ if self.processor is not None and hasattr(self.processor, 'image_processor'):
+ # Use the transformers image processor
+ inputs = self.processor.image_processor(images=image, return_tensors="pt")
+ return inputs["pixel_values"]
+ else:
+ # Manual preprocessing
+ target_size = self.image_size[0] if isinstance(self.image_size, list) else self.image_size
+ return preprocess_image_manual(
+ image,
+ target_size=target_size,
+ image_mean=self.image_mean,
+ image_std=self.image_std,
+ )
+
+ def format_prompt(self, prompt: str, has_image: bool = True) -> str:
+ """Format prompt with Gemma3's chat template.
+
+ Args:
+ prompt: User's text prompt
+ has_image: Whether an image is included
+
+ Returns:
+ Formatted prompt string
+ """
+ if has_image:
+ # Gemma3 format with image placeholder
+ # Use a placeholder that we'll expand later with vision tokens
+ formatted = f"user\n{prompt}\nmodel\n"
+ else:
+ formatted = f"user\n{prompt}\nmodel\n"
+ return formatted
+
+ def build_input_ids_with_image(self, prompt: str) -> torch.Tensor:
+ """Build input_ids with vision token placeholders expanded.
+
+ For Gemma3, we need to:
+ 1. Tokenize the prompt with the processor (produces marker)
+ 2. Find the marker position
+ 3. Replace it with `image_seq_length` vision tokens
+
+ Args:
+ prompt: User's text prompt
+
+ Returns:
+ input_ids tensor with vision token placeholders
+ """
+ # Use processor if available (handles chat template correctly)
+ if self.processor is not None:
+ # Create a conversation format for the processor
+ messages = [
+ {
+ "role": "user",
+ "content": [
+ {"type": "image"},
+ {"type": "text", "text": prompt},
+ ],
+ }
+ ]
+ text = self.processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
+ # Tokenize the text
+ input_ids = self.tokenizer.encode(text, return_tensors="pt")
+
+ # Find the token (ID 255999) and replace with vision tokens
+ start_of_image_id = self.tokenizer.convert_tokens_to_ids("")
+ if start_of_image_id is not None:
+ # Find position of
+ input_ids_list = input_ids[0].tolist()
+ try:
+ img_pos = input_ids_list.index(start_of_image_id)
+ # Replace with image_seq_length vision tokens
+ new_ids = (
+ input_ids_list[:img_pos]
+ + [self.vision_token_id] * self.image_seq_length
+ + input_ids_list[img_pos + 1:]
+ )
+ return torch.tensor([new_ids], dtype=torch.long)
+ except ValueError:
+ # not found, return as-is
+ pass
+
+ return input_ids
+
+ # Manual construction if processor not available
+ # Format the prompt
+ formatted = self.format_prompt(prompt, has_image=True)
+
+ # Tokenize parts before and after image placeholder
+ parts = formatted.split("")
+ if len(parts) != 2:
+ # No image placeholder, just tokenize directly
+ return self.tokenizer.encode(formatted, return_tensors="pt")
+
+ before_image = parts[0]
+ after_image = parts[1]
+
+ # Tokenize each part (without special tokens to avoid duplicate BOS)
+ before_tokens = self.tokenizer.encode(before_image, add_special_tokens=True)
+ after_tokens = self.tokenizer.encode(after_image, add_special_tokens=False)
+
+ # Create vision token placeholders
+ vision_tokens = [self.vision_token_id] * self.image_seq_length
+
+ # Combine: before + vision_tokens + after
+ all_tokens = before_tokens + vision_tokens + after_tokens
+
+ return torch.tensor([all_tokens], dtype=torch.long)
+
+ def generate(
+ self,
+ prompt: str,
+ image: Optional[Image.Image] = None,
+ max_new_tokens: int = 100,
+ temperature: float = 0.0,
+ echo: bool = True,
+ ) -> str:
+ """Generate text from prompt and optional image.
+
+ Args:
+ prompt: Text prompt
+ image: Optional PIL Image for vision-language tasks
+ max_new_tokens: Maximum number of tokens to generate
+ temperature: Sampling temperature (0 = greedy)
+ echo: Whether to print tokens as they are generated
+
+ Returns:
+ Generated text
+ """
+ # Build input_ids with proper vision token handling
+ if image is not None:
+ input_ids = self.build_input_ids_with_image(prompt)
+ else:
+ formatted_prompt = self.format_prompt(prompt, has_image=False)
+ input_ids = self.tokenizer.encode(formatted_prompt, return_tensors="pt")
+
+ prompt_len = input_ids.shape[1]
+ print(f"Prompt tokens: {prompt_len}")
+
+ # Preprocess image if provided
+ pixel_values = None
+ if image is not None:
+ print("Preprocessing image...")
+ preprocess_start = time.time()
+ pixel_values = self.preprocess_image(image)
+ preprocess_time = time.time() - preprocess_start
+ print(f" Image shape: {pixel_values.shape}")
+ print(f" Preprocessing time: {preprocess_time:.2f}s")
+
+ generated_tokens = []
+
+ # Prefill phase
+ print("Running prefill (encoder + first decoder pass)...")
+ prefill_start = time.time()
+
+ # Get token embeddings
+ token_embeddings = self.model.run_method("token_embedding", (input_ids,))[0]
+
+ # If we have an image, run vision encoder and merge embeddings
+ if pixel_values is not None:
+ print(" Running vision encoder...")
+ vision_start = time.time()
+ vision_embeddings = self.model.run_method("vision_encoder", (pixel_values,))[0]
+ vision_time = time.time() - vision_start
+ print(f" Vision encoder output shape: {vision_embeddings.shape}")
+ print(f" Vision encoder time: {vision_time:.2f}s")
+
+ # Find vision token positions and replace with vision embeddings
+ # Vision embeddings shape: [1, num_patches, hidden_dim] or [num_patches, hidden_dim]
+ vision_token_mask = input_ids == self.vision_token_id
+ num_vision_tokens = vision_token_mask.sum().item()
+ print(f" Vision token positions: {num_vision_tokens} tokens")
+
+ if num_vision_tokens > 0:
+ # Reshape vision embeddings to match the number of vision tokens
+ vision_emb_flat = vision_embeddings.reshape(-1, vision_embeddings.shape[-1])
+ # Only use as many embeddings as we have vision tokens
+ vision_emb_to_use = vision_emb_flat[:num_vision_tokens]
+ token_embeddings[vision_token_mask] = vision_emb_to_use
+
+ # Run text decoder for prefill
+ cache_position = torch.arange(prompt_len, dtype=torch.long)
+ try:
+ logits = self.model.run_method("text_decoder", (token_embeddings, cache_position))[0]
+ except Exception as e:
+ print(f"\nError during prefill: {e}")
+ raise
+
+ prefill_time = time.time() - prefill_start
+ print(f" Prefill time: {prefill_time:.2f}s")
+
+ # Sample first token
+ if temperature > 0:
+ probs = torch.softmax(logits[0, -1, :] / temperature, dim=-1)
+ next_token = torch.multinomial(probs, num_samples=1).item()
+ else:
+ next_token = torch.argmax(logits[0, -1, :], dim=-1).item()
+
+ generated_tokens.append(next_token)
+
+ if echo:
+ token_text = self.tokenizer.decode([next_token])
+ print(token_text, end="", flush=True)
+
+ # Check if first token is a stop token
+ if next_token in self.stop_token_ids:
+ if echo:
+ print() # Newline after generation
+ decode_time = 0
+ else:
+ # Decode phase - generate tokens one at a time
+ print("\nDecoding...", end="" if echo else "\n")
+ decode_start = time.time()
+ pos = prompt_len
+
+ while len(generated_tokens) < max_new_tokens:
+ # Check for stop tokens (EOS or )
+ if next_token in self.stop_token_ids:
+ break
+
+ # Get embedding for current token
+ token_tensor = torch.tensor([[next_token]], dtype=torch.long)
+ token_emb = self.model.run_method("token_embedding", (token_tensor,))[0]
+
+ # Run decoder
+ pos_tensor = torch.tensor([pos], dtype=torch.long)
+ try:
+ logits = self.model.run_method("text_decoder", (token_emb, pos_tensor))[0]
+ except Exception as e:
+ print(f"\nError during decode step {pos}: {e}")
+ break
+
+ # Sample next token
+ if temperature > 0:
+ probs = torch.softmax(logits[0, -1, :] / temperature, dim=-1)
+ next_token = torch.multinomial(probs, num_samples=1).item()
+ else:
+ next_token = torch.argmax(logits[0, -1, :], dim=-1).item()
+
+ generated_tokens.append(next_token)
+ pos += 1
+
+ if echo:
+ token_text = self.tokenizer.decode([next_token])
+ print(token_text, end="", flush=True)
+
+ decode_time = time.time() - decode_start
+ if echo:
+ print() # Newline after generation
+
+ # Print stats
+ num_tokens = len(generated_tokens)
+ print("-" * 50)
+ print(f"Generated tokens: {num_tokens}")
+ print(f"Prefill: {prefill_time:.2f}s")
+ if decode_time > 0 and num_tokens > 1:
+ print(f"Decode: {decode_time:.2f}s ({(num_tokens - 1) / decode_time:.1f} tok/s)")
+ print(f"Total: {prefill_time + decode_time:.2f}s")
+
+ # Decode all tokens to text
+ generated_text = self.tokenizer.decode(generated_tokens, skip_special_tokens=True)
+ return generated_text
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Run Gemma3 multimodal inference with ExecutorTorch Runtime (low-level API)",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+Examples:
+ # Describe an image
+ python runtime_inference.py --image_path example.jpg --prompt "What is in this image?"
+
+ # Count objects
+ python runtime_inference.py --image_path example.jpg --prompt "How many people are in this image?"
+
+ # Text-only (no image)
+ python runtime_inference.py --prompt "What is the capital of France?"
+ """,
+ )
+ parser.add_argument(
+ "--model_dir",
+ type=str,
+ default="gemma3",
+ help="Path to the model directory containing .pte file and tokenizer",
+ )
+ parser.add_argument(
+ "--model_file",
+ type=str,
+ default="GEMMA3_4B_XNNPACK_INT8_INT4.pte",
+ help="Name of the .pte model file",
+ )
+ parser.add_argument(
+ "--image_path",
+ type=str,
+ default=None,
+ help="Path to the input image file (optional for text-only)",
+ )
+ parser.add_argument(
+ "--prompt",
+ type=str,
+ default="What is in this image?",
+ help="Text prompt for the model",
+ )
+ parser.add_argument(
+ "--max_new_tokens",
+ type=int,
+ default=100,
+ help="Maximum number of tokens to generate",
+ )
+ parser.add_argument(
+ "--temperature",
+ type=float,
+ default=0.0,
+ help="Sampling temperature (0 = greedy decoding)",
+ )
+
+ args = parser.parse_args()
+
+ # Resolve paths relative to script directory
+ script_dir = Path(__file__).parent
+ model_dir = script_dir / args.model_dir
+ model_path = str(model_dir / args.model_file)
+
+ print("=" * 60)
+ print("Gemma3 Multimodal Runtime Inference (low-level API)")
+ print("=" * 60)
+
+ # Load image if provided
+ image = None
+ if args.image_path:
+ image_path = Path(args.image_path)
+ if not image_path.is_absolute():
+ image_path = script_dir / image_path
+
+ print(f"Loading image from {image_path}...")
+ image = Image.open(image_path)
+ print(f" Image size: {image.size}")
+
+ # Create runner
+ runner = Gemma3RuntimeRunner(model_path, str(model_dir))
+
+ print()
+ print("-" * 50)
+ print(f"Prompt: {args.prompt}")
+ print("-" * 50)
+ print("Response: ", end="", flush=True)
+
+ # Generate
+ response = runner.generate(
+ prompt=args.prompt,
+ image=image,
+ max_new_tokens=args.max_new_tokens,
+ temperature=args.temperature,
+ echo=True,
+ )
+
+ print()
+ print("=" * 60)
+ print("Final Response:")
+ print("=" * 60)
+ print(response)
+ print()
+ print("Done!")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/multimodal/text-runtime/.gitignore b/multimodal/text-runtime/.gitignore
new file mode 100644
index 00000000..a73f2f6d
--- /dev/null
+++ b/multimodal/text-runtime/.gitignore
@@ -0,0 +1,21 @@
+# Model directories
+models/
+
+# Model files
+*.pte
+*.bin
+*.onnx
+*.pt
+*.pth
+*.safetensors
+
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+.venv/
+venv/
+env/
+
+# OS
+.DS_Store
diff --git a/multimodal/text-runtime/README.md b/multimodal/text-runtime/README.md
new file mode 100644
index 00000000..84171d04
--- /dev/null
+++ b/multimodal/text-runtime/README.md
@@ -0,0 +1,48 @@
+# Qwen3 Text Runtime
+
+ExecuTorch-based inference for Qwen3-0.6B with XNNPACK backend.
+
+## Prerequisites
+
+- Python 3.10+
+- optimum-executorch
+- transformers
+
+## Installation
+
+```bash
+pip install optimum-executorch transformers
+```
+
+## Download Model
+
+```bash
+hf download larryliu0820/Qwen3-0.6B-ExecuTorch-XNNPACK --local-dir models/Qwen3-0.6B-ExecuTorch-XNNPACK
+```
+
+## Usage
+
+```bash
+# Basic usage
+python qwen_inference.py --prompt "What is the capital of France?"
+
+# With chat template
+python qwen_inference.py --chat --prompt "Hello, how are you?"
+
+# Enable thinking mode (shows reasoning)
+python qwen_inference.py --chat --thinking --prompt "Solve: 2x + 5 = 15"
+
+# Adjust generation length
+python qwen_inference.py --prompt "Explain quantum computing" --max_seq_len 256
+```
+
+## Command Line Arguments
+
+| Argument | Default | Description |
+|----------|---------|-------------|
+| `--model_dir` | `models/Qwen3-0.6B-ExecuTorch-XNNPACK` | Model directory |
+| `--prompt` | - | Input prompt |
+| `--max_seq_len` | `128` | Maximum sequence length |
+| `--chat` | `false` | Use chat template formatting |
+| `--thinking` | `false` | Enable thinking mode |
+| `--echo` | `false` | Include prompt in output |
diff --git a/multimodal/text-runtime/qwen_inference.py b/multimodal/text-runtime/qwen_inference.py
new file mode 100644
index 00000000..86f611dd
--- /dev/null
+++ b/multimodal/text-runtime/qwen_inference.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+"""
+Qwen3-0.6B inference using ExecutorTorch Runtime via optimum-executorch.
+
+This script runs inference on a Qwen3 model exported with ExecutorTorch XNNPACK backend.
+The model was exported with --use_custom_sdpa --use_custom_kv_cache flags.
+
+Usage:
+ python qwen_inference.py --prompt "Hello, how are you?"
+ python qwen_inference.py --prompt "What is the capital of France?" --max_new_tokens 50
+"""
+
+import argparse
+from pathlib import Path
+
+from optimum.executorch import ExecuTorchModelForCausalLM
+from transformers import AutoTokenizer
+
+
+def format_chat_prompt(
+ prompt: str,
+ system_prompt: str = "You are a helpful assistant.",
+ enable_thinking: bool = False,
+) -> str:
+ """Format prompt using Qwen3's chat template.
+
+ Args:
+ prompt: The user's message
+ system_prompt: System instructions for the assistant
+ enable_thinking: If False, disable thinking mode to get direct answers
+ """
+ base = f"<|im_start|>system\n{system_prompt}<|im_end|>\n<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n"
+ if not enable_thinking:
+ # Add empty thinking block to disable thinking mode
+ base += "\n\n\n\n"
+ return base
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Run Qwen3 inference with ExecutorTorch")
+
+ parser.add_argument(
+ "--model_dir",
+ type=str,
+ default="models/Qwen3-0.6B-ExecuTorch-XNNPACK",
+ help="Path to the model directory containing model.pte and tokenizer files",
+ )
+ parser.add_argument(
+ "--prompt",
+ type=str,
+ default="Simply put, the theory of relativity states that",
+ help="Input prompt for generation",
+ )
+ parser.add_argument(
+ "--max_seq_len",
+ type=int,
+ default=128,
+ help="Maximum sequence length (prompt + generated tokens)",
+ )
+ parser.add_argument(
+ "--echo",
+ action="store_true",
+ help="Include the prompt in the output",
+ )
+ parser.add_argument(
+ "--chat",
+ action="store_true",
+ help="Use chat template formatting",
+ )
+ parser.add_argument(
+ "--thinking",
+ action="store_true",
+ help="Enable thinking mode (shows reasoning before answer)",
+ )
+
+ args = parser.parse_args()
+
+ # Resolve paths relative to script directory
+ script_dir = Path(__file__).parent
+ model_dir = script_dir / args.model_dir
+
+ print(f"Loading model from {model_dir}...")
+
+ # Load tokenizer from the model directory
+ tokenizer = AutoTokenizer.from_pretrained(str(model_dir))
+
+ # Load the ExecuTorch model using optimum-executorch
+ model = ExecuTorchModelForCausalLM.from_pretrained(str(model_dir))
+
+ # Format prompt if using chat mode
+ prompt = args.prompt
+ if args.chat:
+ prompt = format_chat_prompt(prompt, enable_thinking=args.thinking)
+ print("Using chat template" + (" with thinking mode" if args.thinking else ""))
+
+ print(f"\nPrompt: {prompt}")
+ print("-" * 50)
+
+ # Generate text
+ generated_text = model.text_generation(
+ tokenizer=tokenizer,
+ prompt=prompt,
+ max_seq_len=args.max_seq_len,
+ echo=args.echo,
+ )
+
+ print(f"\nGenerated text:\n{generated_text}")
+ print("-" * 50)
+ print("Done!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/multimodal/voice-runtime/.gitignore b/multimodal/voice-runtime/.gitignore
new file mode 100644
index 00000000..a73f2f6d
--- /dev/null
+++ b/multimodal/voice-runtime/.gitignore
@@ -0,0 +1,21 @@
+# Model directories
+models/
+
+# Model files
+*.pte
+*.bin
+*.onnx
+*.pt
+*.pth
+*.safetensors
+
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+.venv/
+venv/
+env/
+
+# OS
+.DS_Store
diff --git a/multimodal/voice-runtime/README.md b/multimodal/voice-runtime/README.md
new file mode 100644
index 00000000..f4e7a193
--- /dev/null
+++ b/multimodal/voice-runtime/README.md
@@ -0,0 +1,51 @@
+# Whisper Voice Runtime
+
+ExecuTorch-based speech-to-text inference using Whisper with XNNPACK backend.
+
+## Prerequisites
+
+- Python 3.10+
+- optimum-executorch
+- transformers
+- librosa or soundfile (for audio loading)
+
+## Installation
+
+```bash
+pip install optimum-executorch transformers librosa
+```
+
+## Download Model
+
+```bash
+hf download larryliu0820/whisper-tiny-ExecuTorch-XNNPACK --local-dir models/whisper-tiny-ExecuTorch-XNNPACK
+```
+
+## Usage
+
+```bash
+# Transcribe audio file
+python whisper_inference.py --audio_path obama_short20.wav
+
+# With custom model path
+python whisper_inference.py \
+ --audio_path obama_short20.wav \
+ --model_dir models/whisper-tiny-ExecuTorch-XNNPACK
+```
+
+## Test Audio
+
+The `obama_short20.wav` file is a 20-second speech sample for testing transcription.
+
+## Command Line Arguments
+
+| Argument | Default | Description |
+|----------|---------|-------------|
+| `--audio_path` | Required | Input audio file (WAV, MP3, etc.) |
+| `--model_dir` | `models/whisper-tiny-ExecuTorch-XNNPACK` | Model directory |
+
+## References
+
+- [Whisper](https://github.com/openai/whisper)
+- [ExecuTorch](https://github.com/pytorch/executorch)
+- [optimum-executorch](https://github.com/huggingface/optimum-executorch)
diff --git a/multimodal/voice-runtime/obama_short20.wav b/multimodal/voice-runtime/obama_short20.wav
new file mode 100644
index 00000000..331b7ee6
Binary files /dev/null and b/multimodal/voice-runtime/obama_short20.wav differ
diff --git a/multimodal/voice-runtime/whisper_inference.py b/multimodal/voice-runtime/whisper_inference.py
new file mode 100644
index 00000000..13611620
--- /dev/null
+++ b/multimodal/voice-runtime/whisper_inference.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+"""
+Whisper Speech-to-Text inference using ExecutorTorch Runtime via optimum-executorch.
+
+This script demonstrates how to run Whisper inference using the optimum-executorch
+library for automatic speech recognition (ASR).
+
+Example usage:
+ python whisper_inference.py --audio_path audio.wav
+ python whisper_inference.py --audio_path audio.wav --model_dir ./models/whisper-tiny-ExecuTorch-XNNPACK
+
+Requirements:
+ - optimum-executorch
+ - transformers
+ - soundfile or librosa for audio loading
+"""
+
+import argparse
+from pathlib import Path
+
+import torch
+from optimum.executorch import ExecuTorchModelForSpeechSeq2Seq
+from transformers import WhisperProcessor
+
+
+def load_audio(audio_path: str, sampling_rate: int = 16000) -> torch.Tensor:
+ """Load audio file and resample to target sampling rate.
+
+ Args:
+ audio_path: Path to the audio file (WAV, MP3, etc.)
+ sampling_rate: Target sampling rate (default: 16000 for Whisper)
+
+ Returns:
+ Audio waveform as a 1D tensor
+ """
+ try:
+ import librosa
+ audio, sr = librosa.load(audio_path, sr=sampling_rate, mono=True)
+ return torch.from_numpy(audio).float()
+ except ImportError:
+ pass
+
+ try:
+ import soundfile as sf
+ audio, sr = sf.read(audio_path)
+ if len(audio.shape) > 1:
+ audio = audio.mean(axis=1) # Convert to mono
+ if sr != sampling_rate:
+ # Simple resampling using torch
+ import torchaudio
+ audio = torch.from_numpy(audio).float().unsqueeze(0)
+ audio = torchaudio.functional.resample(audio, sr, sampling_rate)
+ audio = audio.squeeze(0)
+ else:
+ audio = torch.from_numpy(audio).float()
+ return audio
+ except ImportError:
+ pass
+
+ raise ImportError(
+ "Please install either librosa or soundfile for audio loading:\n"
+ " pip install librosa\n"
+ " or\n"
+ " pip install soundfile"
+ )
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Run Whisper speech-to-text inference with ExecutorTorch",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+Examples:
+ # Transcribe an audio file
+ python whisper_inference.py --audio_path audio.wav
+
+ # Use a specific model
+ python whisper_inference.py --audio_path audio.wav --model_dir ./models/whisper-small-ExecuTorch-XNNPACK
+ """,
+ )
+ parser.add_argument(
+ "--model_dir",
+ type=str,
+ default="models/whisper-tiny-ExecuTorch-XNNPACK",
+ help="Path to the model directory containing model.pte and tokenizer files",
+ )
+ parser.add_argument(
+ "--audio_path",
+ type=str,
+ required=True,
+ help="Path to the input audio file (WAV, MP3, etc.)",
+ )
+ parser.add_argument(
+ "--max_seq_len",
+ type=int,
+ default=448,
+ help="Maximum number of tokens to generate",
+ )
+
+ args = parser.parse_args()
+
+ # Resolve paths relative to script directory
+ script_dir = Path(__file__).parent
+ model_dir = script_dir / args.model_dir
+ audio_path = Path(args.audio_path)
+ if not audio_path.is_absolute():
+ audio_path = script_dir / audio_path
+
+ print("=" * 60)
+ print("Whisper Speech-to-Text Inference (optimum-executorch)")
+ print("=" * 60)
+
+ # Load audio
+ print(f"Loading audio from {audio_path}...")
+ audio = load_audio(str(audio_path))
+ print(f" Audio length: {len(audio) / 16000:.2f} seconds ({len(audio)} samples)")
+
+ # Load processor (handles audio preprocessing)
+ print(f"Loading processor from {model_dir}...")
+ processor = WhisperProcessor.from_pretrained(str(model_dir))
+
+ # Preprocess audio to get input features (log-mel spectrogram)
+ print("Preprocessing audio...")
+ input_features = processor(
+ audio.numpy(),
+ sampling_rate=16000,
+ return_tensors="pt",
+ ).input_features
+ print(f" Input features shape: {input_features.shape}")
+
+ # Load the ExecuTorch model
+ print(f"Loading model from {model_dir}...")
+ model = ExecuTorchModelForSpeechSeq2Seq.from_pretrained(str(model_dir))
+
+ print()
+ print("-" * 50)
+ print("Transcribing...")
+ print("-" * 50)
+
+ # Transcribe
+ transcription = model.transcribe(
+ tokenizer=processor.tokenizer,
+ input_features=input_features,
+ max_seq_len=args.max_seq_len,
+ )
+
+ print()
+ print("=" * 60)
+ print("Transcription:")
+ print("=" * 60)
+ print(transcription)
+ print()
+ print("Done!")
+
+
+if __name__ == "__main__":
+ main()