arghadip2002 commited on
Commit
0e038f6
·
1 Parent(s): e39280d
.gitignore ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ venv/
8
+ env/
9
+ ENV/
10
+
11
+ # Node
12
+ node_modules/
13
+ dist/
14
+ build/
15
+
16
+ # Uploads
17
+ backend/uploads/*
18
+ !backend/uploads/.gitkeep
19
+
20
+ # Environment
21
+ .env
22
+ .env.local
23
+
24
+ # IDE
25
+ .vscode/
26
+ .idea/
27
+ *.swp
28
+ *.swo
29
+
30
+ # OS
31
+ .DS_Store
32
+ Thumbs.db
README.md CHANGED
@@ -7,4 +7,96 @@ sdk: docker
7
  pinned: false
8
  ---
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
7
  pinned: false
8
  ---
9
 
10
+ # 🧠 MRI Brain Tumor Detection System
11
+
12
+ Deep Learning application for automated brain tumor classification from MRI scans using a custom ResidualInceptionBlock CNN architecture.
13
+
14
+ ## 🎯 Features
15
+
16
+ - **4-Class Classification**: Glioma, Meningioma, Pituitary, No Tumor
17
+ - **Real-time Inference**: Fast predictions with confidence scores
18
+ - **Modern UI**: Clean, responsive React interface
19
+ - **RESTful API**: FastAPI backend with automatic documentation
20
+
21
+ ## 🏗️ Architecture
22
+
23
+ - **Frontend**: React 18 + Vite
24
+ - **Backend**: FastAPI + PyTorch
25
+ - **Model**: Custom ResidualInceptionBlock CNN (50+ layers)
26
+ - **Deployment**: Docker + Hugging Face Spaces
27
+
28
+ ## 🚀 Quick Start
29
+
30
+ ### Local Development
31
+
32
+ 1. **Clone the repository**
33
+ ```bash
34
+ git clone <your-repo-url>
35
+ cd mri-diagnosis-app
36
+ ```
37
+
38
+ 2. **Start with Docker Compose**
39
+ ```bash
40
+ docker-compose up --build
41
+ ```
42
+
43
+ 3. **Access the application**
44
+ - Frontend: http://localhost:3000
45
+ - API Docs: http://localhost:8000/docs
46
+
47
+ ### Manual Setup
48
+
49
+ **Backend:**
50
+ ```bash
51
+ cd backend
52
+ pip install -r requirements.txt
53
+ uvicorn app.main:app --reload
54
+ ```
55
+
56
+ **Frontend:**
57
+ ```bash
58
+ cd frontend
59
+ npm install
60
+ npm run dev
61
+ ```
62
+
63
+ ## 📋 API Endpoints
64
+
65
+ - `POST /api/predict` - Upload MRI image for prediction
66
+ - `GET /health` - Health check endpoint
67
+ - `GET /docs` - Interactive API documentation
68
+
69
+ ## 🎨 Usage
70
+
71
+ 1. Upload an MRI brain scan (PNG, JPG, JPEG)
72
+ 2. Click "Run Diagnosis"
73
+ 3. View prediction with confidence score
74
+
75
+ ## 📊 Model Performance
76
+
77
+ - **Accuracy**: [Add your accuracy]
78
+ - **Classes**: 4 (Glioma, Meningioma, Pituitary, No Tumor)
79
+ - **Input Size**: 224x224 RGB images
80
+
81
+ ## 🛠️ Technology Stack
82
+
83
+ - **PyTorch** 2.1.0
84
+ - **FastAPI** 0.104.1
85
+ - **React** 18.2.0
86
+ - **Vite** 5.0.0
87
+ - **Docker** & Docker Compose
88
+
89
+ ## 📝 License
90
+
91
+ MIT License
92
+
93
+ ## 👨‍💻 Author
94
+
95
+ Arghadip Biswas
96
+
97
+ ## 🙏 Acknowledgments
98
+
99
+ - Dataset: [Mention your dataset source]
100
+ - Based on ResidualInceptionBlock architecture
101
+
102
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
backend/.dockerignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ venv/
7
+ env/
8
+ uploads/*
9
+ !uploads/.gitkeep
10
+ .env
11
+ *.log
backend/Dockerfile ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile for Hugging Face Spaces
2
+ FROM node:18-alpine AS frontend-builder
3
+
4
+ # Build frontend
5
+ WORKDIR /frontend
6
+ COPY frontend/package*.json ./
7
+ RUN npm install
8
+ COPY frontend/ ./
9
+ RUN npm run build
10
+
11
+ # Python backend stage
12
+ FROM python:3.10-slim
13
+
14
+ WORKDIR /app
15
+
16
+ # Install system dependencies
17
+ RUN apt-get update && apt-get install -y \
18
+ build-essential \
19
+ && rm -rf /var/lib/apt/lists/*
20
+
21
+ # Copy backend requirements and install
22
+ COPY backend/requirements.txt ./
23
+ RUN pip install --no-cache-dir -r requirements.txt
24
+
25
+ # Copy backend application
26
+ COPY backend/app ./app
27
+ COPY backend/models ./models
28
+
29
+ # Create necessary directories
30
+ RUN mkdir -p ./uploads ./static
31
+
32
+ # Copy built frontend from builder stage
33
+ COPY --from=frontend-builder /frontend/dist ./static
34
+
35
+ # Expose port 7860 (required by Hugging Face Spaces)
36
+ EXPOSE 7860
37
+
38
+ # Run the FastAPI application on port 7860
39
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
backend/app/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # backend/app/__init__.py
2
+ # Empty file to make this a Python package
backend/app/inference.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # backend/app/inference.py
2
+ import torch
3
+ import torch.nn.functional as F
4
+ from torchvision import transforms
5
+ from PIL import Image
6
+ from pathlib import Path
7
+ from .model import create_detection_model
8
+
9
+
10
+ class InferenceEngine:
11
+ def __init__(self, model_path: str):
12
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
13
+ self.model = None
14
+ self.model_path = model_path
15
+
16
+ # Class mapping
17
+ self.class_map = {
18
+ 0: 'glioma',
19
+ 1: 'meningioma',
20
+ 2: 'pituitary',
21
+ 3: 'notumor'
22
+ }
23
+
24
+ # Preprocessing transforms
25
+ self.transform = transforms.Compose([
26
+ transforms.Resize((224, 224)),
27
+ transforms.ToTensor(),
28
+ transforms.Normalize([0.485, 0.456, 0.406],
29
+ [0.229, 0.224, 0.225])
30
+ ])
31
+
32
+ self._load_model()
33
+
34
+ def _load_model(self):
35
+ """Load the PyTorch model"""
36
+ try:
37
+ self.model = create_detection_model(num_classes=4)
38
+ self.model.load_state_dict(
39
+ torch.load(self.model_path, map_location=self.device)
40
+ )
41
+ self.model.to(self.device)
42
+ self.model.eval()
43
+ print(f"✅ Model loaded successfully on {self.device}")
44
+ except Exception as e:
45
+ print(f"❌ Error loading model: {e}")
46
+ raise
47
+
48
+ def predict(self, image_path: str) -> dict:
49
+ """
50
+ Run inference on an image
51
+
52
+ Args:
53
+ image_path: Path to the image file
54
+
55
+ Returns:
56
+ Dictionary with prediction results
57
+ """
58
+ try:
59
+ # Load and preprocess image
60
+ image = Image.open(image_path).convert('RGB')
61
+ input_tensor = self.transform(image).unsqueeze(0).to(self.device)
62
+
63
+ # Run inference
64
+ with torch.no_grad():
65
+ output = self.model(input_tensor)
66
+
67
+ # Get probabilities and prediction
68
+ probabilities = F.softmax(output, dim=1)
69
+ confidence, predicted_index = torch.max(probabilities, 1)
70
+
71
+ predicted_class = self.class_map[predicted_index.item()]
72
+
73
+ return {
74
+ "success": True,
75
+ "predicted_class": predicted_class,
76
+ "confidence": float(confidence.item()),
77
+ "all_probabilities": {
78
+ self.class_map[i]: float(probabilities[0][i].item())
79
+ for i in range(len(self.class_map))
80
+ }
81
+ }
82
+
83
+ except Exception as e:
84
+ return {
85
+ "success": False,
86
+ "error": str(e)
87
+ }
backend/app/main.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # backend/app/main.py (for Hugging Face Spaces)
2
+ from fastapi import FastAPI, File, UploadFile, HTTPException
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.responses import JSONResponse, HTMLResponse
5
+ from fastapi.staticfiles import StaticFiles
6
+ import os
7
+ import shutil
8
+ from pathlib import Path
9
+ import time
10
+ from .inference import InferenceEngine
11
+
12
+ # Initialize FastAPI app
13
+ app = FastAPI(
14
+ title="MRI Brain Tumor Detection API",
15
+ description="Deep Learning API for brain tumor classification from MRI scans",
16
+ version="1.0.0"
17
+ )
18
+
19
+ # CORS Configuration
20
+ app.add_middleware(
21
+ CORSMiddleware,
22
+ allow_origins=["*"],
23
+ allow_credentials=True,
24
+ allow_methods=["*"],
25
+ allow_headers=["*"],
26
+ )
27
+
28
+ # Configure paths
29
+ BASE_DIR = Path(__file__).resolve().parent.parent
30
+ UPLOAD_DIR = BASE_DIR / "uploads"
31
+ MODEL_PATH = BASE_DIR / "models" / "model_Full.pth"
32
+ STATIC_DIR = BASE_DIR / "static"
33
+
34
+ # Ensure directories exist
35
+ UPLOAD_DIR.mkdir(exist_ok=True)
36
+
37
+ # Initialize inference engine
38
+ inference_engine = None
39
+
40
+ @app.on_event("startup")
41
+ async def startup_event():
42
+ """Initialize model on startup"""
43
+ global inference_engine
44
+
45
+ if not MODEL_PATH.exists():
46
+ print(f"⚠️ Model file not found at {MODEL_PATH}")
47
+ print("Please place your model_Full.pth in the backend/models/ directory")
48
+ else:
49
+ try:
50
+ inference_engine = InferenceEngine(str(MODEL_PATH))
51
+ print("✅ Inference engine initialized successfully")
52
+ except Exception as e:
53
+ print(f"❌ Failed to initialize inference engine: {e}")
54
+
55
+
56
+ # Serve static files if they exist
57
+ if STATIC_DIR.exists():
58
+ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
59
+
60
+
61
+ @app.get("/", response_class=HTMLResponse)
62
+ async def root():
63
+ """Serve the frontend HTML"""
64
+ html_file = STATIC_DIR / "index.html"
65
+
66
+ if html_file.exists():
67
+ return HTMLResponse(content=html_file.read_text())
68
+
69
+ # Fallback API info if no frontend
70
+ return HTMLResponse(content="""
71
+ <!DOCTYPE html>
72
+ <html>
73
+ <head>
74
+ <title>MRI Brain Tumor Detection API</title>
75
+ <style>
76
+ body {
77
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
78
+ max-width: 800px;
79
+ margin: 50px auto;
80
+ padding: 20px;
81
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
82
+ color: white;
83
+ }
84
+ .container {
85
+ background: rgba(255, 255, 255, 0.1);
86
+ backdrop-filter: blur(10px);
87
+ padding: 40px;
88
+ border-radius: 20px;
89
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
90
+ }
91
+ h1 { margin-top: 0; font-size: 2.5em; }
92
+ .status {
93
+ background: rgba(34, 197, 94, 0.2);
94
+ padding: 15px;
95
+ border-radius: 10px;
96
+ margin: 20px 0;
97
+ }
98
+ .link {
99
+ display: inline-block;
100
+ background: white;
101
+ color: #667eea;
102
+ padding: 12px 24px;
103
+ text-decoration: none;
104
+ border-radius: 8px;
105
+ margin: 10px 10px 10px 0;
106
+ font-weight: bold;
107
+ transition: transform 0.2s;
108
+ }
109
+ .link:hover {
110
+ transform: translateY(-2px);
111
+ }
112
+ .endpoint {
113
+ background: rgba(255, 255, 255, 0.1);
114
+ padding: 10px;
115
+ border-radius: 5px;
116
+ margin: 10px 0;
117
+ font-family: 'Courier New', monospace;
118
+ }
119
+ </style>
120
+ </head>
121
+ <body>
122
+ <div class="container">
123
+ <h1>🧠 MRI Brain Tumor Detection API</h1>
124
+ <div class="status">
125
+ <strong>Status:</strong> ✅ Online<br>
126
+ <strong>Model:</strong> """ + ("✅ Loaded" if inference_engine else "❌ Not Loaded") + """
127
+ </div>
128
+
129
+ <h2>📚 API Documentation</h2>
130
+ <a href="/docs" class="link">📖 Interactive API Docs</a>
131
+ <a href="/redoc" class="link">📋 ReDoc Documentation</a>
132
+
133
+ <h2>🔌 Endpoints</h2>
134
+ <div class="endpoint">POST /api/predict - Upload MRI image for prediction</div>
135
+ <div class="endpoint">GET /health - Health check endpoint</div>
136
+
137
+ <h2>🚀 Usage</h2>
138
+ <p>Send a POST request to <code>/api/predict</code> with an MRI image file:</p>
139
+ <pre style="background: rgba(0,0,0,0.3); padding: 15px; border-radius: 8px; overflow-x: auto;">
140
+ curl -X POST "https://your-space.hf.space/api/predict" \\
141
+ -F "mriImage=@your_mri_image.jpg"
142
+ </pre>
143
+ </div>
144
+ </body>
145
+ </html>
146
+ """)
147
+
148
+
149
+ @app.post("/api/predict")
150
+ async def predict(mriImage: UploadFile = File(...)):
151
+ """
152
+ Predict brain tumor type from MRI image
153
+
154
+ Args:
155
+ mriImage: Uploaded MRI scan image file
156
+
157
+ Returns:
158
+ JSON with prediction results
159
+ """
160
+
161
+ # Check if model is loaded
162
+ if inference_engine is None:
163
+ raise HTTPException(
164
+ status_code=503,
165
+ detail="Model not loaded. Please check server logs."
166
+ )
167
+
168
+ # Validate file type
169
+ allowed_extensions = {'.jpg', '.jpeg', '.png'}
170
+ file_ext = Path(mriImage.filename).suffix.lower()
171
+
172
+ if file_ext not in allowed_extensions:
173
+ raise HTTPException(
174
+ status_code=400,
175
+ detail=f"Invalid file type. Allowed: {', '.join(allowed_extensions)}"
176
+ )
177
+
178
+ # Save uploaded file temporarily
179
+ timestamp = int(time.time() * 1000)
180
+ temp_filename = f"{timestamp}_{mriImage.filename}"
181
+ temp_filepath = UPLOAD_DIR / temp_filename
182
+
183
+ try:
184
+ # Save file
185
+ with temp_filepath.open("wb") as buffer:
186
+ shutil.copyfileobj(mriImage.file, buffer)
187
+
188
+ # Run inference
189
+ result = inference_engine.predict(str(temp_filepath))
190
+
191
+ # Clean up temporary file
192
+ if temp_filepath.exists():
193
+ temp_filepath.unlink()
194
+
195
+ if not result.get("success"):
196
+ raise HTTPException(
197
+ status_code=500,
198
+ detail=f"Inference failed: {result.get('error', 'Unknown error')}"
199
+ )
200
+
201
+ return JSONResponse(content={
202
+ "predicted_class": result["predicted_class"],
203
+ "confidence": result["confidence"],
204
+ "all_probabilities": result["all_probabilities"]
205
+ })
206
+
207
+ except HTTPException:
208
+ # Re-raise HTTP exceptions
209
+ if temp_filepath.exists():
210
+ temp_filepath.unlink()
211
+ raise
212
+
213
+ except Exception as e:
214
+ # Clean up on error
215
+ if temp_filepath.exists():
216
+ temp_filepath.unlink()
217
+
218
+ raise HTTPException(
219
+ status_code=500,
220
+ detail=f"Server error: {str(e)}"
221
+ )
222
+
223
+
224
+ @app.get("/health")
225
+ async def health_check():
226
+ """Detailed health check"""
227
+ return {
228
+ "status": "healthy",
229
+ "model_loaded": inference_engine is not None,
230
+ "model_path": str(MODEL_PATH),
231
+ "model_exists": MODEL_PATH.exists()
232
+ }
233
+
234
+
235
+ if __name__ == "__main__":
236
+ import uvicorn
237
+ uvicorn.run(app, host="0.0.0.0", port=7860)
backend/app/model.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # backend/app/model.py
2
+ import torch
3
+ import torch.nn as nn
4
+
5
+
6
+ class ResidualInceptionBlock(nn.Module):
7
+ def __init__(self, in_channels, out_channels, stride=1):
8
+ super(ResidualInceptionBlock, self).__init__()
9
+ # Branch 1
10
+ self.branch1 = nn.Conv2d(in_channels, out_channels // 4, kernel_size=1, stride=stride)
11
+
12
+ # Branch 2
13
+ self.branch2 = nn.Sequential(
14
+ nn.Conv2d(in_channels, out_channels // 4, kernel_size=1, stride=stride),
15
+ nn.BatchNorm2d(out_channels // 4),
16
+ nn.ReLU(inplace=True),
17
+ nn.Conv2d(out_channels // 4, out_channels // 4, kernel_size=3, stride=1, padding=1)
18
+ )
19
+
20
+ # Branch 3
21
+ self.branch3 = nn.Sequential(
22
+ nn.Conv2d(in_channels, out_channels // 4, kernel_size=1, stride=stride),
23
+ nn.BatchNorm2d(out_channels // 4),
24
+ nn.ReLU(inplace=True),
25
+ nn.Conv2d(out_channels // 4, out_channels // 4, kernel_size=5, stride=1, padding=2)
26
+ )
27
+
28
+ # Branch 4 (Pooling)
29
+ self.branch_pool = nn.Sequential(
30
+ nn.MaxPool2d(kernel_size=3, stride=stride, padding=1),
31
+ nn.Conv2d(in_channels, out_channels // 4, kernel_size=1, stride=1, padding=0)
32
+ )
33
+
34
+ # Batch Norm and Activation
35
+ self.bn = nn.BatchNorm2d(out_channels)
36
+ self.relu = nn.ReLU(inplace=True)
37
+
38
+ # Shortcut Connection
39
+ self.shortcut = nn.Sequential()
40
+ if in_channels != out_channels or stride != 1:
41
+ self.shortcut = nn.Sequential(
42
+ nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride),
43
+ nn.BatchNorm2d(out_channels)
44
+ )
45
+
46
+ def forward(self, x):
47
+ branch1 = self.branch1(x)
48
+ branch2 = self.branch2(x)
49
+ branch3 = self.branch3(x)
50
+ branch4 = self.branch_pool(x)
51
+ outputs = torch.cat([branch1, branch2, branch3, branch4], 1)
52
+ outputs = self.bn(outputs)
53
+ residual = self.shortcut(x)
54
+ outputs += residual
55
+ outputs = self.relu(outputs)
56
+ return outputs
57
+
58
+
59
+ def make_layer(block, in_channels, out_channels, num_blocks, stride=1):
60
+ layers = []
61
+ layers.append(block(in_channels, out_channels, stride=stride))
62
+ in_channels = out_channels
63
+ for _ in range(1, num_blocks):
64
+ layers.append(block(in_channels, out_channels, stride=1))
65
+ return nn.Sequential(*layers)
66
+
67
+
68
+ class DeeperDetectionModel(nn.Module):
69
+ def __init__(self, num_classes=4):
70
+ super(DeeperDetectionModel, self).__init__()
71
+ self.module1 = nn.Sequential(
72
+ nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
73
+ nn.BatchNorm2d(64),
74
+ nn.ReLU(inplace=True),
75
+ nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
76
+ )
77
+ self.layer1 = make_layer(ResidualInceptionBlock, 64, 256, num_blocks=3, stride=1)
78
+ self.layer2 = make_layer(ResidualInceptionBlock, 256, 512, num_blocks=4, stride=2)
79
+ self.layer3 = make_layer(ResidualInceptionBlock, 512, 1024, num_blocks=6, stride=2)
80
+ self.layer4 = make_layer(ResidualInceptionBlock, 1024, 2048, num_blocks=3, stride=2)
81
+ self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
82
+ self.fc = nn.Linear(2048, num_classes)
83
+
84
+ def forward(self, x):
85
+ x = self.module1(x)
86
+ x = self.layer1(x)
87
+ x = self.layer2(x)
88
+ x = self.layer3(x)
89
+ x = self.layer4(x)
90
+ x = self.avgpool(x)
91
+ x = x.view(x.size(0), -1)
92
+ x = self.fc(x)
93
+ return x
94
+
95
+
96
+ def create_detection_model(num_classes=4):
97
+ """Factory function to create the model"""
98
+ model = DeeperDetectionModel(num_classes=num_classes)
99
+ return model
backend/models/model_Full.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7136f0c2b90df6acac1510c9f7da35fcc522d8597298ef25d01a94804052c8e1
3
+ size 252126739
backend/requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ python-multipart==0.0.6
4
+ torch==2.1.0
5
+ torchvision==0.16.0
6
+ Pillow==10.1.0
7
+ python-dotenv==1.0.0
8
+ aiofiles==23.2.1
9
+ numpy==1.26.1
backend/uploads/.gitkeep ADDED
File without changes
docker-compose.yml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ backend:
5
+ build: ./backend
6
+ ports:
7
+ - "8000:8000"
8
+ volumes:
9
+ - ./backend/models:/app/models
10
+ - ./backend/uploads:/app/uploads
11
+ environment:
12
+ - PYTHONUNBUFFERED=1
13
+ restart: unless-stopped
14
+
15
+ frontend:
16
+ image: node:18-alpine
17
+ working_dir: /app
18
+ volumes:
19
+ - ./frontend:/app
20
+ - /app/node_modules
21
+ ports:
22
+ - "3000:3000"
23
+ command: sh -c "npm install && npm run dev -- --host 0.0.0.0"
24
+ # entrypoint: sh -c "npm install"
25
+ # command: npm run dev -- --host 0.0.0.0
26
+ environment:
27
+ - VITE_API_URL=http://localhost:8000
28
+ # - VITE_API_URL=http://backend:8000
29
+ depends_on:
30
+ - backend
31
+ 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/README.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information.
13
+
14
+ Note: This will impact Vite dev & build performances.
15
+
16
+ ## Expanding the ESLint configuration
17
+
18
+ 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/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,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>MRI Brain Tumor Detection</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/index.jsx"></script>
11
+ </body>
12
+ </html>
frontend/package-lock.json ADDED
@@ -0,0 +1,1629 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "mri-diagnosis-frontend",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "mri-diagnosis-frontend",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "react": "^18.2.0",
12
+ "react-dom": "^18.2.0"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-react": "^4.2.0",
16
+ "vite": "^5.0.0"
17
+ }
18
+ },
19
+ "node_modules/@babel/code-frame": {
20
+ "version": "7.27.1",
21
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
22
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
23
+ "dev": true,
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "@babel/helper-validator-identifier": "^7.27.1",
27
+ "js-tokens": "^4.0.0",
28
+ "picocolors": "^1.1.1"
29
+ },
30
+ "engines": {
31
+ "node": ">=6.9.0"
32
+ }
33
+ },
34
+ "node_modules/@babel/compat-data": {
35
+ "version": "7.28.5",
36
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
37
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
38
+ "dev": true,
39
+ "license": "MIT",
40
+ "engines": {
41
+ "node": ">=6.9.0"
42
+ }
43
+ },
44
+ "node_modules/@babel/core": {
45
+ "version": "7.28.5",
46
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
47
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
48
+ "dev": true,
49
+ "license": "MIT",
50
+ "dependencies": {
51
+ "@babel/code-frame": "^7.27.1",
52
+ "@babel/generator": "^7.28.5",
53
+ "@babel/helper-compilation-targets": "^7.27.2",
54
+ "@babel/helper-module-transforms": "^7.28.3",
55
+ "@babel/helpers": "^7.28.4",
56
+ "@babel/parser": "^7.28.5",
57
+ "@babel/template": "^7.27.2",
58
+ "@babel/traverse": "^7.28.5",
59
+ "@babel/types": "^7.28.5",
60
+ "@jridgewell/remapping": "^2.3.5",
61
+ "convert-source-map": "^2.0.0",
62
+ "debug": "^4.1.0",
63
+ "gensync": "^1.0.0-beta.2",
64
+ "json5": "^2.2.3",
65
+ "semver": "^6.3.1"
66
+ },
67
+ "engines": {
68
+ "node": ">=6.9.0"
69
+ },
70
+ "funding": {
71
+ "type": "opencollective",
72
+ "url": "https://opencollective.com/babel"
73
+ }
74
+ },
75
+ "node_modules/@babel/generator": {
76
+ "version": "7.28.5",
77
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
78
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
79
+ "dev": true,
80
+ "license": "MIT",
81
+ "dependencies": {
82
+ "@babel/parser": "^7.28.5",
83
+ "@babel/types": "^7.28.5",
84
+ "@jridgewell/gen-mapping": "^0.3.12",
85
+ "@jridgewell/trace-mapping": "^0.3.28",
86
+ "jsesc": "^3.0.2"
87
+ },
88
+ "engines": {
89
+ "node": ">=6.9.0"
90
+ }
91
+ },
92
+ "node_modules/@babel/helper-compilation-targets": {
93
+ "version": "7.27.2",
94
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
95
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
96
+ "dev": true,
97
+ "license": "MIT",
98
+ "dependencies": {
99
+ "@babel/compat-data": "^7.27.2",
100
+ "@babel/helper-validator-option": "^7.27.1",
101
+ "browserslist": "^4.24.0",
102
+ "lru-cache": "^5.1.1",
103
+ "semver": "^6.3.1"
104
+ },
105
+ "engines": {
106
+ "node": ">=6.9.0"
107
+ }
108
+ },
109
+ "node_modules/@babel/helper-globals": {
110
+ "version": "7.28.0",
111
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
112
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
113
+ "dev": true,
114
+ "license": "MIT",
115
+ "engines": {
116
+ "node": ">=6.9.0"
117
+ }
118
+ },
119
+ "node_modules/@babel/helper-module-imports": {
120
+ "version": "7.27.1",
121
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
122
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
123
+ "dev": true,
124
+ "license": "MIT",
125
+ "dependencies": {
126
+ "@babel/traverse": "^7.27.1",
127
+ "@babel/types": "^7.27.1"
128
+ },
129
+ "engines": {
130
+ "node": ">=6.9.0"
131
+ }
132
+ },
133
+ "node_modules/@babel/helper-module-transforms": {
134
+ "version": "7.28.3",
135
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
136
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
137
+ "dev": true,
138
+ "license": "MIT",
139
+ "dependencies": {
140
+ "@babel/helper-module-imports": "^7.27.1",
141
+ "@babel/helper-validator-identifier": "^7.27.1",
142
+ "@babel/traverse": "^7.28.3"
143
+ },
144
+ "engines": {
145
+ "node": ">=6.9.0"
146
+ },
147
+ "peerDependencies": {
148
+ "@babel/core": "^7.0.0"
149
+ }
150
+ },
151
+ "node_modules/@babel/helper-plugin-utils": {
152
+ "version": "7.27.1",
153
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
154
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
155
+ "dev": true,
156
+ "license": "MIT",
157
+ "engines": {
158
+ "node": ">=6.9.0"
159
+ }
160
+ },
161
+ "node_modules/@babel/helper-string-parser": {
162
+ "version": "7.27.1",
163
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
164
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
165
+ "dev": true,
166
+ "license": "MIT",
167
+ "engines": {
168
+ "node": ">=6.9.0"
169
+ }
170
+ },
171
+ "node_modules/@babel/helper-validator-identifier": {
172
+ "version": "7.28.5",
173
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
174
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
175
+ "dev": true,
176
+ "license": "MIT",
177
+ "engines": {
178
+ "node": ">=6.9.0"
179
+ }
180
+ },
181
+ "node_modules/@babel/helper-validator-option": {
182
+ "version": "7.27.1",
183
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
184
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
185
+ "dev": true,
186
+ "license": "MIT",
187
+ "engines": {
188
+ "node": ">=6.9.0"
189
+ }
190
+ },
191
+ "node_modules/@babel/helpers": {
192
+ "version": "7.28.4",
193
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
194
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
195
+ "dev": true,
196
+ "license": "MIT",
197
+ "dependencies": {
198
+ "@babel/template": "^7.27.2",
199
+ "@babel/types": "^7.28.4"
200
+ },
201
+ "engines": {
202
+ "node": ">=6.9.0"
203
+ }
204
+ },
205
+ "node_modules/@babel/parser": {
206
+ "version": "7.28.5",
207
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
208
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
209
+ "dev": true,
210
+ "license": "MIT",
211
+ "dependencies": {
212
+ "@babel/types": "^7.28.5"
213
+ },
214
+ "bin": {
215
+ "parser": "bin/babel-parser.js"
216
+ },
217
+ "engines": {
218
+ "node": ">=6.0.0"
219
+ }
220
+ },
221
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
222
+ "version": "7.27.1",
223
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
224
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
225
+ "dev": true,
226
+ "license": "MIT",
227
+ "dependencies": {
228
+ "@babel/helper-plugin-utils": "^7.27.1"
229
+ },
230
+ "engines": {
231
+ "node": ">=6.9.0"
232
+ },
233
+ "peerDependencies": {
234
+ "@babel/core": "^7.0.0-0"
235
+ }
236
+ },
237
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
238
+ "version": "7.27.1",
239
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
240
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
241
+ "dev": true,
242
+ "license": "MIT",
243
+ "dependencies": {
244
+ "@babel/helper-plugin-utils": "^7.27.1"
245
+ },
246
+ "engines": {
247
+ "node": ">=6.9.0"
248
+ },
249
+ "peerDependencies": {
250
+ "@babel/core": "^7.0.0-0"
251
+ }
252
+ },
253
+ "node_modules/@babel/template": {
254
+ "version": "7.27.2",
255
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
256
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
257
+ "dev": true,
258
+ "license": "MIT",
259
+ "dependencies": {
260
+ "@babel/code-frame": "^7.27.1",
261
+ "@babel/parser": "^7.27.2",
262
+ "@babel/types": "^7.27.1"
263
+ },
264
+ "engines": {
265
+ "node": ">=6.9.0"
266
+ }
267
+ },
268
+ "node_modules/@babel/traverse": {
269
+ "version": "7.28.5",
270
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
271
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
272
+ "dev": true,
273
+ "license": "MIT",
274
+ "dependencies": {
275
+ "@babel/code-frame": "^7.27.1",
276
+ "@babel/generator": "^7.28.5",
277
+ "@babel/helper-globals": "^7.28.0",
278
+ "@babel/parser": "^7.28.5",
279
+ "@babel/template": "^7.27.2",
280
+ "@babel/types": "^7.28.5",
281
+ "debug": "^4.3.1"
282
+ },
283
+ "engines": {
284
+ "node": ">=6.9.0"
285
+ }
286
+ },
287
+ "node_modules/@babel/types": {
288
+ "version": "7.28.5",
289
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
290
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
291
+ "dev": true,
292
+ "license": "MIT",
293
+ "dependencies": {
294
+ "@babel/helper-string-parser": "^7.27.1",
295
+ "@babel/helper-validator-identifier": "^7.28.5"
296
+ },
297
+ "engines": {
298
+ "node": ">=6.9.0"
299
+ }
300
+ },
301
+ "node_modules/@esbuild/aix-ppc64": {
302
+ "version": "0.21.5",
303
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
304
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
305
+ "cpu": [
306
+ "ppc64"
307
+ ],
308
+ "dev": true,
309
+ "license": "MIT",
310
+ "optional": true,
311
+ "os": [
312
+ "aix"
313
+ ],
314
+ "engines": {
315
+ "node": ">=12"
316
+ }
317
+ },
318
+ "node_modules/@esbuild/android-arm": {
319
+ "version": "0.21.5",
320
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
321
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
322
+ "cpu": [
323
+ "arm"
324
+ ],
325
+ "dev": true,
326
+ "license": "MIT",
327
+ "optional": true,
328
+ "os": [
329
+ "android"
330
+ ],
331
+ "engines": {
332
+ "node": ">=12"
333
+ }
334
+ },
335
+ "node_modules/@esbuild/android-arm64": {
336
+ "version": "0.21.5",
337
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
338
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
339
+ "cpu": [
340
+ "arm64"
341
+ ],
342
+ "dev": true,
343
+ "license": "MIT",
344
+ "optional": true,
345
+ "os": [
346
+ "android"
347
+ ],
348
+ "engines": {
349
+ "node": ">=12"
350
+ }
351
+ },
352
+ "node_modules/@esbuild/android-x64": {
353
+ "version": "0.21.5",
354
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
355
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
356
+ "cpu": [
357
+ "x64"
358
+ ],
359
+ "dev": true,
360
+ "license": "MIT",
361
+ "optional": true,
362
+ "os": [
363
+ "android"
364
+ ],
365
+ "engines": {
366
+ "node": ">=12"
367
+ }
368
+ },
369
+ "node_modules/@esbuild/darwin-arm64": {
370
+ "version": "0.21.5",
371
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
372
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
373
+ "cpu": [
374
+ "arm64"
375
+ ],
376
+ "dev": true,
377
+ "license": "MIT",
378
+ "optional": true,
379
+ "os": [
380
+ "darwin"
381
+ ],
382
+ "engines": {
383
+ "node": ">=12"
384
+ }
385
+ },
386
+ "node_modules/@esbuild/darwin-x64": {
387
+ "version": "0.21.5",
388
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
389
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
390
+ "cpu": [
391
+ "x64"
392
+ ],
393
+ "dev": true,
394
+ "license": "MIT",
395
+ "optional": true,
396
+ "os": [
397
+ "darwin"
398
+ ],
399
+ "engines": {
400
+ "node": ">=12"
401
+ }
402
+ },
403
+ "node_modules/@esbuild/freebsd-arm64": {
404
+ "version": "0.21.5",
405
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
406
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
407
+ "cpu": [
408
+ "arm64"
409
+ ],
410
+ "dev": true,
411
+ "license": "MIT",
412
+ "optional": true,
413
+ "os": [
414
+ "freebsd"
415
+ ],
416
+ "engines": {
417
+ "node": ">=12"
418
+ }
419
+ },
420
+ "node_modules/@esbuild/freebsd-x64": {
421
+ "version": "0.21.5",
422
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
423
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
424
+ "cpu": [
425
+ "x64"
426
+ ],
427
+ "dev": true,
428
+ "license": "MIT",
429
+ "optional": true,
430
+ "os": [
431
+ "freebsd"
432
+ ],
433
+ "engines": {
434
+ "node": ">=12"
435
+ }
436
+ },
437
+ "node_modules/@esbuild/linux-arm": {
438
+ "version": "0.21.5",
439
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
440
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
441
+ "cpu": [
442
+ "arm"
443
+ ],
444
+ "dev": true,
445
+ "license": "MIT",
446
+ "optional": true,
447
+ "os": [
448
+ "linux"
449
+ ],
450
+ "engines": {
451
+ "node": ">=12"
452
+ }
453
+ },
454
+ "node_modules/@esbuild/linux-arm64": {
455
+ "version": "0.21.5",
456
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
457
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
458
+ "cpu": [
459
+ "arm64"
460
+ ],
461
+ "dev": true,
462
+ "license": "MIT",
463
+ "optional": true,
464
+ "os": [
465
+ "linux"
466
+ ],
467
+ "engines": {
468
+ "node": ">=12"
469
+ }
470
+ },
471
+ "node_modules/@esbuild/linux-ia32": {
472
+ "version": "0.21.5",
473
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
474
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
475
+ "cpu": [
476
+ "ia32"
477
+ ],
478
+ "dev": true,
479
+ "license": "MIT",
480
+ "optional": true,
481
+ "os": [
482
+ "linux"
483
+ ],
484
+ "engines": {
485
+ "node": ">=12"
486
+ }
487
+ },
488
+ "node_modules/@esbuild/linux-loong64": {
489
+ "version": "0.21.5",
490
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
491
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
492
+ "cpu": [
493
+ "loong64"
494
+ ],
495
+ "dev": true,
496
+ "license": "MIT",
497
+ "optional": true,
498
+ "os": [
499
+ "linux"
500
+ ],
501
+ "engines": {
502
+ "node": ">=12"
503
+ }
504
+ },
505
+ "node_modules/@esbuild/linux-mips64el": {
506
+ "version": "0.21.5",
507
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
508
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
509
+ "cpu": [
510
+ "mips64el"
511
+ ],
512
+ "dev": true,
513
+ "license": "MIT",
514
+ "optional": true,
515
+ "os": [
516
+ "linux"
517
+ ],
518
+ "engines": {
519
+ "node": ">=12"
520
+ }
521
+ },
522
+ "node_modules/@esbuild/linux-ppc64": {
523
+ "version": "0.21.5",
524
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
525
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
526
+ "cpu": [
527
+ "ppc64"
528
+ ],
529
+ "dev": true,
530
+ "license": "MIT",
531
+ "optional": true,
532
+ "os": [
533
+ "linux"
534
+ ],
535
+ "engines": {
536
+ "node": ">=12"
537
+ }
538
+ },
539
+ "node_modules/@esbuild/linux-riscv64": {
540
+ "version": "0.21.5",
541
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
542
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
543
+ "cpu": [
544
+ "riscv64"
545
+ ],
546
+ "dev": true,
547
+ "license": "MIT",
548
+ "optional": true,
549
+ "os": [
550
+ "linux"
551
+ ],
552
+ "engines": {
553
+ "node": ">=12"
554
+ }
555
+ },
556
+ "node_modules/@esbuild/linux-s390x": {
557
+ "version": "0.21.5",
558
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
559
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
560
+ "cpu": [
561
+ "s390x"
562
+ ],
563
+ "dev": true,
564
+ "license": "MIT",
565
+ "optional": true,
566
+ "os": [
567
+ "linux"
568
+ ],
569
+ "engines": {
570
+ "node": ">=12"
571
+ }
572
+ },
573
+ "node_modules/@esbuild/linux-x64": {
574
+ "version": "0.21.5",
575
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
576
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
577
+ "cpu": [
578
+ "x64"
579
+ ],
580
+ "dev": true,
581
+ "license": "MIT",
582
+ "optional": true,
583
+ "os": [
584
+ "linux"
585
+ ],
586
+ "engines": {
587
+ "node": ">=12"
588
+ }
589
+ },
590
+ "node_modules/@esbuild/netbsd-x64": {
591
+ "version": "0.21.5",
592
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
593
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
594
+ "cpu": [
595
+ "x64"
596
+ ],
597
+ "dev": true,
598
+ "license": "MIT",
599
+ "optional": true,
600
+ "os": [
601
+ "netbsd"
602
+ ],
603
+ "engines": {
604
+ "node": ">=12"
605
+ }
606
+ },
607
+ "node_modules/@esbuild/openbsd-x64": {
608
+ "version": "0.21.5",
609
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
610
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
611
+ "cpu": [
612
+ "x64"
613
+ ],
614
+ "dev": true,
615
+ "license": "MIT",
616
+ "optional": true,
617
+ "os": [
618
+ "openbsd"
619
+ ],
620
+ "engines": {
621
+ "node": ">=12"
622
+ }
623
+ },
624
+ "node_modules/@esbuild/sunos-x64": {
625
+ "version": "0.21.5",
626
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
627
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
628
+ "cpu": [
629
+ "x64"
630
+ ],
631
+ "dev": true,
632
+ "license": "MIT",
633
+ "optional": true,
634
+ "os": [
635
+ "sunos"
636
+ ],
637
+ "engines": {
638
+ "node": ">=12"
639
+ }
640
+ },
641
+ "node_modules/@esbuild/win32-arm64": {
642
+ "version": "0.21.5",
643
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
644
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
645
+ "cpu": [
646
+ "arm64"
647
+ ],
648
+ "dev": true,
649
+ "license": "MIT",
650
+ "optional": true,
651
+ "os": [
652
+ "win32"
653
+ ],
654
+ "engines": {
655
+ "node": ">=12"
656
+ }
657
+ },
658
+ "node_modules/@esbuild/win32-ia32": {
659
+ "version": "0.21.5",
660
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
661
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
662
+ "cpu": [
663
+ "ia32"
664
+ ],
665
+ "dev": true,
666
+ "license": "MIT",
667
+ "optional": true,
668
+ "os": [
669
+ "win32"
670
+ ],
671
+ "engines": {
672
+ "node": ">=12"
673
+ }
674
+ },
675
+ "node_modules/@esbuild/win32-x64": {
676
+ "version": "0.21.5",
677
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
678
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
679
+ "cpu": [
680
+ "x64"
681
+ ],
682
+ "dev": true,
683
+ "license": "MIT",
684
+ "optional": true,
685
+ "os": [
686
+ "win32"
687
+ ],
688
+ "engines": {
689
+ "node": ">=12"
690
+ }
691
+ },
692
+ "node_modules/@jridgewell/gen-mapping": {
693
+ "version": "0.3.13",
694
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
695
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
696
+ "dev": true,
697
+ "license": "MIT",
698
+ "dependencies": {
699
+ "@jridgewell/sourcemap-codec": "^1.5.0",
700
+ "@jridgewell/trace-mapping": "^0.3.24"
701
+ }
702
+ },
703
+ "node_modules/@jridgewell/remapping": {
704
+ "version": "2.3.5",
705
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
706
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
707
+ "dev": true,
708
+ "license": "MIT",
709
+ "dependencies": {
710
+ "@jridgewell/gen-mapping": "^0.3.5",
711
+ "@jridgewell/trace-mapping": "^0.3.24"
712
+ }
713
+ },
714
+ "node_modules/@jridgewell/resolve-uri": {
715
+ "version": "3.1.2",
716
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
717
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
718
+ "dev": true,
719
+ "license": "MIT",
720
+ "engines": {
721
+ "node": ">=6.0.0"
722
+ }
723
+ },
724
+ "node_modules/@jridgewell/sourcemap-codec": {
725
+ "version": "1.5.5",
726
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
727
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
728
+ "dev": true,
729
+ "license": "MIT"
730
+ },
731
+ "node_modules/@jridgewell/trace-mapping": {
732
+ "version": "0.3.31",
733
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
734
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
735
+ "dev": true,
736
+ "license": "MIT",
737
+ "dependencies": {
738
+ "@jridgewell/resolve-uri": "^3.1.0",
739
+ "@jridgewell/sourcemap-codec": "^1.4.14"
740
+ }
741
+ },
742
+ "node_modules/@rolldown/pluginutils": {
743
+ "version": "1.0.0-beta.27",
744
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
745
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
746
+ "dev": true,
747
+ "license": "MIT"
748
+ },
749
+ "node_modules/@rollup/rollup-android-arm-eabi": {
750
+ "version": "4.53.3",
751
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
752
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
753
+ "cpu": [
754
+ "arm"
755
+ ],
756
+ "dev": true,
757
+ "license": "MIT",
758
+ "optional": true,
759
+ "os": [
760
+ "android"
761
+ ]
762
+ },
763
+ "node_modules/@rollup/rollup-android-arm64": {
764
+ "version": "4.53.3",
765
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
766
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
767
+ "cpu": [
768
+ "arm64"
769
+ ],
770
+ "dev": true,
771
+ "license": "MIT",
772
+ "optional": true,
773
+ "os": [
774
+ "android"
775
+ ]
776
+ },
777
+ "node_modules/@rollup/rollup-darwin-arm64": {
778
+ "version": "4.53.3",
779
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
780
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
781
+ "cpu": [
782
+ "arm64"
783
+ ],
784
+ "dev": true,
785
+ "license": "MIT",
786
+ "optional": true,
787
+ "os": [
788
+ "darwin"
789
+ ]
790
+ },
791
+ "node_modules/@rollup/rollup-darwin-x64": {
792
+ "version": "4.53.3",
793
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
794
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
795
+ "cpu": [
796
+ "x64"
797
+ ],
798
+ "dev": true,
799
+ "license": "MIT",
800
+ "optional": true,
801
+ "os": [
802
+ "darwin"
803
+ ]
804
+ },
805
+ "node_modules/@rollup/rollup-freebsd-arm64": {
806
+ "version": "4.53.3",
807
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
808
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
809
+ "cpu": [
810
+ "arm64"
811
+ ],
812
+ "dev": true,
813
+ "license": "MIT",
814
+ "optional": true,
815
+ "os": [
816
+ "freebsd"
817
+ ]
818
+ },
819
+ "node_modules/@rollup/rollup-freebsd-x64": {
820
+ "version": "4.53.3",
821
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
822
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
823
+ "cpu": [
824
+ "x64"
825
+ ],
826
+ "dev": true,
827
+ "license": "MIT",
828
+ "optional": true,
829
+ "os": [
830
+ "freebsd"
831
+ ]
832
+ },
833
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
834
+ "version": "4.53.3",
835
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
836
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
837
+ "cpu": [
838
+ "arm"
839
+ ],
840
+ "dev": true,
841
+ "license": "MIT",
842
+ "optional": true,
843
+ "os": [
844
+ "linux"
845
+ ]
846
+ },
847
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
848
+ "version": "4.53.3",
849
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
850
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
851
+ "cpu": [
852
+ "arm"
853
+ ],
854
+ "dev": true,
855
+ "license": "MIT",
856
+ "optional": true,
857
+ "os": [
858
+ "linux"
859
+ ]
860
+ },
861
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
862
+ "version": "4.53.3",
863
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
864
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
865
+ "cpu": [
866
+ "arm64"
867
+ ],
868
+ "dev": true,
869
+ "license": "MIT",
870
+ "optional": true,
871
+ "os": [
872
+ "linux"
873
+ ]
874
+ },
875
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
876
+ "version": "4.53.3",
877
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
878
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
879
+ "cpu": [
880
+ "arm64"
881
+ ],
882
+ "dev": true,
883
+ "license": "MIT",
884
+ "optional": true,
885
+ "os": [
886
+ "linux"
887
+ ]
888
+ },
889
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
890
+ "version": "4.53.3",
891
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
892
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
893
+ "cpu": [
894
+ "loong64"
895
+ ],
896
+ "dev": true,
897
+ "license": "MIT",
898
+ "optional": true,
899
+ "os": [
900
+ "linux"
901
+ ]
902
+ },
903
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
904
+ "version": "4.53.3",
905
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
906
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
907
+ "cpu": [
908
+ "ppc64"
909
+ ],
910
+ "dev": true,
911
+ "license": "MIT",
912
+ "optional": true,
913
+ "os": [
914
+ "linux"
915
+ ]
916
+ },
917
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
918
+ "version": "4.53.3",
919
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
920
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
921
+ "cpu": [
922
+ "riscv64"
923
+ ],
924
+ "dev": true,
925
+ "license": "MIT",
926
+ "optional": true,
927
+ "os": [
928
+ "linux"
929
+ ]
930
+ },
931
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
932
+ "version": "4.53.3",
933
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
934
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
935
+ "cpu": [
936
+ "riscv64"
937
+ ],
938
+ "dev": true,
939
+ "license": "MIT",
940
+ "optional": true,
941
+ "os": [
942
+ "linux"
943
+ ]
944
+ },
945
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
946
+ "version": "4.53.3",
947
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
948
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
949
+ "cpu": [
950
+ "s390x"
951
+ ],
952
+ "dev": true,
953
+ "license": "MIT",
954
+ "optional": true,
955
+ "os": [
956
+ "linux"
957
+ ]
958
+ },
959
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
960
+ "version": "4.53.3",
961
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
962
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
963
+ "cpu": [
964
+ "x64"
965
+ ],
966
+ "dev": true,
967
+ "license": "MIT",
968
+ "optional": true,
969
+ "os": [
970
+ "linux"
971
+ ]
972
+ },
973
+ "node_modules/@rollup/rollup-linux-x64-musl": {
974
+ "version": "4.53.3",
975
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
976
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
977
+ "cpu": [
978
+ "x64"
979
+ ],
980
+ "dev": true,
981
+ "license": "MIT",
982
+ "optional": true,
983
+ "os": [
984
+ "linux"
985
+ ]
986
+ },
987
+ "node_modules/@rollup/rollup-openharmony-arm64": {
988
+ "version": "4.53.3",
989
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
990
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
991
+ "cpu": [
992
+ "arm64"
993
+ ],
994
+ "dev": true,
995
+ "license": "MIT",
996
+ "optional": true,
997
+ "os": [
998
+ "openharmony"
999
+ ]
1000
+ },
1001
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
1002
+ "version": "4.53.3",
1003
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
1004
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
1005
+ "cpu": [
1006
+ "arm64"
1007
+ ],
1008
+ "dev": true,
1009
+ "license": "MIT",
1010
+ "optional": true,
1011
+ "os": [
1012
+ "win32"
1013
+ ]
1014
+ },
1015
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
1016
+ "version": "4.53.3",
1017
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
1018
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
1019
+ "cpu": [
1020
+ "ia32"
1021
+ ],
1022
+ "dev": true,
1023
+ "license": "MIT",
1024
+ "optional": true,
1025
+ "os": [
1026
+ "win32"
1027
+ ]
1028
+ },
1029
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
1030
+ "version": "4.53.3",
1031
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
1032
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
1033
+ "cpu": [
1034
+ "x64"
1035
+ ],
1036
+ "dev": true,
1037
+ "license": "MIT",
1038
+ "optional": true,
1039
+ "os": [
1040
+ "win32"
1041
+ ]
1042
+ },
1043
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
1044
+ "version": "4.53.3",
1045
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
1046
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
1047
+ "cpu": [
1048
+ "x64"
1049
+ ],
1050
+ "dev": true,
1051
+ "license": "MIT",
1052
+ "optional": true,
1053
+ "os": [
1054
+ "win32"
1055
+ ]
1056
+ },
1057
+ "node_modules/@types/babel__core": {
1058
+ "version": "7.20.5",
1059
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
1060
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
1061
+ "dev": true,
1062
+ "license": "MIT",
1063
+ "dependencies": {
1064
+ "@babel/parser": "^7.20.7",
1065
+ "@babel/types": "^7.20.7",
1066
+ "@types/babel__generator": "*",
1067
+ "@types/babel__template": "*",
1068
+ "@types/babel__traverse": "*"
1069
+ }
1070
+ },
1071
+ "node_modules/@types/babel__generator": {
1072
+ "version": "7.27.0",
1073
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
1074
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
1075
+ "dev": true,
1076
+ "license": "MIT",
1077
+ "dependencies": {
1078
+ "@babel/types": "^7.0.0"
1079
+ }
1080
+ },
1081
+ "node_modules/@types/babel__template": {
1082
+ "version": "7.4.4",
1083
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
1084
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
1085
+ "dev": true,
1086
+ "license": "MIT",
1087
+ "dependencies": {
1088
+ "@babel/parser": "^7.1.0",
1089
+ "@babel/types": "^7.0.0"
1090
+ }
1091
+ },
1092
+ "node_modules/@types/babel__traverse": {
1093
+ "version": "7.28.0",
1094
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
1095
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
1096
+ "dev": true,
1097
+ "license": "MIT",
1098
+ "dependencies": {
1099
+ "@babel/types": "^7.28.2"
1100
+ }
1101
+ },
1102
+ "node_modules/@types/estree": {
1103
+ "version": "1.0.8",
1104
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1105
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1106
+ "dev": true,
1107
+ "license": "MIT"
1108
+ },
1109
+ "node_modules/@vitejs/plugin-react": {
1110
+ "version": "4.7.0",
1111
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
1112
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
1113
+ "dev": true,
1114
+ "license": "MIT",
1115
+ "dependencies": {
1116
+ "@babel/core": "^7.28.0",
1117
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
1118
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
1119
+ "@rolldown/pluginutils": "1.0.0-beta.27",
1120
+ "@types/babel__core": "^7.20.5",
1121
+ "react-refresh": "^0.17.0"
1122
+ },
1123
+ "engines": {
1124
+ "node": "^14.18.0 || >=16.0.0"
1125
+ },
1126
+ "peerDependencies": {
1127
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
1128
+ }
1129
+ },
1130
+ "node_modules/baseline-browser-mapping": {
1131
+ "version": "2.9.3",
1132
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.3.tgz",
1133
+ "integrity": "sha512-8QdH6czo+G7uBsNo0GiUfouPN1lRzKdJTGnKXwe12gkFbnnOUaUKGN55dMkfy+mnxmvjwl9zcI4VncczcVXDhA==",
1134
+ "dev": true,
1135
+ "license": "Apache-2.0",
1136
+ "bin": {
1137
+ "baseline-browser-mapping": "dist/cli.js"
1138
+ }
1139
+ },
1140
+ "node_modules/browserslist": {
1141
+ "version": "4.28.1",
1142
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
1143
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
1144
+ "dev": true,
1145
+ "funding": [
1146
+ {
1147
+ "type": "opencollective",
1148
+ "url": "https://opencollective.com/browserslist"
1149
+ },
1150
+ {
1151
+ "type": "tidelift",
1152
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1153
+ },
1154
+ {
1155
+ "type": "github",
1156
+ "url": "https://github.com/sponsors/ai"
1157
+ }
1158
+ ],
1159
+ "license": "MIT",
1160
+ "dependencies": {
1161
+ "baseline-browser-mapping": "^2.9.0",
1162
+ "caniuse-lite": "^1.0.30001759",
1163
+ "electron-to-chromium": "^1.5.263",
1164
+ "node-releases": "^2.0.27",
1165
+ "update-browserslist-db": "^1.2.0"
1166
+ },
1167
+ "bin": {
1168
+ "browserslist": "cli.js"
1169
+ },
1170
+ "engines": {
1171
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1172
+ }
1173
+ },
1174
+ "node_modules/caniuse-lite": {
1175
+ "version": "1.0.30001759",
1176
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz",
1177
+ "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==",
1178
+ "dev": true,
1179
+ "funding": [
1180
+ {
1181
+ "type": "opencollective",
1182
+ "url": "https://opencollective.com/browserslist"
1183
+ },
1184
+ {
1185
+ "type": "tidelift",
1186
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1187
+ },
1188
+ {
1189
+ "type": "github",
1190
+ "url": "https://github.com/sponsors/ai"
1191
+ }
1192
+ ],
1193
+ "license": "CC-BY-4.0"
1194
+ },
1195
+ "node_modules/convert-source-map": {
1196
+ "version": "2.0.0",
1197
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1198
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1199
+ "dev": true,
1200
+ "license": "MIT"
1201
+ },
1202
+ "node_modules/debug": {
1203
+ "version": "4.4.3",
1204
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1205
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1206
+ "dev": true,
1207
+ "license": "MIT",
1208
+ "dependencies": {
1209
+ "ms": "^2.1.3"
1210
+ },
1211
+ "engines": {
1212
+ "node": ">=6.0"
1213
+ },
1214
+ "peerDependenciesMeta": {
1215
+ "supports-color": {
1216
+ "optional": true
1217
+ }
1218
+ }
1219
+ },
1220
+ "node_modules/electron-to-chromium": {
1221
+ "version": "1.5.266",
1222
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz",
1223
+ "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==",
1224
+ "dev": true,
1225
+ "license": "ISC"
1226
+ },
1227
+ "node_modules/esbuild": {
1228
+ "version": "0.21.5",
1229
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
1230
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
1231
+ "dev": true,
1232
+ "hasInstallScript": true,
1233
+ "license": "MIT",
1234
+ "bin": {
1235
+ "esbuild": "bin/esbuild"
1236
+ },
1237
+ "engines": {
1238
+ "node": ">=12"
1239
+ },
1240
+ "optionalDependencies": {
1241
+ "@esbuild/aix-ppc64": "0.21.5",
1242
+ "@esbuild/android-arm": "0.21.5",
1243
+ "@esbuild/android-arm64": "0.21.5",
1244
+ "@esbuild/android-x64": "0.21.5",
1245
+ "@esbuild/darwin-arm64": "0.21.5",
1246
+ "@esbuild/darwin-x64": "0.21.5",
1247
+ "@esbuild/freebsd-arm64": "0.21.5",
1248
+ "@esbuild/freebsd-x64": "0.21.5",
1249
+ "@esbuild/linux-arm": "0.21.5",
1250
+ "@esbuild/linux-arm64": "0.21.5",
1251
+ "@esbuild/linux-ia32": "0.21.5",
1252
+ "@esbuild/linux-loong64": "0.21.5",
1253
+ "@esbuild/linux-mips64el": "0.21.5",
1254
+ "@esbuild/linux-ppc64": "0.21.5",
1255
+ "@esbuild/linux-riscv64": "0.21.5",
1256
+ "@esbuild/linux-s390x": "0.21.5",
1257
+ "@esbuild/linux-x64": "0.21.5",
1258
+ "@esbuild/netbsd-x64": "0.21.5",
1259
+ "@esbuild/openbsd-x64": "0.21.5",
1260
+ "@esbuild/sunos-x64": "0.21.5",
1261
+ "@esbuild/win32-arm64": "0.21.5",
1262
+ "@esbuild/win32-ia32": "0.21.5",
1263
+ "@esbuild/win32-x64": "0.21.5"
1264
+ }
1265
+ },
1266
+ "node_modules/escalade": {
1267
+ "version": "3.2.0",
1268
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1269
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1270
+ "dev": true,
1271
+ "license": "MIT",
1272
+ "engines": {
1273
+ "node": ">=6"
1274
+ }
1275
+ },
1276
+ "node_modules/fsevents": {
1277
+ "version": "2.3.3",
1278
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1279
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1280
+ "dev": true,
1281
+ "hasInstallScript": true,
1282
+ "license": "MIT",
1283
+ "optional": true,
1284
+ "os": [
1285
+ "darwin"
1286
+ ],
1287
+ "engines": {
1288
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1289
+ }
1290
+ },
1291
+ "node_modules/gensync": {
1292
+ "version": "1.0.0-beta.2",
1293
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1294
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1295
+ "dev": true,
1296
+ "license": "MIT",
1297
+ "engines": {
1298
+ "node": ">=6.9.0"
1299
+ }
1300
+ },
1301
+ "node_modules/js-tokens": {
1302
+ "version": "4.0.0",
1303
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1304
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1305
+ "license": "MIT"
1306
+ },
1307
+ "node_modules/jsesc": {
1308
+ "version": "3.1.0",
1309
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1310
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1311
+ "dev": true,
1312
+ "license": "MIT",
1313
+ "bin": {
1314
+ "jsesc": "bin/jsesc"
1315
+ },
1316
+ "engines": {
1317
+ "node": ">=6"
1318
+ }
1319
+ },
1320
+ "node_modules/json5": {
1321
+ "version": "2.2.3",
1322
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1323
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1324
+ "dev": true,
1325
+ "license": "MIT",
1326
+ "bin": {
1327
+ "json5": "lib/cli.js"
1328
+ },
1329
+ "engines": {
1330
+ "node": ">=6"
1331
+ }
1332
+ },
1333
+ "node_modules/loose-envify": {
1334
+ "version": "1.4.0",
1335
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
1336
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
1337
+ "license": "MIT",
1338
+ "dependencies": {
1339
+ "js-tokens": "^3.0.0 || ^4.0.0"
1340
+ },
1341
+ "bin": {
1342
+ "loose-envify": "cli.js"
1343
+ }
1344
+ },
1345
+ "node_modules/lru-cache": {
1346
+ "version": "5.1.1",
1347
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1348
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1349
+ "dev": true,
1350
+ "license": "ISC",
1351
+ "dependencies": {
1352
+ "yallist": "^3.0.2"
1353
+ }
1354
+ },
1355
+ "node_modules/ms": {
1356
+ "version": "2.1.3",
1357
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1358
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1359
+ "dev": true,
1360
+ "license": "MIT"
1361
+ },
1362
+ "node_modules/nanoid": {
1363
+ "version": "3.3.11",
1364
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1365
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1366
+ "dev": true,
1367
+ "funding": [
1368
+ {
1369
+ "type": "github",
1370
+ "url": "https://github.com/sponsors/ai"
1371
+ }
1372
+ ],
1373
+ "license": "MIT",
1374
+ "bin": {
1375
+ "nanoid": "bin/nanoid.cjs"
1376
+ },
1377
+ "engines": {
1378
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1379
+ }
1380
+ },
1381
+ "node_modules/node-releases": {
1382
+ "version": "2.0.27",
1383
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
1384
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
1385
+ "dev": true,
1386
+ "license": "MIT"
1387
+ },
1388
+ "node_modules/picocolors": {
1389
+ "version": "1.1.1",
1390
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1391
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1392
+ "dev": true,
1393
+ "license": "ISC"
1394
+ },
1395
+ "node_modules/postcss": {
1396
+ "version": "8.5.6",
1397
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
1398
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
1399
+ "dev": true,
1400
+ "funding": [
1401
+ {
1402
+ "type": "opencollective",
1403
+ "url": "https://opencollective.com/postcss/"
1404
+ },
1405
+ {
1406
+ "type": "tidelift",
1407
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1408
+ },
1409
+ {
1410
+ "type": "github",
1411
+ "url": "https://github.com/sponsors/ai"
1412
+ }
1413
+ ],
1414
+ "license": "MIT",
1415
+ "dependencies": {
1416
+ "nanoid": "^3.3.11",
1417
+ "picocolors": "^1.1.1",
1418
+ "source-map-js": "^1.2.1"
1419
+ },
1420
+ "engines": {
1421
+ "node": "^10 || ^12 || >=14"
1422
+ }
1423
+ },
1424
+ "node_modules/react": {
1425
+ "version": "18.3.1",
1426
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
1427
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
1428
+ "license": "MIT",
1429
+ "dependencies": {
1430
+ "loose-envify": "^1.1.0"
1431
+ },
1432
+ "engines": {
1433
+ "node": ">=0.10.0"
1434
+ }
1435
+ },
1436
+ "node_modules/react-dom": {
1437
+ "version": "18.3.1",
1438
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
1439
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
1440
+ "license": "MIT",
1441
+ "dependencies": {
1442
+ "loose-envify": "^1.1.0",
1443
+ "scheduler": "^0.23.2"
1444
+ },
1445
+ "peerDependencies": {
1446
+ "react": "^18.3.1"
1447
+ }
1448
+ },
1449
+ "node_modules/react-refresh": {
1450
+ "version": "0.17.0",
1451
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
1452
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
1453
+ "dev": true,
1454
+ "license": "MIT",
1455
+ "engines": {
1456
+ "node": ">=0.10.0"
1457
+ }
1458
+ },
1459
+ "node_modules/rollup": {
1460
+ "version": "4.53.3",
1461
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
1462
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
1463
+ "dev": true,
1464
+ "license": "MIT",
1465
+ "dependencies": {
1466
+ "@types/estree": "1.0.8"
1467
+ },
1468
+ "bin": {
1469
+ "rollup": "dist/bin/rollup"
1470
+ },
1471
+ "engines": {
1472
+ "node": ">=18.0.0",
1473
+ "npm": ">=8.0.0"
1474
+ },
1475
+ "optionalDependencies": {
1476
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
1477
+ "@rollup/rollup-android-arm64": "4.53.3",
1478
+ "@rollup/rollup-darwin-arm64": "4.53.3",
1479
+ "@rollup/rollup-darwin-x64": "4.53.3",
1480
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
1481
+ "@rollup/rollup-freebsd-x64": "4.53.3",
1482
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
1483
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
1484
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
1485
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
1486
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
1487
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
1488
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
1489
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
1490
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
1491
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
1492
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
1493
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
1494
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
1495
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
1496
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
1497
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
1498
+ "fsevents": "~2.3.2"
1499
+ }
1500
+ },
1501
+ "node_modules/scheduler": {
1502
+ "version": "0.23.2",
1503
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
1504
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
1505
+ "license": "MIT",
1506
+ "dependencies": {
1507
+ "loose-envify": "^1.1.0"
1508
+ }
1509
+ },
1510
+ "node_modules/semver": {
1511
+ "version": "6.3.1",
1512
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
1513
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1514
+ "dev": true,
1515
+ "license": "ISC",
1516
+ "bin": {
1517
+ "semver": "bin/semver.js"
1518
+ }
1519
+ },
1520
+ "node_modules/source-map-js": {
1521
+ "version": "1.2.1",
1522
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1523
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1524
+ "dev": true,
1525
+ "license": "BSD-3-Clause",
1526
+ "engines": {
1527
+ "node": ">=0.10.0"
1528
+ }
1529
+ },
1530
+ "node_modules/update-browserslist-db": {
1531
+ "version": "1.2.2",
1532
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz",
1533
+ "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==",
1534
+ "dev": true,
1535
+ "funding": [
1536
+ {
1537
+ "type": "opencollective",
1538
+ "url": "https://opencollective.com/browserslist"
1539
+ },
1540
+ {
1541
+ "type": "tidelift",
1542
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1543
+ },
1544
+ {
1545
+ "type": "github",
1546
+ "url": "https://github.com/sponsors/ai"
1547
+ }
1548
+ ],
1549
+ "license": "MIT",
1550
+ "dependencies": {
1551
+ "escalade": "^3.2.0",
1552
+ "picocolors": "^1.1.1"
1553
+ },
1554
+ "bin": {
1555
+ "update-browserslist-db": "cli.js"
1556
+ },
1557
+ "peerDependencies": {
1558
+ "browserslist": ">= 4.21.0"
1559
+ }
1560
+ },
1561
+ "node_modules/vite": {
1562
+ "version": "5.4.21",
1563
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
1564
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
1565
+ "dev": true,
1566
+ "license": "MIT",
1567
+ "dependencies": {
1568
+ "esbuild": "^0.21.3",
1569
+ "postcss": "^8.4.43",
1570
+ "rollup": "^4.20.0"
1571
+ },
1572
+ "bin": {
1573
+ "vite": "bin/vite.js"
1574
+ },
1575
+ "engines": {
1576
+ "node": "^18.0.0 || >=20.0.0"
1577
+ },
1578
+ "funding": {
1579
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1580
+ },
1581
+ "optionalDependencies": {
1582
+ "fsevents": "~2.3.3"
1583
+ },
1584
+ "peerDependencies": {
1585
+ "@types/node": "^18.0.0 || >=20.0.0",
1586
+ "less": "*",
1587
+ "lightningcss": "^1.21.0",
1588
+ "sass": "*",
1589
+ "sass-embedded": "*",
1590
+ "stylus": "*",
1591
+ "sugarss": "*",
1592
+ "terser": "^5.4.0"
1593
+ },
1594
+ "peerDependenciesMeta": {
1595
+ "@types/node": {
1596
+ "optional": true
1597
+ },
1598
+ "less": {
1599
+ "optional": true
1600
+ },
1601
+ "lightningcss": {
1602
+ "optional": true
1603
+ },
1604
+ "sass": {
1605
+ "optional": true
1606
+ },
1607
+ "sass-embedded": {
1608
+ "optional": true
1609
+ },
1610
+ "stylus": {
1611
+ "optional": true
1612
+ },
1613
+ "sugarss": {
1614
+ "optional": true
1615
+ },
1616
+ "terser": {
1617
+ "optional": true
1618
+ }
1619
+ }
1620
+ },
1621
+ "node_modules/yallist": {
1622
+ "version": "3.1.1",
1623
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
1624
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
1625
+ "dev": true,
1626
+ "license": "ISC"
1627
+ }
1628
+ }
1629
+ }
frontend/package.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "mri-diagnosis-frontend",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "dependencies": {
11
+ "react": "^18.2.0",
12
+ "react-dom": "^18.2.0"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-react": "^4.2.0",
16
+ "vite": "^5.0.0"
17
+ }
18
+ }
frontend/src/App.jsx ADDED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // frontend/src/App.jsx
2
+ import React, { useState, useCallback, useMemo } from 'react';
3
+
4
+ // Inline SVG Icons
5
+ const IconUpload = (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/></svg>;
6
+ const IconCpu = (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="4" y="4" width="16" height="16" rx="2" ry="2"/><rect x="9" y="9" width="6" height="6"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M20 15h2"/></svg>;
7
+ const IconCheckCircle = (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>;
8
+ const IconXCircle = (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>;
9
+ const IconLoader = (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2v4"/><path d="m16.2 7.8 2.8-2.8"/><path d="M18 12h4"/><path d="m19.72 19.72-2.8-2.8"/><path d="M12 18v4"/><path d="m4.22 19.78 2.8-2.8"/><path d="M2 12h4"/><path d="m4.22 4.22 2.8 2.8"/></svg>;
10
+ const IconBrain = (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2a2 2 0 0 0-2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 0-2-2"/><path d="M20 2a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2"/></svg>;
11
+ const IconAlertTriangle = (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" x2="12" y1="9" y2="13"/><line x1="12" x2="12.01" y1="17" y2="17"/></svg>;
12
+
13
+ const App = () => {
14
+ const [selectedFile, setSelectedFile] = useState(null);
15
+ const [previewUrl, setPreviewUrl] = useState('');
16
+ const [prediction, setPrediction] = useState(null);
17
+ const [loading, setLoading] = useState(false);
18
+ const [error, setError] = useState('');
19
+
20
+ // Get API URL from environment or default to localhost
21
+ // const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
22
+ // const API_URL = import.meta.env.VITE_API_URL;
23
+ const API_URL = import.meta.env.VITE_API_URL || 'https://mri-diagnosis-app.hf.space';
24
+
25
+ if (!API_URL) {
26
+ // Log a severe error if the necessary environment variable is missing
27
+ console.error("VITE_API_URL is not set. API communication will fail.");
28
+ }
29
+
30
+ const CLASS_INFO = useMemo(() => ({
31
+ glioma: { name: "Glioma Tumor (Malignant)", color: "#dc2626", bg: "#fef2f2", icon: <IconAlertTriangle className="icon-sm" /> },
32
+ meningioma: { name: "Meningioma Tumor (Benign)", color: "#ea580c", bg: "#fff7ed", icon: <IconAlertTriangle className="icon-sm" /> },
33
+ pituitary: { name: "Pituitary Tumor (Benign)", color: "#ca8a04", bg: "#fefce8", icon: <IconAlertTriangle className="icon-sm" /> },
34
+ notumor: { name: "No Tumor Detected", color: "#16a34a", bg: "#f0fdf4", icon: <IconCheckCircle className="icon-sm" /> },
35
+ }), []);
36
+
37
+ const handleFileChange = useCallback((event) => {
38
+ const file = event.target.files[0];
39
+ if (file) {
40
+ setSelectedFile(file);
41
+ setPreviewUrl(URL.createObjectURL(file));
42
+ setPrediction(null);
43
+ setError('');
44
+ }
45
+ }, []);
46
+
47
+ const handleSubmit = async (event) => {
48
+ event.preventDefault();
49
+
50
+ if (!selectedFile) {
51
+ setError('Please select an MRI image file.');
52
+ return;
53
+ }
54
+
55
+ setLoading(true);
56
+ setPrediction(null);
57
+ setError('');
58
+
59
+ const formData = new FormData();
60
+ formData.append('mriImage', selectedFile);
61
+
62
+ try {
63
+ const response = await fetch(`${API_URL}/api/predict`, {
64
+ method: 'POST',
65
+ body: formData,
66
+ });
67
+
68
+ const data = await response.json();
69
+
70
+ if (!response.ok) {
71
+ throw new Error(data.detail || data.error || 'Unknown server error.');
72
+ }
73
+
74
+ setPrediction(data);
75
+ } catch (err) {
76
+ console.error('Prediction failed:', err);
77
+ setError(`Prediction failed: ${err.message}`);
78
+ } finally {
79
+ setLoading(false);
80
+ }
81
+ };
82
+
83
+ const ResultDisplay = ({ result }) => {
84
+ const info = CLASS_INFO[result.predicted_class.toLowerCase()] || CLASS_INFO.notumor;
85
+ const confidencePercent = (result.confidence * 100).toFixed(2);
86
+
87
+ return (
88
+ <div
89
+ className="result-card"
90
+ style={{
91
+ backgroundColor: info.bg,
92
+ borderLeftColor: info.color
93
+ }}
94
+ >
95
+ <h3 className="result-title">
96
+ {info.icon}
97
+ <span className="result-title-text">Diagnosis Result</span>
98
+ </h3>
99
+ <div className="result-details">
100
+ <div className="result-row result-row-border">
101
+ <span className="result-label">Predicted Class:</span>
102
+ <span className="result-value" style={{ color: info.color }}>{info.name}</span>
103
+ </div>
104
+ <div className="result-row">
105
+ <span className="result-label">Confidence Score:</span>
106
+ <span className="result-value result-value-indigo">{confidencePercent}%</span>
107
+ </div>
108
+ </div>
109
+
110
+ <div className="confidence-bar-container">
111
+ <div
112
+ className="confidence-bar-fill"
113
+ style={{
114
+ width: `${confidencePercent}%`,
115
+ backgroundColor: info.color
116
+ }}
117
+ ></div>
118
+ </div>
119
+ </div>
120
+ );
121
+ };
122
+
123
+ return (
124
+ <div className="app-container">
125
+ <style>{`
126
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
127
+
128
+ :root {
129
+ --color-indigo-600: #4f46e5;
130
+ --color-indigo-700: #4338ca;
131
+ --color-gray-900: #111827;
132
+ --color-gray-500: #6b7280;
133
+ --color-gray-300: #d1d5db;
134
+ --color-white: #ffffff;
135
+ --color-gray-100: #f3f4f6;
136
+ --color-gray-50: #f9fafb;
137
+ --color-red-700: #b91c1c;
138
+ --color-red-100: #fee2e2;
139
+ }
140
+
141
+ * { margin: 0; padding: 0; box-sizing: border-box; }
142
+
143
+ .app-container {
144
+ min-height: 100vh;
145
+ background-color: var(--color-gray-100);
146
+ padding: 1rem;
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ font-family: 'Inter', sans-serif;
151
+ }
152
+
153
+ .main-card {
154
+ width: 100%;
155
+ max-width: 900px;
156
+ background-color: var(--color-white);
157
+ border-radius: 1.5rem;
158
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
159
+ padding: 2.5rem;
160
+ border-top: 8px solid var(--color-indigo-600);
161
+ }
162
+ @media (max-width: 768px) { .main-card { padding: 1.5rem; } }
163
+
164
+ .header {
165
+ text-align: center;
166
+ margin-bottom: 2.5rem;
167
+ }
168
+ .header-icon {
169
+ color: var(--color-indigo-600);
170
+ width: 3rem;
171
+ height: 3rem;
172
+ margin: 0 auto 0.75rem;
173
+ stroke-width: 2.5;
174
+ }
175
+ .header-title {
176
+ font-size: 2.25rem;
177
+ font-weight: 800;
178
+ color: var(--color-gray-900);
179
+ letter-spacing: -0.025em;
180
+ }
181
+ .header-subtitle {
182
+ margin-top: 0.5rem;
183
+ font-size: 1.125rem;
184
+ color: var(--color-gray-500);
185
+ }
186
+
187
+ .form-grid {
188
+ display: grid;
189
+ grid-template-columns: 1fr;
190
+ gap: 2rem;
191
+ }
192
+ @media (min-width: 768px) {
193
+ .form-grid { grid-template-columns: repeat(2, 1fr); }
194
+ }
195
+
196
+ .section-upload {
197
+ padding: 1rem;
198
+ border: 2px dashed var(--color-gray-300);
199
+ border-radius: 0.75rem;
200
+ transition: border-color 200ms ease-in-out;
201
+ }
202
+ .section-upload:hover { border-color: #6366f1; }
203
+ .section-results {
204
+ padding: 1rem;
205
+ background-color: var(--color-gray-50);
206
+ border-radius: 0.75rem;
207
+ box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);
208
+ }
209
+
210
+ .section-title {
211
+ font-size: 1.25rem;
212
+ font-weight: 600;
213
+ color: var(--color-indigo-700);
214
+ margin-bottom: 1rem;
215
+ display: flex;
216
+ align-items: center;
217
+ }
218
+ .section-title-results { color: var(--color-gray-800); }
219
+ .section-title-results > svg { color: var(--color-indigo-700); }
220
+ .section-title > svg {
221
+ width: 1.25rem;
222
+ height: 1.25rem;
223
+ margin-right: 0.5rem;
224
+ }
225
+
226
+ .upload-label {
227
+ cursor: pointer;
228
+ display: block;
229
+ width: 100%;
230
+ text-align: center;
231
+ padding: 2.5rem 0;
232
+ background-color: var(--color-gray-50);
233
+ border-radius: 0.5rem;
234
+ transition: background-color 200ms ease-in-out;
235
+ }
236
+ .upload-label:hover { background-color: var(--color-gray-100); }
237
+ .upload-text-strong {
238
+ font-weight: 500;
239
+ color: #4b5563;
240
+ }
241
+ .upload-text-file {
242
+ color: var(--color-indigo-600);
243
+ font-weight: 700;
244
+ }
245
+ .upload-text-muted {
246
+ color: #9ca3af;
247
+ font-size: 0.875rem;
248
+ margin-top: 0.25rem;
249
+ }
250
+ .upload-icon {
251
+ width: 2rem;
252
+ height: 2rem;
253
+ margin: 0 auto 0.5rem;
254
+ color: #9ca3af;
255
+ }
256
+
257
+ .submit-button {
258
+ width: 100%;
259
+ margin-top: 1rem;
260
+ padding: 0.75rem 0;
261
+ border-radius: 0.75rem;
262
+ font-weight: 700;
263
+ font-size: 1.125rem;
264
+ transition: all 300ms ease-in-out;
265
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.06);
266
+ display: flex;
267
+ align-items: center;
268
+ justify-content: center;
269
+ color: var(--color-white);
270
+ background-color: var(--color-indigo-600);
271
+ border: none;
272
+ cursor: pointer;
273
+ }
274
+ .submit-button:hover:not(:disabled) {
275
+ background-color: var(--color-indigo-700);
276
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.05);
277
+ }
278
+ .submit-button:disabled {
279
+ background-color: #a5b4fc;
280
+ cursor: not-allowed;
281
+ }
282
+ .submit-button > svg {
283
+ width: 1.25rem;
284
+ height: 1.25rem;
285
+ margin-right: 0.5rem;
286
+ }
287
+
288
+ .preview-container { margin-bottom: 1rem; }
289
+ .preview-label {
290
+ font-size: 1rem;
291
+ font-weight: 500;
292
+ color: #4b5563;
293
+ margin-bottom: 0.5rem;
294
+ }
295
+ .preview-box {
296
+ width: 100%;
297
+ height: 12rem;
298
+ background-color: #e5e7eb;
299
+ border-radius: 0.5rem;
300
+ overflow: hidden;
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ border: 1px solid var(--color-gray-300);
305
+ }
306
+ .preview-placeholder { color: #9ca3af; }
307
+ .preview-image {
308
+ object-fit: cover;
309
+ width: 100%;
310
+ height: 100%;
311
+ }
312
+
313
+ .status-message {
314
+ padding: 0.75rem;
315
+ border-radius: 0.5rem;
316
+ display: flex;
317
+ align-items: center;
318
+ font-weight: 500;
319
+ }
320
+ .status-message > svg {
321
+ width: 1.25rem;
322
+ height: 1.25rem;
323
+ margin-right: 0.5rem;
324
+ }
325
+ .status-error {
326
+ background-color: var(--color-red-100);
327
+ color: var(--color-red-700);
328
+ }
329
+ .status-info {
330
+ background-color: #e0e7ff;
331
+ color: var(--color-indigo-700);
332
+ }
333
+
334
+ .result-card {
335
+ margin-top: 2rem;
336
+ padding: 1.5rem;
337
+ border-radius: 0.75rem;
338
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
339
+ transition: all 300ms ease-in-out;
340
+ border-left-width: 8px;
341
+ border-style: solid;
342
+ }
343
+ .result-title {
344
+ font-size: 1.5rem;
345
+ font-weight: 700;
346
+ margin-bottom: 1rem;
347
+ display: flex;
348
+ align-items: center;
349
+ }
350
+ .result-title-text {
351
+ margin-left: 0.75rem;
352
+ color: var(--color-gray-800);
353
+ }
354
+ .result-details {
355
+ display: flex;
356
+ flex-direction: column;
357
+ gap: 0.5rem;
358
+ }
359
+ .result-row {
360
+ display: flex;
361
+ justify-content: space-between;
362
+ align-items: center;
363
+ padding: 0.5rem 0;
364
+ }
365
+ .result-row-border { border-bottom: 1px solid var(--color-gray-300); }
366
+ .result-label {
367
+ font-size: 1.125rem;
368
+ font-weight: 600;
369
+ color: #4b5563;
370
+ }
371
+ .result-value {
372
+ font-size: 1.25rem;
373
+ font-weight: 800;
374
+ }
375
+ .result-value-indigo {
376
+ font-weight: 700;
377
+ color: var(--color-indigo-700);
378
+ }
379
+
380
+ .confidence-bar-container {
381
+ width: 100%;
382
+ height: 0.75rem;
383
+ margin-top: 1rem;
384
+ border-radius: 9999px;
385
+ background-color: #e5e7eb;
386
+ overflow: hidden;
387
+ }
388
+ .confidence-bar-fill {
389
+ height: 100%;
390
+ transition: width 500ms ease-out;
391
+ }
392
+
393
+ .loader-spin {
394
+ animation: spin 1s linear infinite;
395
+ }
396
+ @keyframes spin {
397
+ from { transform: rotate(0deg); }
398
+ to { transform: rotate(360deg); }
399
+ }
400
+
401
+ .icon-sm {
402
+ width: 1.25rem;
403
+ height: 1.25rem;
404
+ }
405
+ `}</style>
406
+
407
+ <div className="main-card">
408
+ <header className="header">
409
+ <div className="header-icon mx-auto mb-3">
410
+ <IconBrain className="header-icon" strokeWidth={2.5} />
411
+ </div>
412
+ <h1 className="header-title">
413
+ MRI Neuro-Diagnosis System
414
+ </h1>
415
+ <p className="header-subtitle">
416
+ Upload an MRI image for automated brain tumor classification.
417
+ </p>
418
+ </header>
419
+
420
+ <form onSubmit={handleSubmit} className="form-grid">
421
+ <section className="section-upload">
422
+ <h2 className="section-title">
423
+ <IconUpload className="icon-sm mr-2" />
424
+ 1. Upload MRI Scan
425
+ </h2>
426
+
427
+ <label htmlFor="file-upload" className="upload-label">
428
+ {selectedFile ? (
429
+ <p className="upload-text-strong">File Selected: <span className="upload-text-file">{selectedFile.name}</span></p>
430
+ ) : (
431
+ <>
432
+ <IconUpload className="upload-icon" />
433
+ <p className="upload-text-strong" style={{ color: '#4b5563' }}>Click to upload or drag & drop</p>
434
+ <p className="upload-text-muted">PNG, JPG, or JPEG (Max 10MB)</p>
435
+ </>
436
+ )}
437
+ </label>
438
+ <input
439
+ id="file-upload"
440
+ type="file"
441
+ onChange={handleFileChange}
442
+ accept="image/*"
443
+ style={{ display: 'none' }}
444
+ />
445
+
446
+ <button type="submit" disabled={loading || !selectedFile} className="submit-button">
447
+ {loading ? (
448
+ <>
449
+ <IconLoader className="icon-sm mr-2 loader-spin" />
450
+ Analyzing...
451
+ </>
452
+ ) : (
453
+ <>
454
+ <IconCpu className="icon-sm mr-2" />
455
+ Run Diagnosis
456
+ </>
457
+ )}
458
+ </button>
459
+ </section>
460
+
461
+ <section className="section-results">
462
+ <h2 className="section-title section-title-results">
463
+ <IconCheckCircle className="icon-sm mr-2" />
464
+ 2. Review & Diagnosis
465
+ </h2>
466
+
467
+ <div className="preview-container">
468
+ <h3 className="preview-label">Image Preview:</h3>
469
+ <div className="preview-box">
470
+ {previewUrl ? (
471
+ <img src={previewUrl} alt="MRI Preview" className="preview-image" />
472
+ ) : (
473
+ <p className="preview-placeholder">Preview Area</p>
474
+ )}
475
+ </div>
476
+ </div>
477
+
478
+ {error && (
479
+ <div className="status-message status-error">
480
+ <IconXCircle className="icon-sm mr-2" />
481
+ <span style={{ fontWeight: 700 }}>Error:</span> {error}
482
+ </div>
483
+ )}
484
+
485
+ {prediction && <ResultDisplay result={prediction} />}
486
+
487
+ {!loading && !prediction && !error && (
488
+ <div className="status-message status-info">
489
+ <IconBrain className="icon-sm mr-2" />
490
+ Upload an image and click "Run Diagnosis" to begin analysis.
491
+ </div>
492
+ )}
493
+ </section>
494
+ </form>
495
+ </div>
496
+ </div>
497
+ );
498
+ };
499
+
500
+ export default App;
frontend/src/index.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ // frontend/src/index.jsx
2
+ import React from 'react'
3
+ import ReactDOM from 'react-dom/client'
4
+ import App from './App'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ )
frontend/vite.config.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // frontend/vite.config.js
2
+ import { defineConfig } from 'vite'
3
+ import react from '@vitejs/plugin-react'
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ server: {
8
+ port: 3000,
9
+ proxy: {
10
+ '/api': {
11
+ target: 'http://localhost:8000',
12
+ changeOrigin: true,
13
+ }
14
+ }
15
+ }
16
+ })