Spaces:
Build error
Build error
Initial Deployment: Best ViT Model
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +15 -35
- .gitignore +34 -0
- .slugignore.txt +4 -0
- Dockerfile +46 -0
- LICENSE +21 -0
- Procfile +1 -0
- README.md +121 -5
- api/index.py +11 -0
- artifacts.zip +3 -0
- backend/Dockerfile +31 -0
- backend/__init__.py +0 -0
- backend/app/__init__.py +0 -0
- backend/app/api/__init__.py +0 -0
- backend/app/api/routes/__init__.py +0 -0
- backend/app/api/routes/health.py +32 -0
- backend/app/api/routes/metadata.py +57 -0
- backend/app/api/routes/predict.py +94 -0
- backend/app/core/__init__.py +0 -0
- backend/app/core/config.py +61 -0
- backend/app/core/logging.py +27 -0
- backend/app/main.py +119 -0
- backend/app/schemas/__init__.py +0 -0
- backend/app/schemas/breed.py +36 -0
- backend/app/schemas/predict.py +52 -0
- backend/app/services/__init__.py +0 -0
- backend/app/services/breed_info.py +92 -0
- backend/app/services/image_loader.py +60 -0
- backend/app/services/inference.py +196 -0
- backend/requirements.txt +14 -0
- colab_run_all.ipynb +286 -0
- data/breed_metadata.csv +27 -0
- deploy.py +76 -0
- docker-compose.yml +39 -0
- frontend/.gitignore +24 -0
- frontend/Dockerfile +15 -0
- frontend/README.md +16 -0
- frontend/dist/assets/index-CWZymrby.css +1 -0
- frontend/dist/assets/index-sBN3tCmx.js +0 -0
- frontend/dist/favicon.svg +1 -0
- frontend/dist/icons.svg +24 -0
- frontend/dist/index.html +14 -0
- frontend/eslint.config.js +29 -0
- frontend/index.html +13 -0
- frontend/nginx.conf +24 -0
- frontend/package-lock.json +2625 -0
- frontend/package.json +28 -0
- frontend/public/favicon.svg +1 -0
- frontend/public/icons.svg +24 -0
- frontend/src/App.css +1 -0
- frontend/src/App.jsx +42 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,15 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
.pkl filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
artifacts.zip filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
ml/artifacts/figures/class_distribution.png filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
ml/artifacts/figures/cnn/confusion_matrix.png filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
ml/artifacts/figures/cnn/per_class_f1.png filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
ml/artifacts/figures/cnn/training_curves.png filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
ml/artifacts/figures/comparison/comparison_radar.png filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
ml/artifacts/figures/mlp/confusion_matrix.png filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
ml/artifacts/figures/mlp/per_class_f1.png filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
ml/artifacts/figures/mlp/training_curves.png filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
ml/artifacts/figures/resnet/confusion_matrix.png filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
ml/artifacts/figures/resnet/per_class_f1.png filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
ml/artifacts/figures/sample_images.png filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
ml/artifacts/figures/vit/confusion_matrix.png filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
ml/artifacts/figures/vit/per_class_f1.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model checkpoints & artifacts
|
| 2 |
+
ml/artifacts/checkpoints/*.pth
|
| 3 |
+
ml/artifacts/checkpoints/*.pt
|
| 4 |
+
ml/artifacts/checkpoints/*.ckpt
|
| 5 |
+
|
| 6 |
+
# Python
|
| 7 |
+
__pycache__/
|
| 8 |
+
*.py[cod]
|
| 9 |
+
*$py.class
|
| 10 |
+
*.egg-info/
|
| 11 |
+
dist/
|
| 12 |
+
build/
|
| 13 |
+
*.egg
|
| 14 |
+
|
| 15 |
+
# Virtual environments
|
| 16 |
+
venv/
|
| 17 |
+
env/
|
| 18 |
+
.env
|
| 19 |
+
|
| 20 |
+
# Jupyter
|
| 21 |
+
.ipynb_checkpoints/
|
| 22 |
+
|
| 23 |
+
# OS files
|
| 24 |
+
.DS_Store
|
| 25 |
+
Thumbs.db
|
| 26 |
+
|
| 27 |
+
# IDE
|
| 28 |
+
.vscode/
|
| 29 |
+
.idea/
|
| 30 |
+
|
| 31 |
+
backend.log
|
| 32 |
+
tunnel.log
|
| 33 |
+
|
| 34 |
+
models/*.pth
|
.slugignore.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.psd
|
| 2 |
+
*.pdf
|
| 3 |
+
/test
|
| 4 |
+
/spec
|
Dockerfile
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Unified Dockerfile for Hugging Face Spaces (or Render)
|
| 2 |
+
# Builds the Vite React Frontend and serves it statically via FastAPI Backend
|
| 3 |
+
|
| 4 |
+
# Stage 1: Build Frontend
|
| 5 |
+
FROM node:20-alpine as frontend-builder
|
| 6 |
+
WORKDIR /app/frontend
|
| 7 |
+
COPY frontend/package*.json ./
|
| 8 |
+
RUN npm ci
|
| 9 |
+
COPY frontend/ ./
|
| 10 |
+
RUN npm run build
|
| 11 |
+
|
| 12 |
+
# Stage 2: Build Backend & Serve
|
| 13 |
+
FROM python:3.11-slim
|
| 14 |
+
|
| 15 |
+
WORKDIR /app
|
| 16 |
+
|
| 17 |
+
# Install system dependencies
|
| 18 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 19 |
+
gcc \
|
| 20 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 21 |
+
|
| 22 |
+
# Copy backend requirements and install
|
| 23 |
+
COPY backend/requirements.txt ./backend/
|
| 24 |
+
RUN pip install --no-cache-dir -r backend/requirements.txt
|
| 25 |
+
|
| 26 |
+
# Copy backend source code and config
|
| 27 |
+
COPY backend/ ./backend/
|
| 28 |
+
COPY ml/src/ ./ml/src/
|
| 29 |
+
COPY data/breed_metadata.csv ./data/
|
| 30 |
+
# Copy the built frontend
|
| 31 |
+
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
|
| 32 |
+
# Copy the exported model artifact
|
| 33 |
+
COPY models/vit_best.pth ./models/vit_best.pth
|
| 34 |
+
|
| 35 |
+
# Set runtime environments
|
| 36 |
+
ENV PYTHONPATH=/app
|
| 37 |
+
ENV MODEL_PATH=/app/models/vit_best.pth
|
| 38 |
+
ENV METADATA_PATH=/app/data/breed_metadata.csv
|
| 39 |
+
ENV MODEL_NAME=vit
|
| 40 |
+
ENV MODEL_VERSION=v2.0
|
| 41 |
+
|
| 42 |
+
# Hugging Face Spaces run explicitly on 7860
|
| 43 |
+
EXPOSE 7860
|
| 44 |
+
|
| 45 |
+
# Run Uvicorn via HTTP (Hugging Face / Render friendly)
|
| 46 |
+
CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Ajaya and team
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
Procfile
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
web: python app.py
|
README.md
CHANGED
|
@@ -1,10 +1,126 @@
|
|
| 1 |
---
|
| 2 |
-
title: Cattle
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Cattle Breed Classifier
|
| 3 |
+
emoji: 🐄
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
| 10 |
+
# 🐄 Indigenous Cattle Breed Classifier
|
| 11 |
+
|
| 12 |
+
Identifying indigenous Indian cattle and buffalo breeds requires specialized domain expertise that many farmers and agricultural workers may lack. Proper identification is critical for optimizing milk yield projections, managing livestock lifespans, and preserving regional biodiversity.
|
| 13 |
+
|
| 14 |
+
This project solves this by delivering an end-to-end deep learning pipeline and an accessible web application capable of identifying **26 distinct indigenous cattle and buffalo breeds** from an image.
|
| 15 |
+
|
| 16 |
+
    
|
| 17 |
+
|
| 18 |
+
### 🟢 Live Demo
|
| 19 |
+
**Test the fully deployed Vision Transformer natively in the browser here:**
|
| 20 |
+
👉 **[https://huggingface.co/spaces/amoghakoulapure/cattle-breed-classifier](https://huggingface.co/spaces/amoghakoulapure/cattle-breed-classifier)**
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 📊 Data Gathering & Information
|
| 25 |
+
|
| 26 |
+
The foundation of any robust computer vision model is its data. Our dataset comprises thousands of images distributed across 26 target classes, which include **21 Cow breeds** and **5 Buffalo breeds**.
|
| 27 |
+
|
| 28 |
+
To handle raw data efficiently, we developed a dynamic pre-processing layer that structurally manages unbalanced classes, unifies image ratios, and performs aggressive augmentation.
|
| 29 |
+
|
| 30 |
+
<p align="center">
|
| 31 |
+
<img src="ml/artifacts/figures/class_distribution.png" width="48%" />
|
| 32 |
+
<img src="ml/artifacts/figures/sample_images.png" width="48%" />
|
| 33 |
+
</p>
|
| 34 |
+
|
| 35 |
+
*Above: Analyzing the inherent class imbalance constraints of the dataset and some augmented examples.*
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## 🧠 Models Trained: Pros & Cons
|
| 40 |
+
|
| 41 |
+
Rather than settling for one model, we evaluated 4 widely different paradigms for image classification. By building a unified, automated training pipeline config, we systematically benchmarked them against one another.
|
| 42 |
+
|
| 43 |
+
### 1. Multilayer Perceptron (MLP)
|
| 44 |
+
* **The Idea:** Linear spatial flattening.
|
| 45 |
+
* **Pros:** Theoretically the simplest and most interpretable neural structure.
|
| 46 |
+
* **Cons:** Explodes in parameter size (**~590 MB** footprint) and lacks any spatial or localized awareness, resulting in very **poor accuracy (~24%)**.
|
| 47 |
+
|
| 48 |
+
### 2. CNN (From Scratch)
|
| 49 |
+
* **The Idea:** A custom 5-block Convolutional Neural Network built from zero.
|
| 50 |
+
* **Pros:** Learns specialized local spatial features natively on the data. Highly lightweight (**18.5 MB**) and blazing fast (**2.4ms latency**).
|
| 51 |
+
* **Cons:** Training a CNN purely from scratch on limited agricultural data yields heavily under-fitted results, hovering around **~24.4% accuracy**.
|
| 52 |
+
|
| 53 |
+
### 3. ResNet-50 (Transfer Learning)
|
| 54 |
+
* **The Idea:** Leverages massive prior knowledge using a pre-trained ImageNet model, unfreezing the dense classifier layers natively for cattle variants.
|
| 55 |
+
* **Pros:** Phenomenal balance of inference time and capability. Shoots accuracy up drastically (**63.2%**) while maintaining a manageable footprint (**93 MB**).
|
| 56 |
+
* **Cons:** It loses on sheer simplicity when compared to the CNN.
|
| 57 |
+
|
| 58 |
+
### 4. Vision Transformer (ViT-B/16)
|
| 59 |
+
* **The Idea:** Treats image patches like NLP tokens, learning global context via multi-headed self-attention.
|
| 60 |
+
* **Pros:** Tied for the absolute greatest capability and contextual representation capability (**63.2%** accuracy).
|
| 61 |
+
* **Cons:** Major overkill for rapid, on-device basic inference. It is the largest model (**328 MB**) with the slowest inference ceiling (**15.5ms**).
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## 📈 Model Comparison
|
| 66 |
+
|
| 67 |
+
Based on our automated scoring system—which weighs 50% F1, 20% Accuracy, 15% Latency, and 10% Size—the optimal deployment choice falls between **ResNet-50** or **ViT**.
|
| 68 |
+
|
| 69 |
+
| Architecture | Accuracy | Macro F1 | Latency (ms) | Size (MB) |
|
| 70 |
+
|---|---|---|---|---|
|
| 71 |
+
| **CNN** | 24.4% | 14.0% | 2.4 | 18.5 |
|
| 72 |
+
| **MLP** | 24.0% | 13.0% | 2.5 | 590.5 |
|
| 73 |
+
| **ResNet-50** | **63.2%** | **56.0%** | 5.7 | 93.7 |
|
| 74 |
+
| **ViT-B/16** | **63.2%** | 55.0% | 15.5 | 328.9 |
|
| 75 |
+
|
| 76 |
+
<p align="center">
|
| 77 |
+
<img src="ml/artifacts/figures/comparison/comparison_bar.png" width="48%" />
|
| 78 |
+
<img src="ml/artifacts/figures/comparison/comparison_radar.png" width="48%" />
|
| 79 |
+
</p>
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## 🚀 Deployment Pipeline
|
| 84 |
+
|
| 85 |
+
This application leverages a completely unified, containerized deployment mechanic utilizing Docker.
|
| 86 |
+
|
| 87 |
+
### Running Natively (FastAPI Unified Server)
|
| 88 |
+
For zero-dependency deployment, we bundle the built React Static files and serve them natively over the FastAPI Python server.
|
| 89 |
+
```bash
|
| 90 |
+
# 1. Build the frontend (Node 20+)
|
| 91 |
+
cd frontend && npm install && npm run build
|
| 92 |
+
cd ..
|
| 93 |
+
|
| 94 |
+
# 2. Start the unified server mapping the optimal architecture
|
| 95 |
+
export MODEL_PATH=models/vit_best.pth MODEL_NAME=vit
|
| 96 |
+
python3 -m pip install -r backend/requirements.txt
|
| 97 |
+
uvicorn backend.app.main:app --host 0.0.0.0 --port 7860
|
| 98 |
+
```
|
| 99 |
+
Then navigate to **http://localhost:7860**.
|
| 100 |
+
|
| 101 |
+
### Active Cloud Deployment (Hugging Face Spaces)
|
| 102 |
+
The environment has been securely deployed to Hugging Face Spaces via an automated API deployer mapping our unified `Dockerfile`. The space automatically bakes in the Node.js compiled frontend, caches the 460MB ViT model, and publicly maps the server securely.
|
| 103 |
+
|
| 104 |
+
**Access it here:** [https://huggingface.co/spaces/amoghakoulapure/cattle-breed-classifier](https://huggingface.co/spaces/amoghakoulapure/cattle-breed-classifier)
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
## ⚡ How to Train in Colab
|
| 109 |
+
|
| 110 |
+
If you want to train these models yourself utilizing Free T4 GPUs, we designed an automated Google Colab integration:
|
| 111 |
+
|
| 112 |
+
1. Upload or Open `colab_run_all.ipynb` in Google Colab.
|
| 113 |
+
2. Set Runtime to **T4 GPU**.
|
| 114 |
+
3. **Run All Cells**. The script will automatically:
|
| 115 |
+
* Clone this code repository.
|
| 116 |
+
* Auto-fetch all images natively into the drive.
|
| 117 |
+
* Cycle-train through `00_data_audit`, `01_mlp`, `02_cnn`, `03_resnet`, `04_vit`, and finally execution benchmarks.
|
| 118 |
+
* Zip and download `--log-output` evaluation artifacts and new `.pth` files straight back to your computer dynamically!
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
### Breeds Supported (26)
|
| 123 |
+
|
| 124 |
+
**Cows (21)**: Alambadi, Amritmahal, Bargur, Dangi, Deoni, Gir, Hallikar, Kangayam, Kankrej, Kasaragod, Kenkatha, Kherigarh, Malnad Gidda, Nagori, Nimari, Pulikulam, Rathi, Sahiwal, Tharparkar, Umblachery
|
| 125 |
+
|
| 126 |
+
**Buffaloes (5)**: Banni, Jaffrabadi, Mehsana, Nagpuri, Nili Ravi, Shurti
|
api/index.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
# Add project root to python path to allow backend package imports
|
| 5 |
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
| 6 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 7 |
+
|
| 8 |
+
# Import the FastAPI application
|
| 9 |
+
from backend.app.main import app
|
| 10 |
+
|
| 11 |
+
# Vercel Serverless requires the application to be exposed as `app`
|
artifacts.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4fb61dc3352333e5e05a2c3ce96cacd94dd15cdc3eae04a8412ab9716157f921
|
| 3 |
+
size 254
|
backend/Dockerfile
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Backend Dockerfile
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
# Install system dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 8 |
+
gcc \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
# Copy requirements first for layer caching
|
| 12 |
+
COPY backend/requirements.txt ./backend/requirements.txt
|
| 13 |
+
RUN pip install --no-cache-dir -r backend/requirements.txt
|
| 14 |
+
|
| 15 |
+
# Copy project files
|
| 16 |
+
COPY backend/ ./backend/
|
| 17 |
+
COPY ml/ ./ml/
|
| 18 |
+
COPY data/ ./data/
|
| 19 |
+
COPY models/ ./models/
|
| 20 |
+
|
| 21 |
+
# Set environment variables
|
| 22 |
+
ENV PYTHONPATH=/app
|
| 23 |
+
ENV MODEL_PATH=/app/models/vit_best.pth
|
| 24 |
+
ENV METADATA_PATH=/app/data/breed_metadata.csv
|
| 25 |
+
ENV CLASSES_PATH=/app/models/classes.txt
|
| 26 |
+
ENV MODEL_NAME=vit
|
| 27 |
+
ENV MODEL_VERSION=v2.0
|
| 28 |
+
|
| 29 |
+
EXPOSE 8000
|
| 30 |
+
|
| 31 |
+
CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
backend/__init__.py
ADDED
|
File without changes
|
backend/app/__init__.py
ADDED
|
File without changes
|
backend/app/api/__init__.py
ADDED
|
File without changes
|
backend/app/api/routes/__init__.py
ADDED
|
File without changes
|
backend/app/api/routes/health.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Health and version endpoints.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from fastapi import APIRouter
|
| 6 |
+
|
| 7 |
+
from backend.app.core.config import get_settings
|
| 8 |
+
from backend.app.services.inference import inference_service
|
| 9 |
+
|
| 10 |
+
router = APIRouter(tags=["Health"])
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@router.get("/health")
|
| 14 |
+
async def health():
|
| 15 |
+
"""Health check endpoint."""
|
| 16 |
+
return {
|
| 17 |
+
"status": "healthy",
|
| 18 |
+
"model_loaded": inference_service.is_loaded,
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@router.get("/version")
|
| 23 |
+
async def version():
|
| 24 |
+
"""API version info."""
|
| 25 |
+
settings = get_settings()
|
| 26 |
+
return {
|
| 27 |
+
"app_name": settings.app_name,
|
| 28 |
+
"app_version": settings.app_version,
|
| 29 |
+
"model_version": settings.model_version,
|
| 30 |
+
"model_name": settings.model_name,
|
| 31 |
+
"num_classes": len(inference_service.class_names),
|
| 32 |
+
}
|
backend/app/api/routes/metadata.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Breed metadata endpoints.
|
| 3 |
+
GET /breeds - List all breeds
|
| 4 |
+
GET /breeds/{name} - Get breed details
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from fastapi import APIRouter, HTTPException, Query
|
| 8 |
+
from typing import Optional
|
| 9 |
+
|
| 10 |
+
from backend.app.schemas.breed import BreedDetail, BreedListItem, BreedsListResponse
|
| 11 |
+
from backend.app.services.breed_info import breed_info_service
|
| 12 |
+
|
| 13 |
+
router = APIRouter(prefix="/breeds", tags=["Breeds"])
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@router.get("", response_model=BreedsListResponse)
|
| 17 |
+
async def list_breeds(
|
| 18 |
+
animal_type: Optional[str] = Query(None, description="Filter by animal type (Cow/Buffalo)"),
|
| 19 |
+
search: Optional[str] = Query(None, description="Search by breed name"),
|
| 20 |
+
):
|
| 21 |
+
"""List all breeds with optional filtering."""
|
| 22 |
+
breeds = breed_info_service.get_all()
|
| 23 |
+
|
| 24 |
+
if animal_type:
|
| 25 |
+
breeds = [b for b in breeds if b['animal_type'].lower() == animal_type.lower()]
|
| 26 |
+
|
| 27 |
+
if search:
|
| 28 |
+
search_lower = search.lower()
|
| 29 |
+
breeds = [b for b in breeds if search_lower in b['breed_name'].lower()]
|
| 30 |
+
|
| 31 |
+
items = [
|
| 32 |
+
BreedListItem(
|
| 33 |
+
breed_id=b['breed_id'],
|
| 34 |
+
breed_name=b['breed_name'],
|
| 35 |
+
animal_type=b['animal_type'],
|
| 36 |
+
region=b['region'],
|
| 37 |
+
primary_use=b['primary_use'],
|
| 38 |
+
)
|
| 39 |
+
for b in breeds
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
return BreedsListResponse(total=len(items), breeds=items)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@router.get("/{breed_name}", response_model=BreedDetail)
|
| 46 |
+
async def get_breed(breed_name: str):
|
| 47 |
+
"""Get detailed info for a specific breed."""
|
| 48 |
+
info = breed_info_service.get_breed(breed_name)
|
| 49 |
+
if info is None:
|
| 50 |
+
# Try case-insensitive search
|
| 51 |
+
results = breed_info_service.search(breed_name)
|
| 52 |
+
if results:
|
| 53 |
+
info = results[0]
|
| 54 |
+
else:
|
| 55 |
+
raise HTTPException(status_code=404, detail=f"Breed not found: {breed_name}")
|
| 56 |
+
|
| 57 |
+
return BreedDetail(**info)
|
backend/app/api/routes/predict.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Prediction endpoints.
|
| 3 |
+
POST /predict/file - Upload image file
|
| 4 |
+
POST /predict/url - Predict from image URL
|
| 5 |
+
POST /predict/base64 - Predict from base64 image
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from fastapi import APIRouter, UploadFile, File, HTTPException, Query
|
| 9 |
+
|
| 10 |
+
from backend.app.schemas.predict import (
|
| 11 |
+
PredictResponse,
|
| 12 |
+
PredictURLRequest,
|
| 13 |
+
PredictBase64Request,
|
| 14 |
+
PredictionItem,
|
| 15 |
+
BreedInfo,
|
| 16 |
+
)
|
| 17 |
+
from backend.app.services.inference import inference_service
|
| 18 |
+
from backend.app.services.image_loader import (
|
| 19 |
+
load_image_from_upload,
|
| 20 |
+
load_image_from_url,
|
| 21 |
+
load_image_from_base64,
|
| 22 |
+
)
|
| 23 |
+
from backend.app.services.breed_info import breed_info_service
|
| 24 |
+
from backend.app.core.logging import logger
|
| 25 |
+
|
| 26 |
+
router = APIRouter(prefix="/predict", tags=["Prediction"])
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _build_response(result: dict) -> PredictResponse:
|
| 30 |
+
"""Build a PredictResponse from inference result dict."""
|
| 31 |
+
# Get breed info
|
| 32 |
+
breed_info = None
|
| 33 |
+
breed_summary = breed_info_service.get_breed_summary(result['predicted_breed'])
|
| 34 |
+
if breed_summary:
|
| 35 |
+
breed_info = BreedInfo(**breed_summary)
|
| 36 |
+
|
| 37 |
+
return PredictResponse(
|
| 38 |
+
predicted_breed=result['predicted_breed'],
|
| 39 |
+
confidence=result['confidence'],
|
| 40 |
+
top_k=[PredictionItem(**item) for item in result['top_k']],
|
| 41 |
+
breed_info=breed_info,
|
| 42 |
+
model_version=result.get('model_version', 'v1.0'),
|
| 43 |
+
inference_time_ms=result['inference_time_ms'],
|
| 44 |
+
warning=result.get('warning'),
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@router.post("/file", response_model=PredictResponse)
|
| 49 |
+
async def predict_file(
|
| 50 |
+
file: UploadFile = File(...),
|
| 51 |
+
top_k: int = Query(default=3, ge=1, le=10),
|
| 52 |
+
):
|
| 53 |
+
"""Predict breed from uploaded image file."""
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
contents = await file.read()
|
| 57 |
+
image = load_image_from_upload(contents)
|
| 58 |
+
result = inference_service.predict(image, top_k=top_k)
|
| 59 |
+
return _build_response(result)
|
| 60 |
+
except ValueError as e:
|
| 61 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 62 |
+
except Exception as e:
|
| 63 |
+
logger.error(f"Prediction error: {e}")
|
| 64 |
+
raise HTTPException(status_code=500, detail="Prediction failed")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@router.post("/url", response_model=PredictResponse)
|
| 68 |
+
async def predict_url(request: PredictURLRequest):
|
| 69 |
+
"""Predict breed from image URL."""
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
image = load_image_from_url(request.url)
|
| 73 |
+
result = inference_service.predict(image, top_k=request.top_k)
|
| 74 |
+
return _build_response(result)
|
| 75 |
+
except ValueError as e:
|
| 76 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 77 |
+
except Exception as e:
|
| 78 |
+
logger.error(f"Prediction error: {e}")
|
| 79 |
+
raise HTTPException(status_code=500, detail="Prediction failed")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@router.post("/base64", response_model=PredictResponse)
|
| 83 |
+
async def predict_base64(request: PredictBase64Request):
|
| 84 |
+
"""Predict breed from base64-encoded image."""
|
| 85 |
+
|
| 86 |
+
try:
|
| 87 |
+
image = load_image_from_base64(request.image)
|
| 88 |
+
result = inference_service.predict(image, top_k=request.top_k)
|
| 89 |
+
return _build_response(result)
|
| 90 |
+
except ValueError as e:
|
| 91 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 92 |
+
except Exception as e:
|
| 93 |
+
logger.error(f"Prediction error: {e}")
|
| 94 |
+
raise HTTPException(status_code=500, detail="Prediction failed")
|
backend/app/core/__init__.py
ADDED
|
File without changes
|
backend/app/core/config.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Application configuration using Pydantic Settings.
|
| 3 |
+
Reads from environment variables and .env file.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from functools import lru_cache
|
| 9 |
+
|
| 10 |
+
from pydantic_settings import BaseSettings
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class Settings(BaseSettings):
|
| 14 |
+
"""Application configuration from environment variables."""
|
| 15 |
+
|
| 16 |
+
# App
|
| 17 |
+
app_name: str = "Cattle Breed Classifier API"
|
| 18 |
+
app_version: str = "1.0.0"
|
| 19 |
+
debug: bool = False
|
| 20 |
+
|
| 21 |
+
# Model
|
| 22 |
+
model_path: str = os.environ.get(
|
| 23 |
+
"MODEL_PATH",
|
| 24 |
+
str(Path(__file__).resolve().parents[3] / "models" / "cattle_breed_classifier_full_model.pth")
|
| 25 |
+
)
|
| 26 |
+
model_name: str = os.environ.get("MODEL_NAME", "resnet")
|
| 27 |
+
model_version: str = os.environ.get("MODEL_VERSION", "v1.0")
|
| 28 |
+
|
| 29 |
+
# Metadata
|
| 30 |
+
metadata_path: str = os.environ.get(
|
| 31 |
+
"METADATA_PATH",
|
| 32 |
+
str(Path(__file__).resolve().parents[3] / "data" / "breed_metadata.csv")
|
| 33 |
+
)
|
| 34 |
+
classes_path: str = os.environ.get(
|
| 35 |
+
"CLASSES_PATH",
|
| 36 |
+
str(Path(__file__).resolve().parents[3] / "models" / "classes.txt")
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
# Image
|
| 40 |
+
image_size: int = 224
|
| 41 |
+
max_image_size_mb: float = 10.0
|
| 42 |
+
|
| 43 |
+
# Server
|
| 44 |
+
host: str = "0.0.0.0"
|
| 45 |
+
port: int = 8000
|
| 46 |
+
cors_origins: list[str] = ["*"]
|
| 47 |
+
|
| 48 |
+
# Inference
|
| 49 |
+
top_k: int = 3
|
| 50 |
+
url_timeout: int = 10
|
| 51 |
+
|
| 52 |
+
class Config:
|
| 53 |
+
env_file = ".env"
|
| 54 |
+
env_file_encoding = "utf-8"
|
| 55 |
+
case_sensitive = False
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
@lru_cache()
|
| 59 |
+
def get_settings() -> Settings:
|
| 60 |
+
"""Get cached settings instance."""
|
| 61 |
+
return Settings()
|
backend/app/core/logging.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Structured logging setup.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import logging
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def setup_logging(level: str = "INFO") -> logging.Logger:
|
| 10 |
+
"""Configure application logging."""
|
| 11 |
+
logger = logging.getLogger("cattle_classifier")
|
| 12 |
+
logger.setLevel(getattr(logging, level.upper(), logging.INFO))
|
| 13 |
+
|
| 14 |
+
if not logger.handlers:
|
| 15 |
+
handler = logging.StreamHandler(sys.stdout)
|
| 16 |
+
handler.setLevel(logging.DEBUG)
|
| 17 |
+
formatter = logging.Formatter(
|
| 18 |
+
"%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
| 19 |
+
datefmt="%Y-%m-%d %H:%M:%S",
|
| 20 |
+
)
|
| 21 |
+
handler.setFormatter(formatter)
|
| 22 |
+
logger.addHandler(handler)
|
| 23 |
+
|
| 24 |
+
return logger
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
logger = setup_logging()
|
backend/app/main.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
FastAPI main application.
|
| 3 |
+
Cattle Breed Classifier API.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from contextlib import asynccontextmanager
|
| 9 |
+
|
| 10 |
+
from fastapi import FastAPI
|
| 11 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 12 |
+
|
| 13 |
+
# Ensure project root is in path
|
| 14 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
| 15 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 16 |
+
|
| 17 |
+
from backend.app.core.config import get_settings
|
| 18 |
+
from backend.app.core.logging import logger
|
| 19 |
+
from backend.app.api.routes import health, predict, metadata
|
| 20 |
+
from backend.app.services.inference import inference_service
|
| 21 |
+
from backend.app.services.breed_info import breed_info_service
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@asynccontextmanager
|
| 25 |
+
async def lifespan(app: FastAPI):
|
| 26 |
+
"""Startup and shutdown events."""
|
| 27 |
+
# Startup
|
| 28 |
+
logger.info("Starting Cattle Breed Classifier API...")
|
| 29 |
+
try:
|
| 30 |
+
inference_service.load()
|
| 31 |
+
breed_info_service.load()
|
| 32 |
+
logger.info("All services loaded successfully")
|
| 33 |
+
except Exception as e:
|
| 34 |
+
logger.error(f"Startup error: {e}")
|
| 35 |
+
logger.info("API started with limited functionality")
|
| 36 |
+
|
| 37 |
+
yield
|
| 38 |
+
|
| 39 |
+
# Shutdown
|
| 40 |
+
logger.info("Shutting down...")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def create_app() -> FastAPI:
|
| 44 |
+
"""Create and configure FastAPI application."""
|
| 45 |
+
settings = get_settings()
|
| 46 |
+
|
| 47 |
+
app = FastAPI(
|
| 48 |
+
title=settings.app_name,
|
| 49 |
+
version=settings.app_version,
|
| 50 |
+
description=(
|
| 51 |
+
"API for classifying Indian cattle breeds using deep learning. "
|
| 52 |
+
"Supports image upload, URL, and base64 input. "
|
| 53 |
+
"Returns breed prediction with confidence and metadata."
|
| 54 |
+
),
|
| 55 |
+
lifespan=lifespan,
|
| 56 |
+
docs_url="/docs",
|
| 57 |
+
redoc_url="/redoc",
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
# CORS
|
| 61 |
+
app.add_middleware(
|
| 62 |
+
CORSMiddleware,
|
| 63 |
+
allow_origins=settings.cors_origins,
|
| 64 |
+
allow_credentials=True,
|
| 65 |
+
allow_methods=["*"],
|
| 66 |
+
allow_headers=["*"],
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
# Include routers
|
| 70 |
+
app.include_router(health.router)
|
| 71 |
+
app.include_router(predict.router)
|
| 72 |
+
app.include_router(metadata.router)
|
| 73 |
+
|
| 74 |
+
# Serve Static frontend if it exists
|
| 75 |
+
static_dir = PROJECT_ROOT / "frontend" / "dist"
|
| 76 |
+
if static_dir.exists() and static_dir.is_dir():
|
| 77 |
+
from fastapi.staticfiles import StaticFiles
|
| 78 |
+
from fastapi.responses import FileResponse
|
| 79 |
+
|
| 80 |
+
# Mount assets
|
| 81 |
+
assets_dir = static_dir / "assets"
|
| 82 |
+
if assets_dir.exists():
|
| 83 |
+
app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="assets")
|
| 84 |
+
|
| 85 |
+
@app.get("/", tags=["Root"])
|
| 86 |
+
async def root():
|
| 87 |
+
return FileResponse(str(static_dir / "index.html"))
|
| 88 |
+
|
| 89 |
+
# Catch-all for react router
|
| 90 |
+
@app.get("/{full_path:path}", tags=["Root"])
|
| 91 |
+
async def serve_spa(full_path: str):
|
| 92 |
+
# Exclude api requests
|
| 93 |
+
if full_path.startswith("api/") or full_path.startswith("docs") or full_path.startswith("redoc"):
|
| 94 |
+
return {"detail": "Not Found"}
|
| 95 |
+
return FileResponse(str(static_dir / "index.html"))
|
| 96 |
+
else:
|
| 97 |
+
@app.get("/", tags=["Root"])
|
| 98 |
+
async def root():
|
| 99 |
+
return {
|
| 100 |
+
"message": "Cattle Breed Classifier API",
|
| 101 |
+
"version": settings.app_version,
|
| 102 |
+
"docs": "/docs",
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
return app
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
app = create_app()
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
if __name__ == "__main__":
|
| 112 |
+
import uvicorn
|
| 113 |
+
settings = get_settings()
|
| 114 |
+
uvicorn.run(
|
| 115 |
+
"backend.app.main:app",
|
| 116 |
+
host=settings.host,
|
| 117 |
+
port=settings.port,
|
| 118 |
+
reload=settings.debug,
|
| 119 |
+
)
|
backend/app/schemas/__init__.py
ADDED
|
File without changes
|
backend/app/schemas/breed.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Pydantic schemas for breed metadata endpoints.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from typing import Optional
|
| 6 |
+
from pydantic import BaseModel
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class BreedDetail(BaseModel):
|
| 10 |
+
"""Full breed detail."""
|
| 11 |
+
breed_id: int
|
| 12 |
+
breed_name: str
|
| 13 |
+
animal_type: str
|
| 14 |
+
region: str
|
| 15 |
+
avg_milk_liters_per_day: str
|
| 16 |
+
lifespan_years: str
|
| 17 |
+
primary_use: str
|
| 18 |
+
coat_color_notes: str
|
| 19 |
+
horn_notes: str
|
| 20 |
+
description: str
|
| 21 |
+
source_reference: str
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class BreedListItem(BaseModel):
|
| 25 |
+
"""Summary breed item for list endpoints."""
|
| 26 |
+
breed_id: int
|
| 27 |
+
breed_name: str
|
| 28 |
+
animal_type: str
|
| 29 |
+
region: str
|
| 30 |
+
primary_use: str
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class BreedsListResponse(BaseModel):
|
| 34 |
+
"""Response for listing all breeds."""
|
| 35 |
+
total: int
|
| 36 |
+
breeds: list[BreedListItem]
|
backend/app/schemas/predict.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Pydantic schemas for prediction endpoints.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from typing import Optional
|
| 6 |
+
from pydantic import BaseModel, Field
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class PredictionItem(BaseModel):
|
| 10 |
+
"""Single prediction result."""
|
| 11 |
+
breed: str = Field(..., description="Predicted breed name")
|
| 12 |
+
confidence: float = Field(..., ge=0, le=1, description="Confidence score")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class BreedInfo(BaseModel):
|
| 16 |
+
"""Breed metadata for the predicted breed."""
|
| 17 |
+
breed_name: str
|
| 18 |
+
animal_type: str
|
| 19 |
+
region: str
|
| 20 |
+
avg_milk_liters_per_day: str
|
| 21 |
+
lifespan_years: str
|
| 22 |
+
primary_use: str
|
| 23 |
+
description: str
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class PredictResponse(BaseModel):
|
| 27 |
+
"""Full prediction response."""
|
| 28 |
+
predicted_breed: str
|
| 29 |
+
confidence: float = Field(..., ge=0, le=1)
|
| 30 |
+
top_k: list[PredictionItem]
|
| 31 |
+
breed_info: Optional[BreedInfo] = None
|
| 32 |
+
model_version: str = "v1.0"
|
| 33 |
+
inference_time_ms: float
|
| 34 |
+
warning: Optional[str] = None
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class PredictURLRequest(BaseModel):
|
| 38 |
+
"""Request body for URL-based prediction."""
|
| 39 |
+
url: str = Field(..., description="URL of the image to classify")
|
| 40 |
+
top_k: int = Field(default=3, ge=1, le=10, description="Number of top predictions")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class PredictBase64Request(BaseModel):
|
| 44 |
+
"""Request body for base64-based prediction."""
|
| 45 |
+
image: str = Field(..., description="Base64-encoded image string")
|
| 46 |
+
top_k: int = Field(default=3, ge=1, le=10, description="Number of top predictions")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class ErrorResponse(BaseModel):
|
| 50 |
+
"""Error response."""
|
| 51 |
+
detail: str
|
| 52 |
+
error_type: str = "prediction_error"
|
backend/app/services/__init__.py
ADDED
|
File without changes
|
backend/app/services/breed_info.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Breed information service.
|
| 3 |
+
Provides breed metadata lookup from CSV.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import csv
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Optional
|
| 9 |
+
|
| 10 |
+
from backend.app.core.config import get_settings
|
| 11 |
+
from backend.app.core.logging import logger
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class BreedInfoService:
|
| 15 |
+
"""Service for breed metadata lookup."""
|
| 16 |
+
|
| 17 |
+
def __init__(self):
|
| 18 |
+
self._data: dict[str, dict] = {}
|
| 19 |
+
self._loaded = False
|
| 20 |
+
|
| 21 |
+
def load(self) -> None:
|
| 22 |
+
"""Load breed metadata from CSV."""
|
| 23 |
+
if self._loaded:
|
| 24 |
+
return
|
| 25 |
+
|
| 26 |
+
settings = get_settings()
|
| 27 |
+
csv_path = Path(settings.metadata_path)
|
| 28 |
+
|
| 29 |
+
if not csv_path.exists():
|
| 30 |
+
logger.warning(f"Breed metadata file not found: {csv_path}")
|
| 31 |
+
return
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
with open(csv_path, 'r', encoding='utf-8') as f:
|
| 35 |
+
reader = csv.DictReader(f)
|
| 36 |
+
for row in reader:
|
| 37 |
+
breed_name = row['breed_name'].strip()
|
| 38 |
+
self._data[breed_name] = {
|
| 39 |
+
'breed_id': int(row['breed_id']),
|
| 40 |
+
'breed_name': breed_name,
|
| 41 |
+
'animal_type': row['animal_type'].strip(),
|
| 42 |
+
'region': row['region'].strip(),
|
| 43 |
+
'avg_milk_liters_per_day': row['avg_milk_liters_per_day'].strip(),
|
| 44 |
+
'lifespan_years': row['lifespan_years'].strip(),
|
| 45 |
+
'primary_use': row['primary_use'].strip(),
|
| 46 |
+
'coat_color_notes': row['coat_color_notes'].strip(),
|
| 47 |
+
'horn_notes': row['horn_notes'].strip(),
|
| 48 |
+
'description': row['description'].strip(),
|
| 49 |
+
'source_reference': row['source_reference'].strip(),
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
self._loaded = True
|
| 53 |
+
logger.info(f"Loaded metadata for {len(self._data)} breeds")
|
| 54 |
+
|
| 55 |
+
except Exception as e:
|
| 56 |
+
logger.error(f"Failed to load breed metadata: {e}")
|
| 57 |
+
|
| 58 |
+
def get_breed(self, breed_name: str) -> Optional[dict]:
|
| 59 |
+
"""Get breed info by exact name."""
|
| 60 |
+
return self._data.get(breed_name)
|
| 61 |
+
|
| 62 |
+
def get_breed_summary(self, breed_name: str) -> Optional[dict]:
|
| 63 |
+
"""Get condensed breed info for prediction responses."""
|
| 64 |
+
info = self._data.get(breed_name)
|
| 65 |
+
if info is None:
|
| 66 |
+
return None
|
| 67 |
+
return {
|
| 68 |
+
'breed_name': info['breed_name'],
|
| 69 |
+
'animal_type': info['animal_type'],
|
| 70 |
+
'region': info['region'],
|
| 71 |
+
'avg_milk_liters_per_day': info['avg_milk_liters_per_day'],
|
| 72 |
+
'lifespan_years': info['lifespan_years'],
|
| 73 |
+
'primary_use': info['primary_use'],
|
| 74 |
+
'description': info['description'],
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
def get_all(self) -> list[dict]:
|
| 78 |
+
"""Return all breeds."""
|
| 79 |
+
return list(self._data.values())
|
| 80 |
+
|
| 81 |
+
def search(self, query: str) -> list[dict]:
|
| 82 |
+
"""Case-insensitive search."""
|
| 83 |
+
q = query.lower()
|
| 84 |
+
return [b for b in self._data.values() if q in b['breed_name'].lower()]
|
| 85 |
+
|
| 86 |
+
@property
|
| 87 |
+
def num_breeds(self) -> int:
|
| 88 |
+
return len(self._data)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
# Singleton
|
| 92 |
+
breed_info_service = BreedInfoService()
|
backend/app/services/image_loader.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Image loading service.
|
| 3 |
+
Handles loading images from file upload, URL, and base64.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import base64
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
|
| 9 |
+
import requests
|
| 10 |
+
from PIL import Image
|
| 11 |
+
|
| 12 |
+
from backend.app.core.config import get_settings
|
| 13 |
+
from backend.app.core.logging import logger
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def load_image_from_upload(file_bytes: bytes) -> Image.Image:
|
| 17 |
+
"""Load image from uploaded file bytes."""
|
| 18 |
+
try:
|
| 19 |
+
image = Image.open(BytesIO(file_bytes)).convert('RGB')
|
| 20 |
+
return image
|
| 21 |
+
except Exception as e:
|
| 22 |
+
logger.error(f"Failed to load uploaded image: {e}")
|
| 23 |
+
raise ValueError(f"Invalid image file: {e}")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def load_image_from_url(url: str) -> Image.Image:
|
| 27 |
+
"""Load image from a URL."""
|
| 28 |
+
settings = get_settings()
|
| 29 |
+
try:
|
| 30 |
+
response = requests.get(url, timeout=settings.url_timeout, stream=True)
|
| 31 |
+
response.raise_for_status()
|
| 32 |
+
|
| 33 |
+
# Check content length
|
| 34 |
+
content_length = response.headers.get('Content-Length')
|
| 35 |
+
if content_length and int(content_length) > settings.max_image_size_mb * 1024 * 1024:
|
| 36 |
+
raise ValueError(f"Image too large (max {settings.max_image_size_mb}MB)")
|
| 37 |
+
|
| 38 |
+
image = Image.open(BytesIO(response.content)).convert('RGB')
|
| 39 |
+
return image
|
| 40 |
+
except requests.RequestException as e:
|
| 41 |
+
logger.error(f"Failed to fetch image from URL: {e}")
|
| 42 |
+
raise ValueError(f"Could not fetch image from URL: {e}")
|
| 43 |
+
except Exception as e:
|
| 44 |
+
logger.error(f"Failed to load image from URL: {e}")
|
| 45 |
+
raise ValueError(f"Invalid image from URL: {e}")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def load_image_from_base64(b64_string: str) -> Image.Image:
|
| 49 |
+
"""Load image from base64-encoded string."""
|
| 50 |
+
try:
|
| 51 |
+
# Remove data URI prefix if present
|
| 52 |
+
if ',' in b64_string:
|
| 53 |
+
b64_string = b64_string.split(',', 1)[1]
|
| 54 |
+
|
| 55 |
+
image_bytes = base64.b64decode(b64_string)
|
| 56 |
+
image = Image.open(BytesIO(image_bytes)).convert('RGB')
|
| 57 |
+
return image
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.error(f"Failed to load base64 image: {e}")
|
| 60 |
+
raise ValueError(f"Invalid base64 image: {e}")
|
backend/app/services/inference.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Inference service.
|
| 3 |
+
Loads the model on startup and provides prediction functionality.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import time
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Optional
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
import torch.nn.functional as F
|
| 13 |
+
from PIL import Image
|
| 14 |
+
|
| 15 |
+
from backend.app.core.config import get_settings
|
| 16 |
+
from backend.app.core.logging import logger
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class InferenceService:
|
| 20 |
+
"""Singleton service for model inference."""
|
| 21 |
+
|
| 22 |
+
def __init__(self):
|
| 23 |
+
self.model = None
|
| 24 |
+
self.class_names: list[str] = []
|
| 25 |
+
self.device = torch.device('cpu')
|
| 26 |
+
self.settings = get_settings()
|
| 27 |
+
self._loaded = False
|
| 28 |
+
|
| 29 |
+
def load(self) -> None:
|
| 30 |
+
"""Load model and class names on startup."""
|
| 31 |
+
if self._loaded:
|
| 32 |
+
return
|
| 33 |
+
|
| 34 |
+
logger.info("Loading model...")
|
| 35 |
+
start = time.time()
|
| 36 |
+
|
| 37 |
+
# Detect device
|
| 38 |
+
if torch.cuda.is_available():
|
| 39 |
+
self.device = torch.device('cuda')
|
| 40 |
+
elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
|
| 41 |
+
self.device = torch.device('mps')
|
| 42 |
+
|
| 43 |
+
# Load class names
|
| 44 |
+
classes_path = Path(self.settings.classes_path)
|
| 45 |
+
if classes_path.exists():
|
| 46 |
+
with open(classes_path, 'r') as f:
|
| 47 |
+
self.class_names = [c.strip() for c in f.read().split(',') if c.strip()]
|
| 48 |
+
|
| 49 |
+
# Load model
|
| 50 |
+
model_path = Path(self.settings.model_path)
|
| 51 |
+
if not model_path.exists():
|
| 52 |
+
logger.warning(f"Model file not found: {model_path}")
|
| 53 |
+
logger.info("Service will start without a model. Upload a model to enable predictions.")
|
| 54 |
+
return
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
checkpoint = torch.load(model_path, map_location=self.device, weights_only=False)
|
| 58 |
+
|
| 59 |
+
if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
|
| 60 |
+
# State dict checkpoint from new training pipeline
|
| 61 |
+
model_name = checkpoint.get('model_name', self.settings.model_name)
|
| 62 |
+
if 'class_names' in checkpoint:
|
| 63 |
+
self.class_names = checkpoint['class_names']
|
| 64 |
+
self.model = self._create_model(model_name, len(self.class_names))
|
| 65 |
+
self.model.load_state_dict(checkpoint['model_state_dict'])
|
| 66 |
+
elif isinstance(checkpoint, torch.nn.Module):
|
| 67 |
+
# Full model (legacy format)
|
| 68 |
+
self.model = checkpoint
|
| 69 |
+
else:
|
| 70 |
+
# Try loading as full model directly
|
| 71 |
+
self.model = checkpoint
|
| 72 |
+
|
| 73 |
+
if isinstance(self.model, torch.nn.Module):
|
| 74 |
+
self.model = self.model.to(self.device)
|
| 75 |
+
self.model.eval()
|
| 76 |
+
|
| 77 |
+
self._loaded = True
|
| 78 |
+
elapsed = time.time() - start
|
| 79 |
+
logger.info(f"Model loaded in {elapsed:.2f}s on {self.device} ({len(self.class_names)} classes)")
|
| 80 |
+
|
| 81 |
+
except Exception as e:
|
| 82 |
+
logger.error(f"Failed to load model: {e}")
|
| 83 |
+
raise
|
| 84 |
+
|
| 85 |
+
def _create_model(self, model_name: str, num_classes: int) -> torch.nn.Module:
|
| 86 |
+
"""Create model architecture by name."""
|
| 87 |
+
# Add project root to path for ML imports
|
| 88 |
+
project_root = Path(__file__).resolve().parents[3]
|
| 89 |
+
if str(project_root) not in sys.path:
|
| 90 |
+
sys.path.insert(0, str(project_root))
|
| 91 |
+
|
| 92 |
+
if model_name in ('resnet', 'resnet50'):
|
| 93 |
+
from ml.src.models.resnet import CattleResNet
|
| 94 |
+
return CattleResNet(num_classes=num_classes, pretrained=False, freeze_backbone=False)
|
| 95 |
+
elif model_name == 'cnn':
|
| 96 |
+
from ml.src.models.cnn import CattleCNN
|
| 97 |
+
return CattleCNN(num_classes=num_classes)
|
| 98 |
+
elif model_name == 'mlp':
|
| 99 |
+
from ml.src.models.mlp import CattleMLP
|
| 100 |
+
return CattleMLP(num_classes=num_classes)
|
| 101 |
+
elif model_name in ('vit', 'vit_base'):
|
| 102 |
+
from ml.src.models.vit import CattleViT
|
| 103 |
+
return CattleViT(num_classes=num_classes, pretrained=False, freeze_backbone=False)
|
| 104 |
+
else:
|
| 105 |
+
raise ValueError(f"Unknown model type: {model_name}")
|
| 106 |
+
|
| 107 |
+
@torch.no_grad()
|
| 108 |
+
def predict(self, image: Image.Image, top_k: int = 3) -> dict:
|
| 109 |
+
"""
|
| 110 |
+
Run prediction on a PIL Image.
|
| 111 |
+
Returns prediction dict.
|
| 112 |
+
"""
|
| 113 |
+
if self.model is None:
|
| 114 |
+
# Return a mock prediction if model is missing to prevent UI crash
|
| 115 |
+
import random
|
| 116 |
+
|
| 117 |
+
# Use real class names if available, otherwise fallback
|
| 118 |
+
classes = self.class_names if self.class_names else [
|
| 119 |
+
"Gir", "Sahiwal", "Kankrej", "Tharparkar", "Red Sindhi"
|
| 120 |
+
]
|
| 121 |
+
|
| 122 |
+
mock_breed = random.choice(classes)
|
| 123 |
+
other_breeds = [c for c in classes if c != mock_breed]
|
| 124 |
+
|
| 125 |
+
mock_top_k = [{'breed': mock_breed, 'confidence': 0.875}]
|
| 126 |
+
if other_breeds:
|
| 127 |
+
mock_top_k.append({'breed': other_breeds[0], 'confidence': 0.082})
|
| 128 |
+
if len(other_breeds) > 1:
|
| 129 |
+
mock_top_k.append({'breed': other_breeds[1], 'confidence': 0.043})
|
| 130 |
+
|
| 131 |
+
return {
|
| 132 |
+
'predicted_breed': mock_breed,
|
| 133 |
+
'confidence': 0.875,
|
| 134 |
+
'top_k': mock_top_k,
|
| 135 |
+
'model_version': 'mock-v1.0 (No Model Loaded)',
|
| 136 |
+
'inference_time_ms': 12.5,
|
| 137 |
+
'warning': '⚠️ PyTorch model file missing from server. Displaying mock AI prediction.'
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
from torchvision import transforms
|
| 141 |
+
start = time.time()
|
| 142 |
+
|
| 143 |
+
# Preprocess
|
| 144 |
+
transform = transforms.Compose([
|
| 145 |
+
transforms.Resize((self.settings.image_size, self.settings.image_size)),
|
| 146 |
+
transforms.ToTensor(),
|
| 147 |
+
transforms.Normalize(
|
| 148 |
+
mean=[0.485, 0.456, 0.406],
|
| 149 |
+
std=[0.229, 0.224, 0.225],
|
| 150 |
+
),
|
| 151 |
+
])
|
| 152 |
+
|
| 153 |
+
tensor = transform(image).unsqueeze(0).to(self.device)
|
| 154 |
+
|
| 155 |
+
# Inference
|
| 156 |
+
outputs = self.model(tensor)
|
| 157 |
+
probabilities = F.softmax(outputs, dim=1).squeeze()
|
| 158 |
+
|
| 159 |
+
# Top-k
|
| 160 |
+
top_k_val = min(top_k, len(self.class_names))
|
| 161 |
+
top_probs, top_indices = torch.topk(probabilities, top_k_val)
|
| 162 |
+
|
| 163 |
+
top_k_results = []
|
| 164 |
+
for prob, idx in zip(top_probs, top_indices):
|
| 165 |
+
breed_name = self.class_names[idx.item()] if idx.item() < len(self.class_names) else f"class_{idx.item()}"
|
| 166 |
+
top_k_results.append({
|
| 167 |
+
'breed': breed_name,
|
| 168 |
+
'confidence': round(prob.item(), 4),
|
| 169 |
+
})
|
| 170 |
+
|
| 171 |
+
best = top_k_results[0]
|
| 172 |
+
elapsed_ms = (time.time() - start) * 1000
|
| 173 |
+
|
| 174 |
+
result = {
|
| 175 |
+
'predicted_breed': best['breed'],
|
| 176 |
+
'confidence': best['confidence'],
|
| 177 |
+
'top_k': top_k_results,
|
| 178 |
+
'model_version': self.settings.model_version,
|
| 179 |
+
'inference_time_ms': round(elapsed_ms, 2),
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
# Confidence warning
|
| 183 |
+
if best['confidence'] < 0.5:
|
| 184 |
+
result['warning'] = 'Prediction confidence is low. Use a clear side-view image for better results.'
|
| 185 |
+
elif best['confidence'] < 0.75:
|
| 186 |
+
result['warning'] = 'Prediction confidence is moderate. Use a clear side-view image for better results.'
|
| 187 |
+
|
| 188 |
+
return result
|
| 189 |
+
|
| 190 |
+
@property
|
| 191 |
+
def is_loaded(self) -> bool:
|
| 192 |
+
return self._loaded and self.model is not None
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
# Singleton
|
| 196 |
+
inference_service = InferenceService()
|
backend/requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.104.0
|
| 2 |
+
uvicorn>=0.24.0
|
| 3 |
+
pydantic>=2.5.0
|
| 4 |
+
pydantic-settings>=2.1.0
|
| 5 |
+
python-multipart>=0.0.6
|
| 6 |
+
torch>=2.1.0
|
| 7 |
+
torchvision>=0.16.0
|
| 8 |
+
Pillow>=10.0.0
|
| 9 |
+
requests>=2.31.0
|
| 10 |
+
numpy>=1.24.0
|
| 11 |
+
PyYAML>=6.0.1
|
| 12 |
+
gunicorn>=21.2.0
|
| 13 |
+
python-dotenv>=1.0.0
|
| 14 |
+
timm>=0.9.0
|
colab_run_all.ipynb
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# \ud83d\udc04 Cattle Breed Classifier \u2014 Colab Runner\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"This notebook sets up the environment on **Google Colab** and runs all training notebooks sequentially.\n",
|
| 10 |
+
"\n",
|
| 11 |
+
"### What this does\n",
|
| 12 |
+
"1. Verifies GPU availability\n",
|
| 13 |
+
"2. Clones the repo (dataset included)\n",
|
| 14 |
+
"3. Installs missing dependencies\n",
|
| 15 |
+
"4. Runs each notebook in order:\n",
|
| 16 |
+
" - `00_data_audit` \u2192 `01_mlp_baseline` \u2192 `02_cnn_from_scratch` \u2192 `03_resnet_transfer_learning` \u2192 `04_vit_transfer_learning` \u2192 `05_model_comparison`\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"> \u26a0\ufe0f **Runtime**: Select **GPU** runtime before running: *Runtime \u2192 Change runtime type \u2192 T4 GPU*"
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"cell_type": "markdown",
|
| 23 |
+
"metadata": {},
|
| 24 |
+
"source": [
|
| 25 |
+
"## 0. Check GPU"
|
| 26 |
+
]
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"cell_type": "code",
|
| 30 |
+
"execution_count": null,
|
| 31 |
+
"metadata": {},
|
| 32 |
+
"outputs": [],
|
| 33 |
+
"source": [
|
| 34 |
+
"import torch\n",
|
| 35 |
+
"print(f'PyTorch version: {torch.__version__}')\n",
|
| 36 |
+
"print(f'CUDA available: {torch.cuda.is_available()}')\n",
|
| 37 |
+
"if torch.cuda.is_available():\n",
|
| 38 |
+
" print(f'GPU: {torch.cuda.get_device_name(0)}')\n",
|
| 39 |
+
" print(f'Memory: {torch.cuda.get_device_properties(0).total_mem / 1024**3:.1f} GB')\n",
|
| 40 |
+
"else:\n",
|
| 41 |
+
" print('\u26a0\ufe0f No GPU detected. Go to Runtime \u2192 Change runtime type \u2192 T4 GPU')"
|
| 42 |
+
]
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"cell_type": "markdown",
|
| 46 |
+
"metadata": {},
|
| 47 |
+
"source": [
|
| 48 |
+
"## 1. Clone Repository"
|
| 49 |
+
]
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"cell_type": "code",
|
| 53 |
+
"execution_count": null,
|
| 54 |
+
"metadata": {},
|
| 55 |
+
"outputs": [],
|
| 56 |
+
"source": [
|
| 57 |
+
"import os\n",
|
| 58 |
+
"\n",
|
| 59 |
+
"REPO_URL = 'https://github.com/ajitsingh98/cattle-breed-classifier-webapp.git'\n",
|
| 60 |
+
"REPO_DIR = '/content/cattle-breed-classifier-webapp'\n",
|
| 61 |
+
"\n",
|
| 62 |
+
"if not os.path.exists(REPO_DIR):\n",
|
| 63 |
+
" !git clone {REPO_URL} {REPO_DIR}\n",
|
| 64 |
+
"else:\n",
|
| 65 |
+
" print(f'Repo already cloned at {REPO_DIR}')\n",
|
| 66 |
+
" !cd {REPO_DIR} && git pull\n",
|
| 67 |
+
"\n",
|
| 68 |
+
"os.chdir(REPO_DIR)\n",
|
| 69 |
+
"print(f'\\nWorking directory: {os.getcwd()}')"
|
| 70 |
+
]
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"cell_type": "markdown",
|
| 74 |
+
"metadata": {},
|
| 75 |
+
"source": [
|
| 76 |
+
"## 2. Install Dependencies"
|
| 77 |
+
]
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"cell_type": "code",
|
| 81 |
+
"execution_count": null,
|
| 82 |
+
"metadata": {},
|
| 83 |
+
"outputs": [],
|
| 84 |
+
"source": [
|
| 85 |
+
"# Colab already has PyTorch, torchvision, numpy, pandas, matplotlib, seaborn, PIL, scikit-learn.\n",
|
| 86 |
+
"# Install only the missing packages.\n",
|
| 87 |
+
"!pip install -q PyYAML gdown aiofiles aiohttp nest_asyncio"
|
| 88 |
+
]
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"cell_type": "markdown",
|
| 92 |
+
"metadata": {},
|
| 93 |
+
"source": [
|
| 94 |
+
"## 3. Set Up Python Path"
|
| 95 |
+
]
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"cell_type": "code",
|
| 99 |
+
"execution_count": null,
|
| 100 |
+
"metadata": {},
|
| 101 |
+
"outputs": [],
|
| 102 |
+
"source": [
|
| 103 |
+
"import sys\n",
|
| 104 |
+
"from pathlib import Path\n",
|
| 105 |
+
"\n",
|
| 106 |
+
"PROJECT_ROOT = Path(REPO_DIR).resolve()\n",
|
| 107 |
+
"if str(PROJECT_ROOT) not in sys.path:\n",
|
| 108 |
+
" sys.path.insert(0, str(PROJECT_ROOT))\n",
|
| 109 |
+
"\n",
|
| 110 |
+
"# Create artifact directories\n",
|
| 111 |
+
"for d in ['ml/artifacts/manifests', 'ml/artifacts/checkpoints',\n",
|
| 112 |
+
" 'ml/artifacts/figures', 'ml/artifacts/logs', 'ml/artifacts/reports']:\n",
|
| 113 |
+
" (PROJECT_ROOT / d).mkdir(parents=True, exist_ok=True)\n",
|
| 114 |
+
"\n",
|
| 115 |
+
"# Verify dataset exists\n",
|
| 116 |
+
"data_dir = PROJECT_ROOT / 'Cattle_Resized'\n",
|
| 117 |
+
"class_dirs = sorted([d for d in data_dir.iterdir() if d.is_dir()])\n",
|
| 118 |
+
"total_images = sum(len(list(d.glob('*'))) for d in class_dirs)\n",
|
| 119 |
+
"print(f'Dataset: {len(class_dirs)} breeds, {total_images} images')\n",
|
| 120 |
+
"print(f'Project root: {PROJECT_ROOT}')"
|
| 121 |
+
]
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"cell_type": "markdown",
|
| 125 |
+
"metadata": {},
|
| 126 |
+
"source": [
|
| 127 |
+
"## 4. Run All Notebooks\n",
|
| 128 |
+
"\n",
|
| 129 |
+
"Each notebook is executed in order using `nbconvert`. Output is displayed inline.\n",
|
| 130 |
+
"\n",
|
| 131 |
+
"You can also **skip this cell** and open each notebook individually from the file browser on the left:\n",
|
| 132 |
+
"> `cattle-breed-classifier-webapp/ml/notebooks/`"
|
| 133 |
+
]
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"cell_type": "code",
|
| 137 |
+
"execution_count": null,
|
| 138 |
+
"metadata": {},
|
| 139 |
+
"outputs": [],
|
| 140 |
+
"source": [
|
| 141 |
+
"!pip install -q papermill jupyter ipykernel"
|
| 142 |
+
]
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"cell_type": "code",
|
| 146 |
+
"execution_count": null,
|
| 147 |
+
"metadata": {},
|
| 148 |
+
"outputs": [],
|
| 149 |
+
"source": [
|
| 150 |
+
"import subprocess, time\n",
|
| 151 |
+
"\n",
|
| 152 |
+
"NOTEBOOKS = [\n",
|
| 153 |
+
" '00_data_audit.ipynb',\n",
|
| 154 |
+
" '01_mlp_baseline.ipynb',\n",
|
| 155 |
+
" '02_cnn_from_scratch.ipynb',\n",
|
| 156 |
+
" '03_resnet_transfer_learning.ipynb',\n",
|
| 157 |
+
" '04_vit_transfer_learning.ipynb',\n",
|
| 158 |
+
" '05_model_comparison.ipynb',\n",
|
| 159 |
+
"]\n",
|
| 160 |
+
"\n",
|
| 161 |
+
"NOTEBOOK_DIR = PROJECT_ROOT / 'ml' / 'notebooks'\n",
|
| 162 |
+
"results = {}\n",
|
| 163 |
+
"\n",
|
| 164 |
+
"for nb_name in NOTEBOOKS:\n",
|
| 165 |
+
" nb_path = NOTEBOOK_DIR / nb_name\n",
|
| 166 |
+
" print(f'\\n{\"=\" * 60}')\n",
|
| 167 |
+
" print(f'\u25b6 Running: {nb_name}')\n",
|
| 168 |
+
" print(f'{\"=\" * 60}')\n",
|
| 169 |
+
"\n",
|
| 170 |
+
" start = time.time()\n",
|
| 171 |
+
" # We use papermill because it supports streaming cell outputs (--log-output)\n",
|
| 172 |
+
" result = subprocess.run(\n",
|
| 173 |
+
" [\n",
|
| 174 |
+
" \"papermill\",\n",
|
| 175 |
+
" str(nb_path),\n",
|
| 176 |
+
" str(nb_path), # overwrite inplace so output is saved in the notebook\n",
|
| 177 |
+
" \"--log-output\",\n",
|
| 178 |
+
" \"--kernel\", \"python3\"\n",
|
| 179 |
+
" ],\n",
|
| 180 |
+
" cwd=str(PROJECT_ROOT),\n",
|
| 181 |
+
" env={**os.environ, \"PYTHONPATH\": str(PROJECT_ROOT)},\n",
|
| 182 |
+
" )\n",
|
| 183 |
+
" elapsed = time.time() - start\n",
|
| 184 |
+
"\n",
|
| 185 |
+
" if result.returncode == 0:\n",
|
| 186 |
+
" status = '\u2705 PASSED'\n",
|
| 187 |
+
" else:\n",
|
| 188 |
+
" status = '\u274c FAILED'\n",
|
| 189 |
+
"\n",
|
| 190 |
+
" results[nb_name] = {'status': status, 'time': elapsed}\n",
|
| 191 |
+
" print(f'{status} ({elapsed:.1f}s)')\n",
|
| 192 |
+
"\n",
|
| 193 |
+
"# Summary\n",
|
| 194 |
+
"print(f'\\n{\"=\" * 60}')\n",
|
| 195 |
+
"print('Summary')\n",
|
| 196 |
+
"print(f'{\"=\" * 60}')\n",
|
| 197 |
+
"for nb, info in results.items():\n",
|
| 198 |
+
" print(f\" {info['status']} {nb:45s} {info['time']:6.1f}s\")\n",
|
| 199 |
+
"total_time = sum(r['time'] for r in results.values())\n",
|
| 200 |
+
"print(f'\\nTotal time: {total_time/60:.1f} minutes')"
|
| 201 |
+
]
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"cell_type": "markdown",
|
| 205 |
+
"metadata": {},
|
| 206 |
+
"source": [
|
| 207 |
+
"## 5. Download Artifacts (Optional)\n",
|
| 208 |
+
"\n",
|
| 209 |
+
"After training completes, download the best model checkpoint and reports."
|
| 210 |
+
]
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"cell_type": "code",
|
| 214 |
+
"execution_count": null,
|
| 215 |
+
"metadata": {},
|
| 216 |
+
"outputs": [],
|
| 217 |
+
"source": [
|
| 218 |
+
"# List generated artifacts\n",
|
| 219 |
+
"import glob\n",
|
| 220 |
+
"\n",
|
| 221 |
+
"print('=== Checkpoints ===')\n",
|
| 222 |
+
"for f in sorted(glob.glob(str(PROJECT_ROOT / 'ml/artifacts/checkpoints/*.pth'))):\n",
|
| 223 |
+
" size_mb = os.path.getsize(f) / 1024 / 1024\n",
|
| 224 |
+
" print(f' {Path(f).name:40s} {size_mb:8.1f} MB')\n",
|
| 225 |
+
"\n",
|
| 226 |
+
"print('\\n=== Figures ===')\n",
|
| 227 |
+
"for f in sorted(glob.glob(str(PROJECT_ROOT / 'ml/artifacts/figures/*.png'))):\n",
|
| 228 |
+
" print(f' {Path(f).name}')\n",
|
| 229 |
+
"\n",
|
| 230 |
+
"print('\\n=== Reports ===')\n",
|
| 231 |
+
"for f in sorted(glob.glob(str(PROJECT_ROOT / 'ml/artifacts/reports/*.json'))):\n",
|
| 232 |
+
" print(f' {Path(f).name}')"
|
| 233 |
+
]
|
| 234 |
+
},
|
| 235 |
+
{
|
| 236 |
+
"cell_type": "code",
|
| 237 |
+
"execution_count": null,
|
| 238 |
+
"metadata": {},
|
| 239 |
+
"outputs": [],
|
| 240 |
+
"source": [
|
| 241 |
+
"# Zip and download artifacts\n",
|
| 242 |
+
"!cd {REPO_DIR} && zip -r /content/artifacts.zip ml/artifacts/\n",
|
| 243 |
+
"\n",
|
| 244 |
+
"from google.colab import files\n",
|
| 245 |
+
"files.download('/content/artifacts.zip')\n",
|
| 246 |
+
"print('\\n\u2705 Download started! Check your browser downloads.')"
|
| 247 |
+
]
|
| 248 |
+
},
|
| 249 |
+
{
|
| 250 |
+
"cell_type": "markdown",
|
| 251 |
+
"metadata": {},
|
| 252 |
+
"source": [
|
| 253 |
+
"---\n",
|
| 254 |
+
"\n",
|
| 255 |
+
"### \ud83d\udca1 Running Notebooks Individually\n",
|
| 256 |
+
"\n",
|
| 257 |
+
"Instead of the automated runner above, you can open each notebook directly:\n",
|
| 258 |
+
"\n",
|
| 259 |
+
"1. In the **Colab file browser** (left panel), navigate to: \n",
|
| 260 |
+
" `cattle-breed-classifier-webapp/ml/notebooks/`\n",
|
| 261 |
+
"2. Double-click any `.ipynb` file to open it in a new tab\n",
|
| 262 |
+
"3. Run cells with `Shift+Enter`\n",
|
| 263 |
+
"\n",
|
| 264 |
+
"**Important**: Each notebook auto-detects the project root, so they work both from the automated runner and when opened individually."
|
| 265 |
+
]
|
| 266 |
+
}
|
| 267 |
+
],
|
| 268 |
+
"metadata": {
|
| 269 |
+
"kernelspec": {
|
| 270 |
+
"display_name": "Python 3",
|
| 271 |
+
"language": "python",
|
| 272 |
+
"name": "python3"
|
| 273 |
+
},
|
| 274 |
+
"language_info": {
|
| 275 |
+
"name": "python",
|
| 276 |
+
"version": "3.10.0"
|
| 277 |
+
},
|
| 278 |
+
"colab": {
|
| 279 |
+
"provenance": [],
|
| 280 |
+
"gpuType": "T4"
|
| 281 |
+
},
|
| 282 |
+
"accelerator": "GPU"
|
| 283 |
+
},
|
| 284 |
+
"nbformat": 4,
|
| 285 |
+
"nbformat_minor": 4
|
| 286 |
+
}
|
data/breed_metadata.csv
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
breed_id,breed_name,animal_type,region,avg_milk_liters_per_day,lifespan_years,primary_use,coat_color_notes,horn_notes,description,source_reference
|
| 2 |
+
1,Alambadi Cow,Cow,"Tamil Nadu, India",2-3,15-18,Draught,Grey to dark grey with white markings,Long curved horns spreading outward,Hardy draught breed from Tamil Nadu adapted to dry semi-arid conditions. Known for endurance and strength in agricultural work.,NBAGR India
|
| 3 |
+
2,Amritmahal Cow,Cow,"Karnataka, India",1-2,18-20,Draught,Grey to white with darker head and neck,Long lyre-shaped upward curving horns,Elite draught breed from Karnataka known for speed and endurance. Originally bred for the Mysore army for hauling equipment.,NBAGR India
|
| 4 |
+
3,Banni Buffalo,Buffalo,"Gujarat, India",10-14,20-25,Dairy,Jet black with occasional white markings on forehead,Medium curved sickle-shaped horns,High-yielding buffalo breed from the Banni grasslands of Kutch. Well adapted to arid conditions with excellent milk fat content.,NBAGR India
|
| 5 |
+
4,Bargur Cow,Cow,"Tamil Nadu, India",2-3,15-18,Draught,Brown to reddish brown with white patches,Medium curved horns,Agile draught breed from the Bargur hills of Erode district. Known for its speed and hill-terrain adaptability.,NBAGR India
|
| 6 |
+
5,Dangi Cow,Cow,"Maharashtra, India",1-3,15-18,Dual Purpose,White with red or black spots,Short stumpy horns,Hardy breed from the hilly Dangs region. Tolerant to heavy rainfall and humid conditions. Used for both draught and limited milk production.,NBAGR India
|
| 7 |
+
6,Deoni Cow,Cow,"Maharashtra / Karnataka, India",3-5,15-18,Dual Purpose,White body with black or red spots on head and neck,Medium sized curved horns,"Dual-purpose breed from the Deccan plateau. Decent milk yield and good draught capability. Known as ""Dongari"" or ""Dongerpati"" cattle.",NBAGR India
|
| 8 |
+
7,Gir Cow,Cow,"Gujarat, India",6-10,12-15,Dairy,Reddish to spotted red-white with distinctive curved forehead,Long pendulous ears; curved horns,One of the principal dairy breeds of India. Highly heat tolerant with excellent disease resistance. Exported worldwide to tropical countries.,NBAGR India
|
| 9 |
+
8,Hallikar Cow,Cow,"Karnataka, India",1-2,18-20,Draught,Grey to dark grey,Long lyre-shaped horns curving backward,Premier draught breed of South India known for compact muscular build. Progenitor of several other South Indian breeds.,NBAGR India
|
| 10 |
+
9,Jaffrabadi Buffalo,Buffalo,"Gujarat, India",8-12,20-25,Dairy,Black,Heavy massive horns curving downward then upward,Heaviest Indian buffalo breed from Gir forests of Gujarat. Known for high milk yield with excellent fat percentage.,NBAGR India
|
| 11 |
+
10,Kangayam Cow,Cow,"Tamil Nadu, India",2-4,18-20,Draught,Grey to white with red or brown tinge,Short sturdy horns,Powerful draught breed from Tamil Nadu. Known for compact muscular body and endurance in heavy agricultural work.,NBAGR India
|
| 12 |
+
11,Kankrej Cow,Cow,"Gujarat / Rajasthan, India",5-8,15-18,Dual Purpose,Silver-grey to steel grey with darker forequarters,Strong lyre-shaped horns,Large dual-purpose breed. Known for heavy build and fast trotting gait. Popular for both milk production and draught work.,NBAGR India
|
| 13 |
+
12,Kasaragod Cow,Cow,"Kerala, India",2-3,15-18,Dwarf Cattle,Reddish brown to black,Short small horns,Small-sized cattle breed from the Kasaragod district of Kerala. Well adapted to the tropical coastal climate and resistant to local diseases.,NBAGR India
|
| 14 |
+
13,Kenkatha Cow,Cow,"Uttar Pradesh / Madhya Pradesh, India",2-4,15-18,Draught,Grey to dark grey with lighter underbelly,Short thick horns,Compact draught breed from the Ken river valley of Bundelkhand. Hardy and well-suited to rocky terrain.,NBAGR India
|
| 15 |
+
14,Kherigarh Cow,Cow,"Uttar Pradesh, India",3-5,15-18,Dual Purpose,White to light grey,Medium lyre-shaped horns,Medium-sized dual-purpose breed from the Kheri district of UP. Known for reasonable milk yield and good draught capability.,NBAGR India
|
| 16 |
+
15,Malnad gidda Cow,Cow,"Karnataka, India",1-3,18-20,Dairy (small-scale),Varied: black brown red or white,Short small horns or polled,Smallest Indian cattle breed from the Western Ghats of Karnataka. Extremely hardy with strong disease resistance adapted to heavy rainfall and hilly terrain.,NBAGR India
|
| 17 |
+
16,Mehsana Buffalo,Buffalo,"Gujarat, India",8-12,20-25,Dairy,Black to brownish black,Sickle-shaped curving upward and backward,Important dairy buffalo from North Gujarat. Cross between Murrah and Surti. Consistent milk producer with good fat content.,NBAGR India
|
| 18 |
+
17,Nagori Cow,Cow,"Rajasthan, India",2-4,15-18,Draught,White to light grey with darker extremities,Medium upward-pointing horns,Tall well-built draught breed from the Nagaur district of Rajasthan. Prized for fast trotting ability and endurance in desert conditions.,NBAGR India
|
| 19 |
+
18,Nagpuri Buffalo,Buffalo,"Maharashtra, India",5-7,20-25,Dual Purpose,Black,Very long flat curved sickle-shaped horns (can exceed 60cm),Distinctive buffalo breed from the Nagpur region. Known for extremely long horns and adaptation to hot dry climate of Vidarbha.,NBAGR India
|
| 20 |
+
19,Nili ravi Buffalo,Buffalo,"Punjab, India / Pakistan",8-14,20-25,Dairy,Black with white markings on forehead and legs,Small tightly curled horns,Premier dairy buffalo breed from the Punjab region. One of the highest milk-yielding buffalo breeds in the world.,NBAGR India
|
| 21 |
+
20,Nimari Cow,Cow,"Madhya Pradesh, India",2-4,15-18,Dual Purpose,Red to reddish brown with darker extremities,Medium lyre-shaped horns,"Medium-sized dual-purpose breed from the Nimar valley (Narmada basin). Suitable for both draught and moderate milk production.",NBAGR India
|
| 22 |
+
21,Pulikulam Cow,Cow,"Tamil Nadu, India",1-2,15-18,Draught / Sport,Grey to dark grey,Sharp curved horns,"Small agile breed from the Madurai-Sivaganga region. Traditionally used in the Jallikattu bull-taming sport. Known for agility and tenacity.",NBAGR India
|
| 23 |
+
22,Rathi Cow,Cow,"Rajasthan, India",6-8,15-18,Dairy,Brown with white patches,Short to medium curved horns,"One of the best dairy breeds of Rajasthan from the Bikaner region. Adapted to arid conditions with efficient feed conversion.",NBAGR India
|
| 24 |
+
23,Sahiwal Cow,Cow,"Punjab, India / Pakistan",8-12,15-18,Dairy,Reddish to reddish brown with occasional white patches,Short stumpy horns (often polled),"One of the best dairy breeds of the Indian subcontinent. Highly heat-tolerant and tick-resistant. Exported globally to tropical countries.",NBAGR India
|
| 25 |
+
24,Shurti Buffalo,Buffalo,"Karnataka, India",4-6,20-25,Dairy,Black to brownish black,Medium flat horns curving backward,"Small to medium dairy buffalo from the North Karnataka region. Adapted to medium rainfall zone and maintained by small farmers.",NBAGR India
|
| 26 |
+
25,Tharparkar Cow,Cow,"Rajasthan, India / Sindh, Pakistan",6-10,18-20,Dairy,White to grey,Medium lyre-shaped horns curving upward,"Hardy dual-purpose breed from the Thar desert. Among the best milkers of Indian zebu breeds. Well adapted to extreme heat and scarce water.",NBAGR India
|
| 27 |
+
26,Umblachery Cow,Cow,"Tamil Nadu, India",1-2,15-18,Draught,Reddish brown calves turning grey as adults,Short curved horns,"Compact draught breed from the Cauvery delta region. Known for ability to work in waterlogged paddy fields. Important for rice cultivation.",NBAGR India
|
deploy.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
from huggingface_hub import HfApi, create_repo
|
| 7 |
+
except ImportError:
|
| 8 |
+
print("Error: huggingface_hub not found. Run: .venv/bin/pip install huggingface_hub")
|
| 9 |
+
sys.exit(1)
|
| 10 |
+
|
| 11 |
+
def deploy():
|
| 12 |
+
print("🚀 Cattle Breed Classifier Hugging Face Deployer")
|
| 13 |
+
print("-" * 50)
|
| 14 |
+
|
| 15 |
+
# 1. Get credentials securely
|
| 16 |
+
username = input("Enter your Hugging Face Username: ").strip()
|
| 17 |
+
space_name = input("Enter your desired Space Name (e.g. cattle-classifier): ").strip()
|
| 18 |
+
token = input("Enter your Hugging Face Write Token: ").strip()
|
| 19 |
+
|
| 20 |
+
if not username or not space_name or not token:
|
| 21 |
+
print("Error: All fields are required.")
|
| 22 |
+
sys.exit(1)
|
| 23 |
+
|
| 24 |
+
repo_id = f"{username}/{space_name}"
|
| 25 |
+
api = HfApi(token=token)
|
| 26 |
+
|
| 27 |
+
print(f"\n[1/3] Creating Space '{repo_id}' on Hugging Face...")
|
| 28 |
+
try:
|
| 29 |
+
create_repo(
|
| 30 |
+
repo_id=repo_id,
|
| 31 |
+
repo_type="space",
|
| 32 |
+
space_sdk="docker",
|
| 33 |
+
token=token,
|
| 34 |
+
exist_ok=True
|
| 35 |
+
)
|
| 36 |
+
print("✅ Space created/verified successfully!")
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"❌ Failed to create space: {e}")
|
| 39 |
+
sys.exit(1)
|
| 40 |
+
|
| 41 |
+
print("\n[2/3] Uploading Application & Massive ViT Model...")
|
| 42 |
+
print("⏳ This may take several minutes depending on your internet upload speed (Model is ~460MB). Please wait...")
|
| 43 |
+
|
| 44 |
+
# Files to ignore (don't upload heavy training folders or virtual environments)
|
| 45 |
+
ignore_patterns = [
|
| 46 |
+
".venv/*",
|
| 47 |
+
".git/*",
|
| 48 |
+
"__pycache__/*",
|
| 49 |
+
"*.pyc",
|
| 50 |
+
"Cattle_Resized/*",
|
| 51 |
+
"ml/artifacts/checkpoints/cnn_best.pth",
|
| 52 |
+
"ml/artifacts/checkpoints/mlp_best.pth",
|
| 53 |
+
"ml/artifacts/checkpoints/resnet_best.pth",
|
| 54 |
+
# Ignore models dir except vit_best.pth and classes.txt
|
| 55 |
+
"frontend/node_modules/*"
|
| 56 |
+
]
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
api.upload_folder(
|
| 60 |
+
folder_path=".",
|
| 61 |
+
repo_id=repo_id,
|
| 62 |
+
repo_type="space",
|
| 63 |
+
ignore_patterns=ignore_patterns,
|
| 64 |
+
commit_message="Initial Deployment: Best ViT Model"
|
| 65 |
+
)
|
| 66 |
+
print("✅ Upload complete!")
|
| 67 |
+
except Exception as e:
|
| 68 |
+
print(f"❌ Failed to upload files: {e}")
|
| 69 |
+
sys.exit(1)
|
| 70 |
+
|
| 71 |
+
print(f"\n[3/3] Deployment Successful! 🎉")
|
| 72 |
+
print(f"Your application is now building in the cloud.")
|
| 73 |
+
print(f"View it live here: https://huggingface.co/spaces/{username}/{space_name}")
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
deploy()
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Docker Compose for Cattle Breed Classifier
|
| 2 |
+
# Run: docker-compose up --build
|
| 3 |
+
|
| 4 |
+
version: "3.9"
|
| 5 |
+
|
| 6 |
+
services:
|
| 7 |
+
backend:
|
| 8 |
+
build:
|
| 9 |
+
context: .
|
| 10 |
+
dockerfile: backend/Dockerfile
|
| 11 |
+
container_name: cattle-api
|
| 12 |
+
ports:
|
| 13 |
+
- "8000:8000"
|
| 14 |
+
environment:
|
| 15 |
+
- MODEL_PATH=/app/models/vit_best.pth
|
| 16 |
+
- METADATA_PATH=/app/data/breed_metadata.csv
|
| 17 |
+
- CLASSES_PATH=/app/models/classes.txt
|
| 18 |
+
- MODEL_NAME=vit
|
| 19 |
+
- MODEL_VERSION=v2.0
|
| 20 |
+
volumes:
|
| 21 |
+
- ./models:/app/models
|
| 22 |
+
- ./data:/app/data
|
| 23 |
+
restart: unless-stopped
|
| 24 |
+
healthcheck:
|
| 25 |
+
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
| 26 |
+
interval: 30s
|
| 27 |
+
timeout: 10s
|
| 28 |
+
retries: 3
|
| 29 |
+
|
| 30 |
+
frontend:
|
| 31 |
+
build:
|
| 32 |
+
context: ./frontend
|
| 33 |
+
dockerfile: Dockerfile
|
| 34 |
+
container_name: cattle-frontend
|
| 35 |
+
ports:
|
| 36 |
+
- "3000:80"
|
| 37 |
+
depends_on:
|
| 38 |
+
- backend
|
| 39 |
+
restart: unless-stopped
|
frontend/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
frontend/Dockerfile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Frontend Dockerfile
|
| 2 |
+
FROM node:20-alpine AS build
|
| 3 |
+
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
COPY package*.json ./
|
| 6 |
+
RUN npm ci
|
| 7 |
+
COPY . .
|
| 8 |
+
RUN npm run build
|
| 9 |
+
|
| 10 |
+
# Serve with nginx
|
| 11 |
+
FROM nginx:alpine
|
| 12 |
+
COPY --from=build /app/dist /usr/share/nginx/html
|
| 13 |
+
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
| 14 |
+
EXPOSE 80
|
| 15 |
+
CMD ["nginx", "-g", "daemon off;"]
|
frontend/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# React + Vite
|
| 2 |
+
|
| 3 |
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
| 4 |
+
|
| 5 |
+
Currently, two official plugins are available:
|
| 6 |
+
|
| 7 |
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
| 8 |
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
| 9 |
+
|
| 10 |
+
## React Compiler
|
| 11 |
+
|
| 12 |
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
| 13 |
+
|
| 14 |
+
## Expanding the ESLint configuration
|
| 15 |
+
|
| 16 |
+
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
frontend/dist/assets/index-CWZymrby.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
@import "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Outfit:wght@400;500;600;700;800&display=swap";:root{--color-bg-primary:#1f2330;--color-bg-secondary:#2c3144;--color-bg-card:#373e56;--color-bg-card-hover:#434b68;--color-bg-glass:#767f9e4d;--color-bg-input:#2c3144;--color-accent:#daa464;--color-accent-light:#dec384;--color-accent-dark:#b6854d;--color-accent-glow:#daa46433;--color-success:#10b981;--color-error:#ef4444;--color-warning:#daa464;--color-info:#767f9e;--color-text-primary:#e8ddb4;--color-text-secondary:#dec384;--color-text-muted:#767f9e;--color-border:#767f9e4d;--color-border-accent:#daa46466;--gradient-hero:linear-gradient(135deg, #1f2330 0%, #373e56 50%, #2c3144 100%);--gradient-accent:linear-gradient(135deg, #dec384 0%, #daa464 100%);--gradient-card:linear-gradient(145deg, #373e56e6, #2c3144b3);--shadow-sm:0 1px 3px #0000004d;--shadow-md:0 4px 14px #0006;--shadow-lg:0 10px 40px #00000080;--shadow-glow:0 0 30px #daa46426;--radius-sm:8px;--radius-md:12px;--radius-lg:16px;--radius-xl:24px;--font-heading:"Outfit", sans-serif;--font-body:"Inter", sans-serif;--transition-fast:.15s ease;--transition-normal:.25s ease;--transition-slow:.4s cubic-bezier(.4, 0, .2, 1)}*,:before,:after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth;font-size:16px}body{font-family:var(--font-body);background:var(--color-bg-primary);color:var(--color-text-primary);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;min-height:100vh;line-height:1.6}#root{flex-direction:column;min-height:100vh;display:flex}h1,h2,h3,h4,h5,h6{font-family:var(--font-heading);font-weight:700;line-height:1.2}a{color:var(--color-accent);transition:color var(--transition-fast);text-decoration:none}a:hover{color:var(--color-accent-light)}.navbar{z-index:100;-webkit-backdrop-filter:blur(16px)saturate(180%);backdrop-filter:blur(16px)saturate(180%);border-bottom:1px solid var(--color-border);background:#0a0e17d9;padding:0 2rem;position:sticky;top:0}.navbar-inner{justify-content:space-between;align-items:center;max-width:1200px;height:64px;margin:0 auto;display:flex}.navbar-brand{font-family:var(--font-heading);color:var(--color-text-primary);align-items:center;gap:10px;font-size:1.25rem;font-weight:700;display:flex}.navbar-brand span{background:var(--gradient-accent);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text}.nav-links{gap:6px;list-style:none;display:flex}.nav-links a{border-radius:var(--radius-sm);color:var(--color-text-secondary);transition:all var(--transition-fast);padding:8px 16px;font-size:.9rem;font-weight:500}.nav-links a:hover,.nav-links a.active{background:var(--color-accent-glow);color:var(--color-accent)}.page{flex:1;width:100%;max-width:1200px;margin:0 auto;padding:2rem}.section-title{margin-bottom:.5rem;font-size:2rem}.section-subtitle{color:var(--color-text-secondary);margin-bottom:2rem;font-size:1.05rem}.card{background:var(--gradient-card);border:1px solid var(--color-border);border-radius:var(--radius-lg);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all var(--transition-normal);padding:1.5rem}.card:hover{border-color:var(--color-border-accent);box-shadow:var(--shadow-glow);transform:translateY(-2px)}.glass-card{background:var(--color-bg-glass);border:1px solid var(--color-border);border-radius:var(--radius-lg);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);padding:2rem}.hero{text-align:center;padding:4rem 0 3rem}.hero h1{letter-spacing:-.02em;margin-bottom:1rem;font-size:3.2rem;font-weight:800}.hero h1 .accent{background:var(--gradient-accent);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text}.hero p{color:var(--color-text-secondary);max-width:600px;margin:0 auto 2rem;font-size:1.15rem}.hero-cta{background:var(--gradient-accent);color:#000;border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-normal);border:none;align-items:center;gap:8px;padding:14px 32px;font-size:1rem;font-weight:600;display:inline-flex;box-shadow:0 4px 20px #f59e0b4d}.hero-cta:hover{transform:translateY(-2px);box-shadow:0 6px 30px #f59e0b66}.features-grid{grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem;margin-top:3rem;display:grid}.feature-card{text-align:center;padding:2rem 1.5rem}.feature-icon{margin-bottom:1rem;font-size:2.5rem}.feature-card h3{margin-bottom:.5rem;font-size:1.1rem}.feature-card p{color:var(--color-text-secondary);font-size:.9rem}.predict-layout{grid-template-columns:1fr 1fr;gap:2rem;margin-top:1.5rem;display:grid}@media (width<=768px){.predict-layout{grid-template-columns:1fr}}.input-tabs{background:var(--color-bg-primary);border-radius:var(--radius-md);gap:4px;margin-bottom:1.5rem;padding:4px;display:flex}.input-tab{border-radius:var(--radius-sm);color:var(--color-text-secondary);cursor:pointer;transition:all var(--transition-fast);background:0 0;border:none;flex:1;padding:10px 16px;font-size:.85rem;font-weight:500}.input-tab.active{background:var(--color-accent-glow);color:var(--color-accent)}.upload-zone{border:2px dashed var(--color-border);border-radius:var(--radius-lg);text-align:center;cursor:pointer;transition:all var(--transition-normal);background:var(--color-bg-input);padding:3rem 2rem}.upload-zone:hover,.upload-zone.drag-over{border-color:var(--color-accent);background:var(--color-accent-glow)}.upload-zone .icon{margin-bottom:1rem;font-size:3rem}.upload-zone p{color:var(--color-text-secondary)}.upload-zone .hint{color:var(--color-text-muted);margin-top:.5rem;font-size:.8rem}.image-preview{border-radius:var(--radius-lg);margin-top:1rem;position:relative;overflow:hidden}.image-preview img{object-fit:contain;border-radius:var(--radius-lg);background:var(--color-bg-input);width:100%;max-height:350px}.image-preview .remove-btn{color:#fff;cursor:pointer;background:#000000b3;border:none;border-radius:50%;justify-content:center;align-items:center;width:32px;height:32px;font-size:1rem;display:flex;position:absolute;top:8px;right:8px}.url-input-group{gap:8px;margin-top:.5rem;display:flex}.url-input-group input{border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-bg-input);color:var(--color-text-primary);transition:border-color var(--transition-fast);outline:none;flex:1;padding:12px 16px;font-size:.9rem}.url-input-group input:focus{border-color:var(--color-accent)}.btn-primary{background:var(--gradient-accent);color:#000;border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-normal);border:none;padding:12px 28px;font-size:.95rem;font-weight:600}.btn-primary:hover{transform:translateY(-1px);box-shadow:0 4px 20px #f59e0b4d}.btn-primary:disabled{opacity:.5;cursor:not-allowed;transform:none}.btn-secondary{background:var(--color-bg-card);color:var(--color-text-primary);border:1px solid var(--color-border);border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-fast);padding:10px 20px;font-size:.9rem}.btn-secondary:hover{border-color:var(--color-accent)}.predict-btn{width:100%;margin-top:1.5rem;padding:14px;font-size:1rem}.result-card{animation:slideIn .4s var(--transition-slow)}@keyframes slideIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.result-header{justify-content:space-between;align-items:flex-start;margin-bottom:1.5rem;display:flex}.result-breed-name{font-size:1.6rem;font-weight:700;font-family:var(--font-heading)}.result-badge{border-radius:20px;align-items:center;gap:6px;padding:6px 14px;font-size:.8rem;font-weight:600;display:inline-flex}.result-badge.high{color:var(--color-success);background:#10b98126}.result-badge.medium{color:var(--color-warning);background:#f59e0b26}.result-badge.low{color:var(--color-error);background:#ef444426}.confidence-bar-container{margin-bottom:1.5rem}.confidence-bar-label{color:var(--color-text-secondary);justify-content:space-between;margin-bottom:6px;font-size:.85rem;display:flex}.confidence-bar{background:var(--color-bg-primary);border-radius:4px;height:8px;overflow:hidden}.confidence-bar-fill{background:var(--gradient-accent);border-radius:4px;height:100%;transition:width .6s cubic-bezier(.4,0,.2,1)}.topk-list{margin-bottom:1.5rem}.topk-item{border-bottom:1px solid var(--color-border);justify-content:space-between;align-items:center;padding:10px 0;display:flex}.topk-item:last-child{border-bottom:none}.topk-rank{background:var(--color-bg-primary);width:28px;height:28px;color:var(--color-text-secondary);border-radius:50%;justify-content:center;align-items:center;margin-right:12px;font-size:.75rem;font-weight:600;display:flex}.topk-rank.first{background:var(--color-accent-glow);color:var(--color-accent)}.topk-name{flex:1;font-weight:500}.topk-conf{color:var(--color-text-secondary);font-size:.9rem}.breed-info-grid{grid-template-columns:repeat(2,1fr);gap:12px;display:grid}.breed-info-item{background:var(--color-bg-primary);border-radius:var(--radius-sm);padding:12px}.breed-info-label{color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px;font-size:.75rem}.breed-info-value{font-size:.95rem;font-weight:600}.prediction-warning{border-radius:var(--radius-md);color:var(--color-warning);background:#f59e0b1a;border:1px solid #f59e0b33;align-items:flex-start;gap:10px;margin-top:1rem;padding:12px 16px;font-size:.85rem;display:flex}.breed-filters{flex-wrap:wrap;gap:12px;margin-bottom:2rem;display:flex}.breed-filters input,.breed-filters select{border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-bg-input);color:var(--color-text-primary);outline:none;padding:10px 16px;font-size:.9rem}.breed-filters input:focus,.breed-filters select:focus{border-color:var(--color-accent)}.breeds-grid{grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1.5rem;display:grid}.breed-card-header{justify-content:space-between;align-items:center;margin-bottom:.75rem;display:flex}.breed-card-name{font-size:1.1rem;font-weight:600;font-family:var(--font-heading)}.breed-type-badge{text-transform:uppercase;letter-spacing:.05em;border-radius:12px;padding:4px 10px;font-size:.7rem;font-weight:600}.breed-type-badge.cow{color:var(--color-info);background:#3b82f626}.breed-type-badge.buffalo{color:#8b5cf6;background:#8b5cf626}.breed-card-detail{justify-content:space-between;padding:6px 0;font-size:.85rem;display:flex}.breed-card-detail .label{color:var(--color-text-muted)}.breed-card-detail .value{font-weight:500}.breed-card-description{color:var(--color-text-secondary);-webkit-line-clamp:3;-webkit-box-orient:vertical;margin-top:.75rem;font-size:.85rem;line-height:1.5;display:-webkit-box;overflow:hidden}.camera-container{border-radius:var(--radius-lg);background:#000;position:relative;overflow:hidden}.camera-container video{object-fit:cover;width:100%;max-height:350px}.camera-controls{justify-content:center;gap:12px;margin-top:1rem;display:flex}.camera-capture-btn{border:3px solid var(--color-accent);cursor:pointer;width:56px;height:56px;transition:all var(--transition-fast);background:0 0;border-radius:50%;justify-content:center;align-items:center;display:flex}.camera-capture-btn:hover{background:var(--color-accent-glow)}.camera-capture-btn:after{content:"";background:var(--color-accent);border-radius:50%;width:40px;height:40px}.loading-spinner{flex-direction:column;align-items:center;gap:1rem;padding:3rem;display:flex}.spinner{border:3px solid var(--color-border);border-top-color:var(--color-accent);border-radius:50%;width:40px;height:40px;animation:.8s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.footer{border-top:1px solid var(--color-border);text-align:center;color:var(--color-text-muted);margin-top:auto;padding:2rem;font-size:.85rem}.footer a{color:var(--color-accent)}.about-grid{grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1.5rem;margin-top:2rem;display:grid}.about-card h3{color:var(--color-accent);margin-bottom:.75rem;font-size:1.1rem}.about-card p,.about-card li{color:var(--color-text-secondary);font-size:.9rem;line-height:1.7}.about-card ul{padding:0;list-style:none}.about-card li:before{content:"→ ";color:var(--color-accent)}.tips-box{border-radius:var(--radius-md);background:#3b82f614;border:1px solid #3b82f633;margin-top:1rem;padding:1rem 1.25rem}.tips-box h4{color:var(--color-info);margin-bottom:.5rem;font-size:.9rem}.tips-box li{color:var(--color-text-secondary);margin-bottom:4px;font-size:.8rem}@media (width<=768px){.hero h1{font-size:2rem}.page{padding:1rem}.breed-info-grid{grid-template-columns:1fr}.navbar-inner{padding:0}.nav-links a{padding:8px 10px;font-size:.8rem}.features-grid,.breeds-grid{grid-template-columns:1fr}}
|
frontend/dist/assets/index-sBN3tCmx.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/dist/favicon.svg
ADDED
|
|
frontend/dist/icons.svg
ADDED
|
|
frontend/dist/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>frontend</title>
|
| 8 |
+
<script type="module" crossorigin src="/assets/index-sBN3tCmx.js"></script>
|
| 9 |
+
<link rel="stylesheet" crossorigin href="/assets/index-CWZymrby.css">
|
| 10 |
+
</head>
|
| 11 |
+
<body>
|
| 12 |
+
<div id="root"></div>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
frontend/eslint.config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import js from '@eslint/js'
|
| 2 |
+
import globals from 'globals'
|
| 3 |
+
import reactHooks from 'eslint-plugin-react-hooks'
|
| 4 |
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
| 5 |
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
| 6 |
+
|
| 7 |
+
export default defineConfig([
|
| 8 |
+
globalIgnores(['dist']),
|
| 9 |
+
{
|
| 10 |
+
files: ['**/*.{js,jsx}'],
|
| 11 |
+
extends: [
|
| 12 |
+
js.configs.recommended,
|
| 13 |
+
reactHooks.configs.flat.recommended,
|
| 14 |
+
reactRefresh.configs.vite,
|
| 15 |
+
],
|
| 16 |
+
languageOptions: {
|
| 17 |
+
ecmaVersion: 2020,
|
| 18 |
+
globals: globals.browser,
|
| 19 |
+
parserOptions: {
|
| 20 |
+
ecmaVersion: 'latest',
|
| 21 |
+
ecmaFeatures: { jsx: true },
|
| 22 |
+
sourceType: 'module',
|
| 23 |
+
},
|
| 24 |
+
},
|
| 25 |
+
rules: {
|
| 26 |
+
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
| 27 |
+
},
|
| 28 |
+
},
|
| 29 |
+
])
|
frontend/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>frontend</title>
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div id="root"></div>
|
| 11 |
+
<script type="module" src="/src/main.jsx"></script>
|
| 12 |
+
</body>
|
| 13 |
+
</html>
|
frontend/nginx.conf
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
server {
|
| 2 |
+
listen 80;
|
| 3 |
+
server_name localhost;
|
| 4 |
+
root /usr/share/nginx/html;
|
| 5 |
+
index index.html;
|
| 6 |
+
|
| 7 |
+
# SPA fallback
|
| 8 |
+
location / {
|
| 9 |
+
try_files $uri $uri/ /index.html;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
# Proxy API calls to backend
|
| 13 |
+
location /api/ {
|
| 14 |
+
proxy_pass http://backend:8000/;
|
| 15 |
+
proxy_set_header Host $host;
|
| 16 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
# Cache static assets
|
| 20 |
+
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
| 21 |
+
expires 1y;
|
| 22 |
+
add_header Cache-Control "public, immutable";
|
| 23 |
+
}
|
| 24 |
+
}
|
frontend/package-lock.json
ADDED
|
@@ -0,0 +1,2625 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"version": "0.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "frontend",
|
| 9 |
+
"version": "0.0.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"react": "^19.2.4",
|
| 12 |
+
"react-dom": "^19.2.4",
|
| 13 |
+
"react-router-dom": "^7.13.2"
|
| 14 |
+
},
|
| 15 |
+
"devDependencies": {
|
| 16 |
+
"@eslint/js": "^9.39.4",
|
| 17 |
+
"@types/react": "^19.2.14",
|
| 18 |
+
"@types/react-dom": "^19.2.3",
|
| 19 |
+
"@vitejs/plugin-react": "^6.0.1",
|
| 20 |
+
"eslint": "^9.39.4",
|
| 21 |
+
"eslint-plugin-react-hooks": "^7.0.1",
|
| 22 |
+
"eslint-plugin-react-refresh": "^0.5.2",
|
| 23 |
+
"globals": "^17.4.0",
|
| 24 |
+
"vite": "^8.0.1"
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
"node_modules/@babel/code-frame": {
|
| 28 |
+
"version": "7.29.0",
|
| 29 |
+
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
| 30 |
+
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
| 31 |
+
"dev": true,
|
| 32 |
+
"license": "MIT",
|
| 33 |
+
"dependencies": {
|
| 34 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 35 |
+
"js-tokens": "^4.0.0",
|
| 36 |
+
"picocolors": "^1.1.1"
|
| 37 |
+
},
|
| 38 |
+
"engines": {
|
| 39 |
+
"node": ">=6.9.0"
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
"node_modules/@babel/compat-data": {
|
| 43 |
+
"version": "7.29.0",
|
| 44 |
+
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
|
| 45 |
+
"integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
|
| 46 |
+
"dev": true,
|
| 47 |
+
"license": "MIT",
|
| 48 |
+
"engines": {
|
| 49 |
+
"node": ">=6.9.0"
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
"node_modules/@babel/core": {
|
| 53 |
+
"version": "7.29.0",
|
| 54 |
+
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
| 55 |
+
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
| 56 |
+
"dev": true,
|
| 57 |
+
"license": "MIT",
|
| 58 |
+
"dependencies": {
|
| 59 |
+
"@babel/code-frame": "^7.29.0",
|
| 60 |
+
"@babel/generator": "^7.29.0",
|
| 61 |
+
"@babel/helper-compilation-targets": "^7.28.6",
|
| 62 |
+
"@babel/helper-module-transforms": "^7.28.6",
|
| 63 |
+
"@babel/helpers": "^7.28.6",
|
| 64 |
+
"@babel/parser": "^7.29.0",
|
| 65 |
+
"@babel/template": "^7.28.6",
|
| 66 |
+
"@babel/traverse": "^7.29.0",
|
| 67 |
+
"@babel/types": "^7.29.0",
|
| 68 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 69 |
+
"convert-source-map": "^2.0.0",
|
| 70 |
+
"debug": "^4.1.0",
|
| 71 |
+
"gensync": "^1.0.0-beta.2",
|
| 72 |
+
"json5": "^2.2.3",
|
| 73 |
+
"semver": "^6.3.1"
|
| 74 |
+
},
|
| 75 |
+
"engines": {
|
| 76 |
+
"node": ">=6.9.0"
|
| 77 |
+
},
|
| 78 |
+
"funding": {
|
| 79 |
+
"type": "opencollective",
|
| 80 |
+
"url": "https://opencollective.com/babel"
|
| 81 |
+
}
|
| 82 |
+
},
|
| 83 |
+
"node_modules/@babel/generator": {
|
| 84 |
+
"version": "7.29.1",
|
| 85 |
+
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
| 86 |
+
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
| 87 |
+
"dev": true,
|
| 88 |
+
"license": "MIT",
|
| 89 |
+
"dependencies": {
|
| 90 |
+
"@babel/parser": "^7.29.0",
|
| 91 |
+
"@babel/types": "^7.29.0",
|
| 92 |
+
"@jridgewell/gen-mapping": "^0.3.12",
|
| 93 |
+
"@jridgewell/trace-mapping": "^0.3.28",
|
| 94 |
+
"jsesc": "^3.0.2"
|
| 95 |
+
},
|
| 96 |
+
"engines": {
|
| 97 |
+
"node": ">=6.9.0"
|
| 98 |
+
}
|
| 99 |
+
},
|
| 100 |
+
"node_modules/@babel/helper-compilation-targets": {
|
| 101 |
+
"version": "7.28.6",
|
| 102 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
| 103 |
+
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
|
| 104 |
+
"dev": true,
|
| 105 |
+
"license": "MIT",
|
| 106 |
+
"dependencies": {
|
| 107 |
+
"@babel/compat-data": "^7.28.6",
|
| 108 |
+
"@babel/helper-validator-option": "^7.27.1",
|
| 109 |
+
"browserslist": "^4.24.0",
|
| 110 |
+
"lru-cache": "^5.1.1",
|
| 111 |
+
"semver": "^6.3.1"
|
| 112 |
+
},
|
| 113 |
+
"engines": {
|
| 114 |
+
"node": ">=6.9.0"
|
| 115 |
+
}
|
| 116 |
+
},
|
| 117 |
+
"node_modules/@babel/helper-globals": {
|
| 118 |
+
"version": "7.28.0",
|
| 119 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
| 120 |
+
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
| 121 |
+
"dev": true,
|
| 122 |
+
"license": "MIT",
|
| 123 |
+
"engines": {
|
| 124 |
+
"node": ">=6.9.0"
|
| 125 |
+
}
|
| 126 |
+
},
|
| 127 |
+
"node_modules/@babel/helper-module-imports": {
|
| 128 |
+
"version": "7.28.6",
|
| 129 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
| 130 |
+
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
| 131 |
+
"dev": true,
|
| 132 |
+
"license": "MIT",
|
| 133 |
+
"dependencies": {
|
| 134 |
+
"@babel/traverse": "^7.28.6",
|
| 135 |
+
"@babel/types": "^7.28.6"
|
| 136 |
+
},
|
| 137 |
+
"engines": {
|
| 138 |
+
"node": ">=6.9.0"
|
| 139 |
+
}
|
| 140 |
+
},
|
| 141 |
+
"node_modules/@babel/helper-module-transforms": {
|
| 142 |
+
"version": "7.28.6",
|
| 143 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
| 144 |
+
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
| 145 |
+
"dev": true,
|
| 146 |
+
"license": "MIT",
|
| 147 |
+
"dependencies": {
|
| 148 |
+
"@babel/helper-module-imports": "^7.28.6",
|
| 149 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 150 |
+
"@babel/traverse": "^7.28.6"
|
| 151 |
+
},
|
| 152 |
+
"engines": {
|
| 153 |
+
"node": ">=6.9.0"
|
| 154 |
+
},
|
| 155 |
+
"peerDependencies": {
|
| 156 |
+
"@babel/core": "^7.0.0"
|
| 157 |
+
}
|
| 158 |
+
},
|
| 159 |
+
"node_modules/@babel/helper-string-parser": {
|
| 160 |
+
"version": "7.27.1",
|
| 161 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
| 162 |
+
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
| 163 |
+
"dev": true,
|
| 164 |
+
"license": "MIT",
|
| 165 |
+
"engines": {
|
| 166 |
+
"node": ">=6.9.0"
|
| 167 |
+
}
|
| 168 |
+
},
|
| 169 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 170 |
+
"version": "7.28.5",
|
| 171 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
| 172 |
+
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
| 173 |
+
"dev": true,
|
| 174 |
+
"license": "MIT",
|
| 175 |
+
"engines": {
|
| 176 |
+
"node": ">=6.9.0"
|
| 177 |
+
}
|
| 178 |
+
},
|
| 179 |
+
"node_modules/@babel/helper-validator-option": {
|
| 180 |
+
"version": "7.27.1",
|
| 181 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
| 182 |
+
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
| 183 |
+
"dev": true,
|
| 184 |
+
"license": "MIT",
|
| 185 |
+
"engines": {
|
| 186 |
+
"node": ">=6.9.0"
|
| 187 |
+
}
|
| 188 |
+
},
|
| 189 |
+
"node_modules/@babel/helpers": {
|
| 190 |
+
"version": "7.29.2",
|
| 191 |
+
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
|
| 192 |
+
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
|
| 193 |
+
"dev": true,
|
| 194 |
+
"license": "MIT",
|
| 195 |
+
"dependencies": {
|
| 196 |
+
"@babel/template": "^7.28.6",
|
| 197 |
+
"@babel/types": "^7.29.0"
|
| 198 |
+
},
|
| 199 |
+
"engines": {
|
| 200 |
+
"node": ">=6.9.0"
|
| 201 |
+
}
|
| 202 |
+
},
|
| 203 |
+
"node_modules/@babel/parser": {
|
| 204 |
+
"version": "7.29.2",
|
| 205 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
|
| 206 |
+
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
|
| 207 |
+
"dev": true,
|
| 208 |
+
"license": "MIT",
|
| 209 |
+
"dependencies": {
|
| 210 |
+
"@babel/types": "^7.29.0"
|
| 211 |
+
},
|
| 212 |
+
"bin": {
|
| 213 |
+
"parser": "bin/babel-parser.js"
|
| 214 |
+
},
|
| 215 |
+
"engines": {
|
| 216 |
+
"node": ">=6.0.0"
|
| 217 |
+
}
|
| 218 |
+
},
|
| 219 |
+
"node_modules/@babel/template": {
|
| 220 |
+
"version": "7.28.6",
|
| 221 |
+
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
| 222 |
+
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
| 223 |
+
"dev": true,
|
| 224 |
+
"license": "MIT",
|
| 225 |
+
"dependencies": {
|
| 226 |
+
"@babel/code-frame": "^7.28.6",
|
| 227 |
+
"@babel/parser": "^7.28.6",
|
| 228 |
+
"@babel/types": "^7.28.6"
|
| 229 |
+
},
|
| 230 |
+
"engines": {
|
| 231 |
+
"node": ">=6.9.0"
|
| 232 |
+
}
|
| 233 |
+
},
|
| 234 |
+
"node_modules/@babel/traverse": {
|
| 235 |
+
"version": "7.29.0",
|
| 236 |
+
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
| 237 |
+
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
| 238 |
+
"dev": true,
|
| 239 |
+
"license": "MIT",
|
| 240 |
+
"dependencies": {
|
| 241 |
+
"@babel/code-frame": "^7.29.0",
|
| 242 |
+
"@babel/generator": "^7.29.0",
|
| 243 |
+
"@babel/helper-globals": "^7.28.0",
|
| 244 |
+
"@babel/parser": "^7.29.0",
|
| 245 |
+
"@babel/template": "^7.28.6",
|
| 246 |
+
"@babel/types": "^7.29.0",
|
| 247 |
+
"debug": "^4.3.1"
|
| 248 |
+
},
|
| 249 |
+
"engines": {
|
| 250 |
+
"node": ">=6.9.0"
|
| 251 |
+
}
|
| 252 |
+
},
|
| 253 |
+
"node_modules/@babel/types": {
|
| 254 |
+
"version": "7.29.0",
|
| 255 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
| 256 |
+
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
| 257 |
+
"dev": true,
|
| 258 |
+
"license": "MIT",
|
| 259 |
+
"dependencies": {
|
| 260 |
+
"@babel/helper-string-parser": "^7.27.1",
|
| 261 |
+
"@babel/helper-validator-identifier": "^7.28.5"
|
| 262 |
+
},
|
| 263 |
+
"engines": {
|
| 264 |
+
"node": ">=6.9.0"
|
| 265 |
+
}
|
| 266 |
+
},
|
| 267 |
+
"node_modules/@eslint-community/eslint-utils": {
|
| 268 |
+
"version": "4.9.1",
|
| 269 |
+
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
| 270 |
+
"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
|
| 271 |
+
"dev": true,
|
| 272 |
+
"license": "MIT",
|
| 273 |
+
"dependencies": {
|
| 274 |
+
"eslint-visitor-keys": "^3.4.3"
|
| 275 |
+
},
|
| 276 |
+
"engines": {
|
| 277 |
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
| 278 |
+
},
|
| 279 |
+
"funding": {
|
| 280 |
+
"url": "https://opencollective.com/eslint"
|
| 281 |
+
},
|
| 282 |
+
"peerDependencies": {
|
| 283 |
+
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
|
| 284 |
+
}
|
| 285 |
+
},
|
| 286 |
+
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
|
| 287 |
+
"version": "3.4.3",
|
| 288 |
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
| 289 |
+
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
| 290 |
+
"dev": true,
|
| 291 |
+
"license": "Apache-2.0",
|
| 292 |
+
"engines": {
|
| 293 |
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
| 294 |
+
},
|
| 295 |
+
"funding": {
|
| 296 |
+
"url": "https://opencollective.com/eslint"
|
| 297 |
+
}
|
| 298 |
+
},
|
| 299 |
+
"node_modules/@eslint-community/regexpp": {
|
| 300 |
+
"version": "4.12.2",
|
| 301 |
+
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
|
| 302 |
+
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
|
| 303 |
+
"dev": true,
|
| 304 |
+
"license": "MIT",
|
| 305 |
+
"engines": {
|
| 306 |
+
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
| 307 |
+
}
|
| 308 |
+
},
|
| 309 |
+
"node_modules/@eslint/config-array": {
|
| 310 |
+
"version": "0.21.2",
|
| 311 |
+
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
|
| 312 |
+
"integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
|
| 313 |
+
"dev": true,
|
| 314 |
+
"license": "Apache-2.0",
|
| 315 |
+
"dependencies": {
|
| 316 |
+
"@eslint/object-schema": "^2.1.7",
|
| 317 |
+
"debug": "^4.3.1",
|
| 318 |
+
"minimatch": "^3.1.5"
|
| 319 |
+
},
|
| 320 |
+
"engines": {
|
| 321 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 322 |
+
}
|
| 323 |
+
},
|
| 324 |
+
"node_modules/@eslint/config-helpers": {
|
| 325 |
+
"version": "0.4.2",
|
| 326 |
+
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
|
| 327 |
+
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
|
| 328 |
+
"dev": true,
|
| 329 |
+
"license": "Apache-2.0",
|
| 330 |
+
"dependencies": {
|
| 331 |
+
"@eslint/core": "^0.17.0"
|
| 332 |
+
},
|
| 333 |
+
"engines": {
|
| 334 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 335 |
+
}
|
| 336 |
+
},
|
| 337 |
+
"node_modules/@eslint/core": {
|
| 338 |
+
"version": "0.17.0",
|
| 339 |
+
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
|
| 340 |
+
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
|
| 341 |
+
"dev": true,
|
| 342 |
+
"license": "Apache-2.0",
|
| 343 |
+
"dependencies": {
|
| 344 |
+
"@types/json-schema": "^7.0.15"
|
| 345 |
+
},
|
| 346 |
+
"engines": {
|
| 347 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 348 |
+
}
|
| 349 |
+
},
|
| 350 |
+
"node_modules/@eslint/eslintrc": {
|
| 351 |
+
"version": "3.3.5",
|
| 352 |
+
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
|
| 353 |
+
"integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
|
| 354 |
+
"dev": true,
|
| 355 |
+
"license": "MIT",
|
| 356 |
+
"dependencies": {
|
| 357 |
+
"ajv": "^6.14.0",
|
| 358 |
+
"debug": "^4.3.2",
|
| 359 |
+
"espree": "^10.0.1",
|
| 360 |
+
"globals": "^14.0.0",
|
| 361 |
+
"ignore": "^5.2.0",
|
| 362 |
+
"import-fresh": "^3.2.1",
|
| 363 |
+
"js-yaml": "^4.1.1",
|
| 364 |
+
"minimatch": "^3.1.5",
|
| 365 |
+
"strip-json-comments": "^3.1.1"
|
| 366 |
+
},
|
| 367 |
+
"engines": {
|
| 368 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 369 |
+
},
|
| 370 |
+
"funding": {
|
| 371 |
+
"url": "https://opencollective.com/eslint"
|
| 372 |
+
}
|
| 373 |
+
},
|
| 374 |
+
"node_modules/@eslint/eslintrc/node_modules/globals": {
|
| 375 |
+
"version": "14.0.0",
|
| 376 |
+
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
|
| 377 |
+
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
|
| 378 |
+
"dev": true,
|
| 379 |
+
"license": "MIT",
|
| 380 |
+
"engines": {
|
| 381 |
+
"node": ">=18"
|
| 382 |
+
},
|
| 383 |
+
"funding": {
|
| 384 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 385 |
+
}
|
| 386 |
+
},
|
| 387 |
+
"node_modules/@eslint/js": {
|
| 388 |
+
"version": "9.39.4",
|
| 389 |
+
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
|
| 390 |
+
"integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
|
| 391 |
+
"dev": true,
|
| 392 |
+
"license": "MIT",
|
| 393 |
+
"engines": {
|
| 394 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 395 |
+
},
|
| 396 |
+
"funding": {
|
| 397 |
+
"url": "https://eslint.org/donate"
|
| 398 |
+
}
|
| 399 |
+
},
|
| 400 |
+
"node_modules/@eslint/object-schema": {
|
| 401 |
+
"version": "2.1.7",
|
| 402 |
+
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
|
| 403 |
+
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
|
| 404 |
+
"dev": true,
|
| 405 |
+
"license": "Apache-2.0",
|
| 406 |
+
"engines": {
|
| 407 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 408 |
+
}
|
| 409 |
+
},
|
| 410 |
+
"node_modules/@eslint/plugin-kit": {
|
| 411 |
+
"version": "0.4.1",
|
| 412 |
+
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
|
| 413 |
+
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
|
| 414 |
+
"dev": true,
|
| 415 |
+
"license": "Apache-2.0",
|
| 416 |
+
"dependencies": {
|
| 417 |
+
"@eslint/core": "^0.17.0",
|
| 418 |
+
"levn": "^0.4.1"
|
| 419 |
+
},
|
| 420 |
+
"engines": {
|
| 421 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 422 |
+
}
|
| 423 |
+
},
|
| 424 |
+
"node_modules/@humanfs/core": {
|
| 425 |
+
"version": "0.19.1",
|
| 426 |
+
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
| 427 |
+
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
|
| 428 |
+
"dev": true,
|
| 429 |
+
"license": "Apache-2.0",
|
| 430 |
+
"engines": {
|
| 431 |
+
"node": ">=18.18.0"
|
| 432 |
+
}
|
| 433 |
+
},
|
| 434 |
+
"node_modules/@humanfs/node": {
|
| 435 |
+
"version": "0.16.7",
|
| 436 |
+
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
|
| 437 |
+
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
|
| 438 |
+
"dev": true,
|
| 439 |
+
"license": "Apache-2.0",
|
| 440 |
+
"dependencies": {
|
| 441 |
+
"@humanfs/core": "^0.19.1",
|
| 442 |
+
"@humanwhocodes/retry": "^0.4.0"
|
| 443 |
+
},
|
| 444 |
+
"engines": {
|
| 445 |
+
"node": ">=18.18.0"
|
| 446 |
+
}
|
| 447 |
+
},
|
| 448 |
+
"node_modules/@humanwhocodes/module-importer": {
|
| 449 |
+
"version": "1.0.1",
|
| 450 |
+
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
| 451 |
+
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
|
| 452 |
+
"dev": true,
|
| 453 |
+
"license": "Apache-2.0",
|
| 454 |
+
"engines": {
|
| 455 |
+
"node": ">=12.22"
|
| 456 |
+
},
|
| 457 |
+
"funding": {
|
| 458 |
+
"type": "github",
|
| 459 |
+
"url": "https://github.com/sponsors/nzakas"
|
| 460 |
+
}
|
| 461 |
+
},
|
| 462 |
+
"node_modules/@humanwhocodes/retry": {
|
| 463 |
+
"version": "0.4.3",
|
| 464 |
+
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
|
| 465 |
+
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
|
| 466 |
+
"dev": true,
|
| 467 |
+
"license": "Apache-2.0",
|
| 468 |
+
"engines": {
|
| 469 |
+
"node": ">=18.18"
|
| 470 |
+
},
|
| 471 |
+
"funding": {
|
| 472 |
+
"type": "github",
|
| 473 |
+
"url": "https://github.com/sponsors/nzakas"
|
| 474 |
+
}
|
| 475 |
+
},
|
| 476 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 477 |
+
"version": "0.3.13",
|
| 478 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 479 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 480 |
+
"dev": true,
|
| 481 |
+
"license": "MIT",
|
| 482 |
+
"dependencies": {
|
| 483 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 484 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 485 |
+
}
|
| 486 |
+
},
|
| 487 |
+
"node_modules/@jridgewell/remapping": {
|
| 488 |
+
"version": "2.3.5",
|
| 489 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 490 |
+
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
| 491 |
+
"dev": true,
|
| 492 |
+
"license": "MIT",
|
| 493 |
+
"dependencies": {
|
| 494 |
+
"@jridgewell/gen-mapping": "^0.3.5",
|
| 495 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 496 |
+
}
|
| 497 |
+
},
|
| 498 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 499 |
+
"version": "3.1.2",
|
| 500 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 501 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 502 |
+
"dev": true,
|
| 503 |
+
"license": "MIT",
|
| 504 |
+
"engines": {
|
| 505 |
+
"node": ">=6.0.0"
|
| 506 |
+
}
|
| 507 |
+
},
|
| 508 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 509 |
+
"version": "1.5.5",
|
| 510 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 511 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 512 |
+
"dev": true,
|
| 513 |
+
"license": "MIT"
|
| 514 |
+
},
|
| 515 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 516 |
+
"version": "0.3.31",
|
| 517 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 518 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 519 |
+
"dev": true,
|
| 520 |
+
"license": "MIT",
|
| 521 |
+
"dependencies": {
|
| 522 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 523 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 524 |
+
}
|
| 525 |
+
},
|
| 526 |
+
"node_modules/@napi-rs/wasm-runtime": {
|
| 527 |
+
"version": "1.1.2",
|
| 528 |
+
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz",
|
| 529 |
+
"integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==",
|
| 530 |
+
"dev": true,
|
| 531 |
+
"license": "MIT",
|
| 532 |
+
"optional": true,
|
| 533 |
+
"dependencies": {
|
| 534 |
+
"@tybys/wasm-util": "^0.10.1"
|
| 535 |
+
},
|
| 536 |
+
"funding": {
|
| 537 |
+
"type": "github",
|
| 538 |
+
"url": "https://github.com/sponsors/Brooooooklyn"
|
| 539 |
+
},
|
| 540 |
+
"peerDependencies": {
|
| 541 |
+
"@emnapi/core": "^1.7.1",
|
| 542 |
+
"@emnapi/runtime": "^1.7.1"
|
| 543 |
+
}
|
| 544 |
+
},
|
| 545 |
+
"node_modules/@oxc-project/types": {
|
| 546 |
+
"version": "0.122.0",
|
| 547 |
+
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz",
|
| 548 |
+
"integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==",
|
| 549 |
+
"dev": true,
|
| 550 |
+
"license": "MIT",
|
| 551 |
+
"funding": {
|
| 552 |
+
"url": "https://github.com/sponsors/Boshen"
|
| 553 |
+
}
|
| 554 |
+
},
|
| 555 |
+
"node_modules/@rolldown/binding-android-arm64": {
|
| 556 |
+
"version": "1.0.0-rc.12",
|
| 557 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz",
|
| 558 |
+
"integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==",
|
| 559 |
+
"cpu": [
|
| 560 |
+
"arm64"
|
| 561 |
+
],
|
| 562 |
+
"dev": true,
|
| 563 |
+
"license": "MIT",
|
| 564 |
+
"optional": true,
|
| 565 |
+
"os": [
|
| 566 |
+
"android"
|
| 567 |
+
],
|
| 568 |
+
"engines": {
|
| 569 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 570 |
+
}
|
| 571 |
+
},
|
| 572 |
+
"node_modules/@rolldown/binding-darwin-arm64": {
|
| 573 |
+
"version": "1.0.0-rc.12",
|
| 574 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz",
|
| 575 |
+
"integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==",
|
| 576 |
+
"cpu": [
|
| 577 |
+
"arm64"
|
| 578 |
+
],
|
| 579 |
+
"dev": true,
|
| 580 |
+
"license": "MIT",
|
| 581 |
+
"optional": true,
|
| 582 |
+
"os": [
|
| 583 |
+
"darwin"
|
| 584 |
+
],
|
| 585 |
+
"engines": {
|
| 586 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 587 |
+
}
|
| 588 |
+
},
|
| 589 |
+
"node_modules/@rolldown/binding-darwin-x64": {
|
| 590 |
+
"version": "1.0.0-rc.12",
|
| 591 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz",
|
| 592 |
+
"integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==",
|
| 593 |
+
"cpu": [
|
| 594 |
+
"x64"
|
| 595 |
+
],
|
| 596 |
+
"dev": true,
|
| 597 |
+
"license": "MIT",
|
| 598 |
+
"optional": true,
|
| 599 |
+
"os": [
|
| 600 |
+
"darwin"
|
| 601 |
+
],
|
| 602 |
+
"engines": {
|
| 603 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 604 |
+
}
|
| 605 |
+
},
|
| 606 |
+
"node_modules/@rolldown/binding-freebsd-x64": {
|
| 607 |
+
"version": "1.0.0-rc.12",
|
| 608 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz",
|
| 609 |
+
"integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==",
|
| 610 |
+
"cpu": [
|
| 611 |
+
"x64"
|
| 612 |
+
],
|
| 613 |
+
"dev": true,
|
| 614 |
+
"license": "MIT",
|
| 615 |
+
"optional": true,
|
| 616 |
+
"os": [
|
| 617 |
+
"freebsd"
|
| 618 |
+
],
|
| 619 |
+
"engines": {
|
| 620 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 621 |
+
}
|
| 622 |
+
},
|
| 623 |
+
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
| 624 |
+
"version": "1.0.0-rc.12",
|
| 625 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz",
|
| 626 |
+
"integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==",
|
| 627 |
+
"cpu": [
|
| 628 |
+
"arm"
|
| 629 |
+
],
|
| 630 |
+
"dev": true,
|
| 631 |
+
"license": "MIT",
|
| 632 |
+
"optional": true,
|
| 633 |
+
"os": [
|
| 634 |
+
"linux"
|
| 635 |
+
],
|
| 636 |
+
"engines": {
|
| 637 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 638 |
+
}
|
| 639 |
+
},
|
| 640 |
+
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
| 641 |
+
"version": "1.0.0-rc.12",
|
| 642 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz",
|
| 643 |
+
"integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==",
|
| 644 |
+
"cpu": [
|
| 645 |
+
"arm64"
|
| 646 |
+
],
|
| 647 |
+
"dev": true,
|
| 648 |
+
"license": "MIT",
|
| 649 |
+
"optional": true,
|
| 650 |
+
"os": [
|
| 651 |
+
"linux"
|
| 652 |
+
],
|
| 653 |
+
"engines": {
|
| 654 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 655 |
+
}
|
| 656 |
+
},
|
| 657 |
+
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
| 658 |
+
"version": "1.0.0-rc.12",
|
| 659 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz",
|
| 660 |
+
"integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==",
|
| 661 |
+
"cpu": [
|
| 662 |
+
"arm64"
|
| 663 |
+
],
|
| 664 |
+
"dev": true,
|
| 665 |
+
"license": "MIT",
|
| 666 |
+
"optional": true,
|
| 667 |
+
"os": [
|
| 668 |
+
"linux"
|
| 669 |
+
],
|
| 670 |
+
"engines": {
|
| 671 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 672 |
+
}
|
| 673 |
+
},
|
| 674 |
+
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
| 675 |
+
"version": "1.0.0-rc.12",
|
| 676 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz",
|
| 677 |
+
"integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==",
|
| 678 |
+
"cpu": [
|
| 679 |
+
"ppc64"
|
| 680 |
+
],
|
| 681 |
+
"dev": true,
|
| 682 |
+
"license": "MIT",
|
| 683 |
+
"optional": true,
|
| 684 |
+
"os": [
|
| 685 |
+
"linux"
|
| 686 |
+
],
|
| 687 |
+
"engines": {
|
| 688 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 689 |
+
}
|
| 690 |
+
},
|
| 691 |
+
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
| 692 |
+
"version": "1.0.0-rc.12",
|
| 693 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz",
|
| 694 |
+
"integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==",
|
| 695 |
+
"cpu": [
|
| 696 |
+
"s390x"
|
| 697 |
+
],
|
| 698 |
+
"dev": true,
|
| 699 |
+
"license": "MIT",
|
| 700 |
+
"optional": true,
|
| 701 |
+
"os": [
|
| 702 |
+
"linux"
|
| 703 |
+
],
|
| 704 |
+
"engines": {
|
| 705 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 706 |
+
}
|
| 707 |
+
},
|
| 708 |
+
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
| 709 |
+
"version": "1.0.0-rc.12",
|
| 710 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz",
|
| 711 |
+
"integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==",
|
| 712 |
+
"cpu": [
|
| 713 |
+
"x64"
|
| 714 |
+
],
|
| 715 |
+
"dev": true,
|
| 716 |
+
"license": "MIT",
|
| 717 |
+
"optional": true,
|
| 718 |
+
"os": [
|
| 719 |
+
"linux"
|
| 720 |
+
],
|
| 721 |
+
"engines": {
|
| 722 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 723 |
+
}
|
| 724 |
+
},
|
| 725 |
+
"node_modules/@rolldown/binding-linux-x64-musl": {
|
| 726 |
+
"version": "1.0.0-rc.12",
|
| 727 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz",
|
| 728 |
+
"integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==",
|
| 729 |
+
"cpu": [
|
| 730 |
+
"x64"
|
| 731 |
+
],
|
| 732 |
+
"dev": true,
|
| 733 |
+
"license": "MIT",
|
| 734 |
+
"optional": true,
|
| 735 |
+
"os": [
|
| 736 |
+
"linux"
|
| 737 |
+
],
|
| 738 |
+
"engines": {
|
| 739 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 740 |
+
}
|
| 741 |
+
},
|
| 742 |
+
"node_modules/@rolldown/binding-openharmony-arm64": {
|
| 743 |
+
"version": "1.0.0-rc.12",
|
| 744 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz",
|
| 745 |
+
"integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==",
|
| 746 |
+
"cpu": [
|
| 747 |
+
"arm64"
|
| 748 |
+
],
|
| 749 |
+
"dev": true,
|
| 750 |
+
"license": "MIT",
|
| 751 |
+
"optional": true,
|
| 752 |
+
"os": [
|
| 753 |
+
"openharmony"
|
| 754 |
+
],
|
| 755 |
+
"engines": {
|
| 756 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 757 |
+
}
|
| 758 |
+
},
|
| 759 |
+
"node_modules/@rolldown/binding-wasm32-wasi": {
|
| 760 |
+
"version": "1.0.0-rc.12",
|
| 761 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz",
|
| 762 |
+
"integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==",
|
| 763 |
+
"cpu": [
|
| 764 |
+
"wasm32"
|
| 765 |
+
],
|
| 766 |
+
"dev": true,
|
| 767 |
+
"license": "MIT",
|
| 768 |
+
"optional": true,
|
| 769 |
+
"dependencies": {
|
| 770 |
+
"@napi-rs/wasm-runtime": "^1.1.1"
|
| 771 |
+
},
|
| 772 |
+
"engines": {
|
| 773 |
+
"node": ">=14.0.0"
|
| 774 |
+
}
|
| 775 |
+
},
|
| 776 |
+
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
| 777 |
+
"version": "1.0.0-rc.12",
|
| 778 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz",
|
| 779 |
+
"integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==",
|
| 780 |
+
"cpu": [
|
| 781 |
+
"arm64"
|
| 782 |
+
],
|
| 783 |
+
"dev": true,
|
| 784 |
+
"license": "MIT",
|
| 785 |
+
"optional": true,
|
| 786 |
+
"os": [
|
| 787 |
+
"win32"
|
| 788 |
+
],
|
| 789 |
+
"engines": {
|
| 790 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 791 |
+
}
|
| 792 |
+
},
|
| 793 |
+
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
| 794 |
+
"version": "1.0.0-rc.12",
|
| 795 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz",
|
| 796 |
+
"integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==",
|
| 797 |
+
"cpu": [
|
| 798 |
+
"x64"
|
| 799 |
+
],
|
| 800 |
+
"dev": true,
|
| 801 |
+
"license": "MIT",
|
| 802 |
+
"optional": true,
|
| 803 |
+
"os": [
|
| 804 |
+
"win32"
|
| 805 |
+
],
|
| 806 |
+
"engines": {
|
| 807 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 808 |
+
}
|
| 809 |
+
},
|
| 810 |
+
"node_modules/@rolldown/pluginutils": {
|
| 811 |
+
"version": "1.0.0-rc.7",
|
| 812 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
|
| 813 |
+
"integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
|
| 814 |
+
"dev": true,
|
| 815 |
+
"license": "MIT"
|
| 816 |
+
},
|
| 817 |
+
"node_modules/@tybys/wasm-util": {
|
| 818 |
+
"version": "0.10.1",
|
| 819 |
+
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
| 820 |
+
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
| 821 |
+
"dev": true,
|
| 822 |
+
"license": "MIT",
|
| 823 |
+
"optional": true,
|
| 824 |
+
"dependencies": {
|
| 825 |
+
"tslib": "^2.4.0"
|
| 826 |
+
}
|
| 827 |
+
},
|
| 828 |
+
"node_modules/@types/estree": {
|
| 829 |
+
"version": "1.0.8",
|
| 830 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
| 831 |
+
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
| 832 |
+
"dev": true,
|
| 833 |
+
"license": "MIT"
|
| 834 |
+
},
|
| 835 |
+
"node_modules/@types/json-schema": {
|
| 836 |
+
"version": "7.0.15",
|
| 837 |
+
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
| 838 |
+
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
| 839 |
+
"dev": true,
|
| 840 |
+
"license": "MIT"
|
| 841 |
+
},
|
| 842 |
+
"node_modules/@types/react": {
|
| 843 |
+
"version": "19.2.14",
|
| 844 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
| 845 |
+
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
| 846 |
+
"dev": true,
|
| 847 |
+
"license": "MIT",
|
| 848 |
+
"dependencies": {
|
| 849 |
+
"csstype": "^3.2.2"
|
| 850 |
+
}
|
| 851 |
+
},
|
| 852 |
+
"node_modules/@types/react-dom": {
|
| 853 |
+
"version": "19.2.3",
|
| 854 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
| 855 |
+
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
| 856 |
+
"dev": true,
|
| 857 |
+
"license": "MIT",
|
| 858 |
+
"peerDependencies": {
|
| 859 |
+
"@types/react": "^19.2.0"
|
| 860 |
+
}
|
| 861 |
+
},
|
| 862 |
+
"node_modules/@vitejs/plugin-react": {
|
| 863 |
+
"version": "6.0.1",
|
| 864 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
|
| 865 |
+
"integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
|
| 866 |
+
"dev": true,
|
| 867 |
+
"license": "MIT",
|
| 868 |
+
"dependencies": {
|
| 869 |
+
"@rolldown/pluginutils": "1.0.0-rc.7"
|
| 870 |
+
},
|
| 871 |
+
"engines": {
|
| 872 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 873 |
+
},
|
| 874 |
+
"peerDependencies": {
|
| 875 |
+
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
|
| 876 |
+
"babel-plugin-react-compiler": "^1.0.0",
|
| 877 |
+
"vite": "^8.0.0"
|
| 878 |
+
},
|
| 879 |
+
"peerDependenciesMeta": {
|
| 880 |
+
"@rolldown/plugin-babel": {
|
| 881 |
+
"optional": true
|
| 882 |
+
},
|
| 883 |
+
"babel-plugin-react-compiler": {
|
| 884 |
+
"optional": true
|
| 885 |
+
}
|
| 886 |
+
}
|
| 887 |
+
},
|
| 888 |
+
"node_modules/acorn": {
|
| 889 |
+
"version": "8.16.0",
|
| 890 |
+
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
| 891 |
+
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
| 892 |
+
"dev": true,
|
| 893 |
+
"license": "MIT",
|
| 894 |
+
"bin": {
|
| 895 |
+
"acorn": "bin/acorn"
|
| 896 |
+
},
|
| 897 |
+
"engines": {
|
| 898 |
+
"node": ">=0.4.0"
|
| 899 |
+
}
|
| 900 |
+
},
|
| 901 |
+
"node_modules/acorn-jsx": {
|
| 902 |
+
"version": "5.3.2",
|
| 903 |
+
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
| 904 |
+
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
| 905 |
+
"dev": true,
|
| 906 |
+
"license": "MIT",
|
| 907 |
+
"peerDependencies": {
|
| 908 |
+
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
| 909 |
+
}
|
| 910 |
+
},
|
| 911 |
+
"node_modules/ajv": {
|
| 912 |
+
"version": "6.14.0",
|
| 913 |
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
| 914 |
+
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
| 915 |
+
"dev": true,
|
| 916 |
+
"license": "MIT",
|
| 917 |
+
"dependencies": {
|
| 918 |
+
"fast-deep-equal": "^3.1.1",
|
| 919 |
+
"fast-json-stable-stringify": "^2.0.0",
|
| 920 |
+
"json-schema-traverse": "^0.4.1",
|
| 921 |
+
"uri-js": "^4.2.2"
|
| 922 |
+
},
|
| 923 |
+
"funding": {
|
| 924 |
+
"type": "github",
|
| 925 |
+
"url": "https://github.com/sponsors/epoberezkin"
|
| 926 |
+
}
|
| 927 |
+
},
|
| 928 |
+
"node_modules/ansi-styles": {
|
| 929 |
+
"version": "4.3.0",
|
| 930 |
+
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
| 931 |
+
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
| 932 |
+
"dev": true,
|
| 933 |
+
"license": "MIT",
|
| 934 |
+
"dependencies": {
|
| 935 |
+
"color-convert": "^2.0.1"
|
| 936 |
+
},
|
| 937 |
+
"engines": {
|
| 938 |
+
"node": ">=8"
|
| 939 |
+
},
|
| 940 |
+
"funding": {
|
| 941 |
+
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
| 942 |
+
}
|
| 943 |
+
},
|
| 944 |
+
"node_modules/argparse": {
|
| 945 |
+
"version": "2.0.1",
|
| 946 |
+
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
| 947 |
+
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
| 948 |
+
"dev": true,
|
| 949 |
+
"license": "Python-2.0"
|
| 950 |
+
},
|
| 951 |
+
"node_modules/balanced-match": {
|
| 952 |
+
"version": "1.0.2",
|
| 953 |
+
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
| 954 |
+
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
| 955 |
+
"dev": true,
|
| 956 |
+
"license": "MIT"
|
| 957 |
+
},
|
| 958 |
+
"node_modules/baseline-browser-mapping": {
|
| 959 |
+
"version": "2.10.12",
|
| 960 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz",
|
| 961 |
+
"integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==",
|
| 962 |
+
"dev": true,
|
| 963 |
+
"license": "Apache-2.0",
|
| 964 |
+
"bin": {
|
| 965 |
+
"baseline-browser-mapping": "dist/cli.cjs"
|
| 966 |
+
},
|
| 967 |
+
"engines": {
|
| 968 |
+
"node": ">=6.0.0"
|
| 969 |
+
}
|
| 970 |
+
},
|
| 971 |
+
"node_modules/brace-expansion": {
|
| 972 |
+
"version": "1.1.13",
|
| 973 |
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
| 974 |
+
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
| 975 |
+
"dev": true,
|
| 976 |
+
"license": "MIT",
|
| 977 |
+
"dependencies": {
|
| 978 |
+
"balanced-match": "^1.0.0",
|
| 979 |
+
"concat-map": "0.0.1"
|
| 980 |
+
}
|
| 981 |
+
},
|
| 982 |
+
"node_modules/browserslist": {
|
| 983 |
+
"version": "4.28.1",
|
| 984 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
| 985 |
+
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
| 986 |
+
"dev": true,
|
| 987 |
+
"funding": [
|
| 988 |
+
{
|
| 989 |
+
"type": "opencollective",
|
| 990 |
+
"url": "https://opencollective.com/browserslist"
|
| 991 |
+
},
|
| 992 |
+
{
|
| 993 |
+
"type": "tidelift",
|
| 994 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 995 |
+
},
|
| 996 |
+
{
|
| 997 |
+
"type": "github",
|
| 998 |
+
"url": "https://github.com/sponsors/ai"
|
| 999 |
+
}
|
| 1000 |
+
],
|
| 1001 |
+
"license": "MIT",
|
| 1002 |
+
"dependencies": {
|
| 1003 |
+
"baseline-browser-mapping": "^2.9.0",
|
| 1004 |
+
"caniuse-lite": "^1.0.30001759",
|
| 1005 |
+
"electron-to-chromium": "^1.5.263",
|
| 1006 |
+
"node-releases": "^2.0.27",
|
| 1007 |
+
"update-browserslist-db": "^1.2.0"
|
| 1008 |
+
},
|
| 1009 |
+
"bin": {
|
| 1010 |
+
"browserslist": "cli.js"
|
| 1011 |
+
},
|
| 1012 |
+
"engines": {
|
| 1013 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1014 |
+
}
|
| 1015 |
+
},
|
| 1016 |
+
"node_modules/callsites": {
|
| 1017 |
+
"version": "3.1.0",
|
| 1018 |
+
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
| 1019 |
+
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
| 1020 |
+
"dev": true,
|
| 1021 |
+
"license": "MIT",
|
| 1022 |
+
"engines": {
|
| 1023 |
+
"node": ">=6"
|
| 1024 |
+
}
|
| 1025 |
+
},
|
| 1026 |
+
"node_modules/caniuse-lite": {
|
| 1027 |
+
"version": "1.0.30001781",
|
| 1028 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz",
|
| 1029 |
+
"integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==",
|
| 1030 |
+
"dev": true,
|
| 1031 |
+
"funding": [
|
| 1032 |
+
{
|
| 1033 |
+
"type": "opencollective",
|
| 1034 |
+
"url": "https://opencollective.com/browserslist"
|
| 1035 |
+
},
|
| 1036 |
+
{
|
| 1037 |
+
"type": "tidelift",
|
| 1038 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 1039 |
+
},
|
| 1040 |
+
{
|
| 1041 |
+
"type": "github",
|
| 1042 |
+
"url": "https://github.com/sponsors/ai"
|
| 1043 |
+
}
|
| 1044 |
+
],
|
| 1045 |
+
"license": "CC-BY-4.0"
|
| 1046 |
+
},
|
| 1047 |
+
"node_modules/chalk": {
|
| 1048 |
+
"version": "4.1.2",
|
| 1049 |
+
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
| 1050 |
+
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
| 1051 |
+
"dev": true,
|
| 1052 |
+
"license": "MIT",
|
| 1053 |
+
"dependencies": {
|
| 1054 |
+
"ansi-styles": "^4.1.0",
|
| 1055 |
+
"supports-color": "^7.1.0"
|
| 1056 |
+
},
|
| 1057 |
+
"engines": {
|
| 1058 |
+
"node": ">=10"
|
| 1059 |
+
},
|
| 1060 |
+
"funding": {
|
| 1061 |
+
"url": "https://github.com/chalk/chalk?sponsor=1"
|
| 1062 |
+
}
|
| 1063 |
+
},
|
| 1064 |
+
"node_modules/color-convert": {
|
| 1065 |
+
"version": "2.0.1",
|
| 1066 |
+
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
| 1067 |
+
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
| 1068 |
+
"dev": true,
|
| 1069 |
+
"license": "MIT",
|
| 1070 |
+
"dependencies": {
|
| 1071 |
+
"color-name": "~1.1.4"
|
| 1072 |
+
},
|
| 1073 |
+
"engines": {
|
| 1074 |
+
"node": ">=7.0.0"
|
| 1075 |
+
}
|
| 1076 |
+
},
|
| 1077 |
+
"node_modules/color-name": {
|
| 1078 |
+
"version": "1.1.4",
|
| 1079 |
+
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
| 1080 |
+
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
| 1081 |
+
"dev": true,
|
| 1082 |
+
"license": "MIT"
|
| 1083 |
+
},
|
| 1084 |
+
"node_modules/concat-map": {
|
| 1085 |
+
"version": "0.0.1",
|
| 1086 |
+
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
| 1087 |
+
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
| 1088 |
+
"dev": true,
|
| 1089 |
+
"license": "MIT"
|
| 1090 |
+
},
|
| 1091 |
+
"node_modules/convert-source-map": {
|
| 1092 |
+
"version": "2.0.0",
|
| 1093 |
+
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
| 1094 |
+
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
| 1095 |
+
"dev": true,
|
| 1096 |
+
"license": "MIT"
|
| 1097 |
+
},
|
| 1098 |
+
"node_modules/cookie": {
|
| 1099 |
+
"version": "1.1.1",
|
| 1100 |
+
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
| 1101 |
+
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
| 1102 |
+
"license": "MIT",
|
| 1103 |
+
"engines": {
|
| 1104 |
+
"node": ">=18"
|
| 1105 |
+
},
|
| 1106 |
+
"funding": {
|
| 1107 |
+
"type": "opencollective",
|
| 1108 |
+
"url": "https://opencollective.com/express"
|
| 1109 |
+
}
|
| 1110 |
+
},
|
| 1111 |
+
"node_modules/cross-spawn": {
|
| 1112 |
+
"version": "7.0.6",
|
| 1113 |
+
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
| 1114 |
+
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
| 1115 |
+
"dev": true,
|
| 1116 |
+
"license": "MIT",
|
| 1117 |
+
"dependencies": {
|
| 1118 |
+
"path-key": "^3.1.0",
|
| 1119 |
+
"shebang-command": "^2.0.0",
|
| 1120 |
+
"which": "^2.0.1"
|
| 1121 |
+
},
|
| 1122 |
+
"engines": {
|
| 1123 |
+
"node": ">= 8"
|
| 1124 |
+
}
|
| 1125 |
+
},
|
| 1126 |
+
"node_modules/csstype": {
|
| 1127 |
+
"version": "3.2.3",
|
| 1128 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1129 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1130 |
+
"dev": true,
|
| 1131 |
+
"license": "MIT"
|
| 1132 |
+
},
|
| 1133 |
+
"node_modules/debug": {
|
| 1134 |
+
"version": "4.4.3",
|
| 1135 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1136 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1137 |
+
"dev": true,
|
| 1138 |
+
"license": "MIT",
|
| 1139 |
+
"dependencies": {
|
| 1140 |
+
"ms": "^2.1.3"
|
| 1141 |
+
},
|
| 1142 |
+
"engines": {
|
| 1143 |
+
"node": ">=6.0"
|
| 1144 |
+
},
|
| 1145 |
+
"peerDependenciesMeta": {
|
| 1146 |
+
"supports-color": {
|
| 1147 |
+
"optional": true
|
| 1148 |
+
}
|
| 1149 |
+
}
|
| 1150 |
+
},
|
| 1151 |
+
"node_modules/deep-is": {
|
| 1152 |
+
"version": "0.1.4",
|
| 1153 |
+
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
| 1154 |
+
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
| 1155 |
+
"dev": true,
|
| 1156 |
+
"license": "MIT"
|
| 1157 |
+
},
|
| 1158 |
+
"node_modules/detect-libc": {
|
| 1159 |
+
"version": "2.1.2",
|
| 1160 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 1161 |
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 1162 |
+
"dev": true,
|
| 1163 |
+
"license": "Apache-2.0",
|
| 1164 |
+
"engines": {
|
| 1165 |
+
"node": ">=8"
|
| 1166 |
+
}
|
| 1167 |
+
},
|
| 1168 |
+
"node_modules/electron-to-chromium": {
|
| 1169 |
+
"version": "1.5.328",
|
| 1170 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz",
|
| 1171 |
+
"integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==",
|
| 1172 |
+
"dev": true,
|
| 1173 |
+
"license": "ISC"
|
| 1174 |
+
},
|
| 1175 |
+
"node_modules/escalade": {
|
| 1176 |
+
"version": "3.2.0",
|
| 1177 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1178 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1179 |
+
"dev": true,
|
| 1180 |
+
"license": "MIT",
|
| 1181 |
+
"engines": {
|
| 1182 |
+
"node": ">=6"
|
| 1183 |
+
}
|
| 1184 |
+
},
|
| 1185 |
+
"node_modules/escape-string-regexp": {
|
| 1186 |
+
"version": "4.0.0",
|
| 1187 |
+
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
| 1188 |
+
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
| 1189 |
+
"dev": true,
|
| 1190 |
+
"license": "MIT",
|
| 1191 |
+
"engines": {
|
| 1192 |
+
"node": ">=10"
|
| 1193 |
+
},
|
| 1194 |
+
"funding": {
|
| 1195 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1196 |
+
}
|
| 1197 |
+
},
|
| 1198 |
+
"node_modules/eslint": {
|
| 1199 |
+
"version": "9.39.4",
|
| 1200 |
+
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
|
| 1201 |
+
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
| 1202 |
+
"dev": true,
|
| 1203 |
+
"license": "MIT",
|
| 1204 |
+
"dependencies": {
|
| 1205 |
+
"@eslint-community/eslint-utils": "^4.8.0",
|
| 1206 |
+
"@eslint-community/regexpp": "^4.12.1",
|
| 1207 |
+
"@eslint/config-array": "^0.21.2",
|
| 1208 |
+
"@eslint/config-helpers": "^0.4.2",
|
| 1209 |
+
"@eslint/core": "^0.17.0",
|
| 1210 |
+
"@eslint/eslintrc": "^3.3.5",
|
| 1211 |
+
"@eslint/js": "9.39.4",
|
| 1212 |
+
"@eslint/plugin-kit": "^0.4.1",
|
| 1213 |
+
"@humanfs/node": "^0.16.6",
|
| 1214 |
+
"@humanwhocodes/module-importer": "^1.0.1",
|
| 1215 |
+
"@humanwhocodes/retry": "^0.4.2",
|
| 1216 |
+
"@types/estree": "^1.0.6",
|
| 1217 |
+
"ajv": "^6.14.0",
|
| 1218 |
+
"chalk": "^4.0.0",
|
| 1219 |
+
"cross-spawn": "^7.0.6",
|
| 1220 |
+
"debug": "^4.3.2",
|
| 1221 |
+
"escape-string-regexp": "^4.0.0",
|
| 1222 |
+
"eslint-scope": "^8.4.0",
|
| 1223 |
+
"eslint-visitor-keys": "^4.2.1",
|
| 1224 |
+
"espree": "^10.4.0",
|
| 1225 |
+
"esquery": "^1.5.0",
|
| 1226 |
+
"esutils": "^2.0.2",
|
| 1227 |
+
"fast-deep-equal": "^3.1.3",
|
| 1228 |
+
"file-entry-cache": "^8.0.0",
|
| 1229 |
+
"find-up": "^5.0.0",
|
| 1230 |
+
"glob-parent": "^6.0.2",
|
| 1231 |
+
"ignore": "^5.2.0",
|
| 1232 |
+
"imurmurhash": "^0.1.4",
|
| 1233 |
+
"is-glob": "^4.0.0",
|
| 1234 |
+
"json-stable-stringify-without-jsonify": "^1.0.1",
|
| 1235 |
+
"lodash.merge": "^4.6.2",
|
| 1236 |
+
"minimatch": "^3.1.5",
|
| 1237 |
+
"natural-compare": "^1.4.0",
|
| 1238 |
+
"optionator": "^0.9.3"
|
| 1239 |
+
},
|
| 1240 |
+
"bin": {
|
| 1241 |
+
"eslint": "bin/eslint.js"
|
| 1242 |
+
},
|
| 1243 |
+
"engines": {
|
| 1244 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 1245 |
+
},
|
| 1246 |
+
"funding": {
|
| 1247 |
+
"url": "https://eslint.org/donate"
|
| 1248 |
+
},
|
| 1249 |
+
"peerDependencies": {
|
| 1250 |
+
"jiti": "*"
|
| 1251 |
+
},
|
| 1252 |
+
"peerDependenciesMeta": {
|
| 1253 |
+
"jiti": {
|
| 1254 |
+
"optional": true
|
| 1255 |
+
}
|
| 1256 |
+
}
|
| 1257 |
+
},
|
| 1258 |
+
"node_modules/eslint-plugin-react-hooks": {
|
| 1259 |
+
"version": "7.0.1",
|
| 1260 |
+
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
|
| 1261 |
+
"integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
|
| 1262 |
+
"dev": true,
|
| 1263 |
+
"license": "MIT",
|
| 1264 |
+
"dependencies": {
|
| 1265 |
+
"@babel/core": "^7.24.4",
|
| 1266 |
+
"@babel/parser": "^7.24.4",
|
| 1267 |
+
"hermes-parser": "^0.25.1",
|
| 1268 |
+
"zod": "^3.25.0 || ^4.0.0",
|
| 1269 |
+
"zod-validation-error": "^3.5.0 || ^4.0.0"
|
| 1270 |
+
},
|
| 1271 |
+
"engines": {
|
| 1272 |
+
"node": ">=18"
|
| 1273 |
+
},
|
| 1274 |
+
"peerDependencies": {
|
| 1275 |
+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
|
| 1276 |
+
}
|
| 1277 |
+
},
|
| 1278 |
+
"node_modules/eslint-plugin-react-refresh": {
|
| 1279 |
+
"version": "0.5.2",
|
| 1280 |
+
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
|
| 1281 |
+
"integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
|
| 1282 |
+
"dev": true,
|
| 1283 |
+
"license": "MIT",
|
| 1284 |
+
"peerDependencies": {
|
| 1285 |
+
"eslint": "^9 || ^10"
|
| 1286 |
+
}
|
| 1287 |
+
},
|
| 1288 |
+
"node_modules/eslint-scope": {
|
| 1289 |
+
"version": "8.4.0",
|
| 1290 |
+
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
| 1291 |
+
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
| 1292 |
+
"dev": true,
|
| 1293 |
+
"license": "BSD-2-Clause",
|
| 1294 |
+
"dependencies": {
|
| 1295 |
+
"esrecurse": "^4.3.0",
|
| 1296 |
+
"estraverse": "^5.2.0"
|
| 1297 |
+
},
|
| 1298 |
+
"engines": {
|
| 1299 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 1300 |
+
},
|
| 1301 |
+
"funding": {
|
| 1302 |
+
"url": "https://opencollective.com/eslint"
|
| 1303 |
+
}
|
| 1304 |
+
},
|
| 1305 |
+
"node_modules/eslint-visitor-keys": {
|
| 1306 |
+
"version": "4.2.1",
|
| 1307 |
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
| 1308 |
+
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
| 1309 |
+
"dev": true,
|
| 1310 |
+
"license": "Apache-2.0",
|
| 1311 |
+
"engines": {
|
| 1312 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 1313 |
+
},
|
| 1314 |
+
"funding": {
|
| 1315 |
+
"url": "https://opencollective.com/eslint"
|
| 1316 |
+
}
|
| 1317 |
+
},
|
| 1318 |
+
"node_modules/espree": {
|
| 1319 |
+
"version": "10.4.0",
|
| 1320 |
+
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
| 1321 |
+
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
| 1322 |
+
"dev": true,
|
| 1323 |
+
"license": "BSD-2-Clause",
|
| 1324 |
+
"dependencies": {
|
| 1325 |
+
"acorn": "^8.15.0",
|
| 1326 |
+
"acorn-jsx": "^5.3.2",
|
| 1327 |
+
"eslint-visitor-keys": "^4.2.1"
|
| 1328 |
+
},
|
| 1329 |
+
"engines": {
|
| 1330 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 1331 |
+
},
|
| 1332 |
+
"funding": {
|
| 1333 |
+
"url": "https://opencollective.com/eslint"
|
| 1334 |
+
}
|
| 1335 |
+
},
|
| 1336 |
+
"node_modules/esquery": {
|
| 1337 |
+
"version": "1.7.0",
|
| 1338 |
+
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
|
| 1339 |
+
"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
|
| 1340 |
+
"dev": true,
|
| 1341 |
+
"license": "BSD-3-Clause",
|
| 1342 |
+
"dependencies": {
|
| 1343 |
+
"estraverse": "^5.1.0"
|
| 1344 |
+
},
|
| 1345 |
+
"engines": {
|
| 1346 |
+
"node": ">=0.10"
|
| 1347 |
+
}
|
| 1348 |
+
},
|
| 1349 |
+
"node_modules/esrecurse": {
|
| 1350 |
+
"version": "4.3.0",
|
| 1351 |
+
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
| 1352 |
+
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
| 1353 |
+
"dev": true,
|
| 1354 |
+
"license": "BSD-2-Clause",
|
| 1355 |
+
"dependencies": {
|
| 1356 |
+
"estraverse": "^5.2.0"
|
| 1357 |
+
},
|
| 1358 |
+
"engines": {
|
| 1359 |
+
"node": ">=4.0"
|
| 1360 |
+
}
|
| 1361 |
+
},
|
| 1362 |
+
"node_modules/estraverse": {
|
| 1363 |
+
"version": "5.3.0",
|
| 1364 |
+
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
| 1365 |
+
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
| 1366 |
+
"dev": true,
|
| 1367 |
+
"license": "BSD-2-Clause",
|
| 1368 |
+
"engines": {
|
| 1369 |
+
"node": ">=4.0"
|
| 1370 |
+
}
|
| 1371 |
+
},
|
| 1372 |
+
"node_modules/esutils": {
|
| 1373 |
+
"version": "2.0.3",
|
| 1374 |
+
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
| 1375 |
+
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
| 1376 |
+
"dev": true,
|
| 1377 |
+
"license": "BSD-2-Clause",
|
| 1378 |
+
"engines": {
|
| 1379 |
+
"node": ">=0.10.0"
|
| 1380 |
+
}
|
| 1381 |
+
},
|
| 1382 |
+
"node_modules/fast-deep-equal": {
|
| 1383 |
+
"version": "3.1.3",
|
| 1384 |
+
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
| 1385 |
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
| 1386 |
+
"dev": true,
|
| 1387 |
+
"license": "MIT"
|
| 1388 |
+
},
|
| 1389 |
+
"node_modules/fast-json-stable-stringify": {
|
| 1390 |
+
"version": "2.1.0",
|
| 1391 |
+
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
| 1392 |
+
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
| 1393 |
+
"dev": true,
|
| 1394 |
+
"license": "MIT"
|
| 1395 |
+
},
|
| 1396 |
+
"node_modules/fast-levenshtein": {
|
| 1397 |
+
"version": "2.0.6",
|
| 1398 |
+
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
| 1399 |
+
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
| 1400 |
+
"dev": true,
|
| 1401 |
+
"license": "MIT"
|
| 1402 |
+
},
|
| 1403 |
+
"node_modules/fdir": {
|
| 1404 |
+
"version": "6.5.0",
|
| 1405 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1406 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1407 |
+
"dev": true,
|
| 1408 |
+
"license": "MIT",
|
| 1409 |
+
"engines": {
|
| 1410 |
+
"node": ">=12.0.0"
|
| 1411 |
+
},
|
| 1412 |
+
"peerDependencies": {
|
| 1413 |
+
"picomatch": "^3 || ^4"
|
| 1414 |
+
},
|
| 1415 |
+
"peerDependenciesMeta": {
|
| 1416 |
+
"picomatch": {
|
| 1417 |
+
"optional": true
|
| 1418 |
+
}
|
| 1419 |
+
}
|
| 1420 |
+
},
|
| 1421 |
+
"node_modules/file-entry-cache": {
|
| 1422 |
+
"version": "8.0.0",
|
| 1423 |
+
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
| 1424 |
+
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
|
| 1425 |
+
"dev": true,
|
| 1426 |
+
"license": "MIT",
|
| 1427 |
+
"dependencies": {
|
| 1428 |
+
"flat-cache": "^4.0.0"
|
| 1429 |
+
},
|
| 1430 |
+
"engines": {
|
| 1431 |
+
"node": ">=16.0.0"
|
| 1432 |
+
}
|
| 1433 |
+
},
|
| 1434 |
+
"node_modules/find-up": {
|
| 1435 |
+
"version": "5.0.0",
|
| 1436 |
+
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
| 1437 |
+
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
|
| 1438 |
+
"dev": true,
|
| 1439 |
+
"license": "MIT",
|
| 1440 |
+
"dependencies": {
|
| 1441 |
+
"locate-path": "^6.0.0",
|
| 1442 |
+
"path-exists": "^4.0.0"
|
| 1443 |
+
},
|
| 1444 |
+
"engines": {
|
| 1445 |
+
"node": ">=10"
|
| 1446 |
+
},
|
| 1447 |
+
"funding": {
|
| 1448 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1449 |
+
}
|
| 1450 |
+
},
|
| 1451 |
+
"node_modules/flat-cache": {
|
| 1452 |
+
"version": "4.0.1",
|
| 1453 |
+
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
|
| 1454 |
+
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
|
| 1455 |
+
"dev": true,
|
| 1456 |
+
"license": "MIT",
|
| 1457 |
+
"dependencies": {
|
| 1458 |
+
"flatted": "^3.2.9",
|
| 1459 |
+
"keyv": "^4.5.4"
|
| 1460 |
+
},
|
| 1461 |
+
"engines": {
|
| 1462 |
+
"node": ">=16"
|
| 1463 |
+
}
|
| 1464 |
+
},
|
| 1465 |
+
"node_modules/flatted": {
|
| 1466 |
+
"version": "3.4.2",
|
| 1467 |
+
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
| 1468 |
+
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
| 1469 |
+
"dev": true,
|
| 1470 |
+
"license": "ISC"
|
| 1471 |
+
},
|
| 1472 |
+
"node_modules/fsevents": {
|
| 1473 |
+
"version": "2.3.3",
|
| 1474 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1475 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1476 |
+
"dev": true,
|
| 1477 |
+
"hasInstallScript": true,
|
| 1478 |
+
"license": "MIT",
|
| 1479 |
+
"optional": true,
|
| 1480 |
+
"os": [
|
| 1481 |
+
"darwin"
|
| 1482 |
+
],
|
| 1483 |
+
"engines": {
|
| 1484 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1485 |
+
}
|
| 1486 |
+
},
|
| 1487 |
+
"node_modules/gensync": {
|
| 1488 |
+
"version": "1.0.0-beta.2",
|
| 1489 |
+
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
| 1490 |
+
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
| 1491 |
+
"dev": true,
|
| 1492 |
+
"license": "MIT",
|
| 1493 |
+
"engines": {
|
| 1494 |
+
"node": ">=6.9.0"
|
| 1495 |
+
}
|
| 1496 |
+
},
|
| 1497 |
+
"node_modules/glob-parent": {
|
| 1498 |
+
"version": "6.0.2",
|
| 1499 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
| 1500 |
+
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
| 1501 |
+
"dev": true,
|
| 1502 |
+
"license": "ISC",
|
| 1503 |
+
"dependencies": {
|
| 1504 |
+
"is-glob": "^4.0.3"
|
| 1505 |
+
},
|
| 1506 |
+
"engines": {
|
| 1507 |
+
"node": ">=10.13.0"
|
| 1508 |
+
}
|
| 1509 |
+
},
|
| 1510 |
+
"node_modules/globals": {
|
| 1511 |
+
"version": "17.4.0",
|
| 1512 |
+
"resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
|
| 1513 |
+
"integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
|
| 1514 |
+
"dev": true,
|
| 1515 |
+
"license": "MIT",
|
| 1516 |
+
"engines": {
|
| 1517 |
+
"node": ">=18"
|
| 1518 |
+
},
|
| 1519 |
+
"funding": {
|
| 1520 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1521 |
+
}
|
| 1522 |
+
},
|
| 1523 |
+
"node_modules/has-flag": {
|
| 1524 |
+
"version": "4.0.0",
|
| 1525 |
+
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
| 1526 |
+
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
| 1527 |
+
"dev": true,
|
| 1528 |
+
"license": "MIT",
|
| 1529 |
+
"engines": {
|
| 1530 |
+
"node": ">=8"
|
| 1531 |
+
}
|
| 1532 |
+
},
|
| 1533 |
+
"node_modules/hermes-estree": {
|
| 1534 |
+
"version": "0.25.1",
|
| 1535 |
+
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
| 1536 |
+
"integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
|
| 1537 |
+
"dev": true,
|
| 1538 |
+
"license": "MIT"
|
| 1539 |
+
},
|
| 1540 |
+
"node_modules/hermes-parser": {
|
| 1541 |
+
"version": "0.25.1",
|
| 1542 |
+
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
|
| 1543 |
+
"integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
|
| 1544 |
+
"dev": true,
|
| 1545 |
+
"license": "MIT",
|
| 1546 |
+
"dependencies": {
|
| 1547 |
+
"hermes-estree": "0.25.1"
|
| 1548 |
+
}
|
| 1549 |
+
},
|
| 1550 |
+
"node_modules/ignore": {
|
| 1551 |
+
"version": "5.3.2",
|
| 1552 |
+
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
| 1553 |
+
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
| 1554 |
+
"dev": true,
|
| 1555 |
+
"license": "MIT",
|
| 1556 |
+
"engines": {
|
| 1557 |
+
"node": ">= 4"
|
| 1558 |
+
}
|
| 1559 |
+
},
|
| 1560 |
+
"node_modules/import-fresh": {
|
| 1561 |
+
"version": "3.3.1",
|
| 1562 |
+
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
| 1563 |
+
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
| 1564 |
+
"dev": true,
|
| 1565 |
+
"license": "MIT",
|
| 1566 |
+
"dependencies": {
|
| 1567 |
+
"parent-module": "^1.0.0",
|
| 1568 |
+
"resolve-from": "^4.0.0"
|
| 1569 |
+
},
|
| 1570 |
+
"engines": {
|
| 1571 |
+
"node": ">=6"
|
| 1572 |
+
},
|
| 1573 |
+
"funding": {
|
| 1574 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1575 |
+
}
|
| 1576 |
+
},
|
| 1577 |
+
"node_modules/imurmurhash": {
|
| 1578 |
+
"version": "0.1.4",
|
| 1579 |
+
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
| 1580 |
+
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
|
| 1581 |
+
"dev": true,
|
| 1582 |
+
"license": "MIT",
|
| 1583 |
+
"engines": {
|
| 1584 |
+
"node": ">=0.8.19"
|
| 1585 |
+
}
|
| 1586 |
+
},
|
| 1587 |
+
"node_modules/is-extglob": {
|
| 1588 |
+
"version": "2.1.1",
|
| 1589 |
+
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
| 1590 |
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
| 1591 |
+
"dev": true,
|
| 1592 |
+
"license": "MIT",
|
| 1593 |
+
"engines": {
|
| 1594 |
+
"node": ">=0.10.0"
|
| 1595 |
+
}
|
| 1596 |
+
},
|
| 1597 |
+
"node_modules/is-glob": {
|
| 1598 |
+
"version": "4.0.3",
|
| 1599 |
+
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
| 1600 |
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
| 1601 |
+
"dev": true,
|
| 1602 |
+
"license": "MIT",
|
| 1603 |
+
"dependencies": {
|
| 1604 |
+
"is-extglob": "^2.1.1"
|
| 1605 |
+
},
|
| 1606 |
+
"engines": {
|
| 1607 |
+
"node": ">=0.10.0"
|
| 1608 |
+
}
|
| 1609 |
+
},
|
| 1610 |
+
"node_modules/isexe": {
|
| 1611 |
+
"version": "2.0.0",
|
| 1612 |
+
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
| 1613 |
+
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
| 1614 |
+
"dev": true,
|
| 1615 |
+
"license": "ISC"
|
| 1616 |
+
},
|
| 1617 |
+
"node_modules/js-tokens": {
|
| 1618 |
+
"version": "4.0.0",
|
| 1619 |
+
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 1620 |
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
| 1621 |
+
"dev": true,
|
| 1622 |
+
"license": "MIT"
|
| 1623 |
+
},
|
| 1624 |
+
"node_modules/js-yaml": {
|
| 1625 |
+
"version": "4.1.1",
|
| 1626 |
+
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
| 1627 |
+
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
| 1628 |
+
"dev": true,
|
| 1629 |
+
"license": "MIT",
|
| 1630 |
+
"dependencies": {
|
| 1631 |
+
"argparse": "^2.0.1"
|
| 1632 |
+
},
|
| 1633 |
+
"bin": {
|
| 1634 |
+
"js-yaml": "bin/js-yaml.js"
|
| 1635 |
+
}
|
| 1636 |
+
},
|
| 1637 |
+
"node_modules/jsesc": {
|
| 1638 |
+
"version": "3.1.0",
|
| 1639 |
+
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
| 1640 |
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
| 1641 |
+
"dev": true,
|
| 1642 |
+
"license": "MIT",
|
| 1643 |
+
"bin": {
|
| 1644 |
+
"jsesc": "bin/jsesc"
|
| 1645 |
+
},
|
| 1646 |
+
"engines": {
|
| 1647 |
+
"node": ">=6"
|
| 1648 |
+
}
|
| 1649 |
+
},
|
| 1650 |
+
"node_modules/json-buffer": {
|
| 1651 |
+
"version": "3.0.1",
|
| 1652 |
+
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
| 1653 |
+
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
| 1654 |
+
"dev": true,
|
| 1655 |
+
"license": "MIT"
|
| 1656 |
+
},
|
| 1657 |
+
"node_modules/json-schema-traverse": {
|
| 1658 |
+
"version": "0.4.1",
|
| 1659 |
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
| 1660 |
+
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
| 1661 |
+
"dev": true,
|
| 1662 |
+
"license": "MIT"
|
| 1663 |
+
},
|
| 1664 |
+
"node_modules/json-stable-stringify-without-jsonify": {
|
| 1665 |
+
"version": "1.0.1",
|
| 1666 |
+
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
| 1667 |
+
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
| 1668 |
+
"dev": true,
|
| 1669 |
+
"license": "MIT"
|
| 1670 |
+
},
|
| 1671 |
+
"node_modules/json5": {
|
| 1672 |
+
"version": "2.2.3",
|
| 1673 |
+
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
| 1674 |
+
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
| 1675 |
+
"dev": true,
|
| 1676 |
+
"license": "MIT",
|
| 1677 |
+
"bin": {
|
| 1678 |
+
"json5": "lib/cli.js"
|
| 1679 |
+
},
|
| 1680 |
+
"engines": {
|
| 1681 |
+
"node": ">=6"
|
| 1682 |
+
}
|
| 1683 |
+
},
|
| 1684 |
+
"node_modules/keyv": {
|
| 1685 |
+
"version": "4.5.4",
|
| 1686 |
+
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
| 1687 |
+
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
| 1688 |
+
"dev": true,
|
| 1689 |
+
"license": "MIT",
|
| 1690 |
+
"dependencies": {
|
| 1691 |
+
"json-buffer": "3.0.1"
|
| 1692 |
+
}
|
| 1693 |
+
},
|
| 1694 |
+
"node_modules/levn": {
|
| 1695 |
+
"version": "0.4.1",
|
| 1696 |
+
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
| 1697 |
+
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
|
| 1698 |
+
"dev": true,
|
| 1699 |
+
"license": "MIT",
|
| 1700 |
+
"dependencies": {
|
| 1701 |
+
"prelude-ls": "^1.2.1",
|
| 1702 |
+
"type-check": "~0.4.0"
|
| 1703 |
+
},
|
| 1704 |
+
"engines": {
|
| 1705 |
+
"node": ">= 0.8.0"
|
| 1706 |
+
}
|
| 1707 |
+
},
|
| 1708 |
+
"node_modules/lightningcss": {
|
| 1709 |
+
"version": "1.32.0",
|
| 1710 |
+
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
| 1711 |
+
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
| 1712 |
+
"dev": true,
|
| 1713 |
+
"license": "MPL-2.0",
|
| 1714 |
+
"dependencies": {
|
| 1715 |
+
"detect-libc": "^2.0.3"
|
| 1716 |
+
},
|
| 1717 |
+
"engines": {
|
| 1718 |
+
"node": ">= 12.0.0"
|
| 1719 |
+
},
|
| 1720 |
+
"funding": {
|
| 1721 |
+
"type": "opencollective",
|
| 1722 |
+
"url": "https://opencollective.com/parcel"
|
| 1723 |
+
},
|
| 1724 |
+
"optionalDependencies": {
|
| 1725 |
+
"lightningcss-android-arm64": "1.32.0",
|
| 1726 |
+
"lightningcss-darwin-arm64": "1.32.0",
|
| 1727 |
+
"lightningcss-darwin-x64": "1.32.0",
|
| 1728 |
+
"lightningcss-freebsd-x64": "1.32.0",
|
| 1729 |
+
"lightningcss-linux-arm-gnueabihf": "1.32.0",
|
| 1730 |
+
"lightningcss-linux-arm64-gnu": "1.32.0",
|
| 1731 |
+
"lightningcss-linux-arm64-musl": "1.32.0",
|
| 1732 |
+
"lightningcss-linux-x64-gnu": "1.32.0",
|
| 1733 |
+
"lightningcss-linux-x64-musl": "1.32.0",
|
| 1734 |
+
"lightningcss-win32-arm64-msvc": "1.32.0",
|
| 1735 |
+
"lightningcss-win32-x64-msvc": "1.32.0"
|
| 1736 |
+
}
|
| 1737 |
+
},
|
| 1738 |
+
"node_modules/lightningcss-android-arm64": {
|
| 1739 |
+
"version": "1.32.0",
|
| 1740 |
+
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
|
| 1741 |
+
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
|
| 1742 |
+
"cpu": [
|
| 1743 |
+
"arm64"
|
| 1744 |
+
],
|
| 1745 |
+
"dev": true,
|
| 1746 |
+
"license": "MPL-2.0",
|
| 1747 |
+
"optional": true,
|
| 1748 |
+
"os": [
|
| 1749 |
+
"android"
|
| 1750 |
+
],
|
| 1751 |
+
"engines": {
|
| 1752 |
+
"node": ">= 12.0.0"
|
| 1753 |
+
},
|
| 1754 |
+
"funding": {
|
| 1755 |
+
"type": "opencollective",
|
| 1756 |
+
"url": "https://opencollective.com/parcel"
|
| 1757 |
+
}
|
| 1758 |
+
},
|
| 1759 |
+
"node_modules/lightningcss-darwin-arm64": {
|
| 1760 |
+
"version": "1.32.0",
|
| 1761 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
|
| 1762 |
+
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
|
| 1763 |
+
"cpu": [
|
| 1764 |
+
"arm64"
|
| 1765 |
+
],
|
| 1766 |
+
"dev": true,
|
| 1767 |
+
"license": "MPL-2.0",
|
| 1768 |
+
"optional": true,
|
| 1769 |
+
"os": [
|
| 1770 |
+
"darwin"
|
| 1771 |
+
],
|
| 1772 |
+
"engines": {
|
| 1773 |
+
"node": ">= 12.0.0"
|
| 1774 |
+
},
|
| 1775 |
+
"funding": {
|
| 1776 |
+
"type": "opencollective",
|
| 1777 |
+
"url": "https://opencollective.com/parcel"
|
| 1778 |
+
}
|
| 1779 |
+
},
|
| 1780 |
+
"node_modules/lightningcss-darwin-x64": {
|
| 1781 |
+
"version": "1.32.0",
|
| 1782 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
|
| 1783 |
+
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
|
| 1784 |
+
"cpu": [
|
| 1785 |
+
"x64"
|
| 1786 |
+
],
|
| 1787 |
+
"dev": true,
|
| 1788 |
+
"license": "MPL-2.0",
|
| 1789 |
+
"optional": true,
|
| 1790 |
+
"os": [
|
| 1791 |
+
"darwin"
|
| 1792 |
+
],
|
| 1793 |
+
"engines": {
|
| 1794 |
+
"node": ">= 12.0.0"
|
| 1795 |
+
},
|
| 1796 |
+
"funding": {
|
| 1797 |
+
"type": "opencollective",
|
| 1798 |
+
"url": "https://opencollective.com/parcel"
|
| 1799 |
+
}
|
| 1800 |
+
},
|
| 1801 |
+
"node_modules/lightningcss-freebsd-x64": {
|
| 1802 |
+
"version": "1.32.0",
|
| 1803 |
+
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
|
| 1804 |
+
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
|
| 1805 |
+
"cpu": [
|
| 1806 |
+
"x64"
|
| 1807 |
+
],
|
| 1808 |
+
"dev": true,
|
| 1809 |
+
"license": "MPL-2.0",
|
| 1810 |
+
"optional": true,
|
| 1811 |
+
"os": [
|
| 1812 |
+
"freebsd"
|
| 1813 |
+
],
|
| 1814 |
+
"engines": {
|
| 1815 |
+
"node": ">= 12.0.0"
|
| 1816 |
+
},
|
| 1817 |
+
"funding": {
|
| 1818 |
+
"type": "opencollective",
|
| 1819 |
+
"url": "https://opencollective.com/parcel"
|
| 1820 |
+
}
|
| 1821 |
+
},
|
| 1822 |
+
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
| 1823 |
+
"version": "1.32.0",
|
| 1824 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
|
| 1825 |
+
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
|
| 1826 |
+
"cpu": [
|
| 1827 |
+
"arm"
|
| 1828 |
+
],
|
| 1829 |
+
"dev": true,
|
| 1830 |
+
"license": "MPL-2.0",
|
| 1831 |
+
"optional": true,
|
| 1832 |
+
"os": [
|
| 1833 |
+
"linux"
|
| 1834 |
+
],
|
| 1835 |
+
"engines": {
|
| 1836 |
+
"node": ">= 12.0.0"
|
| 1837 |
+
},
|
| 1838 |
+
"funding": {
|
| 1839 |
+
"type": "opencollective",
|
| 1840 |
+
"url": "https://opencollective.com/parcel"
|
| 1841 |
+
}
|
| 1842 |
+
},
|
| 1843 |
+
"node_modules/lightningcss-linux-arm64-gnu": {
|
| 1844 |
+
"version": "1.32.0",
|
| 1845 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
|
| 1846 |
+
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
|
| 1847 |
+
"cpu": [
|
| 1848 |
+
"arm64"
|
| 1849 |
+
],
|
| 1850 |
+
"dev": true,
|
| 1851 |
+
"license": "MPL-2.0",
|
| 1852 |
+
"optional": true,
|
| 1853 |
+
"os": [
|
| 1854 |
+
"linux"
|
| 1855 |
+
],
|
| 1856 |
+
"engines": {
|
| 1857 |
+
"node": ">= 12.0.0"
|
| 1858 |
+
},
|
| 1859 |
+
"funding": {
|
| 1860 |
+
"type": "opencollective",
|
| 1861 |
+
"url": "https://opencollective.com/parcel"
|
| 1862 |
+
}
|
| 1863 |
+
},
|
| 1864 |
+
"node_modules/lightningcss-linux-arm64-musl": {
|
| 1865 |
+
"version": "1.32.0",
|
| 1866 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
|
| 1867 |
+
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
|
| 1868 |
+
"cpu": [
|
| 1869 |
+
"arm64"
|
| 1870 |
+
],
|
| 1871 |
+
"dev": true,
|
| 1872 |
+
"license": "MPL-2.0",
|
| 1873 |
+
"optional": true,
|
| 1874 |
+
"os": [
|
| 1875 |
+
"linux"
|
| 1876 |
+
],
|
| 1877 |
+
"engines": {
|
| 1878 |
+
"node": ">= 12.0.0"
|
| 1879 |
+
},
|
| 1880 |
+
"funding": {
|
| 1881 |
+
"type": "opencollective",
|
| 1882 |
+
"url": "https://opencollective.com/parcel"
|
| 1883 |
+
}
|
| 1884 |
+
},
|
| 1885 |
+
"node_modules/lightningcss-linux-x64-gnu": {
|
| 1886 |
+
"version": "1.32.0",
|
| 1887 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
|
| 1888 |
+
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
|
| 1889 |
+
"cpu": [
|
| 1890 |
+
"x64"
|
| 1891 |
+
],
|
| 1892 |
+
"dev": true,
|
| 1893 |
+
"license": "MPL-2.0",
|
| 1894 |
+
"optional": true,
|
| 1895 |
+
"os": [
|
| 1896 |
+
"linux"
|
| 1897 |
+
],
|
| 1898 |
+
"engines": {
|
| 1899 |
+
"node": ">= 12.0.0"
|
| 1900 |
+
},
|
| 1901 |
+
"funding": {
|
| 1902 |
+
"type": "opencollective",
|
| 1903 |
+
"url": "https://opencollective.com/parcel"
|
| 1904 |
+
}
|
| 1905 |
+
},
|
| 1906 |
+
"node_modules/lightningcss-linux-x64-musl": {
|
| 1907 |
+
"version": "1.32.0",
|
| 1908 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
|
| 1909 |
+
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
|
| 1910 |
+
"cpu": [
|
| 1911 |
+
"x64"
|
| 1912 |
+
],
|
| 1913 |
+
"dev": true,
|
| 1914 |
+
"license": "MPL-2.0",
|
| 1915 |
+
"optional": true,
|
| 1916 |
+
"os": [
|
| 1917 |
+
"linux"
|
| 1918 |
+
],
|
| 1919 |
+
"engines": {
|
| 1920 |
+
"node": ">= 12.0.0"
|
| 1921 |
+
},
|
| 1922 |
+
"funding": {
|
| 1923 |
+
"type": "opencollective",
|
| 1924 |
+
"url": "https://opencollective.com/parcel"
|
| 1925 |
+
}
|
| 1926 |
+
},
|
| 1927 |
+
"node_modules/lightningcss-win32-arm64-msvc": {
|
| 1928 |
+
"version": "1.32.0",
|
| 1929 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
|
| 1930 |
+
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
|
| 1931 |
+
"cpu": [
|
| 1932 |
+
"arm64"
|
| 1933 |
+
],
|
| 1934 |
+
"dev": true,
|
| 1935 |
+
"license": "MPL-2.0",
|
| 1936 |
+
"optional": true,
|
| 1937 |
+
"os": [
|
| 1938 |
+
"win32"
|
| 1939 |
+
],
|
| 1940 |
+
"engines": {
|
| 1941 |
+
"node": ">= 12.0.0"
|
| 1942 |
+
},
|
| 1943 |
+
"funding": {
|
| 1944 |
+
"type": "opencollective",
|
| 1945 |
+
"url": "https://opencollective.com/parcel"
|
| 1946 |
+
}
|
| 1947 |
+
},
|
| 1948 |
+
"node_modules/lightningcss-win32-x64-msvc": {
|
| 1949 |
+
"version": "1.32.0",
|
| 1950 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
|
| 1951 |
+
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
|
| 1952 |
+
"cpu": [
|
| 1953 |
+
"x64"
|
| 1954 |
+
],
|
| 1955 |
+
"dev": true,
|
| 1956 |
+
"license": "MPL-2.0",
|
| 1957 |
+
"optional": true,
|
| 1958 |
+
"os": [
|
| 1959 |
+
"win32"
|
| 1960 |
+
],
|
| 1961 |
+
"engines": {
|
| 1962 |
+
"node": ">= 12.0.0"
|
| 1963 |
+
},
|
| 1964 |
+
"funding": {
|
| 1965 |
+
"type": "opencollective",
|
| 1966 |
+
"url": "https://opencollective.com/parcel"
|
| 1967 |
+
}
|
| 1968 |
+
},
|
| 1969 |
+
"node_modules/locate-path": {
|
| 1970 |
+
"version": "6.0.0",
|
| 1971 |
+
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
| 1972 |
+
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
|
| 1973 |
+
"dev": true,
|
| 1974 |
+
"license": "MIT",
|
| 1975 |
+
"dependencies": {
|
| 1976 |
+
"p-locate": "^5.0.0"
|
| 1977 |
+
},
|
| 1978 |
+
"engines": {
|
| 1979 |
+
"node": ">=10"
|
| 1980 |
+
},
|
| 1981 |
+
"funding": {
|
| 1982 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1983 |
+
}
|
| 1984 |
+
},
|
| 1985 |
+
"node_modules/lodash.merge": {
|
| 1986 |
+
"version": "4.6.2",
|
| 1987 |
+
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
| 1988 |
+
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
| 1989 |
+
"dev": true,
|
| 1990 |
+
"license": "MIT"
|
| 1991 |
+
},
|
| 1992 |
+
"node_modules/lru-cache": {
|
| 1993 |
+
"version": "5.1.1",
|
| 1994 |
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
| 1995 |
+
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
| 1996 |
+
"dev": true,
|
| 1997 |
+
"license": "ISC",
|
| 1998 |
+
"dependencies": {
|
| 1999 |
+
"yallist": "^3.0.2"
|
| 2000 |
+
}
|
| 2001 |
+
},
|
| 2002 |
+
"node_modules/minimatch": {
|
| 2003 |
+
"version": "3.1.5",
|
| 2004 |
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
| 2005 |
+
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
| 2006 |
+
"dev": true,
|
| 2007 |
+
"license": "ISC",
|
| 2008 |
+
"dependencies": {
|
| 2009 |
+
"brace-expansion": "^1.1.7"
|
| 2010 |
+
},
|
| 2011 |
+
"engines": {
|
| 2012 |
+
"node": "*"
|
| 2013 |
+
}
|
| 2014 |
+
},
|
| 2015 |
+
"node_modules/ms": {
|
| 2016 |
+
"version": "2.1.3",
|
| 2017 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 2018 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 2019 |
+
"dev": true,
|
| 2020 |
+
"license": "MIT"
|
| 2021 |
+
},
|
| 2022 |
+
"node_modules/nanoid": {
|
| 2023 |
+
"version": "3.3.11",
|
| 2024 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 2025 |
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 2026 |
+
"dev": true,
|
| 2027 |
+
"funding": [
|
| 2028 |
+
{
|
| 2029 |
+
"type": "github",
|
| 2030 |
+
"url": "https://github.com/sponsors/ai"
|
| 2031 |
+
}
|
| 2032 |
+
],
|
| 2033 |
+
"license": "MIT",
|
| 2034 |
+
"bin": {
|
| 2035 |
+
"nanoid": "bin/nanoid.cjs"
|
| 2036 |
+
},
|
| 2037 |
+
"engines": {
|
| 2038 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 2039 |
+
}
|
| 2040 |
+
},
|
| 2041 |
+
"node_modules/natural-compare": {
|
| 2042 |
+
"version": "1.4.0",
|
| 2043 |
+
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
| 2044 |
+
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
| 2045 |
+
"dev": true,
|
| 2046 |
+
"license": "MIT"
|
| 2047 |
+
},
|
| 2048 |
+
"node_modules/node-releases": {
|
| 2049 |
+
"version": "2.0.36",
|
| 2050 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
|
| 2051 |
+
"integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
|
| 2052 |
+
"dev": true,
|
| 2053 |
+
"license": "MIT"
|
| 2054 |
+
},
|
| 2055 |
+
"node_modules/optionator": {
|
| 2056 |
+
"version": "0.9.4",
|
| 2057 |
+
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
| 2058 |
+
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
|
| 2059 |
+
"dev": true,
|
| 2060 |
+
"license": "MIT",
|
| 2061 |
+
"dependencies": {
|
| 2062 |
+
"deep-is": "^0.1.3",
|
| 2063 |
+
"fast-levenshtein": "^2.0.6",
|
| 2064 |
+
"levn": "^0.4.1",
|
| 2065 |
+
"prelude-ls": "^1.2.1",
|
| 2066 |
+
"type-check": "^0.4.0",
|
| 2067 |
+
"word-wrap": "^1.2.5"
|
| 2068 |
+
},
|
| 2069 |
+
"engines": {
|
| 2070 |
+
"node": ">= 0.8.0"
|
| 2071 |
+
}
|
| 2072 |
+
},
|
| 2073 |
+
"node_modules/p-limit": {
|
| 2074 |
+
"version": "3.1.0",
|
| 2075 |
+
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
| 2076 |
+
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
| 2077 |
+
"dev": true,
|
| 2078 |
+
"license": "MIT",
|
| 2079 |
+
"dependencies": {
|
| 2080 |
+
"yocto-queue": "^0.1.0"
|
| 2081 |
+
},
|
| 2082 |
+
"engines": {
|
| 2083 |
+
"node": ">=10"
|
| 2084 |
+
},
|
| 2085 |
+
"funding": {
|
| 2086 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2087 |
+
}
|
| 2088 |
+
},
|
| 2089 |
+
"node_modules/p-locate": {
|
| 2090 |
+
"version": "5.0.0",
|
| 2091 |
+
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
|
| 2092 |
+
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
|
| 2093 |
+
"dev": true,
|
| 2094 |
+
"license": "MIT",
|
| 2095 |
+
"dependencies": {
|
| 2096 |
+
"p-limit": "^3.0.2"
|
| 2097 |
+
},
|
| 2098 |
+
"engines": {
|
| 2099 |
+
"node": ">=10"
|
| 2100 |
+
},
|
| 2101 |
+
"funding": {
|
| 2102 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2103 |
+
}
|
| 2104 |
+
},
|
| 2105 |
+
"node_modules/parent-module": {
|
| 2106 |
+
"version": "1.0.1",
|
| 2107 |
+
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
| 2108 |
+
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
| 2109 |
+
"dev": true,
|
| 2110 |
+
"license": "MIT",
|
| 2111 |
+
"dependencies": {
|
| 2112 |
+
"callsites": "^3.0.0"
|
| 2113 |
+
},
|
| 2114 |
+
"engines": {
|
| 2115 |
+
"node": ">=6"
|
| 2116 |
+
}
|
| 2117 |
+
},
|
| 2118 |
+
"node_modules/path-exists": {
|
| 2119 |
+
"version": "4.0.0",
|
| 2120 |
+
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
| 2121 |
+
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
| 2122 |
+
"dev": true,
|
| 2123 |
+
"license": "MIT",
|
| 2124 |
+
"engines": {
|
| 2125 |
+
"node": ">=8"
|
| 2126 |
+
}
|
| 2127 |
+
},
|
| 2128 |
+
"node_modules/path-key": {
|
| 2129 |
+
"version": "3.1.1",
|
| 2130 |
+
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
| 2131 |
+
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
| 2132 |
+
"dev": true,
|
| 2133 |
+
"license": "MIT",
|
| 2134 |
+
"engines": {
|
| 2135 |
+
"node": ">=8"
|
| 2136 |
+
}
|
| 2137 |
+
},
|
| 2138 |
+
"node_modules/picocolors": {
|
| 2139 |
+
"version": "1.1.1",
|
| 2140 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 2141 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 2142 |
+
"dev": true,
|
| 2143 |
+
"license": "ISC"
|
| 2144 |
+
},
|
| 2145 |
+
"node_modules/picomatch": {
|
| 2146 |
+
"version": "4.0.4",
|
| 2147 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 2148 |
+
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 2149 |
+
"dev": true,
|
| 2150 |
+
"license": "MIT",
|
| 2151 |
+
"engines": {
|
| 2152 |
+
"node": ">=12"
|
| 2153 |
+
},
|
| 2154 |
+
"funding": {
|
| 2155 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 2156 |
+
}
|
| 2157 |
+
},
|
| 2158 |
+
"node_modules/postcss": {
|
| 2159 |
+
"version": "8.5.8",
|
| 2160 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
| 2161 |
+
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
|
| 2162 |
+
"dev": true,
|
| 2163 |
+
"funding": [
|
| 2164 |
+
{
|
| 2165 |
+
"type": "opencollective",
|
| 2166 |
+
"url": "https://opencollective.com/postcss/"
|
| 2167 |
+
},
|
| 2168 |
+
{
|
| 2169 |
+
"type": "tidelift",
|
| 2170 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 2171 |
+
},
|
| 2172 |
+
{
|
| 2173 |
+
"type": "github",
|
| 2174 |
+
"url": "https://github.com/sponsors/ai"
|
| 2175 |
+
}
|
| 2176 |
+
],
|
| 2177 |
+
"license": "MIT",
|
| 2178 |
+
"dependencies": {
|
| 2179 |
+
"nanoid": "^3.3.11",
|
| 2180 |
+
"picocolors": "^1.1.1",
|
| 2181 |
+
"source-map-js": "^1.2.1"
|
| 2182 |
+
},
|
| 2183 |
+
"engines": {
|
| 2184 |
+
"node": "^10 || ^12 || >=14"
|
| 2185 |
+
}
|
| 2186 |
+
},
|
| 2187 |
+
"node_modules/prelude-ls": {
|
| 2188 |
+
"version": "1.2.1",
|
| 2189 |
+
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
| 2190 |
+
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
| 2191 |
+
"dev": true,
|
| 2192 |
+
"license": "MIT",
|
| 2193 |
+
"engines": {
|
| 2194 |
+
"node": ">= 0.8.0"
|
| 2195 |
+
}
|
| 2196 |
+
},
|
| 2197 |
+
"node_modules/punycode": {
|
| 2198 |
+
"version": "2.3.1",
|
| 2199 |
+
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
| 2200 |
+
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
| 2201 |
+
"dev": true,
|
| 2202 |
+
"license": "MIT",
|
| 2203 |
+
"engines": {
|
| 2204 |
+
"node": ">=6"
|
| 2205 |
+
}
|
| 2206 |
+
},
|
| 2207 |
+
"node_modules/react": {
|
| 2208 |
+
"version": "19.2.4",
|
| 2209 |
+
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
| 2210 |
+
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
| 2211 |
+
"license": "MIT",
|
| 2212 |
+
"engines": {
|
| 2213 |
+
"node": ">=0.10.0"
|
| 2214 |
+
}
|
| 2215 |
+
},
|
| 2216 |
+
"node_modules/react-dom": {
|
| 2217 |
+
"version": "19.2.4",
|
| 2218 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
| 2219 |
+
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
| 2220 |
+
"license": "MIT",
|
| 2221 |
+
"dependencies": {
|
| 2222 |
+
"scheduler": "^0.27.0"
|
| 2223 |
+
},
|
| 2224 |
+
"peerDependencies": {
|
| 2225 |
+
"react": "^19.2.4"
|
| 2226 |
+
}
|
| 2227 |
+
},
|
| 2228 |
+
"node_modules/react-router": {
|
| 2229 |
+
"version": "7.13.2",
|
| 2230 |
+
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.2.tgz",
|
| 2231 |
+
"integrity": "sha512-tX1Aee+ArlKQP+NIUd7SE6Li+CiGKwQtbS+FfRxPX6Pe4vHOo6nr9d++u5cwg+Z8K/x8tP+7qLmujDtfrAoUJA==",
|
| 2232 |
+
"license": "MIT",
|
| 2233 |
+
"dependencies": {
|
| 2234 |
+
"cookie": "^1.0.1",
|
| 2235 |
+
"set-cookie-parser": "^2.6.0"
|
| 2236 |
+
},
|
| 2237 |
+
"engines": {
|
| 2238 |
+
"node": ">=20.0.0"
|
| 2239 |
+
},
|
| 2240 |
+
"peerDependencies": {
|
| 2241 |
+
"react": ">=18",
|
| 2242 |
+
"react-dom": ">=18"
|
| 2243 |
+
},
|
| 2244 |
+
"peerDependenciesMeta": {
|
| 2245 |
+
"react-dom": {
|
| 2246 |
+
"optional": true
|
| 2247 |
+
}
|
| 2248 |
+
}
|
| 2249 |
+
},
|
| 2250 |
+
"node_modules/react-router-dom": {
|
| 2251 |
+
"version": "7.13.2",
|
| 2252 |
+
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.2.tgz",
|
| 2253 |
+
"integrity": "sha512-aR7SUORwTqAW0JDeiWF07e9SBE9qGpByR9I8kJT5h/FrBKxPMS6TiC7rmVO+gC0q52Bx7JnjWe8Z1sR9faN4YA==",
|
| 2254 |
+
"license": "MIT",
|
| 2255 |
+
"dependencies": {
|
| 2256 |
+
"react-router": "7.13.2"
|
| 2257 |
+
},
|
| 2258 |
+
"engines": {
|
| 2259 |
+
"node": ">=20.0.0"
|
| 2260 |
+
},
|
| 2261 |
+
"peerDependencies": {
|
| 2262 |
+
"react": ">=18",
|
| 2263 |
+
"react-dom": ">=18"
|
| 2264 |
+
}
|
| 2265 |
+
},
|
| 2266 |
+
"node_modules/resolve-from": {
|
| 2267 |
+
"version": "4.0.0",
|
| 2268 |
+
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
| 2269 |
+
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
| 2270 |
+
"dev": true,
|
| 2271 |
+
"license": "MIT",
|
| 2272 |
+
"engines": {
|
| 2273 |
+
"node": ">=4"
|
| 2274 |
+
}
|
| 2275 |
+
},
|
| 2276 |
+
"node_modules/rolldown": {
|
| 2277 |
+
"version": "1.0.0-rc.12",
|
| 2278 |
+
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz",
|
| 2279 |
+
"integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==",
|
| 2280 |
+
"dev": true,
|
| 2281 |
+
"license": "MIT",
|
| 2282 |
+
"dependencies": {
|
| 2283 |
+
"@oxc-project/types": "=0.122.0",
|
| 2284 |
+
"@rolldown/pluginutils": "1.0.0-rc.12"
|
| 2285 |
+
},
|
| 2286 |
+
"bin": {
|
| 2287 |
+
"rolldown": "bin/cli.mjs"
|
| 2288 |
+
},
|
| 2289 |
+
"engines": {
|
| 2290 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 2291 |
+
},
|
| 2292 |
+
"optionalDependencies": {
|
| 2293 |
+
"@rolldown/binding-android-arm64": "1.0.0-rc.12",
|
| 2294 |
+
"@rolldown/binding-darwin-arm64": "1.0.0-rc.12",
|
| 2295 |
+
"@rolldown/binding-darwin-x64": "1.0.0-rc.12",
|
| 2296 |
+
"@rolldown/binding-freebsd-x64": "1.0.0-rc.12",
|
| 2297 |
+
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12",
|
| 2298 |
+
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12",
|
| 2299 |
+
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12",
|
| 2300 |
+
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12",
|
| 2301 |
+
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12",
|
| 2302 |
+
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12",
|
| 2303 |
+
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.12",
|
| 2304 |
+
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.12",
|
| 2305 |
+
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.12",
|
| 2306 |
+
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12",
|
| 2307 |
+
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12"
|
| 2308 |
+
}
|
| 2309 |
+
},
|
| 2310 |
+
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
|
| 2311 |
+
"version": "1.0.0-rc.12",
|
| 2312 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz",
|
| 2313 |
+
"integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==",
|
| 2314 |
+
"dev": true,
|
| 2315 |
+
"license": "MIT"
|
| 2316 |
+
},
|
| 2317 |
+
"node_modules/scheduler": {
|
| 2318 |
+
"version": "0.27.0",
|
| 2319 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
| 2320 |
+
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
| 2321 |
+
"license": "MIT"
|
| 2322 |
+
},
|
| 2323 |
+
"node_modules/semver": {
|
| 2324 |
+
"version": "6.3.1",
|
| 2325 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
| 2326 |
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
| 2327 |
+
"dev": true,
|
| 2328 |
+
"license": "ISC",
|
| 2329 |
+
"bin": {
|
| 2330 |
+
"semver": "bin/semver.js"
|
| 2331 |
+
}
|
| 2332 |
+
},
|
| 2333 |
+
"node_modules/set-cookie-parser": {
|
| 2334 |
+
"version": "2.7.2",
|
| 2335 |
+
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
| 2336 |
+
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
| 2337 |
+
"license": "MIT"
|
| 2338 |
+
},
|
| 2339 |
+
"node_modules/shebang-command": {
|
| 2340 |
+
"version": "2.0.0",
|
| 2341 |
+
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
| 2342 |
+
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
| 2343 |
+
"dev": true,
|
| 2344 |
+
"license": "MIT",
|
| 2345 |
+
"dependencies": {
|
| 2346 |
+
"shebang-regex": "^3.0.0"
|
| 2347 |
+
},
|
| 2348 |
+
"engines": {
|
| 2349 |
+
"node": ">=8"
|
| 2350 |
+
}
|
| 2351 |
+
},
|
| 2352 |
+
"node_modules/shebang-regex": {
|
| 2353 |
+
"version": "3.0.0",
|
| 2354 |
+
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
| 2355 |
+
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
| 2356 |
+
"dev": true,
|
| 2357 |
+
"license": "MIT",
|
| 2358 |
+
"engines": {
|
| 2359 |
+
"node": ">=8"
|
| 2360 |
+
}
|
| 2361 |
+
},
|
| 2362 |
+
"node_modules/source-map-js": {
|
| 2363 |
+
"version": "1.2.1",
|
| 2364 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 2365 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 2366 |
+
"dev": true,
|
| 2367 |
+
"license": "BSD-3-Clause",
|
| 2368 |
+
"engines": {
|
| 2369 |
+
"node": ">=0.10.0"
|
| 2370 |
+
}
|
| 2371 |
+
},
|
| 2372 |
+
"node_modules/strip-json-comments": {
|
| 2373 |
+
"version": "3.1.1",
|
| 2374 |
+
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
| 2375 |
+
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
| 2376 |
+
"dev": true,
|
| 2377 |
+
"license": "MIT",
|
| 2378 |
+
"engines": {
|
| 2379 |
+
"node": ">=8"
|
| 2380 |
+
},
|
| 2381 |
+
"funding": {
|
| 2382 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2383 |
+
}
|
| 2384 |
+
},
|
| 2385 |
+
"node_modules/supports-color": {
|
| 2386 |
+
"version": "7.2.0",
|
| 2387 |
+
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
| 2388 |
+
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
| 2389 |
+
"dev": true,
|
| 2390 |
+
"license": "MIT",
|
| 2391 |
+
"dependencies": {
|
| 2392 |
+
"has-flag": "^4.0.0"
|
| 2393 |
+
},
|
| 2394 |
+
"engines": {
|
| 2395 |
+
"node": ">=8"
|
| 2396 |
+
}
|
| 2397 |
+
},
|
| 2398 |
+
"node_modules/tinyglobby": {
|
| 2399 |
+
"version": "0.2.15",
|
| 2400 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
| 2401 |
+
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
| 2402 |
+
"dev": true,
|
| 2403 |
+
"license": "MIT",
|
| 2404 |
+
"dependencies": {
|
| 2405 |
+
"fdir": "^6.5.0",
|
| 2406 |
+
"picomatch": "^4.0.3"
|
| 2407 |
+
},
|
| 2408 |
+
"engines": {
|
| 2409 |
+
"node": ">=12.0.0"
|
| 2410 |
+
},
|
| 2411 |
+
"funding": {
|
| 2412 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 2413 |
+
}
|
| 2414 |
+
},
|
| 2415 |
+
"node_modules/tslib": {
|
| 2416 |
+
"version": "2.8.1",
|
| 2417 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2418 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 2419 |
+
"dev": true,
|
| 2420 |
+
"license": "0BSD",
|
| 2421 |
+
"optional": true
|
| 2422 |
+
},
|
| 2423 |
+
"node_modules/type-check": {
|
| 2424 |
+
"version": "0.4.0",
|
| 2425 |
+
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
| 2426 |
+
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
|
| 2427 |
+
"dev": true,
|
| 2428 |
+
"license": "MIT",
|
| 2429 |
+
"dependencies": {
|
| 2430 |
+
"prelude-ls": "^1.2.1"
|
| 2431 |
+
},
|
| 2432 |
+
"engines": {
|
| 2433 |
+
"node": ">= 0.8.0"
|
| 2434 |
+
}
|
| 2435 |
+
},
|
| 2436 |
+
"node_modules/update-browserslist-db": {
|
| 2437 |
+
"version": "1.2.3",
|
| 2438 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
| 2439 |
+
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
| 2440 |
+
"dev": true,
|
| 2441 |
+
"funding": [
|
| 2442 |
+
{
|
| 2443 |
+
"type": "opencollective",
|
| 2444 |
+
"url": "https://opencollective.com/browserslist"
|
| 2445 |
+
},
|
| 2446 |
+
{
|
| 2447 |
+
"type": "tidelift",
|
| 2448 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 2449 |
+
},
|
| 2450 |
+
{
|
| 2451 |
+
"type": "github",
|
| 2452 |
+
"url": "https://github.com/sponsors/ai"
|
| 2453 |
+
}
|
| 2454 |
+
],
|
| 2455 |
+
"license": "MIT",
|
| 2456 |
+
"dependencies": {
|
| 2457 |
+
"escalade": "^3.2.0",
|
| 2458 |
+
"picocolors": "^1.1.1"
|
| 2459 |
+
},
|
| 2460 |
+
"bin": {
|
| 2461 |
+
"update-browserslist-db": "cli.js"
|
| 2462 |
+
},
|
| 2463 |
+
"peerDependencies": {
|
| 2464 |
+
"browserslist": ">= 4.21.0"
|
| 2465 |
+
}
|
| 2466 |
+
},
|
| 2467 |
+
"node_modules/uri-js": {
|
| 2468 |
+
"version": "4.4.1",
|
| 2469 |
+
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
| 2470 |
+
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
| 2471 |
+
"dev": true,
|
| 2472 |
+
"license": "BSD-2-Clause",
|
| 2473 |
+
"dependencies": {
|
| 2474 |
+
"punycode": "^2.1.0"
|
| 2475 |
+
}
|
| 2476 |
+
},
|
| 2477 |
+
"node_modules/vite": {
|
| 2478 |
+
"version": "8.0.3",
|
| 2479 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz",
|
| 2480 |
+
"integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==",
|
| 2481 |
+
"dev": true,
|
| 2482 |
+
"license": "MIT",
|
| 2483 |
+
"dependencies": {
|
| 2484 |
+
"lightningcss": "^1.32.0",
|
| 2485 |
+
"picomatch": "^4.0.4",
|
| 2486 |
+
"postcss": "^8.5.8",
|
| 2487 |
+
"rolldown": "1.0.0-rc.12",
|
| 2488 |
+
"tinyglobby": "^0.2.15"
|
| 2489 |
+
},
|
| 2490 |
+
"bin": {
|
| 2491 |
+
"vite": "bin/vite.js"
|
| 2492 |
+
},
|
| 2493 |
+
"engines": {
|
| 2494 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 2495 |
+
},
|
| 2496 |
+
"funding": {
|
| 2497 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 2498 |
+
},
|
| 2499 |
+
"optionalDependencies": {
|
| 2500 |
+
"fsevents": "~2.3.3"
|
| 2501 |
+
},
|
| 2502 |
+
"peerDependencies": {
|
| 2503 |
+
"@types/node": "^20.19.0 || >=22.12.0",
|
| 2504 |
+
"@vitejs/devtools": "^0.1.0",
|
| 2505 |
+
"esbuild": "^0.27.0",
|
| 2506 |
+
"jiti": ">=1.21.0",
|
| 2507 |
+
"less": "^4.0.0",
|
| 2508 |
+
"sass": "^1.70.0",
|
| 2509 |
+
"sass-embedded": "^1.70.0",
|
| 2510 |
+
"stylus": ">=0.54.8",
|
| 2511 |
+
"sugarss": "^5.0.0",
|
| 2512 |
+
"terser": "^5.16.0",
|
| 2513 |
+
"tsx": "^4.8.1",
|
| 2514 |
+
"yaml": "^2.4.2"
|
| 2515 |
+
},
|
| 2516 |
+
"peerDependenciesMeta": {
|
| 2517 |
+
"@types/node": {
|
| 2518 |
+
"optional": true
|
| 2519 |
+
},
|
| 2520 |
+
"@vitejs/devtools": {
|
| 2521 |
+
"optional": true
|
| 2522 |
+
},
|
| 2523 |
+
"esbuild": {
|
| 2524 |
+
"optional": true
|
| 2525 |
+
},
|
| 2526 |
+
"jiti": {
|
| 2527 |
+
"optional": true
|
| 2528 |
+
},
|
| 2529 |
+
"less": {
|
| 2530 |
+
"optional": true
|
| 2531 |
+
},
|
| 2532 |
+
"sass": {
|
| 2533 |
+
"optional": true
|
| 2534 |
+
},
|
| 2535 |
+
"sass-embedded": {
|
| 2536 |
+
"optional": true
|
| 2537 |
+
},
|
| 2538 |
+
"stylus": {
|
| 2539 |
+
"optional": true
|
| 2540 |
+
},
|
| 2541 |
+
"sugarss": {
|
| 2542 |
+
"optional": true
|
| 2543 |
+
},
|
| 2544 |
+
"terser": {
|
| 2545 |
+
"optional": true
|
| 2546 |
+
},
|
| 2547 |
+
"tsx": {
|
| 2548 |
+
"optional": true
|
| 2549 |
+
},
|
| 2550 |
+
"yaml": {
|
| 2551 |
+
"optional": true
|
| 2552 |
+
}
|
| 2553 |
+
}
|
| 2554 |
+
},
|
| 2555 |
+
"node_modules/which": {
|
| 2556 |
+
"version": "2.0.2",
|
| 2557 |
+
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
| 2558 |
+
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
| 2559 |
+
"dev": true,
|
| 2560 |
+
"license": "ISC",
|
| 2561 |
+
"dependencies": {
|
| 2562 |
+
"isexe": "^2.0.0"
|
| 2563 |
+
},
|
| 2564 |
+
"bin": {
|
| 2565 |
+
"node-which": "bin/node-which"
|
| 2566 |
+
},
|
| 2567 |
+
"engines": {
|
| 2568 |
+
"node": ">= 8"
|
| 2569 |
+
}
|
| 2570 |
+
},
|
| 2571 |
+
"node_modules/word-wrap": {
|
| 2572 |
+
"version": "1.2.5",
|
| 2573 |
+
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
| 2574 |
+
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
| 2575 |
+
"dev": true,
|
| 2576 |
+
"license": "MIT",
|
| 2577 |
+
"engines": {
|
| 2578 |
+
"node": ">=0.10.0"
|
| 2579 |
+
}
|
| 2580 |
+
},
|
| 2581 |
+
"node_modules/yallist": {
|
| 2582 |
+
"version": "3.1.1",
|
| 2583 |
+
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
| 2584 |
+
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 2585 |
+
"dev": true,
|
| 2586 |
+
"license": "ISC"
|
| 2587 |
+
},
|
| 2588 |
+
"node_modules/yocto-queue": {
|
| 2589 |
+
"version": "0.1.0",
|
| 2590 |
+
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
| 2591 |
+
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
| 2592 |
+
"dev": true,
|
| 2593 |
+
"license": "MIT",
|
| 2594 |
+
"engines": {
|
| 2595 |
+
"node": ">=10"
|
| 2596 |
+
},
|
| 2597 |
+
"funding": {
|
| 2598 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2599 |
+
}
|
| 2600 |
+
},
|
| 2601 |
+
"node_modules/zod": {
|
| 2602 |
+
"version": "4.3.6",
|
| 2603 |
+
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
| 2604 |
+
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
| 2605 |
+
"dev": true,
|
| 2606 |
+
"license": "MIT",
|
| 2607 |
+
"funding": {
|
| 2608 |
+
"url": "https://github.com/sponsors/colinhacks"
|
| 2609 |
+
}
|
| 2610 |
+
},
|
| 2611 |
+
"node_modules/zod-validation-error": {
|
| 2612 |
+
"version": "4.0.2",
|
| 2613 |
+
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
|
| 2614 |
+
"integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
|
| 2615 |
+
"dev": true,
|
| 2616 |
+
"license": "MIT",
|
| 2617 |
+
"engines": {
|
| 2618 |
+
"node": ">=18.0.0"
|
| 2619 |
+
},
|
| 2620 |
+
"peerDependencies": {
|
| 2621 |
+
"zod": "^3.25.0 || ^4.0.0"
|
| 2622 |
+
}
|
| 2623 |
+
}
|
| 2624 |
+
}
|
| 2625 |
+
}
|
frontend/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"lint": "eslint .",
|
| 10 |
+
"preview": "vite preview"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"react": "^19.2.4",
|
| 14 |
+
"react-dom": "^19.2.4",
|
| 15 |
+
"react-router-dom": "^7.13.2"
|
| 16 |
+
},
|
| 17 |
+
"devDependencies": {
|
| 18 |
+
"@eslint/js": "^9.39.4",
|
| 19 |
+
"@types/react": "^19.2.14",
|
| 20 |
+
"@types/react-dom": "^19.2.3",
|
| 21 |
+
"@vitejs/plugin-react": "^6.0.1",
|
| 22 |
+
"eslint": "^9.39.4",
|
| 23 |
+
"eslint-plugin-react-hooks": "^7.0.1",
|
| 24 |
+
"eslint-plugin-react-refresh": "^0.5.2",
|
| 25 |
+
"globals": "^17.4.0",
|
| 26 |
+
"vite": "^8.0.1"
|
| 27 |
+
}
|
| 28 |
+
}
|
frontend/public/favicon.svg
ADDED
|
|
frontend/public/icons.svg
ADDED
|
|
frontend/src/App.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
/* Minimal App-level overrides (global styles in index.css) */
|
frontend/src/App.jsx
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { BrowserRouter as Router, Routes, Route, NavLink } from 'react-router-dom';
|
| 2 |
+
import HomePage from './pages/HomePage';
|
| 3 |
+
import PredictPage from './pages/PredictPage';
|
| 4 |
+
import BreedExplorerPage from './pages/BreedExplorerPage';
|
| 5 |
+
import AboutPage from './pages/AboutPage';
|
| 6 |
+
import './App.css';
|
| 7 |
+
|
| 8 |
+
function App() {
|
| 9 |
+
return (
|
| 10 |
+
<Router>
|
| 11 |
+
<nav className="navbar">
|
| 12 |
+
<div className="navbar-inner">
|
| 13 |
+
<NavLink to="/" className="navbar-brand">
|
| 14 |
+
🐄 <span>CattleAI</span>
|
| 15 |
+
</NavLink>
|
| 16 |
+
<ul className="nav-links">
|
| 17 |
+
<li><NavLink to="/" end>Home</NavLink></li>
|
| 18 |
+
<li><NavLink to="/predict">Predict</NavLink></li>
|
| 19 |
+
<li><NavLink to="/breeds">Breeds</NavLink></li>
|
| 20 |
+
<li><NavLink to="/about">About</NavLink></li>
|
| 21 |
+
</ul>
|
| 22 |
+
</div>
|
| 23 |
+
</nav>
|
| 24 |
+
|
| 25 |
+
<Routes>
|
| 26 |
+
<Route path="/" element={<HomePage />} />
|
| 27 |
+
<Route path="/predict" element={<PredictPage />} />
|
| 28 |
+
<Route path="/breeds" element={<BreedExplorerPage />} />
|
| 29 |
+
<Route path="/about" element={<AboutPage />} />
|
| 30 |
+
</Routes>
|
| 31 |
+
|
| 32 |
+
<footer className="footer">
|
| 33 |
+
<p>
|
| 34 |
+
Built by Ajaya and team
|
| 35 |
+
{' '} · Powered by PyTorch & FastAPI · {new Date().getFullYear()}
|
| 36 |
+
</p>
|
| 37 |
+
</footer>
|
| 38 |
+
</Router>
|
| 39 |
+
);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
export default App;
|