Spaces:
Paused
Paused
Himanshu Gangwar
commited on
Commit
·
eff8aa5
1
Parent(s):
85c810b
initial commit
Browse files- .env.example +14 -0
- .gitignore +4 -0
- HF_DEPLOYMENT_CHECKLIST.md +156 -0
- README.md +114 -13
- README_HF.md +158 -0
- app.py +194 -0
- backend/.env.example +2 -0
- backend/app/agents/graph.py +424 -0
- backend/app/api/routes.py +152 -0
- backend/app/core/config.py +11 -0
- backend/app/db/database.py +85 -0
- backend/app/main.py +31 -0
- backend/app/services/analytics.py +332 -0
- backend/app/services/csv_loader.py +91 -0
- backend/app/services/pdf_generator.py +451 -0
- backend/create_db.py +57 -0
- backend/requirements.txt +17 -0
- build_frontend.bat +19 -0
- build_frontend.sh +19 -0
- data/archive.zip +3 -0
- data/report_f86d7e18-bf33-4d6f-a4fc-e57eb10590f7.pdf +97 -0
- data/sales_data_sample.csv +0 -0
- docs/agent-workflow.drawio +52 -0
- docs/agent-workflow.svg +45 -0
- frontend/.gitignore +24 -0
- frontend/README.md +16 -0
- frontend/eslint.config.js +29 -0
- frontend/index.html +13 -0
- frontend/package-lock.json +2675 -0
- frontend/package.json +30 -0
- frontend/public/vite.svg +1 -0
- frontend/src/App.css +885 -0
- frontend/src/App.jsx +546 -0
- frontend/src/assets/react.svg +1 -0
- frontend/src/index.css +13 -0
- frontend/src/main.jsx +10 -0
- frontend/vite.config.js +7 -0
- requirements.txt +24 -0
- setup_hf_deployment.sh +52 -0
.env.example
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment Variables for InsightPilot
|
| 2 |
+
|
| 3 |
+
# Required: Groq API Key for LLM access
|
| 4 |
+
# Get your key from: https://console.groq.com/
|
| 5 |
+
GROQ_API_KEY=your_groq_api_key_here
|
| 6 |
+
|
| 7 |
+
# Optional: Database URL (defaults to SQLite)
|
| 8 |
+
# For SQLite (default): sqlite:///./test.db
|
| 9 |
+
# For PostgreSQL: postgresql://user:password@host:port/dbname
|
| 10 |
+
DATABASE_URL=sqlite:///./test.db
|
| 11 |
+
|
| 12 |
+
# Optional: Configure other settings
|
| 13 |
+
# DEBUG=false
|
| 14 |
+
# LOG_LEVEL=info
|
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
.venv/
|
| 3 |
+
backend/.env
|
| 4 |
+
backend/backend/static
|
HF_DEPLOYMENT_CHECKLIST.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚀 HF Spaces Deployment Checklist
|
| 2 |
+
|
| 3 |
+
Use this checklist to ensure smooth deployment to Hugging Face Spaces.
|
| 4 |
+
|
| 5 |
+
## Pre-Deployment
|
| 6 |
+
|
| 7 |
+
- [ ] **Get Groq API Key**
|
| 8 |
+
- Sign up at https://console.groq.com/
|
| 9 |
+
- Generate API key
|
| 10 |
+
- Keep it safe for Step 5
|
| 11 |
+
|
| 12 |
+
- [ ] **Create HF Account**
|
| 13 |
+
- Sign up at https://huggingface.co/join
|
| 14 |
+
- Verify email
|
| 15 |
+
|
| 16 |
+
- [ ] **Build Frontend (Optional)**
|
| 17 |
+
- Windows: Run `build_frontend.bat`
|
| 18 |
+
- Mac/Linux: Run `./build_frontend.sh`
|
| 19 |
+
- Verify `frontend/dist/` folder exists
|
| 20 |
+
|
| 21 |
+
## Deployment Steps
|
| 22 |
+
|
| 23 |
+
### 1. Create New Space
|
| 24 |
+
- [ ] Go to https://huggingface.co/new-space
|
| 25 |
+
- [ ] Set Space name (e.g., `insightpilot`)
|
| 26 |
+
- [ ] Choose SDK: **Gradio**
|
| 27 |
+
- [ ] Choose License: **MIT**
|
| 28 |
+
- [ ] Choose Hardware: **CPU basic** (free)
|
| 29 |
+
- [ ] Set Visibility: Public or Private
|
| 30 |
+
- [ ] Click **Create Space**
|
| 31 |
+
|
| 32 |
+
### 2. Prepare Files
|
| 33 |
+
- [ ] Copy/create these files:
|
| 34 |
+
```
|
| 35 |
+
✅ app.py
|
| 36 |
+
✅ requirements.txt
|
| 37 |
+
✅ README_HF.md → rename to README.md
|
| 38 |
+
✅ backend/ (entire folder)
|
| 39 |
+
✅ data/ (optional - sample data)
|
| 40 |
+
✅ frontend/dist/ (optional - if built)
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### 3. Upload to Space
|
| 44 |
+
|
| 45 |
+
**Option A: Git (Recommended)**
|
| 46 |
+
- [ ] Clone your Space:
|
| 47 |
+
```bash
|
| 48 |
+
git clone https://huggingface.co/spaces/YOUR_USERNAME/SPACE_NAME
|
| 49 |
+
cd SPACE_NAME
|
| 50 |
+
```
|
| 51 |
+
- [ ] Copy files into the cloned directory
|
| 52 |
+
- [ ] Add files:
|
| 53 |
+
```bash
|
| 54 |
+
git add .
|
| 55 |
+
git commit -m "Initial deployment of InsightPilot"
|
| 56 |
+
git push
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
**Option B: Web Upload**
|
| 60 |
+
- [ ] Go to your Space page
|
| 61 |
+
- [ ] Click **Files** → **Add file** → **Upload files**
|
| 62 |
+
- [ ] Drag and drop all files
|
| 63 |
+
- [ ] Commit changes
|
| 64 |
+
|
| 65 |
+
### 4. Configure Space
|
| 66 |
+
|
| 67 |
+
- [ ] Go to **Settings** → **Repository secrets**
|
| 68 |
+
- [ ] Click **New secret**
|
| 69 |
+
- [ ] Add:
|
| 70 |
+
- Name: `GROQ_API_KEY`
|
| 71 |
+
- Value: Your Groq API key
|
| 72 |
+
- [ ] Click **Add**
|
| 73 |
+
|
| 74 |
+
### 5. Monitor Build
|
| 75 |
+
|
| 76 |
+
- [ ] Go to **Logs** tab
|
| 77 |
+
- [ ] Wait for build to complete (3-5 minutes)
|
| 78 |
+
- [ ] Check for errors
|
| 79 |
+
- [ ] Wait for "Running" status
|
| 80 |
+
|
| 81 |
+
### 6. Test Deployment
|
| 82 |
+
|
| 83 |
+
- [ ] Visit your Space URL
|
| 84 |
+
- [ ] Test **API Access** tab:
|
| 85 |
+
- [ ] Enter a question
|
| 86 |
+
- [ ] Click Analyze
|
| 87 |
+
- [ ] Verify response
|
| 88 |
+
|
| 89 |
+
- [ ] Test **Upload Dataset** tab:
|
| 90 |
+
- [ ] Upload a CSV
|
| 91 |
+
- [ ] Verify success message
|
| 92 |
+
|
| 93 |
+
- [ ] Test **Analytics Dashboard** (if frontend built):
|
| 94 |
+
- [ ] Check UI loads
|
| 95 |
+
- [ ] Try sample questions
|
| 96 |
+
- [ ] Verify visualizations
|
| 97 |
+
- [ ] Download PDF report
|
| 98 |
+
|
| 99 |
+
## Post-Deployment
|
| 100 |
+
|
| 101 |
+
### Optional Enhancements
|
| 102 |
+
|
| 103 |
+
- [ ] **Add README badge** to your GitHub repo:
|
| 104 |
+
```markdown
|
| 105 |
+
[](https://huggingface.co/spaces/YOUR_USERNAME/SPACE_NAME)
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
- [ ] **Enable Analytics**
|
| 109 |
+
- Settings → Enable analytics
|
| 110 |
+
- Track visitor count
|
| 111 |
+
|
| 112 |
+
- [ ] **Customize Space**
|
| 113 |
+
- Edit README.md for better description
|
| 114 |
+
- Add screenshots
|
| 115 |
+
- Update emoji/colors in metadata
|
| 116 |
+
|
| 117 |
+
- [ ] **Set up monitoring**
|
| 118 |
+
- Check Logs regularly
|
| 119 |
+
- Monitor Usage in Settings
|
| 120 |
+
|
| 121 |
+
### Troubleshooting Common Issues
|
| 122 |
+
|
| 123 |
+
- [ ] **Build failed**: Check `requirements.txt` syntax
|
| 124 |
+
- [ ] **Import errors**: Verify all backend files uploaded
|
| 125 |
+
- [ ] **GROQ_API_KEY error**: Check secret name matches exactly
|
| 126 |
+
- [ ] **Frontend not loading**: Verify `frontend/dist/` uploaded
|
| 127 |
+
- [ ] **Port errors**: App uses 7860 (HF default)
|
| 128 |
+
- [ ] **Database errors**: Check file paths in backend
|
| 129 |
+
|
| 130 |
+
## Success Indicators
|
| 131 |
+
|
| 132 |
+
✅ Space shows "Running" status
|
| 133 |
+
✅ No errors in Logs
|
| 134 |
+
✅ Can ask questions and get responses
|
| 135 |
+
✅ Can upload CSV files
|
| 136 |
+
✅ Can download PDF reports
|
| 137 |
+
✅ Visualizations appear correctly
|
| 138 |
+
|
| 139 |
+
## Share Your Space
|
| 140 |
+
|
| 141 |
+
Once deployed, share at:
|
| 142 |
+
```
|
| 143 |
+
https://huggingface.co/spaces/YOUR_USERNAME/SPACE_NAME
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
Add to:
|
| 147 |
+
- [ ] GitHub README
|
| 148 |
+
- [ ] LinkedIn
|
| 149 |
+
- [ ] Twitter
|
| 150 |
+
- [ ] Portfolio
|
| 151 |
+
|
| 152 |
+
---
|
| 153 |
+
|
| 154 |
+
**🎉 Congratulations on deploying InsightPilot to Hugging Face Spaces!**
|
| 155 |
+
|
| 156 |
+
Need help? See `DEPLOYMENT.md` for detailed instructions.
|
README.md
CHANGED
|
@@ -1,13 +1,114 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# InsightPilot – Autonomous Analytics Agent
|
| 2 |
+
|
| 3 |
+
InsightPilot is a production-style AI analyst that turns natural-language business questions into validated SQL, visualizations, insights, and executive-ready PDF reports. The stack combines LangGraph (deterministic agent workflow), FastAPI, SQLAlchemy, ReportLab, and a modern React dashboard.
|
| 4 |
+
|
| 5 |
+
## Project Highlights
|
| 6 |
+
|
| 7 |
+
- **Agentic LangGraph pipeline** – deterministic tool-calling chain (intent → schema → NL2SQL → execution → diagnostics → visualization → PDF) keeps results auditable.
|
| 8 |
+
- **Advanced analytics automation** – dedicated trend and anomaly modules mine every result set and stream context-aware summaries to the UI and PDF.
|
| 9 |
+
- **ReportLab title-page exports** – branded title page, SQL appendix, visuals, and analytics sections ready for exec briefings.
|
| 10 |
+
- **Draw.io workflow assets** – editable diagram (`docs/agent-workflow.drawio`) plus lightweight SVG for README embeds.
|
| 11 |
+
- **Reusable data ingestion** – dataset catalog + multi-table CSV upload lets you retarget the agent to any warehouse quickly.
|
| 12 |
+
- **Groq Llama‑3 tooling** – low-latency NL→SQL and narrative insight generation.
|
| 13 |
+
|
| 14 |
+
## Workflow Diagram (Draw.io)
|
| 15 |
+
|
| 16 |
+

|
| 17 |
+
|
| 18 |
+
> Edit the source diagram at `docs/agent-workflow.drawio` in Draw.io/Lucidchart and export a fresh SVG/PNG if the workflow changes.
|
| 19 |
+
|
| 20 |
+
## Advanced Analytics Modules
|
| 21 |
+
|
| 22 |
+
| Module | What it does | Output surfaces |
|
| 23 |
+
| --- | --- | --- |
|
| 24 |
+
| **Trend detection** | Converts the query result into a monthly time series, fits a regression, and quantifies direction, slope, and % change. | Chat insights, Latest Analysis cards, PDF “Trend Diagnostics”. |
|
| 25 |
+
| **Anomaly detection** | Computes z-scores across the same period and flags statistically significant outliers. | Latest Analysis cards, PDF “Anomaly Highlights”. |
|
| 26 |
+
|
| 27 |
+
The insight LLM receives both diagnostics, so follow-up prompts remain grounded in prior conclusions.
|
| 28 |
+
|
| 29 |
+
## Architecture
|
| 30 |
+
|
| 31 |
+
| Layer | Tech | Notes |
|
| 32 |
+
| --- | --- | --- |
|
| 33 |
+
| API & Orchestration | FastAPI + LangGraph | Defines graph nodes for schema, NL2SQL, execution, viz, insight, and report building. |
|
| 34 |
+
| LLM | Groq `llama3-70b-8192` | Deterministic, low-latency NL2SQL + insight generation. |
|
| 35 |
+
| Data | SQLite via SQLAlchemy | Auto-generated `sales` dataset for local testing. |
|
| 36 |
+
| Visualization & Reports | Matplotlib, ReportLab/Platypus | Charts saved to `backend/static`, referenced in UI/PDF. |
|
| 37 |
+
| Frontend | React + Vite | Modern dashboard with prompt chips, chat, result stack, PDF download. |
|
| 38 |
+
|
| 39 |
+
## Prerequisites
|
| 40 |
+
|
| 41 |
+
- Python 3.9+
|
| 42 |
+
- Node.js 16+
|
| 43 |
+
- Groq API Key (for NL2SQL + insight generation)
|
| 44 |
+
|
| 45 |
+
## Backend Setup
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
cd backend
|
| 49 |
+
python -m venv venv
|
| 50 |
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
| 51 |
+
pip install -r requirements.txt
|
| 52 |
+
cp .env.example .env # add GROQ_API_KEY and optional DATABASE_URL
|
| 53 |
+
python create_db.py # optional; startup also seeds the sales table
|
| 54 |
+
python -m app.main # runs on http://localhost:8000
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
`DATABASE_URL` defaults to `sqlite:///./test.db`. Ensure the `.env` file includes your `GROQ_API_KEY` for Groq access.
|
| 58 |
+
|
| 59 |
+
## Frontend Setup
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
cd frontend
|
| 63 |
+
npm install
|
| 64 |
+
npm run dev # http://localhost:5173
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
## Usage Flow
|
| 68 |
+
|
| 69 |
+
1. Start backend and frontend dev servers.
|
| 70 |
+
1. Upload CSVs via the “Dataset Control” card (or call `POST /api/upload-csv` with `file` + `table_name`). The agent now validates table names, swaps in new tables on demand, and refreshes the dataset catalog for quick reuse.
|
| 71 |
+
1. Open the React app, pick a quick prompt (e.g., “What were total sales by category?”) or type your own.
|
| 72 |
+
1. InsightPilot:
|
| 73 |
+
|
| 74 |
+
- Reads DB schema & crafts SQL via Groq Llama‑3.
|
| 75 |
+
- Executes SQL with SQLAlchemy/Pandas.
|
| 76 |
+
- Generates Matplotlib charts and Groq insight summaries.
|
| 77 |
+
- Streams insights to the chat, shows SQL/data preview, and produces a PDF.
|
| 78 |
+
|
| 79 |
+
1. Download the PDF to share with stakeholders.
|
| 80 |
+
|
| 81 |
+
## Dataset Upload & Multi-table Ingestion
|
| 82 |
+
|
| 83 |
+
- **Upload endpoint:** `POST /api/upload-csv` accepts `file` + `table_name`. Table names are validated (`[A-Za-z_][A-Za-z0-9_]*`) to keep SQL safe, and uploads replace/create tables atomically.
|
| 84 |
+
- **Catalog endpoint:** `GET /api/datasets` returns discovered tables along with row counts and column names so the frontend can surface a live catalog.
|
| 85 |
+
- **Frontend controls:** the Dataset Control card now includes a target-table input, catalog refresh button, and a list of available tables—making it trivial to pivot the agent to another fact table without redeploying anything.
|
| 86 |
+
|
| 87 |
+
## Project Structure
|
| 88 |
+
|
| 89 |
+
```text
|
| 90 |
+
backend/
|
| 91 |
+
app/
|
| 92 |
+
agents/graph.py # LangGraph workflow
|
| 93 |
+
api/routes.py # FastAPI routes
|
| 94 |
+
core/config.py # Settings + env
|
| 95 |
+
db/database.py # Engine + auto seed
|
| 96 |
+
services/analytics.py # Trend & anomaly detection modules
|
| 97 |
+
services/pdf_generator.py# ReportLab report builder
|
| 98 |
+
services/csv_loader.py # CSV ingestion + dataset catalog helpers
|
| 99 |
+
static/ # Generated charts & PDFs
|
| 100 |
+
create_db.py # Manual seed script
|
| 101 |
+
requirements.txt
|
| 102 |
+
|
| 103 |
+
frontend/
|
| 104 |
+
src/App.jsx, App.css # React dashboard
|
| 105 |
+
package.json
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
## Future Ideas
|
| 109 |
+
|
| 110 |
+
- Intent-specific LangGraph branches (comparison vs. forecasting).
|
| 111 |
+
- Supabase/Postgres adapters with connection pooling.
|
| 112 |
+
- Auth + team workspaces for insights and PDF history.
|
| 113 |
+
|
| 114 |
+
Enjoy exploring data with InsightPilot! 🚀
|
README_HF.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: InsightPilot - Autonomous Analytics Agent
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 4.16.0
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
python_version: 3.10
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# InsightPilot – Autonomous Analytics Agent
|
| 15 |
+
|
| 16 |
+
<div align="center">
|
| 17 |
+
|
| 18 |
+
[](https://github.com/langchain-ai/langgraph)
|
| 19 |
+
[](https://fastapi.tiangolo.com/)
|
| 20 |
+
[](https://groq.com/)
|
| 21 |
+
|
| 22 |
+
</div>
|
| 23 |
+
|
| 24 |
+
InsightPilot is a production-ready AI analyst that transforms natural language questions into validated SQL queries, interactive visualizations, comprehensive insights, and executive-ready PDF reports.
|
| 25 |
+
|
| 26 |
+
## 🌟 Features
|
| 27 |
+
|
| 28 |
+
- **🤖 Agentic LangGraph Pipeline** – Deterministic tool-calling workflow (intent → schema → NL2SQL → execution → diagnostics → visualization → PDF)
|
| 29 |
+
- **📊 Advanced Analytics** – Automated trend detection and anomaly analysis with statistical insights
|
| 30 |
+
- **📄 PDF Report Generation** – Executive-ready reports with branded title pages, charts, and SQL appendix
|
| 31 |
+
- **📁 Multi-table Support** – Easy CSV upload and dataset catalog management
|
| 32 |
+
- **⚡ Real-time Streaming** – Live insights streamed to the UI as they're generated
|
| 33 |
+
- **🔍 Groq Llama-3 Powered** – Low-latency NL→SQL and narrative insight generation
|
| 34 |
+
|
| 35 |
+
## 🚀 Quick Start on Hugging Face Spaces
|
| 36 |
+
|
| 37 |
+
1. **Set Environment Variables** (Required)
|
| 38 |
+
- Go to Settings → Repository Secrets
|
| 39 |
+
- Add `GROQ_API_KEY` with your Groq API key ([Get one here](https://console.groq.com/))
|
| 40 |
+
|
| 41 |
+
2. **Upload Your Data** (Optional)
|
| 42 |
+
- Use the "Upload Dataset" tab to add your CSV files
|
| 43 |
+
- Or work with the pre-loaded sample sales dataset
|
| 44 |
+
|
| 45 |
+
3. **Ask Questions**
|
| 46 |
+
- Use the Analytics Dashboard to ask natural language questions
|
| 47 |
+
- Example: "What were the total sales by category last quarter?"
|
| 48 |
+
- Get SQL, visualizations, insights, and downloadable PDF reports
|
| 49 |
+
|
| 50 |
+
## 🏗️ Architecture
|
| 51 |
+
|
| 52 |
+
| Component | Technology | Purpose |
|
| 53 |
+
|-----------|-----------|---------|
|
| 54 |
+
| **LLM Orchestration** | LangGraph + Groq Llama-3 70B | Deterministic agent workflow with tool calling |
|
| 55 |
+
| **API & Backend** | FastAPI + SQLAlchemy | RESTful API, database management |
|
| 56 |
+
| **Analytics** | Pandas, NumPy, SciPy | Trend detection, anomaly analysis |
|
| 57 |
+
| **Visualization** | Matplotlib, ReportLab | Charts and PDF report generation |
|
| 58 |
+
| **Database** | SQLite | Lightweight, persistent data storage |
|
| 59 |
+
| **Frontend** | React + Vite (optional) | Modern interactive dashboard |
|
| 60 |
+
| **Interface** | Gradio | HF Spaces integration |
|
| 61 |
+
|
| 62 |
+
## 📊 Advanced Analytics Modules
|
| 63 |
+
|
| 64 |
+
- **Trend Detection**: Time series regression analysis with slope quantification and % change metrics
|
| 65 |
+
- **Anomaly Detection**: Z-score based statistical outlier identification
|
| 66 |
+
- **Insight Generation**: Context-aware narrative summaries powered by Groq LLM
|
| 67 |
+
|
| 68 |
+
## 🛠️ Tech Stack
|
| 69 |
+
|
| 70 |
+
```
|
| 71 |
+
Backend: FastAPI + LangGraph + LangChain + Groq
|
| 72 |
+
Data: SQLite + SQLAlchemy + Pandas
|
| 73 |
+
Viz: Matplotlib + ReportLab/Platypus
|
| 74 |
+
Frontend: React + Vite (embedded in Gradio)
|
| 75 |
+
Deploy: Hugging Face Spaces (Gradio SDK)
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
## 📁 Project Structure
|
| 79 |
+
|
| 80 |
+
```
|
| 81 |
+
.
|
| 82 |
+
├── app.py # Gradio wrapper for HF Spaces
|
| 83 |
+
├── requirements.txt # Python dependencies
|
| 84 |
+
├── backend/
|
| 85 |
+
│ ├── app/
|
| 86 |
+
│ │ ├── main.py # FastAPI application
|
| 87 |
+
│ │ ├── agents/graph.py # LangGraph workflow
|
| 88 |
+
│ │ ├── api/routes.py # API endpoints
|
| 89 |
+
│ │ ├── core/config.py # Settings & environment
|
| 90 |
+
│ │ ├── db/database.py # Database engine & seeding
|
| 91 |
+
│ │ └── services/ # Analytics, PDF, CSV modules
|
| 92 |
+
│ ├── static/ # Generated charts & PDFs
|
| 93 |
+
│ └── requirements.txt # Backend-specific deps
|
| 94 |
+
├── frontend/ # React dashboard (optional)
|
| 95 |
+
└── data/ # Sample datasets
|
| 96 |
+
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
## 🔑 Environment Variables
|
| 100 |
+
|
| 101 |
+
| Variable | Description | Required |
|
| 102 |
+
|----------|-------------|----------|
|
| 103 |
+
| `GROQ_API_KEY` | Groq API key for LLM access | ✅ Yes |
|
| 104 |
+
| `DATABASE_URL` | Database connection string | ⚪ Optional (defaults to SQLite) |
|
| 105 |
+
|
| 106 |
+
## 📖 Usage Examples
|
| 107 |
+
|
| 108 |
+
**Question:** "What were the top 5 products by revenue last year?"
|
| 109 |
+
|
| 110 |
+
**InsightPilot will:**
|
| 111 |
+
1. ✅ Analyze your database schema
|
| 112 |
+
2. ✅ Generate optimized SQL query
|
| 113 |
+
3. ✅ Execute query and validate results
|
| 114 |
+
4. ✅ Create visualizations (bar charts, trends)
|
| 115 |
+
5. ✅ Perform trend & anomaly analysis
|
| 116 |
+
6. ✅ Generate narrative insights
|
| 117 |
+
7. ✅ Build downloadable PDF report
|
| 118 |
+
|
| 119 |
+
## 🎯 Use Cases
|
| 120 |
+
|
| 121 |
+
- **Business Analytics**: Ad-hoc reporting without SQL knowledge
|
| 122 |
+
- **Executive Briefings**: Automated PDF reports with insights
|
| 123 |
+
- **Data Exploration**: Quick analysis of uploaded CSV datasets
|
| 124 |
+
- **Trend Analysis**: Automated time-series analytics
|
| 125 |
+
- **Anomaly Detection**: Statistical outlier identification
|
| 126 |
+
|
| 127 |
+
## 🚧 Limitations & Notes
|
| 128 |
+
|
| 129 |
+
- **Free HF Spaces**: CPU-only tier; suitable for moderate traffic
|
| 130 |
+
- **Database**: SQLite with persistent storage (50GB limit)
|
| 131 |
+
- **File Cleanup**: Old PDFs/charts should be periodically removed
|
| 132 |
+
- **Concurrent Users**: May need rate limiting for production use
|
| 133 |
+
|
| 134 |
+
## 🔮 Future Enhancements
|
| 135 |
+
|
| 136 |
+
- Multi-tenant workspaces with authentication
|
| 137 |
+
- Postgres/Supabase adapter for production databases
|
| 138 |
+
- Real-time collaborative dashboards
|
| 139 |
+
- Forecast & prediction modules
|
| 140 |
+
- Custom visualization templates
|
| 141 |
+
|
| 142 |
+
## 📝 License
|
| 143 |
+
|
| 144 |
+
MIT License - see LICENSE file for details
|
| 145 |
+
|
| 146 |
+
## 🤝 Contributing
|
| 147 |
+
|
| 148 |
+
Contributions welcome! Please open an issue or submit a PR.
|
| 149 |
+
|
| 150 |
+
## 🔗 Links
|
| 151 |
+
|
| 152 |
+
- **Repository**: [GitHub](https://github.com/zenitsu0509/InsightPilot)
|
| 153 |
+
- **Documentation**: See original README in repo
|
| 154 |
+
- **Groq Platform**: [Get API Key](https://console.groq.com/)
|
| 155 |
+
|
| 156 |
+
---
|
| 157 |
+
|
| 158 |
+
**Built with ❤️ using LangGraph, FastAPI, and Groq**
|
app.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import uvicorn
|
| 5 |
+
from threading import Thread
|
| 6 |
+
import time
|
| 7 |
+
import shutil
|
| 8 |
+
|
| 9 |
+
# Add backend to path
|
| 10 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
|
| 11 |
+
|
| 12 |
+
from app.main import app as fastapi_app
|
| 13 |
+
|
| 14 |
+
# Set up paths for HF Spaces
|
| 15 |
+
STATIC_DIR = "backend/static"
|
| 16 |
+
FRONTEND_BUILD_DIR = "frontend/dist"
|
| 17 |
+
DB_PATH = "test.db"
|
| 18 |
+
|
| 19 |
+
os.makedirs(STATIC_DIR, exist_ok=True)
|
| 20 |
+
|
| 21 |
+
def start_fastapi():
|
| 22 |
+
"""Start FastAPI server in background thread"""
|
| 23 |
+
uvicorn.run(fastapi_app, host="0.0.0.0", port=7860, log_level="info")
|
| 24 |
+
|
| 25 |
+
# Start FastAPI in a separate thread
|
| 26 |
+
fastapi_thread = Thread(target=start_fastapi, daemon=True)
|
| 27 |
+
fastapi_thread.start()
|
| 28 |
+
|
| 29 |
+
# Give FastAPI time to start
|
| 30 |
+
time.sleep(2)
|
| 31 |
+
|
| 32 |
+
# Create Gradio interface
|
| 33 |
+
with gr.Blocks(title="InsightPilot - Autonomous Analytics Agent", theme=gr.themes.Soft()) as demo:
|
| 34 |
+
gr.Markdown("""
|
| 35 |
+
# 🚀 InsightPilot – Autonomous Analytics Agent
|
| 36 |
+
|
| 37 |
+
An AI-powered analyst that turns natural language questions into SQL queries, visualizations, and executive-ready PDF reports.
|
| 38 |
+
|
| 39 |
+
**Powered by:** LangGraph + FastAPI + Groq Llama-3
|
| 40 |
+
""")
|
| 41 |
+
|
| 42 |
+
with gr.Tab("📊 Analytics Dashboard"):
|
| 43 |
+
gr.Markdown("""
|
| 44 |
+
### Access the full React dashboard below
|
| 45 |
+
|
| 46 |
+
The dashboard provides:
|
| 47 |
+
- Natural language query interface
|
| 48 |
+
- Real-time SQL generation and execution
|
| 49 |
+
- Interactive visualizations
|
| 50 |
+
- PDF report generation
|
| 51 |
+
- Dataset management and CSV upload
|
| 52 |
+
""")
|
| 53 |
+
|
| 54 |
+
# Embed the React app if built, otherwise show API info
|
| 55 |
+
if os.path.exists(FRONTEND_BUILD_DIR):
|
| 56 |
+
gr.HTML(f"""
|
| 57 |
+
<iframe src="/frontend" width="100%" height="800px" frameborder="0"></iframe>
|
| 58 |
+
""")
|
| 59 |
+
else:
|
| 60 |
+
gr.Markdown("""
|
| 61 |
+
⚠️ **Frontend not built yet.**
|
| 62 |
+
|
| 63 |
+
To build the frontend:
|
| 64 |
+
```bash
|
| 65 |
+
cd frontend
|
| 66 |
+
npm install
|
| 67 |
+
npm run build
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
For now, you can interact with the API directly at the endpoints below.
|
| 71 |
+
""")
|
| 72 |
+
|
| 73 |
+
with gr.Tab("🔧 API Access"):
|
| 74 |
+
gr.Markdown("""
|
| 75 |
+
### Direct API Endpoints
|
| 76 |
+
|
| 77 |
+
- **Base URL:** `http://localhost:7860/api`
|
| 78 |
+
- **Query Analysis:** `POST /api/analyze` - Send natural language questions
|
| 79 |
+
- **Upload CSV:** `POST /api/upload-csv` - Upload datasets
|
| 80 |
+
- **List Datasets:** `GET /api/datasets` - View available tables
|
| 81 |
+
- **API Docs:** [Interactive Swagger UI](http://localhost:7860/docs)
|
| 82 |
+
|
| 83 |
+
### Quick Test
|
| 84 |
+
""")
|
| 85 |
+
|
| 86 |
+
with gr.Row():
|
| 87 |
+
question_input = gr.Textbox(
|
| 88 |
+
label="Ask a Question",
|
| 89 |
+
placeholder="What were the total sales by category?",
|
| 90 |
+
lines=2
|
| 91 |
+
)
|
| 92 |
+
submit_btn = gr.Button("Analyze", variant="primary")
|
| 93 |
+
|
| 94 |
+
result_output = gr.JSON(label="Analysis Result")
|
| 95 |
+
|
| 96 |
+
def analyze_question(question):
|
| 97 |
+
"""Call the FastAPI analyze endpoint"""
|
| 98 |
+
import requests
|
| 99 |
+
try:
|
| 100 |
+
response = requests.post(
|
| 101 |
+
"http://localhost:7860/api/analyze",
|
| 102 |
+
json={"question": question},
|
| 103 |
+
timeout=60
|
| 104 |
+
)
|
| 105 |
+
return response.json()
|
| 106 |
+
except Exception as e:
|
| 107 |
+
return {"error": str(e)}
|
| 108 |
+
|
| 109 |
+
submit_btn.click(
|
| 110 |
+
fn=analyze_question,
|
| 111 |
+
inputs=[question_input],
|
| 112 |
+
outputs=[result_output]
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
with gr.Tab("📤 Upload Dataset"):
|
| 116 |
+
gr.Markdown("""
|
| 117 |
+
### Upload CSV Dataset
|
| 118 |
+
|
| 119 |
+
Upload your own CSV files to analyze custom data.
|
| 120 |
+
""")
|
| 121 |
+
|
| 122 |
+
with gr.Row():
|
| 123 |
+
csv_file = gr.File(label="Select CSV File", file_types=[".csv"])
|
| 124 |
+
table_name = gr.Textbox(
|
| 125 |
+
label="Table Name",
|
| 126 |
+
placeholder="sales_data",
|
| 127 |
+
value="uploaded_data"
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
upload_btn = gr.Button("Upload", variant="primary")
|
| 131 |
+
upload_result = gr.Textbox(label="Upload Status", lines=3)
|
| 132 |
+
|
| 133 |
+
def upload_csv(file, table):
|
| 134 |
+
"""Upload CSV to the database"""
|
| 135 |
+
import requests
|
| 136 |
+
try:
|
| 137 |
+
if file is None:
|
| 138 |
+
return "Please select a CSV file"
|
| 139 |
+
|
| 140 |
+
with open(file.name, 'rb') as f:
|
| 141 |
+
files = {'file': (os.path.basename(file.name), f, 'text/csv')}
|
| 142 |
+
data = {'table_name': table}
|
| 143 |
+
response = requests.post(
|
| 144 |
+
"http://localhost:7860/api/upload-csv",
|
| 145 |
+
files=files,
|
| 146 |
+
data=data,
|
| 147 |
+
timeout=30
|
| 148 |
+
)
|
| 149 |
+
if response.status_code == 200:
|
| 150 |
+
result = response.json()
|
| 151 |
+
return f"✅ Success! Uploaded {result.get('rows', 0)} rows to table '{table}'"
|
| 152 |
+
else:
|
| 153 |
+
return f"❌ Error: {response.text}"
|
| 154 |
+
except Exception as e:
|
| 155 |
+
return f"❌ Error: {str(e)}"
|
| 156 |
+
|
| 157 |
+
upload_btn.click(
|
| 158 |
+
fn=upload_csv,
|
| 159 |
+
inputs=[csv_file, table_name],
|
| 160 |
+
outputs=[upload_result]
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
with gr.Tab("ℹ️ About"):
|
| 164 |
+
gr.Markdown("""
|
| 165 |
+
## About InsightPilot
|
| 166 |
+
|
| 167 |
+
InsightPilot is a production-style AI analyst that combines:
|
| 168 |
+
|
| 169 |
+
- **Agentic LangGraph Pipeline:** Deterministic tool-calling workflow
|
| 170 |
+
- **Advanced Analytics:** Automated trend detection and anomaly analysis
|
| 171 |
+
- **PDF Reports:** Executive-ready reports with visualizations
|
| 172 |
+
- **Multi-table Support:** Easy CSV upload and dataset management
|
| 173 |
+
|
| 174 |
+
### Tech Stack
|
| 175 |
+
- **Backend:** FastAPI + LangGraph + SQLAlchemy
|
| 176 |
+
- **LLM:** Groq Llama-3 (70B)
|
| 177 |
+
- **Frontend:** React + Vite
|
| 178 |
+
- **Database:** SQLite
|
| 179 |
+
- **Visualization:** Matplotlib + ReportLab
|
| 180 |
+
|
| 181 |
+
### Environment Variables
|
| 182 |
+
Make sure to set your `GROQ_API_KEY` in the Hugging Face Space secrets!
|
| 183 |
+
|
| 184 |
+
### Repository
|
| 185 |
+
[View on GitHub](https://github.com/zenitsu0509/InsightPilot)
|
| 186 |
+
""")
|
| 187 |
+
|
| 188 |
+
# Mount frontend if built
|
| 189 |
+
if os.path.exists(FRONTEND_BUILD_DIR):
|
| 190 |
+
from fastapi.staticfiles import StaticFiles
|
| 191 |
+
fastapi_app.mount("/frontend", StaticFiles(directory=FRONTEND_BUILD_DIR, html=True), name="frontend")
|
| 192 |
+
|
| 193 |
+
if __name__ == "__main__":
|
| 194 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
backend/.env.example
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
GROQ_API_KEY="your_groq_api_key_here"
|
| 2 |
+
DATABASE_URL=sqlite:///./test.db
|
backend/app/agents/graph.py
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from typing import TypedDict, Annotated, List, Union, Any
|
| 3 |
+
|
| 4 |
+
import matplotlib
|
| 5 |
+
import matplotlib.pyplot as plt
|
| 6 |
+
import os
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import uuid
|
| 9 |
+
from langchain_groq import ChatGroq
|
| 10 |
+
from langchain_core.output_parsers import StrOutputParser
|
| 11 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 12 |
+
from langgraph.graph import StateGraph, END
|
| 13 |
+
|
| 14 |
+
from app.core.config import settings
|
| 15 |
+
from app.db.database import get_db_schema, engine
|
| 16 |
+
from app.services.pdf_generator import generate_pdf_report
|
| 17 |
+
from app.services.analytics import run_advanced_analytics
|
| 18 |
+
|
| 19 |
+
matplotlib.use('Agg') # Use non-interactive backend
|
| 20 |
+
|
| 21 |
+
# Define State
|
| 22 |
+
class AgentState(TypedDict, total=False):
|
| 23 |
+
query: str
|
| 24 |
+
history: List[dict]
|
| 25 |
+
schema: str
|
| 26 |
+
sql_query: str
|
| 27 |
+
data: Any # Pandas DataFrame as dict or list
|
| 28 |
+
visualization_path: str
|
| 29 |
+
visualization_summary: str
|
| 30 |
+
trend_analysis: dict
|
| 31 |
+
anomaly_analysis: dict
|
| 32 |
+
forecast_analysis: dict
|
| 33 |
+
statistical_tests: dict
|
| 34 |
+
insights: str
|
| 35 |
+
report_path: str
|
| 36 |
+
error: str
|
| 37 |
+
def _format_history(history: List[dict]) -> str:
|
| 38 |
+
if not history:
|
| 39 |
+
return "None"
|
| 40 |
+
rendered = []
|
| 41 |
+
for turn in history[-5:]:
|
| 42 |
+
question = turn.get("question", "")
|
| 43 |
+
answer = turn.get("insights", "")
|
| 44 |
+
rendered.append(f"User: {question}\nAgent: {answer}")
|
| 45 |
+
return "\n---\n".join(rendered)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
# LLM Setup
|
| 49 |
+
def get_llm():
|
| 50 |
+
if not settings.GROQ_API_KEY:
|
| 51 |
+
# Fallback or mock if needed, but for now assume key is present or will fail
|
| 52 |
+
return None
|
| 53 |
+
return ChatGroq(
|
| 54 |
+
temperature=0,
|
| 55 |
+
model_name="openai/gpt-oss-120b",
|
| 56 |
+
api_key=settings.GROQ_API_KEY,
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _summarize_dataframe(df: pd.DataFrame) -> List[dict]:
|
| 61 |
+
summary = []
|
| 62 |
+
for col in df.columns:
|
| 63 |
+
series = df[col]
|
| 64 |
+
summary.append(
|
| 65 |
+
{
|
| 66 |
+
"name": col,
|
| 67 |
+
"dtype": str(series.dtype),
|
| 68 |
+
"numeric": pd.api.types.is_numeric_dtype(series),
|
| 69 |
+
"unique_count": int(series.nunique()),
|
| 70 |
+
"sample_values": series.dropna().astype(str).unique().tolist()[:3],
|
| 71 |
+
}
|
| 72 |
+
)
|
| 73 |
+
return summary
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _fallback_chart_plan(df: pd.DataFrame) -> dict:
|
| 77 |
+
numeric_cols = list(df.select_dtypes(include=["number", "bool"]).columns)
|
| 78 |
+
categorical_cols = list(df.select_dtypes(include=["object", "category"]).columns)
|
| 79 |
+
|
| 80 |
+
if categorical_cols and numeric_cols:
|
| 81 |
+
return {
|
| 82 |
+
"chart_type": "bar",
|
| 83 |
+
"x_field": categorical_cols[0],
|
| 84 |
+
"y_field": numeric_cols[0],
|
| 85 |
+
"aggregation": "sum",
|
| 86 |
+
"top_n": 10,
|
| 87 |
+
"explanation": f"Bar chart of {numeric_cols[0]} by {categorical_cols[0]}"
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
if len(numeric_cols) >= 2:
|
| 91 |
+
return {
|
| 92 |
+
"chart_type": "line",
|
| 93 |
+
"x_field": numeric_cols[0],
|
| 94 |
+
"y_field": numeric_cols[1],
|
| 95 |
+
"aggregation": "none",
|
| 96 |
+
"top_n": 50,
|
| 97 |
+
"explanation": f"Line plot of {numeric_cols[1]} vs {numeric_cols[0]}"
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
return {
|
| 101 |
+
"chart_type": "table",
|
| 102 |
+
"x_field": None,
|
| 103 |
+
"y_field": None,
|
| 104 |
+
"aggregation": "none",
|
| 105 |
+
"top_n": 0,
|
| 106 |
+
"explanation": "No suitable numeric/categorical combination for chart"
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _parse_json_response(text: str) -> dict:
|
| 111 |
+
text = text.strip()
|
| 112 |
+
if text.startswith("```"):
|
| 113 |
+
text = text.strip("`")
|
| 114 |
+
if text.startswith("json"):
|
| 115 |
+
text = text[4:]
|
| 116 |
+
start = text.find("{")
|
| 117 |
+
end = text.rfind("}")
|
| 118 |
+
if start != -1 and end != -1:
|
| 119 |
+
candidate = text[start:end + 1]
|
| 120 |
+
return json.loads(candidate)
|
| 121 |
+
return json.loads(text)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def _suggest_chart_plan(df: pd.DataFrame, query: str) -> dict:
|
| 125 |
+
plan = _fallback_chart_plan(df)
|
| 126 |
+
llm = get_llm()
|
| 127 |
+
if not llm:
|
| 128 |
+
return plan
|
| 129 |
+
|
| 130 |
+
columns_meta = _summarize_dataframe(df)
|
| 131 |
+
sample_rows = df.head(5).to_dict(orient="records")
|
| 132 |
+
|
| 133 |
+
template = """
|
| 134 |
+
You are an analytics visualization planner. Based on the user's question, the column metadata, and sample rows, choose the most appropriate chart to highlight the insight.
|
| 135 |
+
|
| 136 |
+
Allowed chart_type values: bar, line, area, scatter, pie, table.
|
| 137 |
+
aggregation can be sum, mean, avg, average, count, or none. Use count when only frequency matters.
|
| 138 |
+
Return ONLY valid JSON with keys: chart_type, x_field, y_field (nullable), aggregation, top_n (int), explanation.
|
| 139 |
+
Make sure fields exist in the dataset and chart type matches their dtypes (categorical for x axis on bar/pie, numeric for y).
|
| 140 |
+
Pick at most top 12 categories when using bar/pie.
|
| 141 |
+
|
| 142 |
+
Columns: {columns}
|
| 143 |
+
Sample rows: {sample}
|
| 144 |
+
User question: {query}
|
| 145 |
+
"""
|
| 146 |
+
|
| 147 |
+
prompt = ChatPromptTemplate.from_template(template)
|
| 148 |
+
chain = prompt | llm | StrOutputParser()
|
| 149 |
+
|
| 150 |
+
try:
|
| 151 |
+
response = chain.invoke({
|
| 152 |
+
"columns": json.dumps(columns_meta, ensure_ascii=False),
|
| 153 |
+
"sample": json.dumps(sample_rows, ensure_ascii=False),
|
| 154 |
+
"query": query,
|
| 155 |
+
})
|
| 156 |
+
plan = _parse_json_response(response)
|
| 157 |
+
except Exception:
|
| 158 |
+
# keep fallback plan
|
| 159 |
+
plan.setdefault("explanation", "Heuristic visualization applied")
|
| 160 |
+
return plan
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def _aggregate_for_chart(df: pd.DataFrame, x_field: str, y_field: str, aggregation: str) -> pd.DataFrame:
|
| 164 |
+
if not x_field or x_field not in df.columns:
|
| 165 |
+
return pd.DataFrame()
|
| 166 |
+
|
| 167 |
+
agg = (aggregation or "sum").lower()
|
| 168 |
+
|
| 169 |
+
if agg in ("sum", "total", "mean", "avg", "average"):
|
| 170 |
+
target_col = y_field if y_field in df.columns else None
|
| 171 |
+
if not target_col:
|
| 172 |
+
numeric_cols = df.select_dtypes(include=["number", "bool"]).columns
|
| 173 |
+
target_col = numeric_cols[0] if len(numeric_cols) else None
|
| 174 |
+
if not target_col or not pd.api.types.is_numeric_dtype(df[target_col]):
|
| 175 |
+
return pd.DataFrame()
|
| 176 |
+
agg_fn = "mean" if agg in ("mean", "avg", "average") else "sum"
|
| 177 |
+
grouped = df.groupby(x_field)[target_col].agg(agg_fn).reset_index()
|
| 178 |
+
return grouped.rename(columns={target_col: "value"})
|
| 179 |
+
|
| 180 |
+
if agg == "count":
|
| 181 |
+
grouped = df.groupby(x_field).size().reset_index(name="value")
|
| 182 |
+
return grouped
|
| 183 |
+
|
| 184 |
+
if y_field and y_field in df.columns and pd.api.types.is_numeric_dtype(df[y_field]):
|
| 185 |
+
subset = df[[x_field, y_field]].copy()
|
| 186 |
+
subset = subset.rename(columns={y_field: "value"})
|
| 187 |
+
return subset
|
| 188 |
+
|
| 189 |
+
return pd.DataFrame()
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _render_chart(path: str, df: pd.DataFrame, plan: dict) -> str:
|
| 193 |
+
chart_type = (plan.get("chart_type") or "bar").lower()
|
| 194 |
+
x_field = plan.get("x_field")
|
| 195 |
+
y_field = plan.get("y_field")
|
| 196 |
+
agg = plan.get("aggregation")
|
| 197 |
+
top_n = int(plan.get("top_n") or 12)
|
| 198 |
+
|
| 199 |
+
plt.figure(figsize=(10, 6))
|
| 200 |
+
|
| 201 |
+
if chart_type == "scatter" and x_field and y_field:
|
| 202 |
+
if x_field in df.columns and y_field in df.columns and \
|
| 203 |
+
pd.api.types.is_numeric_dtype(df[x_field]) and pd.api.types.is_numeric_dtype(df[y_field]):
|
| 204 |
+
plot_df = df[[x_field, y_field]].dropna().head(top_n)
|
| 205 |
+
if plot_df.empty:
|
| 206 |
+
return ""
|
| 207 |
+
plt.scatter(plot_df[x_field], plot_df[y_field], color="#5cd4f4")
|
| 208 |
+
plt.xlabel(x_field)
|
| 209 |
+
plt.ylabel(y_field)
|
| 210 |
+
plt.title(plan.get("explanation", f"{y_field} vs {x_field}"))
|
| 211 |
+
plt.tight_layout()
|
| 212 |
+
plt.savefig(path, bbox_inches="tight")
|
| 213 |
+
plt.close()
|
| 214 |
+
return path
|
| 215 |
+
return ""
|
| 216 |
+
|
| 217 |
+
if not x_field:
|
| 218 |
+
return ""
|
| 219 |
+
|
| 220 |
+
plot_df = _aggregate_for_chart(df, x_field, y_field, agg)
|
| 221 |
+
if plot_df.empty:
|
| 222 |
+
return ""
|
| 223 |
+
|
| 224 |
+
plot_df = plot_df.sort_values("value", ascending=False)
|
| 225 |
+
if top_n > 0:
|
| 226 |
+
plot_df = plot_df.head(top_n)
|
| 227 |
+
|
| 228 |
+
if chart_type == "pie":
|
| 229 |
+
plot_df.set_index(x_field)["value"].plot(kind="pie", autopct="%1.1f%%")
|
| 230 |
+
plt.ylabel("")
|
| 231 |
+
elif chart_type == "line":
|
| 232 |
+
plt.plot(plot_df[x_field], plot_df["value"], marker="o")
|
| 233 |
+
elif chart_type == "area":
|
| 234 |
+
plt.fill_between(plot_df[x_field], plot_df["value"], alpha=0.4)
|
| 235 |
+
plt.plot(plot_df[x_field], plot_df["value"], color="#7a83ff")
|
| 236 |
+
else:
|
| 237 |
+
plt.bar(plot_df[x_field], plot_df["value"], color="#7a83ff")
|
| 238 |
+
|
| 239 |
+
plt.xticks(rotation=45, ha="right")
|
| 240 |
+
plt.xlabel(x_field)
|
| 241 |
+
plt.ylabel(plan.get("y_field") or "Value")
|
| 242 |
+
plt.title(plan.get("explanation", "Visualization"))
|
| 243 |
+
plt.tight_layout()
|
| 244 |
+
plt.savefig(path, bbox_inches="tight")
|
| 245 |
+
plt.close()
|
| 246 |
+
return path
|
| 247 |
+
|
| 248 |
+
# Nodes
|
| 249 |
+
def get_schema_node(state: AgentState):
|
| 250 |
+
schema = get_db_schema()
|
| 251 |
+
return {"schema": schema}
|
| 252 |
+
|
| 253 |
+
def generate_sql_node(state: AgentState):
|
| 254 |
+
llm = get_llm()
|
| 255 |
+
if not llm:
|
| 256 |
+
return {"error": "LLM not configured"}
|
| 257 |
+
|
| 258 |
+
template = """
|
| 259 |
+
You are a SQL expert. Convert the following natural language query into a SQL query for SQLite.
|
| 260 |
+
|
| 261 |
+
Schema:
|
| 262 |
+
{schema}
|
| 263 |
+
|
| 264 |
+
Recent conversation:
|
| 265 |
+
{history}
|
| 266 |
+
|
| 267 |
+
Current Query: {query}
|
| 268 |
+
|
| 269 |
+
Return ONLY the SQL query, nothing else. Do not wrap in markdown code blocks.
|
| 270 |
+
"""
|
| 271 |
+
prompt = ChatPromptTemplate.from_template(template)
|
| 272 |
+
chain = prompt | llm | StrOutputParser()
|
| 273 |
+
|
| 274 |
+
try:
|
| 275 |
+
sql_query = chain.invoke({
|
| 276 |
+
"schema": state["schema"],
|
| 277 |
+
"history": _format_history(state.get("history", [])),
|
| 278 |
+
"query": state["query"],
|
| 279 |
+
})
|
| 280 |
+
# Clean up sql if needed
|
| 281 |
+
sql_query = sql_query.replace("```sql", "").replace("```", "").strip()
|
| 282 |
+
return {"sql_query": sql_query}
|
| 283 |
+
except Exception as e:
|
| 284 |
+
return {"error": str(e)}
|
| 285 |
+
|
| 286 |
+
def execute_sql_node(state: AgentState):
|
| 287 |
+
if state.get("error"):
|
| 288 |
+
return state
|
| 289 |
+
|
| 290 |
+
try:
|
| 291 |
+
df = pd.read_sql(state["sql_query"], engine)
|
| 292 |
+
return {"data": df.to_dict(orient="records")}
|
| 293 |
+
except Exception as e:
|
| 294 |
+
return {"error": f"SQL Execution failed: {str(e)}"}
|
| 295 |
+
|
| 296 |
+
def generate_visualization_node(state: AgentState):
|
| 297 |
+
if state.get("error") or not state.get("data"):
|
| 298 |
+
return state
|
| 299 |
+
|
| 300 |
+
df = pd.DataFrame(state["data"])
|
| 301 |
+
if df.empty:
|
| 302 |
+
return {"visualization_path": None, "visualization_summary": "No data to visualize."}
|
| 303 |
+
|
| 304 |
+
plan = _suggest_chart_plan(df, state.get("query", ""))
|
| 305 |
+
|
| 306 |
+
filename = f"chart_{uuid.uuid4()}.png"
|
| 307 |
+
path = os.path.join("backend", "static", filename)
|
| 308 |
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
| 309 |
+
|
| 310 |
+
image_path = _render_chart(path, df, plan)
|
| 311 |
+
|
| 312 |
+
if not image_path:
|
| 313 |
+
return {"visualization_path": None, "visualization_summary": plan.get("explanation")}
|
| 314 |
+
|
| 315 |
+
return {"visualization_path": image_path, "visualization_summary": plan.get("explanation")}
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def advanced_analytics_node(state: AgentState):
|
| 319 |
+
if state.get("error") or not state.get("data"):
|
| 320 |
+
return state
|
| 321 |
+
|
| 322 |
+
df = pd.DataFrame(state["data"])
|
| 323 |
+
if df.empty:
|
| 324 |
+
return {"trend_analysis": None, "anomaly_analysis": None, "forecast_analysis": None, "statistical_tests": None}
|
| 325 |
+
|
| 326 |
+
analytics = run_advanced_analytics(df)
|
| 327 |
+
return {
|
| 328 |
+
"trend_analysis": analytics.get("trend"),
|
| 329 |
+
"anomaly_analysis": analytics.get("anomaly"),
|
| 330 |
+
"forecast_analysis": analytics.get("forecast"),
|
| 331 |
+
"statistical_tests": analytics.get("statistical_tests"),
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
def generate_insights_node(state: AgentState):
|
| 335 |
+
if state.get("error"):
|
| 336 |
+
return state
|
| 337 |
+
|
| 338 |
+
llm = get_llm()
|
| 339 |
+
if not llm:
|
| 340 |
+
return {"insights": "LLM not configured"}
|
| 341 |
+
|
| 342 |
+
data_summary = str(state["data"])[:2000] # Truncate if too long
|
| 343 |
+
|
| 344 |
+
template = """
|
| 345 |
+
You are an analytics copilot. Use the latest query, the conversation history, the data sample, and the derived diagnostics (trends, anomalies, forecasts, and statistical tests) to provide incremental insights. If the user repeats a question, reference earlier answers instead of restating everything.
|
| 346 |
+
|
| 347 |
+
History:
|
| 348 |
+
{history}
|
| 349 |
+
|
| 350 |
+
Current Query: {query}
|
| 351 |
+
Data Sample: {data}
|
| 352 |
+
Trend Analysis: {trend}
|
| 353 |
+
Anomaly Analysis: {anomaly}
|
| 354 |
+
Forecast Analysis: {forecast}
|
| 355 |
+
Statistical Tests: {stats}
|
| 356 |
+
|
| 357 |
+
Provide 3-5 concise bullet insights plus a short summary paragraph. Mention forecasts and statistical significance when available.
|
| 358 |
+
"""
|
| 359 |
+
prompt = ChatPromptTemplate.from_template(template)
|
| 360 |
+
chain = prompt | llm | StrOutputParser()
|
| 361 |
+
|
| 362 |
+
try:
|
| 363 |
+
insights = chain.invoke({
|
| 364 |
+
"query": state["query"],
|
| 365 |
+
"history": _format_history(state.get("history", [])),
|
| 366 |
+
"data": data_summary,
|
| 367 |
+
"trend": json.dumps(state.get("trend_analysis") or {}, ensure_ascii=False),
|
| 368 |
+
"anomaly": json.dumps(state.get("anomaly_analysis") or {}, ensure_ascii=False),
|
| 369 |
+
"forecast": json.dumps(state.get("forecast_analysis") or {}, ensure_ascii=False),
|
| 370 |
+
"stats": json.dumps(state.get("statistical_tests") or {}, ensure_ascii=False),
|
| 371 |
+
})
|
| 372 |
+
return {"insights": insights}
|
| 373 |
+
except Exception as e:
|
| 374 |
+
return {"insights": f"Failed to generate insights: {str(e)}"}
|
| 375 |
+
|
| 376 |
+
def build_report_node(state: AgentState):
|
| 377 |
+
if state.get("error"):
|
| 378 |
+
return state
|
| 379 |
+
|
| 380 |
+
filename = f"report_{uuid.uuid4()}.pdf"
|
| 381 |
+
path = os.path.join("backend", "static", filename)
|
| 382 |
+
|
| 383 |
+
try:
|
| 384 |
+
generate_pdf_report(
|
| 385 |
+
report_path=path,
|
| 386 |
+
title="Autonomous Data Analyst Report",
|
| 387 |
+
query=state.get("query", ""),
|
| 388 |
+
sql_query=state.get("sql_query", ""),
|
| 389 |
+
insights=state.get("insights", "No insights generated."),
|
| 390 |
+
chart_image_path=state.get("visualization_path"),
|
| 391 |
+
chart_summary=state.get("visualization_summary"),
|
| 392 |
+
trend_analysis=state.get("trend_analysis"),
|
| 393 |
+
anomaly_analysis=state.get("anomaly_analysis"),
|
| 394 |
+
forecast_analysis=state.get("forecast_analysis"),
|
| 395 |
+
statistical_tests=state.get("statistical_tests"),
|
| 396 |
+
data_sample=state.get("data"),
|
| 397 |
+
)
|
| 398 |
+
return {"report_path": path}
|
| 399 |
+
except Exception as e:
|
| 400 |
+
return {"error": f"Report generation failed: {str(e)}"}
|
| 401 |
+
|
| 402 |
+
def create_agent_graph():
|
| 403 |
+
workflow = StateGraph(AgentState)
|
| 404 |
+
|
| 405 |
+
# Add nodes
|
| 406 |
+
workflow.add_node("get_schema", get_schema_node)
|
| 407 |
+
workflow.add_node("generate_sql", generate_sql_node)
|
| 408 |
+
workflow.add_node("execute_sql", execute_sql_node)
|
| 409 |
+
workflow.add_node("visualize", generate_visualization_node)
|
| 410 |
+
workflow.add_node("advanced_analytics", advanced_analytics_node)
|
| 411 |
+
workflow.add_node("generate_insights", generate_insights_node)
|
| 412 |
+
workflow.add_node("build_report", build_report_node)
|
| 413 |
+
|
| 414 |
+
# Define edges
|
| 415 |
+
workflow.set_entry_point("get_schema")
|
| 416 |
+
workflow.add_edge("get_schema", "generate_sql")
|
| 417 |
+
workflow.add_edge("generate_sql", "execute_sql")
|
| 418 |
+
workflow.add_edge("execute_sql", "visualize")
|
| 419 |
+
workflow.add_edge("visualize", "advanced_analytics")
|
| 420 |
+
workflow.add_edge("advanced_analytics", "generate_insights")
|
| 421 |
+
workflow.add_edge("generate_insights", "build_report")
|
| 422 |
+
workflow.add_edge("build_report", END)
|
| 423 |
+
|
| 424 |
+
return workflow.compile()
|
backend/app/api/routes.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import defaultdict
|
| 2 |
+
from decimal import Decimal
|
| 3 |
+
import math
|
| 4 |
+
from typing import Optional, List, Dict, Any
|
| 5 |
+
|
| 6 |
+
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
|
| 7 |
+
from fastapi.encoders import jsonable_encoder
|
| 8 |
+
from fastapi.responses import JSONResponse
|
| 9 |
+
from pydantic import BaseModel
|
| 10 |
+
|
| 11 |
+
from app.agents.graph import create_agent_graph
|
| 12 |
+
from app.services.csv_loader import ingest_csv_dataset, list_dataset_tables
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
import numpy as np # type: ignore
|
| 16 |
+
except Exception: # pragma: no cover - numpy optional at runtime
|
| 17 |
+
np = None
|
| 18 |
+
|
| 19 |
+
router = APIRouter()
|
| 20 |
+
agent_graph = create_agent_graph()
|
| 21 |
+
session_histories: Dict[str, List[dict]] = defaultdict(list)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _sanitize_for_json(value: Any):
|
| 25 |
+
"""Recursively coerce values so FastAPI's JSON encoding never sees NaN/Inf."""
|
| 26 |
+
if value is None:
|
| 27 |
+
return None
|
| 28 |
+
if isinstance(value, bool):
|
| 29 |
+
return value
|
| 30 |
+
if isinstance(value, (int, str)):
|
| 31 |
+
return value
|
| 32 |
+
if isinstance(value, float):
|
| 33 |
+
return value if math.isfinite(value) else None
|
| 34 |
+
if isinstance(value, Decimal):
|
| 35 |
+
as_float = float(value)
|
| 36 |
+
return as_float if math.isfinite(as_float) else None
|
| 37 |
+
if np is not None:
|
| 38 |
+
if isinstance(value, (np.floating, np.integer)):
|
| 39 |
+
coerced = value.item()
|
| 40 |
+
if isinstance(coerced, float) and not math.isfinite(coerced):
|
| 41 |
+
return None
|
| 42 |
+
return coerced
|
| 43 |
+
if isinstance(value, np.ndarray):
|
| 44 |
+
return [_sanitize_for_json(item) for item in value.tolist()]
|
| 45 |
+
if isinstance(value, dict):
|
| 46 |
+
return {key: _sanitize_for_json(val) for key, val in value.items()}
|
| 47 |
+
if isinstance(value, (list, tuple, set)):
|
| 48 |
+
return [_sanitize_for_json(item) for item in value]
|
| 49 |
+
# Fallback to string representation for unsupported objects (e.g., Timestamp)
|
| 50 |
+
return str(value)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def _safe_json(payload: Any) -> JSONResponse:
|
| 54 |
+
sanitized = _sanitize_for_json(payload)
|
| 55 |
+
encoded = jsonable_encoder(
|
| 56 |
+
sanitized,
|
| 57 |
+
custom_encoder={
|
| 58 |
+
float: lambda v: v if math.isfinite(v) else None,
|
| 59 |
+
Decimal: lambda v: float(v) if math.isfinite(float(v)) else None,
|
| 60 |
+
},
|
| 61 |
+
)
|
| 62 |
+
return JSONResponse(content=encoded)
|
| 63 |
+
|
| 64 |
+
class QueryRequest(BaseModel):
|
| 65 |
+
query: str
|
| 66 |
+
session_id: Optional[str] = "default"
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class SessionResetRequest(BaseModel):
|
| 70 |
+
session_id: str
|
| 71 |
+
|
| 72 |
+
@router.post("/analyze")
|
| 73 |
+
async def analyze_data(request: QueryRequest):
|
| 74 |
+
try:
|
| 75 |
+
history = list(session_histories.get(request.session_id, []))
|
| 76 |
+
initial_state = {"query": request.query, "history": history}
|
| 77 |
+
result = agent_graph.invoke(initial_state)
|
| 78 |
+
|
| 79 |
+
# Construct response
|
| 80 |
+
response = {
|
| 81 |
+
"status": "success",
|
| 82 |
+
"query": result.get("query"),
|
| 83 |
+
"sql_query": result.get("sql_query"),
|
| 84 |
+
"data": result.get("data"),
|
| 85 |
+
"insights": result.get("insights"),
|
| 86 |
+
"visualization_url": None,
|
| 87 |
+
"visualization_summary": result.get("visualization_summary"),
|
| 88 |
+
"trend_analysis": result.get("trend_analysis"),
|
| 89 |
+
"anomaly_analysis": result.get("anomaly_analysis"),
|
| 90 |
+
"forecast_analysis": result.get("forecast_analysis"),
|
| 91 |
+
"statistical_tests": result.get("statistical_tests"),
|
| 92 |
+
"report_url": None,
|
| 93 |
+
"error": result.get("error")
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
if result.get("visualization_path"):
|
| 97 |
+
# Convert path to URL (assuming static mount)
|
| 98 |
+
filename = result["visualization_path"].split("\\")[-1].split("/")[-1]
|
| 99 |
+
response["visualization_url"] = f"/static/{filename}"
|
| 100 |
+
|
| 101 |
+
if result.get("report_path"):
|
| 102 |
+
filename = result["report_path"].split("\\")[-1].split("/")[-1]
|
| 103 |
+
response["report_url"] = f"/static/{filename}"
|
| 104 |
+
|
| 105 |
+
if not result.get("error"):
|
| 106 |
+
session_histories[request.session_id].append(
|
| 107 |
+
{
|
| 108 |
+
"question": request.query,
|
| 109 |
+
"insights": result.get("insights", ""),
|
| 110 |
+
"sql": result.get("sql_query", ""),
|
| 111 |
+
}
|
| 112 |
+
)
|
| 113 |
+
# limit memory
|
| 114 |
+
session_histories[request.session_id] = session_histories[request.session_id][-10:]
|
| 115 |
+
|
| 116 |
+
return _safe_json(response)
|
| 117 |
+
|
| 118 |
+
except Exception as e:
|
| 119 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 120 |
+
|
| 121 |
+
@router.get("/health")
|
| 122 |
+
async def health_check():
|
| 123 |
+
return {"status": "healthy"}
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
@router.post("/upload-csv")
|
| 127 |
+
async def upload_csv_dataset(
|
| 128 |
+
file: UploadFile = File(...),
|
| 129 |
+
table_name: str = Form("sales")
|
| 130 |
+
):
|
| 131 |
+
try:
|
| 132 |
+
payload = ingest_csv_dataset(file, table_name)
|
| 133 |
+
return _safe_json({"status": "success", **payload})
|
| 134 |
+
except ValueError as exc:
|
| 135 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 136 |
+
except Exception as exc:
|
| 137 |
+
raise HTTPException(status_code=500, detail=str(exc))
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
@router.get("/datasets")
|
| 141 |
+
async def get_dataset_catalog():
|
| 142 |
+
try:
|
| 143 |
+
catalog = list_dataset_tables()
|
| 144 |
+
return _safe_json({"tables": catalog})
|
| 145 |
+
except Exception as exc:
|
| 146 |
+
raise HTTPException(status_code=500, detail=str(exc))
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
@router.post("/session/reset")
|
| 150 |
+
async def reset_session(req: SessionResetRequest):
|
| 151 |
+
session_histories.pop(req.session_id, None)
|
| 152 |
+
return _safe_json({"status": "cleared", "session_id": req.session_id})
|
backend/app/core/config.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
|
| 4 |
+
load_dotenv()
|
| 5 |
+
|
| 6 |
+
class Settings:
|
| 7 |
+
PROJECT_NAME: str = "Autonomous Data Analyst Agent"
|
| 8 |
+
GROQ_API_KEY: str = os.getenv("GROQ_API_KEY", "")
|
| 9 |
+
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./test.db")
|
| 10 |
+
|
| 11 |
+
settings = Settings()
|
backend/app/db/database.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime, timedelta
|
| 2 |
+
import random
|
| 3 |
+
|
| 4 |
+
from sqlalchemy import create_engine, MetaData, inspect, text
|
| 5 |
+
|
| 6 |
+
from app.core.config import settings
|
| 7 |
+
|
| 8 |
+
engine = create_engine(settings.DATABASE_URL)
|
| 9 |
+
metadata = MetaData()
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def ensure_sales_dataset():
|
| 13 |
+
inspector = inspect(engine)
|
| 14 |
+
if inspector.has_table("sales"):
|
| 15 |
+
return
|
| 16 |
+
|
| 17 |
+
with engine.begin() as conn:
|
| 18 |
+
conn.exec_driver_sql(
|
| 19 |
+
"""
|
| 20 |
+
CREATE TABLE IF NOT EXISTS sales (
|
| 21 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 22 |
+
date DATE,
|
| 23 |
+
product_category TEXT,
|
| 24 |
+
product_name TEXT,
|
| 25 |
+
quantity INTEGER,
|
| 26 |
+
unit_price REAL,
|
| 27 |
+
total_amount REAL,
|
| 28 |
+
region TEXT
|
| 29 |
+
)
|
| 30 |
+
"""
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
categories = ["Electronics", "Clothing", "Home", "Books"]
|
| 34 |
+
products = {
|
| 35 |
+
"Electronics": ["Laptop", "Smartphone", "Headphones", "Monitor"],
|
| 36 |
+
"Clothing": ["T-Shirt", "Jeans", "Jacket", "Sneakers"],
|
| 37 |
+
"Home": ["Sofa", "Table", "Lamp", "Rug"],
|
| 38 |
+
"Books": ["Fiction", "Non-Fiction", "Sci-Fi", "Biography"],
|
| 39 |
+
}
|
| 40 |
+
regions = ["North", "South", "East", "West"]
|
| 41 |
+
|
| 42 |
+
rows = []
|
| 43 |
+
start_date = datetime(2023, 1, 1)
|
| 44 |
+
for _ in range(365):
|
| 45 |
+
date = start_date + timedelta(days=random.randint(0, 364))
|
| 46 |
+
category = random.choice(categories)
|
| 47 |
+
product = random.choice(products[category])
|
| 48 |
+
quantity = random.randint(1, 10)
|
| 49 |
+
unit_price = round(random.uniform(10.0, 1200.0), 2)
|
| 50 |
+
total_amount = round(quantity * unit_price, 2)
|
| 51 |
+
region = random.choice(regions)
|
| 52 |
+
|
| 53 |
+
rows.append(
|
| 54 |
+
{
|
| 55 |
+
"date": date.strftime("%Y-%m-%d"),
|
| 56 |
+
"product_category": category,
|
| 57 |
+
"product_name": product,
|
| 58 |
+
"quantity": quantity,
|
| 59 |
+
"unit_price": unit_price,
|
| 60 |
+
"total_amount": total_amount,
|
| 61 |
+
"region": region,
|
| 62 |
+
}
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
conn.execute(
|
| 66 |
+
text(
|
| 67 |
+
"""
|
| 68 |
+
INSERT INTO sales (date, product_category, product_name, quantity, unit_price, total_amount, region)
|
| 69 |
+
VALUES (:date, :product_category, :product_name, :quantity, :unit_price, :total_amount, :region)
|
| 70 |
+
"""
|
| 71 |
+
),
|
| 72 |
+
rows,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
ensure_sales_dataset()
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def get_db_schema():
|
| 80 |
+
metadata.reflect(bind=engine)
|
| 81 |
+
schema_info = []
|
| 82 |
+
for table in metadata.tables.values():
|
| 83 |
+
columns = [f"{col.name} ({col.type})" for col in table.columns]
|
| 84 |
+
schema_info.append(f"Table: {table.name}\nColumns: {', '.join(columns)}")
|
| 85 |
+
return "\n\n".join(schema_info)
|
backend/app/main.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from fastapi.staticfiles import StaticFiles
|
| 4 |
+
from app.api import routes
|
| 5 |
+
import uvicorn
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
app = FastAPI(title="Autonomous Data Analyst Agent")
|
| 9 |
+
|
| 10 |
+
# CORS
|
| 11 |
+
app.add_middleware(
|
| 12 |
+
CORSMiddleware,
|
| 13 |
+
allow_origins=["*"], # In production, replace with specific frontend origin
|
| 14 |
+
allow_credentials=True,
|
| 15 |
+
allow_methods=["*"],
|
| 16 |
+
allow_headers=["*"],
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
# Mount static files
|
| 20 |
+
static_dir = os.path.join("backend", "static")
|
| 21 |
+
os.makedirs(static_dir, exist_ok=True)
|
| 22 |
+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
| 23 |
+
|
| 24 |
+
app.include_router(routes.router, prefix="/api")
|
| 25 |
+
|
| 26 |
+
@app.get("/")
|
| 27 |
+
def read_root():
|
| 28 |
+
return {"message": "Autonomous Data Analyst Agent API is running"}
|
| 29 |
+
|
| 30 |
+
if __name__ == "__main__":
|
| 31 |
+
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
|
backend/app/services/analytics.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from typing import Dict, List, Optional, Tuple
|
| 3 |
+
import warnings
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pandas as pd
|
| 7 |
+
from scipy import stats
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
from statsmodels.tsa.holtwinters import ExponentialSmoothing
|
| 11 |
+
from statsmodels.tsa.stattools import adfuller
|
| 12 |
+
HAS_STATSMODELS = True
|
| 13 |
+
except ImportError:
|
| 14 |
+
HAS_STATSMODELS = False
|
| 15 |
+
|
| 16 |
+
DATE_HINTS = ("date", "time", "month", "year", "period")
|
| 17 |
+
METRIC_HINTS = ("sale", "amount", "revenue", "profit", "price", "total")
|
| 18 |
+
MAX_POINTS = 60
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _detect_datetime_column(df: pd.DataFrame) -> Optional[str]:
|
| 22 |
+
# Prefer columns already datetime typed
|
| 23 |
+
for col in df.columns:
|
| 24 |
+
if pd.api.types.is_datetime64_any_dtype(df[col]):
|
| 25 |
+
return col
|
| 26 |
+
# Fallback to columns whose names hint at date/time
|
| 27 |
+
for col in df.columns:
|
| 28 |
+
low = col.lower()
|
| 29 |
+
if any(hint in low for hint in DATE_HINTS):
|
| 30 |
+
try:
|
| 31 |
+
pd.to_datetime(df[col])
|
| 32 |
+
return col
|
| 33 |
+
except Exception:
|
| 34 |
+
continue
|
| 35 |
+
return None
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _detect_metric_column(df: pd.DataFrame) -> Optional[str]:
|
| 39 |
+
numeric_cols = [c for c in df.columns if pd.api.types.is_numeric_dtype(df[c])]
|
| 40 |
+
if not numeric_cols:
|
| 41 |
+
return None
|
| 42 |
+
for col in numeric_cols:
|
| 43 |
+
low = col.lower()
|
| 44 |
+
if any(hint in low for hint in METRIC_HINTS):
|
| 45 |
+
return col
|
| 46 |
+
return numeric_cols[0]
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _build_time_series(df: pd.DataFrame) -> Optional[pd.Series]:
|
| 50 |
+
if df is None or df.empty:
|
| 51 |
+
return None
|
| 52 |
+
|
| 53 |
+
date_col = _detect_datetime_column(df)
|
| 54 |
+
metric_col = _detect_metric_column(df)
|
| 55 |
+
if not date_col or not metric_col:
|
| 56 |
+
return None
|
| 57 |
+
|
| 58 |
+
ts = df[[date_col, metric_col]].copy()
|
| 59 |
+
ts[date_col] = pd.to_datetime(ts[date_col], errors="coerce")
|
| 60 |
+
ts = ts.dropna(subset=[date_col, metric_col])
|
| 61 |
+
if ts.empty:
|
| 62 |
+
return None
|
| 63 |
+
|
| 64 |
+
# Aggregate by month for smoother signals
|
| 65 |
+
ts["period"] = ts[date_col].dt.to_period("M").dt.to_timestamp()
|
| 66 |
+
grouped = ts.groupby("period")[metric_col].sum().sort_index()
|
| 67 |
+
if len(grouped) > MAX_POINTS:
|
| 68 |
+
grouped = grouped[-MAX_POINTS:]
|
| 69 |
+
return grouped
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _linear_trend(series: pd.Series) -> Optional[Dict[str, object]]:
|
| 73 |
+
if series is None or len(series) < 3:
|
| 74 |
+
return None
|
| 75 |
+
|
| 76 |
+
x = np.arange(len(series))
|
| 77 |
+
y = series.values.astype(float)
|
| 78 |
+
|
| 79 |
+
# Linear regression with confidence intervals
|
| 80 |
+
slope, intercept = np.polyfit(x, y, 1)
|
| 81 |
+
y_pred = slope * x + intercept
|
| 82 |
+
|
| 83 |
+
# Calculate standard error and confidence intervals
|
| 84 |
+
residuals = y - y_pred
|
| 85 |
+
n = len(y)
|
| 86 |
+
degrees_freedom = n - 2
|
| 87 |
+
residual_std_error = np.sqrt(np.sum(residuals**2) / degrees_freedom) if degrees_freedom > 0 else 0
|
| 88 |
+
|
| 89 |
+
# Standard error of slope
|
| 90 |
+
x_mean = np.mean(x)
|
| 91 |
+
se_slope = residual_std_error / np.sqrt(np.sum((x - x_mean)**2)) if np.sum((x - x_mean)**2) > 0 else 0
|
| 92 |
+
|
| 93 |
+
# 95% confidence interval for slope
|
| 94 |
+
t_val = stats.t.ppf(0.975, degrees_freedom) if degrees_freedom > 0 else 1.96
|
| 95 |
+
slope_ci_lower = slope - t_val * se_slope
|
| 96 |
+
slope_ci_upper = slope + t_val * se_slope
|
| 97 |
+
|
| 98 |
+
# Prediction intervals for the trend line
|
| 99 |
+
prediction_intervals = []
|
| 100 |
+
for i in range(len(x)):
|
| 101 |
+
se_pred = residual_std_error * np.sqrt(1 + 1/n + (x[i] - x_mean)**2 / np.sum((x - x_mean)**2))
|
| 102 |
+
pi_lower = y_pred[i] - t_val * se_pred
|
| 103 |
+
pi_upper = y_pred[i] + t_val * se_pred
|
| 104 |
+
prediction_intervals.append({
|
| 105 |
+
"lower": float(pi_lower),
|
| 106 |
+
"upper": float(pi_upper)
|
| 107 |
+
})
|
| 108 |
+
|
| 109 |
+
start_val = float(y[0])
|
| 110 |
+
end_val = float(y[-1])
|
| 111 |
+
change_pct = None
|
| 112 |
+
if not math.isclose(start_val, 0.0):
|
| 113 |
+
change_pct = ((end_val - start_val) / abs(start_val)) * 100
|
| 114 |
+
|
| 115 |
+
direction = "flat"
|
| 116 |
+
if slope > 0.02 * np.mean(y):
|
| 117 |
+
direction = "upward"
|
| 118 |
+
elif slope < -0.02 * np.mean(y):
|
| 119 |
+
direction = "downward"
|
| 120 |
+
|
| 121 |
+
summary = f"{direction.capitalize()} trend detected" if direction != "flat" else "Minimal trend detected"
|
| 122 |
+
if change_pct is not None:
|
| 123 |
+
summary += f" ({change_pct:+.1f}% over period)"
|
| 124 |
+
summary += f" with 95% confidence [slope: {slope_ci_lower:.2f} to {slope_ci_upper:.2f}]"
|
| 125 |
+
|
| 126 |
+
return {
|
| 127 |
+
"summary": summary,
|
| 128 |
+
"start": start_val,
|
| 129 |
+
"end": end_val,
|
| 130 |
+
"slope": float(slope),
|
| 131 |
+
"slope_ci_lower": float(slope_ci_lower),
|
| 132 |
+
"slope_ci_upper": float(slope_ci_upper),
|
| 133 |
+
"std_error": float(residual_std_error),
|
| 134 |
+
"r_squared": float(1 - np.sum(residuals**2) / np.sum((y - np.mean(y))**2)) if np.sum((y - np.mean(y))**2) > 0 else 0,
|
| 135 |
+
"change_pct": change_pct,
|
| 136 |
+
"points": [
|
| 137 |
+
{"period": period.strftime("%Y-%m"), "value": float(value)}
|
| 138 |
+
for period, value in series.items()
|
| 139 |
+
],
|
| 140 |
+
"prediction_intervals": prediction_intervals,
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def _anomaly_scan(series: pd.Series) -> Optional[Dict[str, object]]:
|
| 145 |
+
if series is None or len(series) < 4:
|
| 146 |
+
return None
|
| 147 |
+
|
| 148 |
+
values = series.values.astype(float)
|
| 149 |
+
mean = float(np.mean(values))
|
| 150 |
+
std = float(np.std(values))
|
| 151 |
+
if math.isclose(std, 0.0):
|
| 152 |
+
return None
|
| 153 |
+
|
| 154 |
+
z_scores = (values - mean) / std
|
| 155 |
+
anomalies: List[Dict[str, object]] = []
|
| 156 |
+
for idx, z in enumerate(z_scores):
|
| 157 |
+
if abs(z) >= 2.0:
|
| 158 |
+
period = series.index[idx]
|
| 159 |
+
anomalies.append(
|
| 160 |
+
{
|
| 161 |
+
"period": period.strftime("%Y-%m"),
|
| 162 |
+
"value": float(values[idx]),
|
| 163 |
+
"z_score": float(z),
|
| 164 |
+
}
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
if not anomalies:
|
| 168 |
+
return None
|
| 169 |
+
|
| 170 |
+
top = sorted(anomalies, key=lambda a: abs(a["z_score"]), reverse=True)[:3]
|
| 171 |
+
summary = "Anomalies detected at " + ", ".join(
|
| 172 |
+
[f"{a['period']} (z={a['z_score']:+.1f})" for a in top]
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
return {"summary": summary, "anomalies": anomalies, "mean": mean, "std": std}
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def run_advanced_analytics(df: pd.DataFrame) -> Dict[str, Optional[Dict[str, object]]]:
|
| 179 |
+
series = _build_time_series(df)
|
| 180 |
+
trend = _linear_trend(series)
|
| 181 |
+
anomaly = _anomaly_scan(series)
|
| 182 |
+
forecast = _forecast_next_periods(series)
|
| 183 |
+
statistical_tests = _run_statistical_tests(series)
|
| 184 |
+
return {
|
| 185 |
+
"trend": trend,
|
| 186 |
+
"anomaly": anomaly,
|
| 187 |
+
"forecast": forecast,
|
| 188 |
+
"statistical_tests": statistical_tests,
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _forecast_next_periods(series: pd.Series, periods: int = 3) -> Optional[Dict[str, object]]:
|
| 193 |
+
"""Generate forecasts using exponential smoothing with prediction intervals."""
|
| 194 |
+
if series is None or len(series) < 6:
|
| 195 |
+
return None
|
| 196 |
+
|
| 197 |
+
if not HAS_STATSMODELS:
|
| 198 |
+
return {
|
| 199 |
+
"summary": "Forecasting unavailable (statsmodels not installed)",
|
| 200 |
+
"forecasts": [],
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
try:
|
| 204 |
+
with warnings.catch_warnings():
|
| 205 |
+
warnings.simplefilter("ignore")
|
| 206 |
+
|
| 207 |
+
# Try Holt's exponential smoothing (trend method)
|
| 208 |
+
model = ExponentialSmoothing(
|
| 209 |
+
series.values,
|
| 210 |
+
trend='add',
|
| 211 |
+
seasonal=None,
|
| 212 |
+
initialization_method="estimated"
|
| 213 |
+
)
|
| 214 |
+
fitted = model.fit(optimized=True, remove_bias=False)
|
| 215 |
+
|
| 216 |
+
# Generate forecasts
|
| 217 |
+
forecast_values = fitted.forecast(steps=periods)
|
| 218 |
+
|
| 219 |
+
# Calculate prediction intervals using residual std
|
| 220 |
+
residuals = series.values - fitted.fittedvalues
|
| 221 |
+
residual_std = np.std(residuals)
|
| 222 |
+
|
| 223 |
+
# Generate future periods
|
| 224 |
+
last_period = series.index[-1]
|
| 225 |
+
freq = pd.infer_freq(series.index) or 'MS'
|
| 226 |
+
future_periods = pd.date_range(start=last_period, periods=periods + 1, freq=freq)[1:]
|
| 227 |
+
|
| 228 |
+
forecasts = []
|
| 229 |
+
for i, (period, value) in enumerate(zip(future_periods, forecast_values)):
|
| 230 |
+
# Prediction interval widens with forecast horizon
|
| 231 |
+
interval_width = residual_std * np.sqrt(i + 1) * 1.96
|
| 232 |
+
forecasts.append({
|
| 233 |
+
"period": period.strftime("%Y-%m"),
|
| 234 |
+
"value": float(value),
|
| 235 |
+
"lower_bound": float(value - interval_width),
|
| 236 |
+
"upper_bound": float(value + interval_width),
|
| 237 |
+
})
|
| 238 |
+
|
| 239 |
+
summary = f"Forecast for next {periods} periods using exponential smoothing"
|
| 240 |
+
if len(forecasts) > 0:
|
| 241 |
+
first_forecast = forecasts[0]["value"]
|
| 242 |
+
last_actual = float(series.values[-1])
|
| 243 |
+
change = ((first_forecast - last_actual) / abs(last_actual)) * 100 if last_actual != 0 else 0
|
| 244 |
+
summary += f" (next period: {first_forecast:.1f}, {change:+.1f}% vs current)"
|
| 245 |
+
|
| 246 |
+
return {
|
| 247 |
+
"summary": summary,
|
| 248 |
+
"method": "Exponential Smoothing (Holt)",
|
| 249 |
+
"forecasts": forecasts,
|
| 250 |
+
"model_params": {
|
| 251 |
+
"alpha": float(fitted.params.get('smoothing_level', 0)),
|
| 252 |
+
"beta": float(fitted.params.get('smoothing_trend', 0)) if fitted.params.get('smoothing_trend') else None,
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
except Exception as e:
|
| 257 |
+
return {
|
| 258 |
+
"summary": f"Forecasting failed: {str(e)[:100]}",
|
| 259 |
+
"forecasts": [],
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def _run_statistical_tests(series: pd.Series) -> Optional[Dict[str, object]]:
|
| 264 |
+
"""Run statistical comparison tests on time series data."""
|
| 265 |
+
if series is None or len(series) < 6:
|
| 266 |
+
return None
|
| 267 |
+
|
| 268 |
+
results = {}
|
| 269 |
+
|
| 270 |
+
# Split into two halves for comparison (e.g., first half vs second half)
|
| 271 |
+
mid = len(series) // 2
|
| 272 |
+
first_half = series.values[:mid]
|
| 273 |
+
second_half = series.values[mid:]
|
| 274 |
+
|
| 275 |
+
# T-test: Are the two periods significantly different?
|
| 276 |
+
try:
|
| 277 |
+
t_stat, p_value = stats.ttest_ind(first_half, second_half)
|
| 278 |
+
results["period_comparison"] = {
|
| 279 |
+
"test": "Independent t-test",
|
| 280 |
+
"comparison": "First half vs Second half",
|
| 281 |
+
"t_statistic": float(t_stat),
|
| 282 |
+
"p_value": float(p_value),
|
| 283 |
+
"significant": p_value < 0.05,
|
| 284 |
+
"summary": f"{'Significant' if p_value < 0.05 else 'No significant'} difference between periods (p={p_value:.4f})"
|
| 285 |
+
}
|
| 286 |
+
except Exception:
|
| 287 |
+
pass
|
| 288 |
+
|
| 289 |
+
# Test for stationarity (Augmented Dickey-Fuller test)
|
| 290 |
+
if HAS_STATSMODELS:
|
| 291 |
+
try:
|
| 292 |
+
adf_result = adfuller(series.values, autolag='AIC')
|
| 293 |
+
results["stationarity"] = {
|
| 294 |
+
"test": "Augmented Dickey-Fuller",
|
| 295 |
+
"adf_statistic": float(adf_result[0]),
|
| 296 |
+
"p_value": float(adf_result[1]),
|
| 297 |
+
"is_stationary": adf_result[1] < 0.05,
|
| 298 |
+
"summary": f"Series is {'stationary' if adf_result[1] < 0.05 else 'non-stationary'} (p={adf_result[1]:.4f})"
|
| 299 |
+
}
|
| 300 |
+
except Exception:
|
| 301 |
+
pass
|
| 302 |
+
|
| 303 |
+
# Quartile-based comparison (ANOVA-like for segments)
|
| 304 |
+
try:
|
| 305 |
+
quartiles = pd.qcut(series.index.to_series(), q=4, labels=False, duplicates='drop')
|
| 306 |
+
groups = [series.values[quartiles == i] for i in range(4) if len(series.values[quartiles == i]) > 0]
|
| 307 |
+
|
| 308 |
+
if len(groups) >= 2:
|
| 309 |
+
f_stat, p_value = stats.f_oneway(*groups)
|
| 310 |
+
results["quarterly_variance"] = {
|
| 311 |
+
"test": "One-way ANOVA",
|
| 312 |
+
"comparison": "Across quarters",
|
| 313 |
+
"f_statistic": float(f_stat),
|
| 314 |
+
"p_value": float(p_value),
|
| 315 |
+
"significant": p_value < 0.05,
|
| 316 |
+
"summary": f"{'Significant' if p_value < 0.05 else 'No significant'} variance across quarters (p={p_value:.4f})"
|
| 317 |
+
}
|
| 318 |
+
except Exception:
|
| 319 |
+
pass
|
| 320 |
+
|
| 321 |
+
if not results:
|
| 322 |
+
return None
|
| 323 |
+
|
| 324 |
+
# Overall summary
|
| 325 |
+
sig_tests = [v["summary"] for v in results.values() if "significant" in v.get("summary", "").lower()]
|
| 326 |
+
overall = f"{len(sig_tests)} significant finding(s): " + "; ".join(sig_tests[:2]) if sig_tests else "No significant patterns detected"
|
| 327 |
+
|
| 328 |
+
return {
|
| 329 |
+
"summary": overall,
|
| 330 |
+
"tests": results,
|
| 331 |
+
}
|
| 332 |
+
|
backend/app/services/csv_loader.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
import re
|
| 3 |
+
from typing import Any, Dict, List
|
| 4 |
+
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from fastapi import UploadFile
|
| 7 |
+
from sqlalchemy import inspect, text
|
| 8 |
+
|
| 9 |
+
from app.db.database import engine
|
| 10 |
+
|
| 11 |
+
SUPPORTED_ENCODINGS = ("utf-8", "latin1", "cp1252")
|
| 12 |
+
|
| 13 |
+
SAFE_TABLE_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _validate_table_name(name: str) -> str:
|
| 17 |
+
if not SAFE_TABLE_PATTERN.match(name):
|
| 18 |
+
raise ValueError("Table name must start with a letter/underscore and contain only alphanumerics or underscores.")
|
| 19 |
+
return name
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def ingest_csv_dataset(
|
| 23 |
+
upload_file: UploadFile,
|
| 24 |
+
table_name: str = "sales",
|
| 25 |
+
) -> Dict[str, Any]:
|
| 26 |
+
"""Load an uploaded CSV file into the configured database table."""
|
| 27 |
+
raw_bytes = upload_file.file.read()
|
| 28 |
+
upload_file.file.seek(0)
|
| 29 |
+
|
| 30 |
+
dataframe = None
|
| 31 |
+
chosen_encoding = None
|
| 32 |
+
errors = []
|
| 33 |
+
|
| 34 |
+
for encoding in SUPPORTED_ENCODINGS:
|
| 35 |
+
buffer = io.BytesIO(raw_bytes)
|
| 36 |
+
try:
|
| 37 |
+
dataframe = pd.read_csv(buffer, encoding=encoding)
|
| 38 |
+
chosen_encoding = encoding
|
| 39 |
+
break
|
| 40 |
+
except UnicodeDecodeError as exc:
|
| 41 |
+
errors.append(f"{encoding}: {exc}")
|
| 42 |
+
continue
|
| 43 |
+
except Exception as exc:
|
| 44 |
+
raise ValueError(f"Unable to parse CSV: {exc}")
|
| 45 |
+
|
| 46 |
+
if dataframe is None:
|
| 47 |
+
raise ValueError(
|
| 48 |
+
"Unable to decode CSV. Please upload UTF-8 or Latin-1 encoded files." +
|
| 49 |
+
(f" Details: {'; '.join(errors)}" if errors else "")
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
if dataframe.empty:
|
| 53 |
+
raise ValueError("Uploaded CSV is empty.")
|
| 54 |
+
|
| 55 |
+
safe_table_name = _validate_table_name(table_name.strip() or "sales")
|
| 56 |
+
dataframe.to_sql(safe_table_name, engine, if_exists="replace", index=False)
|
| 57 |
+
|
| 58 |
+
# Convert first 5 rows to dict for preview
|
| 59 |
+
preview_rows = dataframe.head(5).to_dict(orient="records")
|
| 60 |
+
|
| 61 |
+
return {
|
| 62 |
+
"table": safe_table_name,
|
| 63 |
+
"rows": len(dataframe),
|
| 64 |
+
"columns": list(dataframe.columns),
|
| 65 |
+
"encoding": chosen_encoding,
|
| 66 |
+
"preview": preview_rows
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def list_dataset_tables(limit: int = 15) -> List[Dict[str, Any]]:
|
| 71 |
+
inspector = inspect(engine)
|
| 72 |
+
tables = [t for t in inspector.get_table_names() if not t.startswith("sqlite_")][:limit]
|
| 73 |
+
catalog: List[Dict[str, Any]] = []
|
| 74 |
+
|
| 75 |
+
with engine.connect() as conn:
|
| 76 |
+
for table_name in tables:
|
| 77 |
+
column_info = inspector.get_columns(table_name)
|
| 78 |
+
try:
|
| 79 |
+
result = conn.execute(text(f"SELECT COUNT(*) as cnt FROM {table_name}"))
|
| 80 |
+
row_count = result.scalar() or 0
|
| 81 |
+
except Exception:
|
| 82 |
+
row_count = None
|
| 83 |
+
|
| 84 |
+
catalog.append(
|
| 85 |
+
{
|
| 86 |
+
"table": table_name,
|
| 87 |
+
"rows": int(row_count) if row_count is not None else None,
|
| 88 |
+
"columns": [col.get("name") for col in column_info],
|
| 89 |
+
}
|
| 90 |
+
)
|
| 91 |
+
return catalog
|
backend/app/services/pdf_generator.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
from reportlab.lib import colors
|
| 6 |
+
from reportlab.lib.pagesizes import letter
|
| 7 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 8 |
+
from reportlab.lib.units import inch
|
| 9 |
+
from reportlab.platypus import (
|
| 10 |
+
SimpleDocTemplate,
|
| 11 |
+
Paragraph,
|
| 12 |
+
Spacer,
|
| 13 |
+
Table,
|
| 14 |
+
TableStyle,
|
| 15 |
+
Image,
|
| 16 |
+
Preformatted,
|
| 17 |
+
HRFlowable,
|
| 18 |
+
PageBreak,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _build_styles():
|
| 23 |
+
styles = getSampleStyleSheet()
|
| 24 |
+
styles.add(
|
| 25 |
+
ParagraphStyle(
|
| 26 |
+
name="HeroTitle",
|
| 27 |
+
parent=styles["Title"],
|
| 28 |
+
fontName="Helvetica-Bold",
|
| 29 |
+
fontSize=20,
|
| 30 |
+
leading=26,
|
| 31 |
+
textColor=colors.HexColor("#111827"),
|
| 32 |
+
)
|
| 33 |
+
)
|
| 34 |
+
styles.add(
|
| 35 |
+
ParagraphStyle(
|
| 36 |
+
name="SectionHeader",
|
| 37 |
+
parent=styles["Heading2"],
|
| 38 |
+
fontName="Helvetica-Bold",
|
| 39 |
+
fontSize=14,
|
| 40 |
+
spaceAfter=6,
|
| 41 |
+
textColor=colors.HexColor("#111827"),
|
| 42 |
+
)
|
| 43 |
+
)
|
| 44 |
+
styles.add(
|
| 45 |
+
ParagraphStyle(
|
| 46 |
+
name="ReportBody",
|
| 47 |
+
parent=styles["BodyText"],
|
| 48 |
+
fontSize=11,
|
| 49 |
+
leading=16,
|
| 50 |
+
)
|
| 51 |
+
)
|
| 52 |
+
styles.add(
|
| 53 |
+
ParagraphStyle(
|
| 54 |
+
name="MetaLabel",
|
| 55 |
+
parent=styles["BodyText"],
|
| 56 |
+
fontSize=8,
|
| 57 |
+
textColor=colors.HexColor("#6b7280"),
|
| 58 |
+
leading=10,
|
| 59 |
+
)
|
| 60 |
+
)
|
| 61 |
+
styles.add(
|
| 62 |
+
ParagraphStyle(
|
| 63 |
+
name="QueryText",
|
| 64 |
+
parent=styles["BodyText"],
|
| 65 |
+
fontSize=12,
|
| 66 |
+
leading=16,
|
| 67 |
+
textColor=colors.HexColor("#111827"),
|
| 68 |
+
)
|
| 69 |
+
)
|
| 70 |
+
styles.add(
|
| 71 |
+
ParagraphStyle(
|
| 72 |
+
name="MetricValue",
|
| 73 |
+
parent=styles["BodyText"],
|
| 74 |
+
fontName="Helvetica-Bold",
|
| 75 |
+
fontSize=13,
|
| 76 |
+
textColor=colors.HexColor("#111827"),
|
| 77 |
+
)
|
| 78 |
+
)
|
| 79 |
+
styles.add(
|
| 80 |
+
ParagraphStyle(
|
| 81 |
+
name="MetricLabel",
|
| 82 |
+
parent=styles["BodyText"],
|
| 83 |
+
fontSize=9,
|
| 84 |
+
textColor=colors.HexColor("#6b7280"),
|
| 85 |
+
)
|
| 86 |
+
)
|
| 87 |
+
styles.add(
|
| 88 |
+
ParagraphStyle(
|
| 89 |
+
name="CaptionSmall",
|
| 90 |
+
parent=styles["BodyText"],
|
| 91 |
+
fontSize=9,
|
| 92 |
+
textColor=colors.HexColor("#6b7280"),
|
| 93 |
+
)
|
| 94 |
+
)
|
| 95 |
+
styles.add(
|
| 96 |
+
ParagraphStyle(
|
| 97 |
+
name="InsightBullet",
|
| 98 |
+
parent=styles["BodyText"],
|
| 99 |
+
leftIndent=14,
|
| 100 |
+
bulletIndent=6,
|
| 101 |
+
bulletFontName="Helvetica",
|
| 102 |
+
bulletFontSize=10,
|
| 103 |
+
leading=16,
|
| 104 |
+
)
|
| 105 |
+
)
|
| 106 |
+
styles.add(
|
| 107 |
+
ParagraphStyle(
|
| 108 |
+
name="CodeBlock",
|
| 109 |
+
parent=styles["BodyText"],
|
| 110 |
+
fontName="Courier",
|
| 111 |
+
fontSize=9,
|
| 112 |
+
leading=14,
|
| 113 |
+
backColor=colors.whitesmoke,
|
| 114 |
+
)
|
| 115 |
+
)
|
| 116 |
+
return styles
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _format_insights(insights: str, styles) -> list:
|
| 120 |
+
blocks = []
|
| 121 |
+
lines = [line.strip() for line in (insights or "").splitlines() if line.strip()]
|
| 122 |
+
if not lines:
|
| 123 |
+
return [Paragraph("No insights provided.", styles["ReportBody"])]
|
| 124 |
+
|
| 125 |
+
for line in lines:
|
| 126 |
+
if line[0] in {"-", "*", "•"}:
|
| 127 |
+
text = line.lstrip("-•* ").strip()
|
| 128 |
+
blocks.append(Paragraph(text, styles["InsightBullet"], bulletText="•"))
|
| 129 |
+
else:
|
| 130 |
+
blocks.append(Paragraph(line, styles["ReportBody"]))
|
| 131 |
+
blocks.append(Spacer(1, 4))
|
| 132 |
+
return blocks
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def _build_data_table(data_sample):
|
| 136 |
+
if not data_sample:
|
| 137 |
+
return None
|
| 138 |
+
|
| 139 |
+
columns = list(data_sample[0].keys())
|
| 140 |
+
rows = [columns]
|
| 141 |
+
for row in data_sample[:10]:
|
| 142 |
+
rows.append([str(row.get(col, "")) for col in columns])
|
| 143 |
+
|
| 144 |
+
table = Table(rows, repeatRows=1)
|
| 145 |
+
table.setStyle(
|
| 146 |
+
TableStyle(
|
| 147 |
+
[
|
| 148 |
+
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#111827")),
|
| 149 |
+
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
|
| 150 |
+
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
|
| 151 |
+
("FONTSIZE", (0, 0), (-1, 0), 10),
|
| 152 |
+
("BOTTOMPADDING", (0, 0), (-1, 0), 6),
|
| 153 |
+
("BACKGROUND", (0, 1), (-1, -1), colors.HexColor("#f9fafb")),
|
| 154 |
+
("TEXTCOLOR", (0, 1), (-1, -1), colors.HexColor("#111827")),
|
| 155 |
+
("GRID", (0, 0), (-1, -1), 0.25, colors.HexColor("#e5e7eb")),
|
| 156 |
+
]
|
| 157 |
+
)
|
| 158 |
+
)
|
| 159 |
+
return table
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def _build_query_callout(query: str, styles):
|
| 163 |
+
display = query or "No question provided"
|
| 164 |
+
content = [
|
| 165 |
+
[Paragraph("BUSINESS QUESTION", styles["MetaLabel"])],
|
| 166 |
+
[Paragraph(display, styles["QueryText"])]
|
| 167 |
+
]
|
| 168 |
+
table = Table(content, colWidths=[6.5 * inch])
|
| 169 |
+
table.setStyle(
|
| 170 |
+
TableStyle(
|
| 171 |
+
[
|
| 172 |
+
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#f3f4ff")),
|
| 173 |
+
("BOX", (0, 0), (-1, -1), 0.6, colors.HexColor("#c7d2fe")),
|
| 174 |
+
("INNERPADDING", (0, 0), (-1, -1), 8),
|
| 175 |
+
]
|
| 176 |
+
)
|
| 177 |
+
)
|
| 178 |
+
return table
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _build_metric_cards(stats: dict, styles):
|
| 182 |
+
if not stats:
|
| 183 |
+
return None
|
| 184 |
+
|
| 185 |
+
data = []
|
| 186 |
+
row = []
|
| 187 |
+
for label, value in stats.items():
|
| 188 |
+
cell = [
|
| 189 |
+
Paragraph(label, styles["MetricLabel"]),
|
| 190 |
+
Paragraph(value, styles["MetricValue"]),
|
| 191 |
+
]
|
| 192 |
+
row.append(cell)
|
| 193 |
+
|
| 194 |
+
data.append(row)
|
| 195 |
+
|
| 196 |
+
col_width = (6.5 * inch) / len(stats)
|
| 197 |
+
table = Table(data, colWidths=[col_width] * len(stats))
|
| 198 |
+
table.setStyle(
|
| 199 |
+
TableStyle(
|
| 200 |
+
[
|
| 201 |
+
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#f9fafb")),
|
| 202 |
+
("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#e5e7eb")),
|
| 203 |
+
("INNERPADDING", (0, 0), (-1, -1), 8),
|
| 204 |
+
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
| 205 |
+
]
|
| 206 |
+
)
|
| 207 |
+
)
|
| 208 |
+
return table
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _format_value(value):
|
| 212 |
+
if value is None:
|
| 213 |
+
return "—"
|
| 214 |
+
if isinstance(value, float):
|
| 215 |
+
return f"{value:,.2f}"
|
| 216 |
+
if isinstance(value, int):
|
| 217 |
+
return f"{value:,}"
|
| 218 |
+
return str(value)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def _render_trend_section(trend_analysis, styles):
|
| 222 |
+
blocks = [Paragraph("Trend Diagnostics", styles["SectionHeader"])]
|
| 223 |
+
if not trend_analysis:
|
| 224 |
+
blocks.append(Paragraph("Not enough chronological data to compute a trend.", styles["ReportBody"]))
|
| 225 |
+
return blocks
|
| 226 |
+
|
| 227 |
+
blocks.append(Paragraph(trend_analysis.get("summary", ""), styles["ReportBody"]))
|
| 228 |
+
stats = [
|
| 229 |
+
["Starting value", _format_value(trend_analysis.get("start"))],
|
| 230 |
+
["Ending value", _format_value(trend_analysis.get("end"))],
|
| 231 |
+
["Slope", _format_value(trend_analysis.get("slope"))],
|
| 232 |
+
]
|
| 233 |
+
if trend_analysis.get("change_pct") is not None:
|
| 234 |
+
stats.append(["% Change", f"{trend_analysis['change_pct']:+.1f}%"])
|
| 235 |
+
|
| 236 |
+
table = Table(stats, colWidths=[2.3 * inch, 4 * inch])
|
| 237 |
+
table.setStyle(
|
| 238 |
+
TableStyle(
|
| 239 |
+
[
|
| 240 |
+
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#f9fafb")),
|
| 241 |
+
("BOX", (0, 0), (-1, -1), 0.4, colors.HexColor("#e5e7eb")),
|
| 242 |
+
("INNERPADDING", (0, 0), (-1, -1), 6),
|
| 243 |
+
("FONTNAME", (0, 0), (-1, -1), "Helvetica"),
|
| 244 |
+
]
|
| 245 |
+
)
|
| 246 |
+
)
|
| 247 |
+
blocks.extend([Spacer(1, 6), table])
|
| 248 |
+
return blocks
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def _render_anomaly_section(anomaly_analysis, styles):
|
| 252 |
+
blocks = [Paragraph("Anomaly Highlights", styles["SectionHeader"])]
|
| 253 |
+
if not anomaly_analysis:
|
| 254 |
+
blocks.append(Paragraph("No statistical anomalies detected across the observed period.", styles["ReportBody"]))
|
| 255 |
+
return blocks
|
| 256 |
+
|
| 257 |
+
blocks.append(Paragraph(anomaly_analysis.get("summary", ""), styles["ReportBody"]))
|
| 258 |
+
table_rows = [["Period", "Value", "z-score"]]
|
| 259 |
+
for entry in anomaly_analysis.get("anomalies", [])[:8]:
|
| 260 |
+
table_rows.append([
|
| 261 |
+
entry.get("period", "?"),
|
| 262 |
+
_format_value(entry.get("value")),
|
| 263 |
+
f"{entry.get('z_score', 0):+.2f}",
|
| 264 |
+
])
|
| 265 |
+
table = Table(table_rows, repeatRows=1, colWidths=[2 * inch, 2.5 * inch, 1.5 * inch])
|
| 266 |
+
table.setStyle(
|
| 267 |
+
TableStyle(
|
| 268 |
+
[
|
| 269 |
+
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#111827")),
|
| 270 |
+
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
|
| 271 |
+
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
|
| 272 |
+
("BACKGROUND", (0, 1), (-1, -1), colors.HexColor("#f3f4f6")),
|
| 273 |
+
("GRID", (0, 0), (-1, -1), 0.25, colors.HexColor("#d1d5db")),
|
| 274 |
+
]
|
| 275 |
+
)
|
| 276 |
+
)
|
| 277 |
+
blocks.extend([Spacer(1, 6), table])
|
| 278 |
+
return blocks
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _render_forecast_section(forecast_analysis, styles):
|
| 282 |
+
blocks = [Paragraph("Forecast", styles["SectionHeader"])]
|
| 283 |
+
if not forecast_analysis or not forecast_analysis.get("forecasts"):
|
| 284 |
+
blocks.append(Paragraph("Forecasting unavailable for this dataset.", styles["ReportBody"]))
|
| 285 |
+
return blocks
|
| 286 |
+
|
| 287 |
+
blocks.append(Paragraph(forecast_analysis.get("summary", ""), styles["ReportBody"]))
|
| 288 |
+
|
| 289 |
+
method = forecast_analysis.get("method", "N/A")
|
| 290 |
+
blocks.append(Spacer(1, 4))
|
| 291 |
+
blocks.append(Paragraph(f"<i>Method: {method}</i>", styles["CaptionSmall"]))
|
| 292 |
+
|
| 293 |
+
table_rows = [["Period", "Forecast", "Lower Bound", "Upper Bound"]]
|
| 294 |
+
for entry in forecast_analysis.get("forecasts", []):
|
| 295 |
+
table_rows.append([
|
| 296 |
+
entry.get("period", "?"),
|
| 297 |
+
_format_value(entry.get("value")),
|
| 298 |
+
_format_value(entry.get("lower_bound")),
|
| 299 |
+
_format_value(entry.get("upper_bound")),
|
| 300 |
+
])
|
| 301 |
+
|
| 302 |
+
table = Table(table_rows, repeatRows=1, colWidths=[1.5 * inch, 1.5 * inch, 1.5 * inch, 1.5 * inch])
|
| 303 |
+
table.setStyle(
|
| 304 |
+
TableStyle(
|
| 305 |
+
[
|
| 306 |
+
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#111827")),
|
| 307 |
+
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
|
| 308 |
+
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
|
| 309 |
+
("BACKGROUND", (0, 1), (-1, -1), colors.HexColor("#f3f4f6")),
|
| 310 |
+
("GRID", (0, 0), (-1, -1), 0.25, colors.HexColor("#d1d5db")),
|
| 311 |
+
]
|
| 312 |
+
)
|
| 313 |
+
)
|
| 314 |
+
blocks.extend([Spacer(1, 6), table])
|
| 315 |
+
return blocks
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def _render_statistical_tests_section(statistical_tests, styles):
|
| 319 |
+
blocks = [Paragraph("Statistical Tests", styles["SectionHeader"])]
|
| 320 |
+
if not statistical_tests or not statistical_tests.get("tests"):
|
| 321 |
+
blocks.append(Paragraph("No statistical tests performed.", styles["ReportBody"]))
|
| 322 |
+
return blocks
|
| 323 |
+
|
| 324 |
+
blocks.append(Paragraph(statistical_tests.get("summary", ""), styles["ReportBody"]))
|
| 325 |
+
blocks.append(Spacer(1, 8))
|
| 326 |
+
|
| 327 |
+
for test_name, test_data in statistical_tests.get("tests", {}).items():
|
| 328 |
+
test_title = test_data.get("test", test_name)
|
| 329 |
+
blocks.append(Paragraph(f"<b>{test_title}</b>", styles["ReportBody"]))
|
| 330 |
+
blocks.append(Spacer(1, 2))
|
| 331 |
+
|
| 332 |
+
details = []
|
| 333 |
+
if "comparison" in test_data:
|
| 334 |
+
details.append(f"Comparison: {test_data['comparison']}")
|
| 335 |
+
if "p_value" in test_data:
|
| 336 |
+
p_val = test_data["p_value"]
|
| 337 |
+
sig = "Yes" if test_data.get("significant", False) else "No"
|
| 338 |
+
details.append(f"p-value: {p_val:.4f} (Significant: {sig})")
|
| 339 |
+
if "summary" in test_data:
|
| 340 |
+
details.append(test_data["summary"])
|
| 341 |
+
|
| 342 |
+
for detail in details:
|
| 343 |
+
blocks.append(Paragraph(f"• {detail}", styles["ReportBody"]))
|
| 344 |
+
|
| 345 |
+
blocks.append(Spacer(1, 6))
|
| 346 |
+
|
| 347 |
+
return blocks
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
def generate_pdf_report(
|
| 351 |
+
report_path: str,
|
| 352 |
+
title: str,
|
| 353 |
+
query: str,
|
| 354 |
+
sql_query: str,
|
| 355 |
+
insights: str,
|
| 356 |
+
chart_image_path: str = None,
|
| 357 |
+
chart_summary: str = None,
|
| 358 |
+
trend_analysis: Optional[dict] = None,
|
| 359 |
+
anomaly_analysis: Optional[dict] = None,
|
| 360 |
+
forecast_analysis: Optional[dict] = None,
|
| 361 |
+
statistical_tests: Optional[dict] = None,
|
| 362 |
+
data_sample=None,
|
| 363 |
+
):
|
| 364 |
+
styles = _build_styles()
|
| 365 |
+
doc = SimpleDocTemplate(
|
| 366 |
+
report_path,
|
| 367 |
+
pagesize=letter,
|
| 368 |
+
leftMargin=0.75 * inch,
|
| 369 |
+
rightMargin=0.75 * inch,
|
| 370 |
+
topMargin=0.75 * inch,
|
| 371 |
+
bottomMargin=0.75 * inch,
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
story = []
|
| 375 |
+
|
| 376 |
+
generated_at = datetime.utcnow().strftime("%B %d, %Y %H:%M UTC")
|
| 377 |
+
row_count = len(data_sample or [])
|
| 378 |
+
column_count = len(data_sample[0]) if data_sample else 0
|
| 379 |
+
|
| 380 |
+
story.append(Paragraph(title, styles["HeroTitle"]))
|
| 381 |
+
story.append(Paragraph("Autonomous insight report", styles["CaptionSmall"]))
|
| 382 |
+
story.append(Spacer(1, 10))
|
| 383 |
+
|
| 384 |
+
story.append(_build_query_callout(query, styles))
|
| 385 |
+
story.append(Spacer(1, 12))
|
| 386 |
+
|
| 387 |
+
metrics = {
|
| 388 |
+
"Rows Returned": f"{row_count:,}" if row_count else "0",
|
| 389 |
+
"Columns": str(column_count),
|
| 390 |
+
"Generated": generated_at,
|
| 391 |
+
}
|
| 392 |
+
metric_table = _build_metric_cards(metrics, styles)
|
| 393 |
+
if metric_table:
|
| 394 |
+
story.append(metric_table)
|
| 395 |
+
story.append(Spacer(1, 18))
|
| 396 |
+
|
| 397 |
+
story.append(Paragraph("This auto-generated briefing captures the freshest SQL results, advanced analytics, and executive visuals from InsightPilot.", styles["ReportBody"]))
|
| 398 |
+
story.append(Spacer(1, 14))
|
| 399 |
+
story.append(PageBreak())
|
| 400 |
+
|
| 401 |
+
story.append(HRFlowable(width="100%", thickness=0.6, color=colors.HexColor("#e5e7eb")))
|
| 402 |
+
story.append(Spacer(1, 14))
|
| 403 |
+
|
| 404 |
+
story.append(Paragraph("SQL Used", styles["SectionHeader"]))
|
| 405 |
+
story.append(
|
| 406 |
+
Preformatted(sql_query.strip() or "No SQL generated", styles["CodeBlock"], maxLineLength=80)
|
| 407 |
+
)
|
| 408 |
+
story.append(Spacer(1, 12))
|
| 409 |
+
|
| 410 |
+
story.append(Paragraph("Insights", styles["SectionHeader"]))
|
| 411 |
+
for block in _format_insights(insights, styles):
|
| 412 |
+
story.append(block)
|
| 413 |
+
story.append(Spacer(1, 10))
|
| 414 |
+
|
| 415 |
+
table = _build_data_table(data_sample or [])
|
| 416 |
+
if table:
|
| 417 |
+
story.append(Paragraph("Data Preview", styles["SectionHeader"]))
|
| 418 |
+
story.append(table)
|
| 419 |
+
story.append(Spacer(1, 12))
|
| 420 |
+
|
| 421 |
+
for block in _render_trend_section(trend_analysis, styles):
|
| 422 |
+
story.append(block)
|
| 423 |
+
story.append(Spacer(1, 12))
|
| 424 |
+
|
| 425 |
+
for block in _render_anomaly_section(anomaly_analysis, styles):
|
| 426 |
+
story.append(block)
|
| 427 |
+
story.append(Spacer(1, 12))
|
| 428 |
+
|
| 429 |
+
for block in _render_forecast_section(forecast_analysis, styles):
|
| 430 |
+
story.append(block)
|
| 431 |
+
story.append(Spacer(1, 12))
|
| 432 |
+
|
| 433 |
+
for block in _render_statistical_tests_section(statistical_tests, styles):
|
| 434 |
+
story.append(block)
|
| 435 |
+
story.append(Spacer(1, 12))
|
| 436 |
+
|
| 437 |
+
if chart_image_path and os.path.exists(chart_image_path):
|
| 438 |
+
story.append(Paragraph("Visualization", styles["SectionHeader"]))
|
| 439 |
+
img = Image(chart_image_path)
|
| 440 |
+
img._restrictSize(6.5 * inch, 4.5 * inch)
|
| 441 |
+
img.hAlign = "CENTER"
|
| 442 |
+
story.append(img)
|
| 443 |
+
if chart_summary:
|
| 444 |
+
story.append(Spacer(1, 6))
|
| 445 |
+
story.append(Paragraph(chart_summary, styles["CaptionSmall"]))
|
| 446 |
+
elif chart_summary:
|
| 447 |
+
story.append(Paragraph("Visualization Summary", styles["SectionHeader"]))
|
| 448 |
+
story.append(Paragraph(chart_summary, styles["ReportBody"]))
|
| 449 |
+
|
| 450 |
+
doc.build(story)
|
| 451 |
+
return report_path
|
backend/create_db.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import random
|
| 4 |
+
from datetime import datetime, timedelta
|
| 5 |
+
|
| 6 |
+
def create_dummy_db():
|
| 7 |
+
conn = sqlite3.connect('test.db')
|
| 8 |
+
cursor = conn.cursor()
|
| 9 |
+
|
| 10 |
+
# Create Sales Table
|
| 11 |
+
cursor.execute('''
|
| 12 |
+
CREATE TABLE IF NOT EXISTS sales (
|
| 13 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 14 |
+
date DATE,
|
| 15 |
+
product_category TEXT,
|
| 16 |
+
product_name TEXT,
|
| 17 |
+
quantity INTEGER,
|
| 18 |
+
unit_price REAL,
|
| 19 |
+
total_amount REAL,
|
| 20 |
+
region TEXT
|
| 21 |
+
)
|
| 22 |
+
''')
|
| 23 |
+
|
| 24 |
+
# Generate sample data
|
| 25 |
+
categories = ['Electronics', 'Clothing', 'Home', 'Books']
|
| 26 |
+
products = {
|
| 27 |
+
'Electronics': ['Laptop', 'Smartphone', 'Headphones', 'Monitor'],
|
| 28 |
+
'Clothing': ['T-Shirt', 'Jeans', 'Jacket', 'Sneakers'],
|
| 29 |
+
'Home': ['Sofa', 'Table', 'Lamp', 'Rug'],
|
| 30 |
+
'Books': ['Fiction', 'Non-Fiction', 'Sci-Fi', 'Biography']
|
| 31 |
+
}
|
| 32 |
+
regions = ['North', 'South', 'East', 'West']
|
| 33 |
+
|
| 34 |
+
data = []
|
| 35 |
+
start_date = datetime(2023, 1, 1)
|
| 36 |
+
for i in range(100):
|
| 37 |
+
date = start_date + timedelta(days=random.randint(0, 365))
|
| 38 |
+
category = random.choice(categories)
|
| 39 |
+
product = random.choice(products[category])
|
| 40 |
+
quantity = random.randint(1, 5)
|
| 41 |
+
unit_price = round(random.uniform(10.0, 1000.0), 2)
|
| 42 |
+
total_amount = round(quantity * unit_price, 2)
|
| 43 |
+
region = random.choice(regions)
|
| 44 |
+
|
| 45 |
+
data.append((date.strftime('%Y-%m-%d'), category, product, quantity, unit_price, total_amount, region))
|
| 46 |
+
|
| 47 |
+
cursor.executemany('''
|
| 48 |
+
INSERT INTO sales (date, product_category, product_name, quantity, unit_price, total_amount, region)
|
| 49 |
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
| 50 |
+
''', data)
|
| 51 |
+
|
| 52 |
+
conn.commit()
|
| 53 |
+
conn.close()
|
| 54 |
+
print("Dummy database created successfully.")
|
| 55 |
+
|
| 56 |
+
if __name__ == "__main__":
|
| 57 |
+
create_dummy_db()
|
backend/requirements.txt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
langgraph
|
| 4 |
+
langchain
|
| 5 |
+
langchain-groq
|
| 6 |
+
langchain-community
|
| 7 |
+
sqlalchemy
|
| 8 |
+
pandas
|
| 9 |
+
numpy
|
| 10 |
+
scipy
|
| 11 |
+
statsmodels
|
| 12 |
+
matplotlib
|
| 13 |
+
reportlab
|
| 14 |
+
python-dotenv
|
| 15 |
+
pydantic
|
| 16 |
+
requests
|
| 17 |
+
python-multipart
|
build_frontend.bat
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
REM Build script for React frontend (Windows) - run this before deploying to HF Spaces
|
| 3 |
+
|
| 4 |
+
echo Building React frontend for production...
|
| 5 |
+
|
| 6 |
+
cd frontend
|
| 7 |
+
|
| 8 |
+
REM Install dependencies
|
| 9 |
+
echo Installing dependencies...
|
| 10 |
+
call npm install
|
| 11 |
+
|
| 12 |
+
REM Build for production
|
| 13 |
+
echo Building production bundle...
|
| 14 |
+
call npm run build
|
| 15 |
+
|
| 16 |
+
echo Frontend build complete! Output in frontend/dist/
|
| 17 |
+
echo Files ready to be served statically by the Gradio app
|
| 18 |
+
|
| 19 |
+
cd ..
|
build_frontend.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Build script for React frontend - run this before deploying to HF Spaces
|
| 3 |
+
|
| 4 |
+
echo "🔨 Building React frontend for production..."
|
| 5 |
+
|
| 6 |
+
cd frontend
|
| 7 |
+
|
| 8 |
+
# Install dependencies
|
| 9 |
+
echo "📦 Installing dependencies..."
|
| 10 |
+
npm install
|
| 11 |
+
|
| 12 |
+
# Build for production
|
| 13 |
+
echo "🏗️ Building production bundle..."
|
| 14 |
+
npm run build
|
| 15 |
+
|
| 16 |
+
echo "✅ Frontend build complete! Output in frontend/dist/"
|
| 17 |
+
echo "📁 Files ready to be served statically by the Gradio app"
|
| 18 |
+
|
| 19 |
+
cd ..
|
data/archive.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:06179cdd0ddf1e4df27d19b80c31bb4ad873453f41a26f00d332d3c720c8e2fd
|
| 3 |
+
size 79402
|
data/report_f86d7e18-bf33-4d6f-a4fc-e57eb10590f7.pdf
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
%PDF-1.3
|
| 2 |
+
%���� ReportLab Generated PDF document http://www.reportlab.com
|
| 3 |
+
1 0 obj
|
| 4 |
+
<<
|
| 5 |
+
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R
|
| 6 |
+
>>
|
| 7 |
+
endobj
|
| 8 |
+
2 0 obj
|
| 9 |
+
<<
|
| 10 |
+
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
| 11 |
+
>>
|
| 12 |
+
endobj
|
| 13 |
+
3 0 obj
|
| 14 |
+
<<
|
| 15 |
+
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
| 16 |
+
>>
|
| 17 |
+
endobj
|
| 18 |
+
4 0 obj
|
| 19 |
+
<<
|
| 20 |
+
/BaseFont /Symbol /Name /F3 /Subtype /Type1 /Type /Font
|
| 21 |
+
>>
|
| 22 |
+
endobj
|
| 23 |
+
5 0 obj
|
| 24 |
+
<<
|
| 25 |
+
/BaseFont /ZapfDingbats /Name /F4 /Subtype /Type1 /Type /Font
|
| 26 |
+
>>
|
| 27 |
+
endobj
|
| 28 |
+
6 0 obj
|
| 29 |
+
<<
|
| 30 |
+
/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 480 /Length 22647 /Subtype /Image
|
| 31 |
+
/Type /XObject /Width 640
|
| 32 |
+
>>
|
| 33 |
+
stream
|
| 34 |
+
Gb"/l&"H@:rr7iNCQ'-i_$hMWAX9\K-+,gs@]:.A&>:DHn?!rTVE\GYVl27Y+-a'JQptFt>@IuJ&Rt\Z1]FJ*M=bF;72V!Z]t\V0cJpg,mU`lq4eMI/cf_&nO)rXXpV+r=FjBrumcVm+=%N\uzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz!!!.4s8Ms3.TCZkp")S'r2Wl#P3823s0>FiR@,L"qoq(Wr3B,a'J?]o!2,l?CY$SJr7uSS?2?poZEjJ318TK<chR%j(GB+]]101K!!)aJA]b])QDGcYr?9FS27r\HRkOa>oPaN(;PhNapYG#E7^X6:!;AeZ#"!H@dD#Y]-=`%W^0!gOp?^IFT7+.FUILB.OUW3c(d33;l-lQ4LWE3IqccKo:f-Z6@umIaVou;O[;+5eSN:W@ZGfkTo`"f]s7r:fYJ*%E-70lZiRbjDot5SAXRuf.*-gth#Lh,LU8+Ka=0LJoH["@+%0$4;DV_nYDS%Ja5$jh\IJ``==0K%krU@]8_hSa=(LesF.I#DfDuJi`ru!)Pr;?I64aMo5d2b,]fVqfIXKD'#c'pXFo+9TmNZC3IqeW"Z\T;E:8b")Z,V*Ycs7_$C=-fVUU9;3'g.e%pq*&?.MA>O4[>U$(X]DQ-f</ENo('rigR^QTUh"&;3:5V<s,TDDBPf>B9W]KKrX]&0#U`icDXGuTQOM'dVG6q\.47l\OS(@sb=\bEXK8LPrVFY,STiq)4_4*B`Moi9='g0KFoT5VrU7%4_hJV%>]I`H:HgdSegr9<P`uk"i=@"oCd$hlqfhc#fp8R!aic?PI/j0>9A`mA(+rkEU.'u[j2T2hrqOMu-71'3S_;grVLPGNUg)T^D>W=N08_Ds8\)B\rH`6pn(tb>k+du$qWb,b:7=$(GJF%2Rr?#Dmf$=BorDHu9("KF`f,95]iT+FmG$7HqT:=laNa,,\(gZWm'G#Kbo:T6;=:d4%QA7YZqd/Q:I"fbm$0sQ(DeIIp$:5%=GeBT\8gQgiaV6Q++3QDjiWjbcmMV\r6-tCe>ueO>he^.XF4m;ZK9ndh`Z&CbWl$a%s%8u-_@QNb&NP1^ZhVbXCLneVk7;f+TMKRB)FlCQDppdOn%+2oD%@,bF4\H='@Q]5C]!PZ!A=qV1gG[^D*r$o2>Fo>ISM@biN_'H1Id"c*_?=qDOm_s.2H)^.FM81AR1=qlpSL2XJeaK6:+-DWoH@_=tmb.P.!4g1qaUkTO(RDd$\^Ycps$Z(jdJZF@ug"e#?oDd`LJ4u]^G:ftGZ\_NG6q8%[gXYo[P_tRHT2:l)3X&c?SJcb88LgG8XAb'0KGM`T7*,%S:?$G+)UIG^iAt[JBK008:!5aSBs%\q86.Yg[gA0is?cnhX*dc,B)<C8$kK]Xqp=jN#q;rWaFkTdV9C-$OF&A>o#.`'J1M<fP:In"n0k,gDd^O]bD;$8YJ,]&3^[XVH9??u2lQ(TL@&m>1m(%BQXMU-p[+FC%kKB=WT7>_/H-\YJaR%G,H82"]SD:#<T0U^L\lW6e[=)!=>Fqj9kK`c>lYXt<3HJa,aH7_HP>ca*+gYS6IIk]ulI;f=k54V^[r)E8h]NF1!.]%JhnK[an:43?V!39q-@OLZHZs8Y^ON*6A2%rW\8e?;Q0^^sroQ/SZ*mtJs%;=LUnl'pj`tC6l2*tTRDY%*4#SrR[:7+4p@Ae30YlE-gV7[?oi?bH*'8>f\qA42`F/#_Xeh3SmbJgqJ#pN@[r7nP7P^)28j:hN,tjQF*BsJt@p*-HCmcitrrYERoP^X&SUB&1rJ(e!@c+OjHJsPo8hNg,XY.&EpD^EdRU9:rG4auqH`P(8CCV=D@,27@^HinB!!(t';#d+MHe.q/_>Eu;kFT"DgK^sjJP6nFptu83qcs!j=&L[(g"H?$pQrFd4Cm6WERt!`n%W4Ql-W!C?+=k4huE\"H2\^>C=rLEgV:#rZ7Mk:nAB]F+g]@nXK6<q]#D\/8f'-d]!U7`naBi,`4(5u3U`V,K4Of7Hge(B]=+dIABh^bb'/h-\aLp)<_B[\-(!E%^)&S0ASN=-9=b&4bX2@1+IpN+l_:"9bG<8SoB5c<oJ6C"U.#H:[VI$Bg&I^pD2O4.Uoe/cD-_'bX]dT/[O]Ekfi2kh=r0KLRPjD/<5/4o/(r5fDnN@1XV^s6=no3#qQIiU7%%2u`Por>&?UG_J'7fg=f0MV5(=&?VQrGY0*$OEr97",P!O?ak0<T<lOT,1#7n8p8.kW'J(:Dj)_#%L-b8s)CN$cF(Ah]=:/6KN?VN#\jSo,OgM_LYNZ:&W-C[6tn:N8nY?kK]`V-1NP:'GAn?n^JTD!2CEUacF>g]?=PN0]Eq``cd?[l?Z3b'`m/G*6*\om=>X]h"%5CouPo)3<gp#kHY4aP3L"+?6Pins%YP1&["A9)Pe0%oscg\n=j$<!bl=IJ+EgS')t!"?]40]$fi*H/U\`e"@%I(t0\ZY.UcX=S/?^6kpr["^&2XSCRlccQ,eXB6W]`f1obpQ:?'4aZlIO$"V/fr.cCJ%'p(`+:mIO!"DeX09ZN4";`FGJF$G[7P[7-G76O&!r-TW2-;f#5\8N\euM/?,(i:BcPeJq1&;8mCRK),+GCRX:Jp@:7Wl(`1T6^`>;l4GCP"TIj@$Vh`pD^]6B[TV5L67qQ$d1._**?[stOK+'j0RQ*TQ*B:hmj8s5%>rF,Iib>aW=Vd`A\dCOJd0'+5V!"`b<8Q/MbA9q7lkP$p,H01NL%3%c)7+A8=chI/m8kM=.Q7Ak;[AlC3Q*&K%Fm@J5M2Ae5`Z*O4?$gWAf)78,J#-AG.&C%BH"@I9o3_CC>j9K6!s[8V=YhZ_m^CkfgUVC'e6/ID'kB@TpYC$t:F5b"T!dE&'GVA)e&p`WG$aH*(1tj^)/+N$_TL'Fle]=eggB@8iJ/M`It,AURZ(%00Hms<J%'o]%mTtg_N[UOj5]5aJH,(V"=[9+8A^V3<qtqJDpZ6!!8n>nHKh8`'eA!FiroD_J5C=dr:s^ul;t]LqZ[$#0TDg$U+ZUFjqk2EkJW2joB4FA1h]^>$5MEGpKa\@#64`H+P%WLb9_913`:d<r`kqSORU:TrmBtY!<<**`j'$or-oL"ilI6,:@B2'zzKSe^Ji^8J2'c%R6%fcTuOc`ngF5MHm'MLt+<hN)s!!#8)19*EA/9"0_s$!D%0;m^;!<Bp]_O=8cq=/M[(-,n?pp[d7!;+0RXp^7+!.[.=4P'bW$NRs_0V7T^iroG@%N*N7&`e2-5C`[VR5:``oLR8jG5.+^!4W@3<u6FJ=&DiBJHH/J+C%IkP.;h#!!'M2qH"Kp*j=S_igi/Tmb+qEYd!WXacfKG%UG)?/3-AWOuZm>#CugkqH"Kp*\SrGR?@ICj,Zt&Pm<GY-$)Z7koBd)XjFho#h$J05r^$/!;Mu/^Ug(f%=u_(i+GCVlGA2PHf'8GFmIWG(OSOUE:Mr,5P%J%SN-\'!W#k6QeoId7fD_@rU1BnnE-3Y*^4uH06O`:qA0-:$N7"5qY0/?rrR]Wd(d.G]RQb.IK*TTrVQ>=o[-12>ITWON3$<sp8Ci`cCR1'mbQ0Z4#_-:^rnCG4?>Ma/M6@dQX962cJ\Z5E8e\+oQ5@dp$1'`1,;Up3$Be:qs:XnDnWPJN>h?Wna5)?s5uWHC#@gWIm<u2o,^&@<3'%O0eoqKf<2D,f(rCI<lo7"`l?/u)fE1E^juT!7-S%YpmKLD\hLdH$O[?s[V\*>d%E$bq;R;cUr"1!kb7qPI&XcI2/?f9B):CZb4j,J1ZQK7gTY,(^4#n8I.L'Xe6?k)ANa\qr]P;_8EJ!ZVbWd4e[]PO[]HR3j2[5(q@tM]r8fS]&R@ujrql.[:/:fILEEUQf_m=p=']s6oY*Famaf!on"rgK()7S+Rfo_H`ufg/?iTthXIhRum_OM\bNRBh!:Ve<Is#1Es-ETTKB5>snW2+3p.mP<3QB%W=he)Or:B*F6\k[?pV42'KD2QCCaBYmq5H$A=LDnopYL8C\nUoB'X[>s8b9s?`%590]V(o_mNXO^+O<XZ!ecakN*YCtrmc_*rk7X#]DD9jDf=@8AW,of24*t(h\e4^>^PkG1.SKFV+O-D7-iF+XnBO-=6@dG)&Z>Uh7Im'4aZn-A&k\D7un_i_]Sm)\8gR(piq)$MA7X6qcfQVrfn@=V+R#Qp`OdM3HM'6P22.UMX&Wdb=aM%,ZAN/?2>?[SC#%/3,\-@"Uc)67n63@DVDI3c^m:P#7k_"U4p#>U4<3.$1-s-X2g2=pT%[".Ocf\hnFNEo![:jCXoH8OsNbAF*$FLV:MP=RqCL6mb]0TL0SrYFaJR0@:>jP2qaB$'.1)'gK*J'CY,^@,>.t;KG41u*68!VmEhQE_[etJ/c1%s//)%Lo4Qsm$*sK`hu;3@.9"qnmp@$%$`Ber]_LKEZ7MjJ`Z&hulTbFIEs^++Iq??aR$`-GPQ1LFYCGVq8'7p/If[Cmr<nWqIfBlr1oPQ_f),41s5oehXR!'E%NV"1TE!1un(?6!Q+<A@o#i]$*$69;CtZ+"]69/O1IdZ`Z%(B->;q$/p^J0#aL!uU-RUZ)Ep;#KZG'nnQ;[5cMqm9gRr4+?M=L&l++O3n8WtL1giCS&rp^Me26U2k8p^3XTgQ-`jHEAgqWXm_%NJTbI!L\i267=b0:Fm$KF%Dj*68!VPld3WU\%iRmiVA_mI]YlrVQ>m<ioOl6Ge?c4t-)JM>\sEDnGDFUnjfbW2R)1[-?&;>O+4bg=3-&X6Ab\C"S+I(H,sR^qYAQ0>IFj?b_'\`]tU%W)4db=!60;4aQbG47B7LViL,P4E0)cEW6!`&cE(K:?Z&OnaXXAGNmOEL=ngVaRHtI&,:Tk.23I1o^:PIC=T?4'E\RM[(bg7Z$#B`G4"#*0V4`%3bd"fhrg,I=js6BAQ*o$qN<-tm.KPfT-$.<X;$lJ6q0YcmbPK.]$DGqrCJdgF+A3rs14/c9:%j8e5M8@/Ln5ZA\CdRlIN*'gV9:?Fcr$Ueuei1P><)UDVV9o05J6(R%0]%Vb`r?gtW&@(<Ek#euW$*VDW/?<2ro3o&RnJ>`TaY-6aXCqtBF.VMPs68rpaYc9'J1?+Y8sfWa='a)h+/&BGh6SC##g`P6o;pu73rPS>QFhp3lc]Tmh]fW`3'q>U9[=0,NonDV9j4EKZ/`5KTgU*=C%W)9=#_J:atm+J^dkK`cW[qX;\VUZuhP@'aR:73lV>$kB6-kg.Umbk_Bg&=-Zn6hOPnGd'YnDK#E!R-,oFXq&"\%&sC2o#0Job3.qrB"Z;S:lOuqeg&mSs>/79Ts%uDr8:CJ"p8ZEhtmroj-'np$2@uTZ<$2f=ZBM=kL?1YM<d^k0<Usp?gTF)`B8BY'RO+-1*kW@K/VRb]k:P!-=f_h+F+7SM4J=LCP?+DpA(Nn+cG5e'cZl+./:WA6g''pE)MBYe>M+NZI\\1-Wm%6q"ukq=EuJ=]nn8C=Od_rENOoj87R@4aQa^ag1s7SN_Juq[4B)FNBsMCDp^n&5MFr\)Q/W)`MY.:/85]<A$#$Ikrl:/,U+Xf5\kO+IpfnDT0)7V/7_`XB=]gZK\[</s@&'EUNG]9:Z$R4F-K6a9!t'8Xo\_#TNE>>Oh+<Dnl6o]+9&Mef?V_eBXUH9.jJ!,njhTT$*HR06@*BR58KW9qt4>p:^I*9:%9><nk'"IBUD3iIr)Lol:?;j6NnoSNA4r-@>a`@Xrq18R!s:jIP->!)Gl[\r>[iO)HX>&st_&W\!pP9^DTOF%PWjO'8u.\UOMReZ2b1C!ss!r_[*4@<-Cob^snWF0i3Z@q0"s^OK4nEb4Lgr;?K:bfhj"5EZ?G]C3JGD;-t:56&[\5:S,\BDAP8Q?h.4#UPY<<PISo]*nZN$Kti`Z"hgh14NB=n%S\Ago!jG().HRkJkK[`Q-"0(bhW<hqpsr8WdKTI?L+jS@#Z1aN2G7c'nJ.re;,r4=VR#A(Rkh(Z"7>a%N#HWaH)CP.(>$ec67rk[/;!i5b0=\T:Nl%Qsh73s1-RqA1-,iroE(K`2.qiHPBR#GHX`EB%8)i'HR6E#alS!-f(O(EA4j"TTTsrWU@$GQE="Tmsmu3*lUC60MZp*0(g8GS']f*/EPX!$6gm"ioSH"le^X4AC9Y!^I(j$O4E:$3^;8i*osel=N01"ii,)oIk`QETO6jnQZkMY<^IL!%u9VYAV@$?#[FX62q"r(g"G_):*HAnHoEt$fGK!q<DQSf*02F&G.W+MfIjZS+0`"*3VWS^Aon+Q*5)FF'Wat!^Zhf\/8;]\f&h:_=7j8r<qOm>l!Ys4P'bW.hM>+J;\F/X./Wa(EBVDd(]Q5`g*`F`cGF)]E1D!!k9(K^:G3.Fi!ST"T8_d@U/`oAj"ZKa"%_n07\t#jf=m@>SeB`5g[7G0#j0[DT6a'E4c6<rrT\I=2)6`cZR(o$3^;8i*osel=N01"ii,)oIk`QETO6jnQZkMY<^IL!%u9VYAV@$?#[FX62q"r(g"G_):*HAnHoEt$fGK!q<DQSf*02F&G.W+MfIjZS+0`"*3VWS^Aon+Q*5)FF'Wat!^Zhf\/8;]\f&h:_=7j8r<qOm>l!Ys4P'bW.hM>+J;\F/X./Wa(EBVDd(]Q5`g*`F`cGF)]E1D!!k9(K^:M!?Sq$\.a'H8uWDf^T,d;O`@Vb(n1M<;(ied="o[qeug!mKLhnt3[gCG_].-TL!GL`]nX&Z2YE8\Ola,euE[VjpTY$JZ<W2S4qe4GiNq$Q6Ej>:`-<&VkI/m5Ku?@VtPq6=GMeui:?3]];H,@gK9h&"m[0-@CUd(]Q5`g*`F09>9hQS*^OWM^L`>3+GQKnY5ihKnKlDnWQ"kK^JNf3W[WFhdqKYO.*elI;e%M\bX0Xo%1C:Du00goFL0CV9k<S=JB4:S6Ftg]\'$`5KU<RPi:BZt@RO8sLl#Sif"d05o(%0bG!0gUD+12GK]$;0r/2GM\JMmb5'a_hJUrSN7L5T=oWbJj]P>\/8;]\f&fthu54FB:jT!>7Vc;.*GM]ZFjV`#&D]bn%W57@RY'U4EKZ/`5KTgU*=C%W)9=#_J:atm+J^dkK`c7[V=4QJHm5E.@pCZb]h3o.Frh0I@23NG6d#*N`R@e?N:o&b#^O^GtHB^rSW;=Uh$TTk006WOH<jg3d^S%9Pe;:/B`J=p?gG5S%WOtb*DOfa,V1(ja-Ub9q+&chDB-*\E;&*bJN,LHf[G\2fH,`[;XrOfW\f#GEq2'#l5@Q`OPKhc$3P-\:7;F3d'htfCnXggU]Cor:mY+1uCA?,pg[W<u*i=kV.8N@!hj4*BW=BA\K_=&J5WHkKgUrAS#H'W`?*qoQ+`Wc]KGBlqKWhoT72#s)+>jIJWgK*'/1tj&I`(07\t#jf=mM/IBe=B?m%Ch=#gEZ=QZj*2iWl1GcAS:\"5M.Mqpt^r<hSeP6(0Ph3ZJ^3AFZBCk91^]!k1H_jZ55;W>Q'^/m((ZWY>2uQt?f=tA^2J^n1l`\(s]pj0Ce]*RfXddM#0/%DFPs!FI1j\e-hs7JWFQm7!8_^[jIBUD3iIr)Lol:>Jrp&Z3rrT\I=2)8Z4J!WZIXH2cP/,SH;e=qG_1U10?+Y8S9hjHLA]pDVYV0h\e>ZBiQ7^^l?b^p[l6]5of\\nC/1aIi:6o%%DJj?8G'7\<XRGnE.T`c(4e&(9T:)*Mk5.Y(TmlalGIMGD-IZVAJ+RfTIfWf5.pq+=*m[oi>#j_RWqY=nhu<TO/,1:^ZlW+A:`u;J(Ddl0+$X/KJ9$-h,:+X)MUbcV-RU8n/M/_5;WcL.^V:r2/W\&qaOmn9f1#GebJu+moIk`QETO6jnQZkMY<^IL!%u9VYAV@$?#[FX62q"r(g"G_):*HAnHoEt$XUWTkIE*K1S3oaitVjGN_A:Z4F<[3dP^'q/i[0gnEdXfT#.hs\LUrAT'M!g'aP&b--m'X&:Q`gZFSC@j1-d%?@ec!_$bM]A94S_`cGF)]E1D!Jf!qCalZ4IN9@e1GQi]u"Y\fePKfAq):*HAnHoEt)h'nbgd+&l\LUrAT'M!g'aXXpr-6f&K6T:q=1W!+f*02F&G0o9nZ6^8nSI?jFNo'5$]\:2khkmi=lU"Og!!adRdjW!*3VWS^Z!5>`Z<"(RCgm)j1-d%?@ec!_iJGi4,@N*GGT)pF'Wat!^Zj@+3tjL&C4tV9%T$b]&7f:K)Q3DSi,pYT$?&UXr5Q._=7j8r>U2JCYV3<S%1%T1S3oaitVjr2uIpO\:]#aF'#qb4P'bW.hM>kq!kG*#OuWmY&qp5Xp^7k+R%_Pj&tCPin;O]l'h-I(EBVDd(eP[Zc4$(Z^JGR2<25"3*q0/J&@=[NB;r/1O#[2`cGF)]E1D!L*+_[G7`)4mRl)ikITSr"bQ^_58XpGCGL*tCW:F$E4c6<s+VVSH`DRd4l!2&dhpDR_=7kcB?);'_76Wd;LP.Knk<OUf*02F&=so/*!G<5C.S#Cm`I>FnQZkMY<[>U>]LC7l`),YkIE+h1raTBE4c82fl>,"0WqI2<1i?V4OY.nFi!STKP/=O#JmpQ)H)!T]CJX*4P'bW.hJ9mQQ-.g\nXe/H1&g2NU^6i*3VVPG$K&!N9dJee`XIncSWX_?#[FXiRr4m5qoIBL/af.Xut/McZR(o$A<'4,mV8rXr:2dT"FPe@m&?Ha"%^m*L@`Kj18trom,uJ1QCp0(EBVDG]m>4&<,qT+k>bd/'g&lF'Wat!o_gPa[%V`X+jTGkH6qr(sd>EdGr3-kd>EP5qoIBL/af.Xut1GgqOq\r,J&U?0]q=,7,o-]\$P?rquSsA&m0'>rt).3[5kZp[6l2VLjDOhS"8;X]i,9[;#dK%Qo4<\@lVBeue>n[>Tp"8ki$N1hi,V:ejb\e##iN;gNcdOsNaR748`>D]h/(/ia3WFk)T=oA#UDR[<nql-cD(4n[=!7>o=\n&1%rV-`&)k8gPOY$JXtCtS7,WDhBF:7XGRB[L;F\ojWmnk%l]NI1?`AZ_:!PEV4-o]_R/S%E(EJs_pm=gM^CS(=ZD;lNZkace+8jlMI>1F=:i@f,ld(DR8[r>Q$gN_9k6EEtGtqE/u`RHEW1[INWkR?PdHFNTgsf$2(K6CG;TV96(XW8)I3L(2_2pYUJ8@`=4[%j.,!R58J&-70KC'j7"qN>a)V?>I)ac^p[hGIFp5Zt]-I!tPMN$3L/F#g\N(%YA\+25L(2GNG5^s-ESiJ+rBe.lTD*hJ@+IhnK&n@DcVacC?m$#mp>%ZY.V,`)`b]ZJ;p-heq8]fpc$F>U`Hhm+=$qY#hg`Zt\"U:#j6QCYl^LE1o%W5(3;)$QM3LM#JtBc]1X.in;P@8s?f"d(^kkmGBd:f#Z)\7ul5+Hh?MFF')Q^5CRu`>+4m^PEM)>jspf[3cl3$Gd+l1a,V22,Vi_mjbS_Kq=Ee(S$K?*C=T=!>c8(!R@+e=$N9:OB?);'nSI>[V9.LLkhd$5qj7-IV;U$Ha[&%Y\Pd,s2\#Vm`;%ZNIGsY0Jai$9j<1&=>cBQJRg/gq;Vq(J0>-e+F]4GPF8aY/N>nuobEjk8F8aXW1M<d`UIUC=^&G$)QS,^IgU:sIYJ2Pg-j]H`+?V_<3J_<(="6r,VW$Y94b!>(k#*IUWDf_;_SLj091lr7,kKf8hgK_'[V[e#/bAq9Q7Z=D?b_'_lIAUt1pOfF<*37iS=D.:i8!,Cm-O)kfN@&"+'#IK=aDtj5<gq[Zcr0)!XT$^rXFNna!Cu\D!"?dakS_cgqTmjXX\Btds^uH=*!840r.TV6CO%!Z"qrDq]Q+H"k'r$EH1O=nH/t(./3Hp;GpDg=FGmDEcTUVdNTn\Rl,!Ds8*bs$3c%\%>8B7,mWsVk";Jlgbc\!^D`eq"6%pQa[&%Y\Pd,s2\#Ub(\DVg4?G/ULhAMF.S]'&63$5%rXFNna!Cu\D!"?dakS`:?X;$nJai$9j<1&=>cBQJRg/hl$i2dnSfO.;`3Yj^PpUS$TRV)Mrs$?Hj-#%>2KP_CA8W=XY:f%r5^7HWEY-Q/Y#Lk69nWGq"oTAGc]1X.in;P@8s?f"`p$i9d(b/$m'u+!T$:N>1mshFSGq%Br>Q$gN_9lAg!#^RPIYDIl!I^5hS2[Z<KBc_in;P@8s?f"`p$i9d(]c'G;aLInSI>[V9.LLj7KN-k[+FO4.Ed5GW'W>dU[6aEV``QoWNhcS^#qU4I`lZBehWl3;olcqU\!mc=ZJeT$:N>1mshFSGq%BrG/'GB/=`CcXs=ZR`nJ^:B0U2r\`'4Z^En2B/<U=:$/;jVgihTru,S+futJTZ^E>/-?IXpe%`JeIgn7&Cuu5eg!#^RPIYDIl!I`C^BPU#2KK,nD!"?dakS`5oWKn2?NZ9LS%4/r2KP_CA8[iUqH"L*YCX]ac.kTIS%2F21,mH;rMp>&f0JBAB'pe5c!75)R@@fYrm_2NCS5Z[1NsDVB!,+%bY9lgs6>482:/k>R6-4;1KQ&#k,'#oIs"'W)JIpZbT4X.R4FOLF46)s^Ug(f%5dKhjppD(b`tea\S9(J?X;$n"r\k1105Vnk,'#oIs"'W)JGYba]sNVR@@fYrm_2NCS5Z&-Z^$NA8[iUqH"L*YCX^8SBOu(PIYDIl!I`C^BPT(F#$f?:$/;jVgihTru,R@bfO+D1mshFSGq%BrG/'G1+.,XdU[6aEV``QoWNhcadgRT8<^Su`p$i9d(]c'-Z_%E+\h*r)5ht,62qj:SBT)[Kf#3eAt8kN"oW50EAAn_("-W1RW81*(\Fn^`lXap<Ci=aBu]CEJ'OTH2eL:$b:gUm3]aiLUINQnqs=.MkH.Ia0Qug8g9k_>VMpImh7ImcO")P+khWjpeZ)WM<NA#si5"BnO5$/4DVa$L?+[&6W79>H8WtLsC?=O;1i:rG2Pm=L03PUAUAIF[)JGYba]sNVR@@fYrq0q%q!g4VeG.eUrjI=Vre1^r`l5p1r:8%^VMPs69"ht1c9"q'?+Y8sfWa>6;l?]kSN:n.dA"UkFmD6ejHITC)u&XJb??RP-7:28l-h"=2g<)i"Yh_dZY%If2Q^,gVGa6_P:'/ObKH*hO,/VE^3t@tIA;9$1f!1DY!ST$9f#QEqU;urB:6Qh2t+KXDJ@F[d`J]:I&BZ+@]_:7/<kF\<2qKBJP(k?2JfTU^&S*/'e<<II/'osc^[!O0eor3W6![LC=FZ"n64!uHhJ.*>Gt8c2@hq>5k'2sIgn50k%(V]R`nJ^:B1/8oA9`])XQ1c9:!&'fi"kOXZ,-b;jR1Gh()WFhRqOUBD_DiLCG1L$ZkIS8OuCVf:P3Q<[P85(+k^2^4#m%ldj>;ZuY8#H'2[XNlc=THM+.0Rq?RW2f;mlk0<l!kOe#fhd><gTgSIUBu\8bbZ.2t\S9(J?[mrkMe/2('A1Hm/'Ir?9UK4i?aF@(A9HtS&c8._HM!:ZCe8>PVkp6c5"SXMP0A;bTgOSFK*MpqfC.@s6_6^.bH`*"&M\e7I!\dm<gIj@s8HP@Ale[UoWM-3adgRT8<^Su`p$i[(+`RLR[P`+hmRCV)fNAd]mB@^F6AiKf>#OZ6S6QmUs:rfPIV%bi9Ks<5>o3OB$@Jt553iRemE6aqY]"_rr-]+EAAn_("-W1RW82U+.;V%;cek`'.,PMd\Ps@oiEfZn:J&S67[-Ue]*RfX\I!*0/%DFeX84+XA,H[O7p9dW_8[2[SY_eD7TYg&S]62FiMOnbZ.1==/UQH\aR90;GpDomiTXk[V!dQ:H\0#[;"X=M)*D*pYC&&D;-DDg+qPMC>#oSqXs/UY?if/D1DSaYcofA3E]t1\[f9(g"EX8Z+f*[Wa**?'RG4WIV*1=h`l-RRW1oiEAAn_("0qKl!Jphpu."e_hZjI?f1_YNMi@ob5V*rp%o]VV_[kgaN1TgOgZYd.$2`6Mi3LSNup&`5e4YE+gip3m]2V:PEV;u()\"CXB@?.J&<^L`[,cIadgRT8-`4oIs"'W)JGYba]sNVR@@fYrm_2NCS5Z&-Z^$NA8[iUqH"L*YCX^8SBOu(PIYDIl!I`C^BPT(F#$f?:$/;jVgihTru,R@bfO+D1mshFSGq%BrG/'G1+.,XdU[6aEV``QoWNhcadgRT8<^Su`p$i9d(]c'-Z_%E+\h*r)5ht,62qj:SBT)[Kf#3eAt8kN"oW50EAAn_("-W1RW81*(\Fn^`lXap<Ci=aBu]CE?-p=j)CKS]=!:<#VAZ:]F[?$GBUm;g>_P#&RqA2ieHPneTQ)_7Eu!,6DOPSA<u:M4J]>g%bZ.2t\S9(J?X;$n"r\k1105Vnk,'#oIs"'W)JGYba]sNVR@@fYrm_2NCS5Z&-Z^$NA8[iUqH"L*YCX^8SBOu(PIYDIl!I`C^BPT(F#$f?:$/;jVgihTru,R@bfO+D1mshFSGq%BrG/'G1+.,XdU[6aEV``QoWNhcadgRT8<^Su`p$i9d(]c'-Z_%E+\h*r)5ht,62qj:SBT)[Kf#3eAt8kN"oW50EAAn_("-W1RW81*(\Fn^`lXap<Ci=aBu]CE?-p=j)CKS]=!:<#VAZ:]F[?$GBUm;g>_P#&RqA2ieHPneTQ)_7Eu!,6DOPSA<u:M4J]>g%bZ.2t\S9(J?X;$n"r\k1105Vnk,'#oIs"'W)JGYba]sNVR@@fYrm_2NCS5Z&-Z^$NA8[iUqH"L*YCX^8SBOu(PIYDIl!I`C^BPT(F#$f?:$/;jVgihTru,R@bfO+D1mshFSGq%BrG/'G1+.,XdU[6aEVd.L^A-]G5(39,Y?j@Q3]]DeID^dKf)#"$c]9).[W_&lZ9BKXA,#EIp-T/o$@;I7?+"FtC=T<n*'!H_n`0>?X]r9CCY(0g.Om;5Fjd'W^-Xh?3DGu$Pte!YABC!mmMPn<TQ)_7Eu!,6DOPSAf:QQnN\tLsTR["@ldr>t[r:0pjid\sYCB]E-;L'Zp%lrqmms9umFnsV_hP'aS[(.rlI;e%M\bX0Xo%1C:Du00goFL0CV=8JS=D-ISsu4]@)7G^R$_$_Am(VSXBAK$;PhL!-g0UQN>XN!+sLLnp?f]jNW(f^315u@$>D7SbrG[fc_s^9A@h'@?7*EBo:7XJMKQ80YYaJa;Q6rQ"Y)7\e)^8\J%oI3MMD]aQK=MjB-nrP()7S;:G*;eCUn*(St.re[\(</Rl1d>n3HJkUON_0:?H)i6CQ+m1J[uHF4F8lN>OCEHhZr"8P.N=F3X()Z`WnIp[4<J2K.Gghm7b.p;;UoPD)#<js22tf^$kT4Y%3#7>hr'Eo4TZ]63FODd'\lV*'T.Z":IP]mA/)9sZd<)`OpFoB*m`NW(f^315u@$>D7SbrG[f:K/d<7uuN6C"'.dZ_F4ikK]W&;YkC<A9HtS&c8._HM!:ZX=T;P5<^YNc1af*]4L+T$QV9M(d*&I]me"^*V(I2?^Bf0LktlukM)K3ZEe5qWo^u[Vb`rarW7ib*MS$X1J[(CY!ST$9f#QEqBNE#[V]7Gjs,pLErG[_QT3U3R@0HXIL?$hP]8H+Eu!,6DOPSAf:Vp4led3lrUrsbdCOJdQ8nX71\9L#9/A3"1J[(CY!P1Z3rSA?Y?nniYJ1u1@Y?TZ[][AkiIr)Lob(Grl`W3agh:P.^3k3ol`\U(3FcnE4aOL(oiGG1FKpNfL$Po^O_.irB:R2f>_R9=Fo'^^cT_6-EokI(a,f,aB@"59o3u3>hgG#iCX^4Y'4md4m^qq*gU9gg[:R(%e?WZ/p$:55?G3pAP:$"tNsZ7&PEV2A(7^k`S"6*%cfY-',8$)*cd0q`bfo/F7nuW3/u^X%k%(V]3k.6L>;#BXpKi/l\kI%G]^e\lqWF"L^V:q?.[r1K13W'=AT'%H.$2`6Mi3LSNup&`5e4YEOckkO%IPa2PEV2rQ51>UfrS=4IXf6aBuVHA`lXap<Cd?rVgihTru,R@bfO+D1mshFSGq%BrG/'G1+.,XdU[6aEV``QoWNhcadgRT8<^Su`p$i9d(]c'-Z_%E+\h*r)5ht,62qj:SBT)[Kf#3eAt8kN"oW50EAAn_("-W1RW81*(\Fn^`lXap<Ci=aBu]CE?-p=j)CKS]=!:<#VAZ:]F[?$GBUm;g>_P#&RqA2ieHPneTQ)_7Eu!,6DOPSA<u:M4J]>g%bZ.2t\S9(J?X;$n"r\k1105Vnk,'#oIs"'W)JGYba]sNVR@@fYrm_2NCS5Z&-Z^$NA8[iUqH"L*YCX^8SBOu(PIYDIl!I`C^BPT(F#$f?:$/;jVgihTru,R@bfO+D1mshFSGq%BrG/'G1+.,XdU[6aEV``QoWNhcadgRT8<^Su`p$i9d(]c'-Z_%E+\h*r)5ht,62qj:SBT)[Kf#3eAt8kN"oW50EAAn_("-W1RW81*(\Fn^`lXap<Ci=aBu]CE?-p=j)CKS]=!:<#VAZ:]F[?$GBUm;g>_P#&RqA2ieHPneTQ)_7Eu!,6DOPSA<u:M4J]>g%bZ.2t\S9(J?X;$n"r\k1105Vnk,'#oIs"'W)JGYba]sNVR@@fYrm_2NCS5Z&-Z^$NA8[iUqH"L*YCX^8SBOu(PIYDIl!I`C^BPT(F#$f?:$/;jVgihTru,R@bfO+D1mshFSGq%BrG/'G1+.,XdU[6aEV``QoWNhcadgRT8<^Su`p$i9d(]c'-Z_%E+\h*r)5ht,62qj:SBT)[Kf#3eAt8kN"oW50EAAn_("-W1RW81*(\Fn^`lXap<Ci=aBu]Dpi&q&;o9.GhiPVdeVbWe/kb3d3=0Gr,8sR$W^AI-AU\6h]*^"\f*I#H?`$?WT4oY6:B@!0W2m$8kWb'A>Ocbc->]IaKYJ9N&r%hs/B[ED<<)h@if3Pk7rVDH?&,g::EAAn_("-W1RW80_#bQP=g#\Q-hRn,T]75SE),dYE]bN%uCtuPjqWk?"iJ&;Qp#jVk92A?up"!>+Dr/-Kl-c"XCQ9H!g=ioPhnIaqfLI$31bUM#qXj$;.TKZ\Cmh[[4$-Y=q6eg\gTb8/TL"2C&e_qTlDrjT7/n0=SBT)[Kf#3eAt8i0`:;m.Rl"jM_$;(;I.PT`rtZ@c7RTWq>e&.l6[jKu7(7@,>$70:^$N&Jm-4#3rq*,`q"USX,$BBSM'n`LCM#fu4nm`QgMOE&b;/6$3cq=.>[1N=[r,K>D?o&LqU\!mA5;8:UrhCKj7I74qXs/5@`/OTYkqM!]opoihgK.pVgeU@;l<BUKs>..[V4(*?8$A+\`]haRq2>lRr5O0c[sAaV4(s]g0*RTM\e&?<UAGIC[f$3Ign50k%(V]R`nJ^:B-X'JcuP1e'h*`3`Y1g^[pU4Mi<Ve]j'gRX^uf/$\JWmlI`A:^&H%oJ!<oD;Cp!HM4QD0K"Ym?OsREujN3U@%j.*k:I"g-hDB968=:(en'(%FVn^!^TgOV!?djst3LQq0-?IXpe%bm0rqY`bC=V%LFfTZ6nc$eWbcL;4k[1*DP<#&3O=#%tN75Q@/7,tk19#?r=ftl3U+C]eiJ3G@rlk:!eC':KlAUoeRB)DZY"o#no>;)'D"jW'X&euOH`nu&f3c5ip=n0SG4+-gk5$V*lK)0TAkC*:DjHVoJ!HUsDEiEM?G1`i^O#\6LTT[tio62e'X#2k7%/*eST-D4Eu!->e`ktL@XnA^;Yp>:RIdRd\T6`MNGV=nem%#rlI`B%Q7Z=,/R+O*PZ6fB7jCNabg4V]b/u4*rM-<iW4@!4k2k\6*]uD*+i3m\`T9Gd)JGYba]sNVR@@fYrk0_orG2B27Ufi$<9db5h=]lr?eT]cE8\P!\)/5?'2u5Lc,UlPXN^-O>]\.IOcbcc)Ds#9q]""^:R=D79emMk[r1"jGD?)Sco@GW\I*%+2pF>\WsedU5iZBMjsm1Kgbe'`Y:f$G"-&L)RB)DrF46)s^Ug(f%5^iBA?J7fbY9lgs6>482:+=NPIV$71,mH;rMp>&f0JA,:?F%%akS`5oWKn2?NZ:O3LQq0-?IXpe%`JeIgn50k%(V]R`nJ^:B0U2r\`%^R?P/gBehWl3;olcqU\!mA5;8:UrhCKj7KN-k[+FOP<#&3O=#%tN75PQTRRD-:?H)i6CQ+m1J[u8K)RXS315u@$>D7SbrG[&$i8I?j((pI.\t2A1un530'HXENKZAjX,nZLdu;bj\tFN^1f!1DY!ST$9f#QEl@T!mco@GW\I*%+2pF>\WsedU5iZBMjsm1Kgbe'`Y:f$G"-&L)RB)DrF46)s^Ug(f%5^iBA?J7fbY9lgs6>482:+=NPIV$71,mH;rMp>&f0JA,:?F%%akS`5oWKn2?NZ:O3LQq0-?IXpe%`JeIgn50k%(V]R`nJ^:B0U2r\`%^R?P/gBehWl3;olcqU\!mA5;8:UrhCKj7KN-k[+FOP<#&3O=#%tN75PQTRRD-:?H)i6CQ+m1J[u8K)RXS315u@$>D7SbrG[&$i8I?j((pI.\t2A1un530'HXENKZAjX,nZLdu;bj\tFN^1f!1DY!ST$9f#QEl@T!mco@GW\I*%+2pF>\WsedU5iZBMjsm1Kgbe'`Y:f$G"-&L)RB)DrF46)s^Ug(f%5^iBA?J7fbY9lgs6>482:+=NPIV$71,mH;rMp>&f0JA,:?F%%akS`5oWKn2?NZ:O3LQq0-?IXpe%`JeIgn50k%(V]R`nJ^:B-X'KF&;q[;$Em(RLANe?)raSND&LRnfM8p!?&:<Nf`B8sLlMhgP7HUIX]G#7hlgj,Q31@UeCqO$EX=fs>>Y9rh<sC>#n(A7Xa9='Y:ll`^2#c'U5\6%]A=;,PqNGMW,gl\"XCrG/'G1+.,XdU[6aEVd.LDS,9oC!#/cgaua=8>Xn@?iS'$'@s$(6\a*k=)Z'8'cT)L]`.cnDn+a1C]1ebkL.i.DnYhpc'pXF?+RCTc'kCtO1$t6gUAgo2YPNg+3i!PRm)7HV+R#=/R)h(b0J:u;L\#@f66N50keq,KS5#^^A9>#NmYTG)CKS]=!:<#VAZ:]0?VmRIk;7\r:.h5>Zb)F&J;9eiBh<YIX]"I>.\s[/5,/4G1gI#FSJX]qGVchHr3b'/3i[C'?ut'Z_-`YHg[#F\!CUN4*9[_CUpsb\8L-6cThGWOI`(p%5^iBA?J7fbY9lgs'CpQHhKuugUD*fAXW87noru>B5a'7n^OO:ARXrXYq9.dYh.'g=73V5djA53l)'r)%j,\-P1d!;ZReX/b?T;^il1VgIf9+cmFntl%\,_2qYLX?3cp2W29e+KPIV$71,mH;r>Q`EPBQBLq8O@lZu40Y2_QpAde=6R>ITp_XXjl!1oOp)NqW;kJ,E/qPtA532eg]N_Lr6hVBnm$aJP.K:ED]I=;F+!oM^+caH7]Y7U0OI1,C^:r"T"Z.-"i5jsm1Kgbe'`DfCF>):G0D".=:82]_-ik.dUUAmA5HSBT)[Klh9VqDS[ILCR$Q:#39sT=r7,c_#XEn%T&F-Z_%E+\h*r)5hu-jo"rG;SI;>f%n#d\*nY/qQ%*_IbVHEF6Ch(f3d340@-(E91i6^XsKpr?_sZugJ?7u="iIsY9&/f@;EE`\2Yc\L$PqZ0?q#&c8_5U\I.TZlL]#*4S[ieeZ)XHkbF3Q]YMXQ-`p*1L('2)n&gnG2f@F'[M3Mjp&1YG*BS/lZ=V^pAP#HbA\NXNEob=>r;<XtX>8ZtMMd:[4*PRK?JaVVK+ek1I(rME&,g::EAAn_("-W1RW825>UoMWs(I/[q\tBKM,L<sh0#&`Zi-g)#7fO<qs?*m0f<atI-e,Yb5D7Y2:l(H)&_,+;55n]^G105F=D=b,]hpeldc6s1&BKej((pI.\t2A1un530'HXENKZAjX,nZLdu;bj\tFN^1f!1DY!ST$9f#QEl@T!mco@GW\I*%+2pF>\WsedU5iZBMjsm1Kgbe'`Y:f$G"-&L)RB)DrF46)s^Ug(f%5^iBA?J7fbY9lgs6>482:+=NPIV$71,mH;rMp>&f0JA,:?F%%akS`5oWKn2?NZ:O3LQq0-?IXpe%`JeIgn50k%(V]R`nJ^:B0U2r\`%^R?P/gBehWl3;olcqU\!mA5;8:UrhCKj7KN-k[+FOP<#&3O=#%tN75PQTRRD-:?H)i6CQ+m1J[u8K)RXS315u@$>D7SbrG[&$i8I?j((pI.\t2A1un530'HXENKZAjX,nZLdu;bj\tFN^1f!1DY!ST$9f#QEl@T!mco@GW\I*%+2pF>\WsedU5iZBMjsm1Kgbe'`Y:f$G"-&L)RB)DrF46)s^Ug(f%5^iBA?J7fbY9lgs6>482:+=NPIV$71,mH;rMp>&f0JA,:?F%%akS`5oWKn2?NZ:O3LQq0-?IXpe%`JeIgn50k%(V]R`nJ^:B0U2r\`%^R?P/gBehWl3;olcqU\!mA5;8:UrhCKj7KN-k[+FOP<#&3O=#%tN75PQTRRD-:?H)i6CQ+m1J[u8K)RXS315u@$>D7SbrG[&$i8I?j((pI.\t2A1un530'HXENKZAjX,nZLdu;bj\tFN^1f!1DY!ST$9f#QEl@T!mco@GW\I*%+2pF>\WsedU5iZBMjsm1Kgbe'`Y:f$G"-&L)RB)DrF46)s^Ug(f%5^iBA?J7fbY9lgs6>482:+=NPIV$71,mH;rTcF!B$=r8TKuOVe90AK[sn;Pi-2&J%5^iBA?J7fbY9lgs#NerG'7D,jD1s4[U[@<6%]@f,U@d1eQ5'):]D>HSBT)[Kf#3eAt8jC(7Fb(*BM\R/hScZ>IX%lcT`N4rq*CF`lXap<Ci=aBu]CE#:V,PW`5tGil-iC2?!CEle8a^RCf"RIJWgKI/`s)EkDl&3qRh#lA`m\3WB8/g.Oi5oZpJaF#$f?:$/;jQ]YOmo>8A]n2:gT+,:@KQLI-;oM^+]aH7]Y7U5(nR@0HX!!'fo8.13^H/S_4`;]G#s*j^uGC0)PQ1mrOfc)s;m8f>5F6N+j['T&SP]g*`S'oT^l]mcu>TR*S>TDV_JcFaC5C`Y9q;/YH2gA9T=`nXT40.dDBkPu0.1JK`RI*39nAEh_]F.>'8I9kEe^)1'-S"/>Vkfk`pSP=(lJ@b[T20WRG#VGjgpqLto'utq*h8=RE:iAAd)gS)6tKWoPdId^hNW1ah4)j^M_KSUo_S7H7n,tj>^5)9FWo9rIJS%tAu>JjUaenSU@(`iB@X[EmB!lXV#^Przzzzz6Ht?#-U.P)A#K&F[%'4%c<ahdDOpPcF68E+Hd]ZYs20U&LL4-\mbYW=r4h\!h07c$SiqGEQnbeWnk+/V2f@Cf#*hH$p!m+o<iiNq*^+gkd\KI\5QCYiD;2q&^3B&&5QCQ*'.6PF4F-MPDnd=Tn"Qsl^]('r?@&mk=h*\1@."O&IkajP*'AJmOduMqCtZ,UZHFj.WIt]Z,=dd,ief6i[r+?Ac5_2E$rEX`5'#`*NAo]rWD]SdmFr^EG-#cJc4ruD91tIC+dEhOq@O;OZ$>eJCH83=1@h&"H*X7<]uOWD4VenQ.8a0L>^=,8!MOu2ES"K?/R5e^(34J;CY#STPa'[p9q15)jP%"WkN1fZ<*GB9.qdT2n`.Oe5%iEG<E6%uOW>"2o!1k^9l8=S8^jP2US=KK04,buBmCNN]mFthgm/$o4!<^6>[6'C))FUZCE`.-g9k]DdL@*O\?6NKOINO2pci4/3on74DacuB+`D:Fo]Ql-*F5^HXg)BVg2HQ"0W]h@#XeW(AnK3T4(ZR!Vk/47>.&*t#7n8obD@]%dZ@)qmG%Ai)fGH:?iPGu4nm%79fLo#`>;n&]2L.iqd3P;*S+V#nY]K)6CkYL^GYXTm"pRUYJ'Y`NZC4<I.L&M/#8V$.P5]:q>%=8qsClembPLRW2QNa4kT`-hqt\Ka,io[P\**mclfSBD`6\DQFtuBrU_>18DV?YFo;#SaZLjq`BM*ZJ,F:HpHg>]Y:2K.[r:0<B?qVJi[:=Bil<-jPkVboZ*Eo_bmUMXnGD,C^2;W54*U,b>q%_O[HpSce]T6I_qM>'_rP[I][_DkPum&FL7E)2W?-e&%FgLWJGAp7:nO\G)`N67^%\U>)'@pOs8MuCT0FEP2i;bCe60D,XXk/#'Zc3l_[eu=eur2\U/@[T+dEgHZtLVd8h$M;V<m*S?V9:0%mI48L/_@#>5d3]A]n5_B%cLHJt"W5(/jCaR$`C%lX(s7iG[GWa^2_tf.M@C'iO:Z$c^Ma4o=s-Dr5QT*AS(gpO:O\U)n&`Gpun979pDqk09Btot,G:%^khq2/Cnf]Z\f#-=tW?Z7PV)CTb2pUsaig[!`IlNU2gE$E8-?Ino3k:Hf*XfM]J5rDfn!=j,]Mo8UujjmD0oe#/W6Jj8g$pp%j\S*S*NcTDm+ZUG(/W?1r6bWt9<A"Pr5%NdkSC2"QA56(P%CUm'&.an=IZ:og\Y<j^E;Yn^C%3Ie3lDq+s7jf1?AKDb)h3-2)qYKN]AYDSQ)DWnMfC.J':X4WT9"/N`B(kTE-)K/\qbMP:rU56>?+Y8NV+Z3mGZ`&/p?]-3b'P^#dr)b]VN=DC$0LC4b(=`-aB<HZ8kT*l+e(eJSWJpU/T3553HIYb1[F8J8pL_r<4\mt*;erpO8RU&4isR_8m<A`q%JLDQt-2Z=-J((LECtYgU?KfJ,<GQ8cM*pDReY[QX5![Isi.tW+jngTfqqQOQMf8ma.Xp(uD%Af7Al!2+hJsf!;dqooNjnFQq6'eC4d<-5774LU2KNO!"Dmb:h$aeG&f#l%6jl`/mUkm;IKT?G64$V-JKFjfcDplC'SG?@2(IHM-Q_*'#dMdNq(!]U(q0^tubOLK(ih[r,K_FBQ;'p#*Z=>IA4r\T?pI-Vd2KGOOD%;l<A6Q'Rc"rGhRr.9#<#>KcTW0+@%>6Ou?0TL"3!M\auII*$uqPbW0sRr0An5!FGDUe)[(n]EAcKR.HKg9g+-,tiX;*'\mW._%p*(A'8E5t9n4T75LD]hAt6p[92%l20qaJ)J+nAQHslUiGG7Tfu>cG3rK$]8dabB#m'aO@oCjo]X]WDnfT??X`8-)%SC8^pIh)m.HO]p"*OmCA#'nITtZLq#9YbAmloRN!R=n@`uuF_hh&FRl"h'4)i_M=39\Yk?d7JP*2"-q9OPF<*Gl#hXf7"?/,2\>S\km@',de]O/F&+dFZ/rMKmT$lNM[itl!Y/BH'@++,clruT!.\(jmLoaLBq44Jb5IH6M\r;X>[Zf!`Yg<dTB%L3I`=LN%6s*C(:c^d-Vr;0DjqHXEb:M%FrbXV'6o?K^Y%Y\';QaLV\QM[Sk?Q?%K#rt(/HiEs=X&c?cE@D<jK$8YgSin#6X`F72gMcIjR<64<hKutP6H>357qU)=4am2!n@ZU/8*7H7=0A'#MCaGDgJ;9]?<7'kjW/9Mdu@^rA&jU4Ir6ZeSEf3b)@lfJ!!$H)W2Sp^\)&#X@`89s72BoaEia'=O`mA.2l3'FpSN$TA.na_1XA%j"-2;6f+=H8HhK:2e0m@=qW=8cmTGDuaN__a)r"Et,OHClDb<*lmS2pgK]1Y15WDkt%_;jS\b4NX]63F?k)V9<;c\YL<tW(omB!lXV4CW\VK>CT^[l%\o5U[s-k%D8TPH6IR$a7]Y;k7ED-NsR$0t<"B@X[EmB!lXV+Vg,SMpdHcp?9VN]M=JfKV^ab?t;G=8Ua(8@q@IS'oT^HW.MOo_1<,baEOfO!RGb\@;Y^F:ILP4*GFL@cGn:;ru9_d8FL@DH<rI4Ynl-BpYfG;qbAsDb:BX4*H#>\``nBLZ(n9%[Nc?Y!F9G-Vfmb_tGalab&:Vormn4=k[#$Db<*lmS2q7M\c9Q07WM!J*bgm:k-==_%Lk:2<\W/^3uW>iqqc48@qmXS'oT^HW/W1-pc5$lIDq&rR5^VS"oCGBkPu0.IFXV;g?bKr+B!2>'9;j]h!K<h4)j<Wd@[u=`jsBd+QW/(NFG7:7WunP]g*`S'oT^HW/X$FU(Rdfuu_uhAdLt4"beWg0&Q:8is*A9kdGE]Qs*DS2c),8(>4$mB!lXV4CV1YQUNA)uouS5q##QQ<.C<V-gudYJ'Xu/8&U.?[:au%3%2F2f9V`Y#P.m['T&SP]g<f.D4m`rWB#lRoZ@XQ+QM!]2"nXs8CjT?)'juq>W7s4;/eY5ASh$Ug9kc9ke7j]d`?Y\l0p=XD?1:S<id/c'*iURl49L>kuoO?a\J._QSIfCSY75mS.D.e\*F!XLa$1k\TCSMjTm!K*Mojb>D,6\[f9%nt&!k&eYh=G+#p#V4CW\VR3\p%[NcoR=d-QSRGi2T!b)WRX3o-!Gbi`QFOFaUg9kc9ke7j]d`?M\q;<mD!%CK)fNAD4$22HrHn"HQ_'e/[*-t4.IF4J2l3'FGH_4@boCi1HM-RJRlC5JccXD!5(,K?/lVMA['T&SP]g*`_%LiJErZ1?#GD8$!!!!A&FuDH,9rsRNufBlpn>ClRr?#TfWZOImSikW['T&SP]g<f.D4m`rW?JUq;p8]fV(ZfhL"_)Ocbb>-;:Yp+$FrBZHFjfg1BR+P]g*`S'm=t4&pmX\cuGFIJ).A(Zg<-CXsaW-&ip,[NmD:M`7K'<VNAtZg-D3G#mG8&p-$Qcp^W%bl7S<m@2k>Dbj,'Ie(T._f3&/;qbAsDb<*lmS.D.eK$2,B/9cue>l[>+92+kn!Y7YG/t!=Y@#%@T.SoaG#mFgda"k>;qbAs#><FsbX!&YHM&12@RQWL\[f9AN\T$rM`7K'<VNAtZg-D3G#mG8&p-$Qd)FS<>HNB!U^ZgSn`csD7bVG=hNW1ah4)j<W]O=7c=ROd!8nua+=L^tqXlMTo[8r"aF`28g;W@6P]g*`S'm=t4,#V!QcoC6G=C/s~>endstream
|
| 35 |
+
endobj
|
| 36 |
+
7 0 obj
|
| 37 |
+
<<
|
| 38 |
+
/Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 10 0 R /Resources <<
|
| 39 |
+
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject <<
|
| 40 |
+
/FormXob.63b9415af58491d57051f0533776330f 6 0 R
|
| 41 |
+
>>
|
| 42 |
+
>> /Rotate 0 /Trans <<
|
| 43 |
+
|
| 44 |
+
>>
|
| 45 |
+
/Type /Page
|
| 46 |
+
>>
|
| 47 |
+
endobj
|
| 48 |
+
8 0 obj
|
| 49 |
+
<<
|
| 50 |
+
/PageMode /UseNone /Pages 10 0 R /Type /Catalog
|
| 51 |
+
>>
|
| 52 |
+
endobj
|
| 53 |
+
9 0 obj
|
| 54 |
+
<<
|
| 55 |
+
/Author (anonymous) /CreationDate (D:20251121162221+05'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20251121162221+05'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
| 56 |
+
/Subject (unspecified) /Title (untitled) /Trapped /False
|
| 57 |
+
>>
|
| 58 |
+
endobj
|
| 59 |
+
10 0 obj
|
| 60 |
+
<<
|
| 61 |
+
/Count 1 /Kids [ 7 0 R ] /Type /Pages
|
| 62 |
+
>>
|
| 63 |
+
endobj
|
| 64 |
+
11 0 obj
|
| 65 |
+
<<
|
| 66 |
+
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1970
|
| 67 |
+
>>
|
| 68 |
+
stream
|
| 69 |
+
Gatm<9lo&I&A@sBlqA`^[`lVdrb/3b1laXhRjP8f:(P'(,U>)k#o4e_d7f":+$`_eS`CnCp=L!L!;j"tp?dGAg%]^6s0O'JAPP1.%QQ/dQfRqcVtofRAq"1V]B&hb^)OERE8MP!#],BGbU_rK02YBmr^cd!\YW2)mCLqI_[FjgX$i6#;CZ^(f=RdQ-^rBT(;=\^a?l9d(1o?cS_almhj_#<CkUO'@QU'=lfu7.ci#Qo4:HSHr;6Kei<n1KfZfu]bj+<WnDH<s9A24]5,oUla#.4QO&\Z^Q_/ckJ&]g<?%)SkmN,E'GJ#+$6H=7$g[SA[l)P'@%I*]7E;OnRSQM6aM.cWUbe/0b/@GU-?s[k1HgpgTSc=)@e[M'!-%hC/]q!?=J:,[C^I?JTJg=c%XKSdTgN%+cq=ZP[CYr^.IC@7!ojH2SXP!98'97N]+tF!%(:n/S"bC^Sn)0S(!>(#eBZD[rpX+$qbOkoPS;5N%QlDWVnm+1.eF>+P%%6LMn@=\C#8-R%-fOY:(4"fRA2,?`%E=&P3J7o2%hiO[kCq5jZ#`+N]%)<d7dA\Y2Q;$4^ORA^o-Eq1[AV<YGVA>*'VQ8X0:Kka(gBiWYUFa20kQGq[`&bk47!R666_-\80jK;3$pED$&j:][f7L04\4J>4>15o0/-^G`<dae,`/ZeT6]Vsj]81jnG62L6OXI(lR,`#%!STG"Xs=*;&Z5`?G)rg`/`\T9tjf?c$g$1"0?u__:uU6IJH+\>UrUA@LQ*c'UjrC=/c1/;q:`Ehe]o>7GBp<`Ql=J`jTYK"]m5%ZrG3qDtAs]:L.+%?Kon$Y0ebeZ>mIfcsL`K3nd!N$;kRXR7N7K')h)WX?B!q-V315'dMFfa49/E./79_:s!U_*aLW.gbkh*dJ!Rpncm!o#,5?.V8qigl#^&rJtJ)$d6aj-i$nc1dK1YEq;KJj9HgbX/U#&E%OJpo[%Sj#/=._[fa5#6Fk<^_EGcr/c[?0N9Qk1[Tg%;5g*<-C$1/NK.;N*:=mA1!091>>BjTU'hT1Qj<:dO5WZ$`eC5FGXc#T"Q3\B8,FRI.5&!uK</Y*RoRXSB$=U+DX964YK2)n9c%'Id\#F.i&OED>4M\%7S"nf#UPh*tH$B&t*dn"Op3iIXecVbO4Yrcn0WN6iG+/Z_[@IQl^8.JO3Y_P[4r"2"VVCr([3S%jierUQlb2ne$G[7bgX9e!D]E4^0M>a=US>52+'Sgr;%-[Qh[;/qU[E5?bA(&mr^2/j?k3JE!@hUL&a?eA@aF1%\F4oQ(3H=_i\=YKmOpkH1&>O?%bd1q<7^$B+F#A9@aibSgk8MZ]W#Q&-r8N+si(iJ@-_[ts/$\,sRtcoNmP]bOI@r<%+R11l\.'F[o2_7K>3j"Ob=(Ktkt;NK@*_XEU.KPKSNo^.=!b>e<p=\s(lu.!g7ka7;@Z\Xj"6%a:3T4sj&0bOHO'?+55V+$?a1LpS1a"E%=F"N(+>R3.6"W;,c),j!7IWW6*i.ER:^.[\!?dG.fN#8L9E^>Z>'#mr99Seg<N!ao1&HcR_UtHRcjTd"j_A!lJ48;%JukeGqH'RDZnm?A]`Kj:^%A?h?qe74_u]O8ut'ABP8!f::j,A!1*^8MC-hg+0iqqE/J7sEm9AkIHViOQ&R3m=T>hhFR]F7-5&+'XB-b@prN<KK8+2/iBjj7e="`Z=Skjr>q9p+G^h<c9JpSs:(tkqVtu?>AjHWF6XCdDHseuhkUEfQpGpa4(@nG`.tKPB9F6D4@Iut,SoW+0o:eLDJm<#Xlmqd9,Y`P+hV/LrY\5b-B@@qAnocsXeQs^@>luH>2rM0Ja@uP"a^+F&@uY6%#*jA&&&EQmbf:RcXD&$tjQi7uY&g:j)i0aHeG0.LBRE`=C=Z]khTRYP]*N6-]T;](.Lp:C8(W:>o&AdA.e^&NYg?T"^(^!I&Gq<J"d'fb90;XU-ji,`?IGsgq"'(irWb8rND0~>endstream
|
| 70 |
+
endobj
|
| 71 |
+
xref
|
| 72 |
+
0 12
|
| 73 |
+
0000000000 65535 f
|
| 74 |
+
0000000073 00000 n
|
| 75 |
+
0000000134 00000 n
|
| 76 |
+
0000000241 00000 n
|
| 77 |
+
0000000353 00000 n
|
| 78 |
+
0000000430 00000 n
|
| 79 |
+
0000000513 00000 n
|
| 80 |
+
0000023352 00000 n
|
| 81 |
+
0000023610 00000 n
|
| 82 |
+
0000023679 00000 n
|
| 83 |
+
0000023975 00000 n
|
| 84 |
+
0000024035 00000 n
|
| 85 |
+
trailer
|
| 86 |
+
<<
|
| 87 |
+
/ID
|
| 88 |
+
[<fee8447b877a2a16b220e9231e30aa79><fee8447b877a2a16b220e9231e30aa79>]
|
| 89 |
+
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
| 90 |
+
|
| 91 |
+
/Info 9 0 R
|
| 92 |
+
/Root 8 0 R
|
| 93 |
+
/Size 12
|
| 94 |
+
>>
|
| 95 |
+
startxref
|
| 96 |
+
26097
|
| 97 |
+
%%EOF
|
data/sales_data_sample.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
docs/agent-workflow.drawio
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<mxfile host="app.diagrams.net" modified="2025-11-22T00:00:00.000Z" agent="5.0 (Windows)" version="22.0.7" editor="diagramly">
|
| 2 |
+
<diagram id="agentFlow" name="Agent Workflow">
|
| 3 |
+
<mxGraphModel dx="1180" dy="740" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" math="0" shadow="0">
|
| 4 |
+
<root>
|
| 5 |
+
<mxCell id="0"/>
|
| 6 |
+
<mxCell id="1" parent="0"/>
|
| 7 |
+
<mxCell id="user" value="User Prompt" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E3F2FD;strokeColor=#1E88E5;fontSize=14;" vertex="1" parent="1">
|
| 8 |
+
<mxGeometry x="60" y="180" width="160" height="60" as="geometry"/>
|
| 9 |
+
</mxCell>
|
| 10 |
+
<mxCell id="orchestrator" value="LangGraph Orchestrator" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E8F5E9;strokeColor=#2E7D32;fontSize=14;" vertex="1" parent="1">
|
| 11 |
+
<mxGeometry x="260" y="180" width="190" height="70" as="geometry"/>
|
| 12 |
+
</mxCell>
|
| 13 |
+
<mxCell id="nl2sql" value="NL2SQL + DB Runner" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFF3E0;strokeColor=#FB8C00;fontSize=14;" vertex="1" parent="1">
|
| 14 |
+
<mxGeometry x="500" y="120" width="200" height="70" as="geometry"/>
|
| 15 |
+
</mxCell>
|
| 16 |
+
<mxCell id="analytics" value="Trend & Anomaly Modules" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#F3E5F5;strokeColor=#8E24AA;fontSize=14;" vertex="1" parent="1">
|
| 17 |
+
<mxGeometry x="500" y="240" width="200" height="70" as="geometry"/>
|
| 18 |
+
</mxCell>
|
| 19 |
+
<mxCell id="reports" value="Visualization + PDF Builder" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E0F7FA;strokeColor=#00838F;fontSize=14;" vertex="1" parent="1">
|
| 20 |
+
<mxGeometry x="750" y="180" width="210" height="80" as="geometry"/>
|
| 21 |
+
</mxCell>
|
| 22 |
+
<mxCell id="frontend" value="React Analyst Dashboard" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FCE4EC;strokeColor=#AD1457;fontSize=14;" vertex="1" parent="1">
|
| 23 |
+
<mxGeometry x="1010" y="180" width="210" height="80" as="geometry"/>
|
| 24 |
+
</mxCell>
|
| 25 |
+
<mxCell id="datasets" value="Dataset Upload & Catalog" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFDE7;strokeColor=#FDD835;fontSize=14;" vertex="1" parent="1">
|
| 26 |
+
<mxGeometry x="260" y="60" width="210" height="70" as="geometry"/>
|
| 27 |
+
</mxCell>
|
| 28 |
+
<mxCell id="edge1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#1E88E5;" edge="1" parent="1" source="user" target="orchestrator">
|
| 29 |
+
<mxGeometry relative="1" as="geometry"/>
|
| 30 |
+
</mxCell>
|
| 31 |
+
<mxCell id="edge2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#2E7D32;" edge="1" parent="1" source="orchestrator" target="nl2sql">
|
| 32 |
+
<mxGeometry relative="1" as="geometry"/>
|
| 33 |
+
</mxCell>
|
| 34 |
+
<mxCell id="edge3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#2E7D32;" edge="1" parent="1" source="orchestrator" target="analytics">
|
| 35 |
+
<mxGeometry relative="1" as="geometry"/>
|
| 36 |
+
</mxCell>
|
| 37 |
+
<mxCell id="edge4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#00838F;" edge="1" parent="1" source="nl2sql" target="reports">
|
| 38 |
+
<mxGeometry relative="1" as="geometry"/>
|
| 39 |
+
</mxCell>
|
| 40 |
+
<mxCell id="edge5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#8E24AA;" edge="1" parent="1" source="analytics" target="reports">
|
| 41 |
+
<mxGeometry relative="1" as="geometry"/>
|
| 42 |
+
</mxCell>
|
| 43 |
+
<mxCell id="edge6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#AD1457;" edge="1" parent="1" source="reports" target="frontend">
|
| 44 |
+
<mxGeometry relative="1" as="geometry"/>
|
| 45 |
+
</mxCell>
|
| 46 |
+
<mxCell id="edge7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#FDD835;" edge="1" parent="1" source="datasets" target="orchestrator">
|
| 47 |
+
<mxGeometry relative="1" as="geometry"/>
|
| 48 |
+
</mxCell>
|
| 49 |
+
</root>
|
| 50 |
+
</mxGraphModel>
|
| 51 |
+
</diagram>
|
| 52 |
+
</mxfile>
|
docs/agent-workflow.svg
ADDED
|
|
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,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# React + Vite
|
| 2 |
+
|
| 3 |
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
| 4 |
+
|
| 5 |
+
Currently, two official plugins are available:
|
| 6 |
+
|
| 7 |
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [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 not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
| 13 |
+
|
| 14 |
+
## Expanding the ESLint configuration
|
| 15 |
+
|
| 16 |
+
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
frontend/eslint.config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import js from '@eslint/js'
|
| 2 |
+
import globals from 'globals'
|
| 3 |
+
import reactHooks from 'eslint-plugin-react-hooks'
|
| 4 |
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
| 5 |
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
| 6 |
+
|
| 7 |
+
export default defineConfig([
|
| 8 |
+
globalIgnores(['dist']),
|
| 9 |
+
{
|
| 10 |
+
files: ['**/*.{js,jsx}'],
|
| 11 |
+
extends: [
|
| 12 |
+
js.configs.recommended,
|
| 13 |
+
reactHooks.configs.flat.recommended,
|
| 14 |
+
reactRefresh.configs.vite,
|
| 15 |
+
],
|
| 16 |
+
languageOptions: {
|
| 17 |
+
ecmaVersion: 2020,
|
| 18 |
+
globals: globals.browser,
|
| 19 |
+
parserOptions: {
|
| 20 |
+
ecmaVersion: 'latest',
|
| 21 |
+
ecmaFeatures: { jsx: true },
|
| 22 |
+
sourceType: 'module',
|
| 23 |
+
},
|
| 24 |
+
},
|
| 25 |
+
rules: {
|
| 26 |
+
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
| 27 |
+
},
|
| 28 |
+
},
|
| 29 |
+
])
|
frontend/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>frontend</title>
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div id="root"></div>
|
| 11 |
+
<script type="module" src="/src/main.jsx"></script>
|
| 12 |
+
</body>
|
| 13 |
+
</html>
|
frontend/package-lock.json
ADDED
|
@@ -0,0 +1,2675 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"version": "0.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "frontend",
|
| 9 |
+
"version": "0.0.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"react": "^19.2.0",
|
| 12 |
+
"react-dom": "^19.2.0"
|
| 13 |
+
},
|
| 14 |
+
"devDependencies": {
|
| 15 |
+
"@eslint/js": "^9.39.1",
|
| 16 |
+
"@types/react": "^19.2.5",
|
| 17 |
+
"@types/react-dom": "^19.2.3",
|
| 18 |
+
"@vitejs/plugin-react": "^5.1.1",
|
| 19 |
+
"eslint": "^9.39.1",
|
| 20 |
+
"eslint-plugin-react-hooks": "^7.0.1",
|
| 21 |
+
"eslint-plugin-react-refresh": "^0.4.24",
|
| 22 |
+
"globals": "^16.5.0",
|
| 23 |
+
"vite": "npm:rolldown-vite@7.2.5"
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"node_modules/@babel/code-frame": {
|
| 27 |
+
"version": "7.27.1",
|
| 28 |
+
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
| 29 |
+
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
| 30 |
+
"dev": true,
|
| 31 |
+
"license": "MIT",
|
| 32 |
+
"dependencies": {
|
| 33 |
+
"@babel/helper-validator-identifier": "^7.27.1",
|
| 34 |
+
"js-tokens": "^4.0.0",
|
| 35 |
+
"picocolors": "^1.1.1"
|
| 36 |
+
},
|
| 37 |
+
"engines": {
|
| 38 |
+
"node": ">=6.9.0"
|
| 39 |
+
}
|
| 40 |
+
},
|
| 41 |
+
"node_modules/@babel/compat-data": {
|
| 42 |
+
"version": "7.28.5",
|
| 43 |
+
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
|
| 44 |
+
"integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
|
| 45 |
+
"dev": true,
|
| 46 |
+
"license": "MIT",
|
| 47 |
+
"engines": {
|
| 48 |
+
"node": ">=6.9.0"
|
| 49 |
+
}
|
| 50 |
+
},
|
| 51 |
+
"node_modules/@babel/core": {
|
| 52 |
+
"version": "7.28.5",
|
| 53 |
+
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
| 54 |
+
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
| 55 |
+
"dev": true,
|
| 56 |
+
"license": "MIT",
|
| 57 |
+
"dependencies": {
|
| 58 |
+
"@babel/code-frame": "^7.27.1",
|
| 59 |
+
"@babel/generator": "^7.28.5",
|
| 60 |
+
"@babel/helper-compilation-targets": "^7.27.2",
|
| 61 |
+
"@babel/helper-module-transforms": "^7.28.3",
|
| 62 |
+
"@babel/helpers": "^7.28.4",
|
| 63 |
+
"@babel/parser": "^7.28.5",
|
| 64 |
+
"@babel/template": "^7.27.2",
|
| 65 |
+
"@babel/traverse": "^7.28.5",
|
| 66 |
+
"@babel/types": "^7.28.5",
|
| 67 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 68 |
+
"convert-source-map": "^2.0.0",
|
| 69 |
+
"debug": "^4.1.0",
|
| 70 |
+
"gensync": "^1.0.0-beta.2",
|
| 71 |
+
"json5": "^2.2.3",
|
| 72 |
+
"semver": "^6.3.1"
|
| 73 |
+
},
|
| 74 |
+
"engines": {
|
| 75 |
+
"node": ">=6.9.0"
|
| 76 |
+
},
|
| 77 |
+
"funding": {
|
| 78 |
+
"type": "opencollective",
|
| 79 |
+
"url": "https://opencollective.com/babel"
|
| 80 |
+
}
|
| 81 |
+
},
|
| 82 |
+
"node_modules/@babel/generator": {
|
| 83 |
+
"version": "7.28.5",
|
| 84 |
+
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
|
| 85 |
+
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
|
| 86 |
+
"dev": true,
|
| 87 |
+
"license": "MIT",
|
| 88 |
+
"dependencies": {
|
| 89 |
+
"@babel/parser": "^7.28.5",
|
| 90 |
+
"@babel/types": "^7.28.5",
|
| 91 |
+
"@jridgewell/gen-mapping": "^0.3.12",
|
| 92 |
+
"@jridgewell/trace-mapping": "^0.3.28",
|
| 93 |
+
"jsesc": "^3.0.2"
|
| 94 |
+
},
|
| 95 |
+
"engines": {
|
| 96 |
+
"node": ">=6.9.0"
|
| 97 |
+
}
|
| 98 |
+
},
|
| 99 |
+
"node_modules/@babel/helper-compilation-targets": {
|
| 100 |
+
"version": "7.27.2",
|
| 101 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
|
| 102 |
+
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
|
| 103 |
+
"dev": true,
|
| 104 |
+
"license": "MIT",
|
| 105 |
+
"dependencies": {
|
| 106 |
+
"@babel/compat-data": "^7.27.2",
|
| 107 |
+
"@babel/helper-validator-option": "^7.27.1",
|
| 108 |
+
"browserslist": "^4.24.0",
|
| 109 |
+
"lru-cache": "^5.1.1",
|
| 110 |
+
"semver": "^6.3.1"
|
| 111 |
+
},
|
| 112 |
+
"engines": {
|
| 113 |
+
"node": ">=6.9.0"
|
| 114 |
+
}
|
| 115 |
+
},
|
| 116 |
+
"node_modules/@babel/helper-globals": {
|
| 117 |
+
"version": "7.28.0",
|
| 118 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
| 119 |
+
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
| 120 |
+
"dev": true,
|
| 121 |
+
"license": "MIT",
|
| 122 |
+
"engines": {
|
| 123 |
+
"node": ">=6.9.0"
|
| 124 |
+
}
|
| 125 |
+
},
|
| 126 |
+
"node_modules/@babel/helper-module-imports": {
|
| 127 |
+
"version": "7.27.1",
|
| 128 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
| 129 |
+
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
| 130 |
+
"dev": true,
|
| 131 |
+
"license": "MIT",
|
| 132 |
+
"dependencies": {
|
| 133 |
+
"@babel/traverse": "^7.27.1",
|
| 134 |
+
"@babel/types": "^7.27.1"
|
| 135 |
+
},
|
| 136 |
+
"engines": {
|
| 137 |
+
"node": ">=6.9.0"
|
| 138 |
+
}
|
| 139 |
+
},
|
| 140 |
+
"node_modules/@babel/helper-module-transforms": {
|
| 141 |
+
"version": "7.28.3",
|
| 142 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
|
| 143 |
+
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
|
| 144 |
+
"dev": true,
|
| 145 |
+
"license": "MIT",
|
| 146 |
+
"dependencies": {
|
| 147 |
+
"@babel/helper-module-imports": "^7.27.1",
|
| 148 |
+
"@babel/helper-validator-identifier": "^7.27.1",
|
| 149 |
+
"@babel/traverse": "^7.28.3"
|
| 150 |
+
},
|
| 151 |
+
"engines": {
|
| 152 |
+
"node": ">=6.9.0"
|
| 153 |
+
},
|
| 154 |
+
"peerDependencies": {
|
| 155 |
+
"@babel/core": "^7.0.0"
|
| 156 |
+
}
|
| 157 |
+
},
|
| 158 |
+
"node_modules/@babel/helper-plugin-utils": {
|
| 159 |
+
"version": "7.27.1",
|
| 160 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
|
| 161 |
+
"integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
|
| 162 |
+
"dev": true,
|
| 163 |
+
"license": "MIT",
|
| 164 |
+
"engines": {
|
| 165 |
+
"node": ">=6.9.0"
|
| 166 |
+
}
|
| 167 |
+
},
|
| 168 |
+
"node_modules/@babel/helper-string-parser": {
|
| 169 |
+
"version": "7.27.1",
|
| 170 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
| 171 |
+
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
| 172 |
+
"dev": true,
|
| 173 |
+
"license": "MIT",
|
| 174 |
+
"engines": {
|
| 175 |
+
"node": ">=6.9.0"
|
| 176 |
+
}
|
| 177 |
+
},
|
| 178 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 179 |
+
"version": "7.28.5",
|
| 180 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
| 181 |
+
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
| 182 |
+
"dev": true,
|
| 183 |
+
"license": "MIT",
|
| 184 |
+
"engines": {
|
| 185 |
+
"node": ">=6.9.0"
|
| 186 |
+
}
|
| 187 |
+
},
|
| 188 |
+
"node_modules/@babel/helper-validator-option": {
|
| 189 |
+
"version": "7.27.1",
|
| 190 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
| 191 |
+
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
| 192 |
+
"dev": true,
|
| 193 |
+
"license": "MIT",
|
| 194 |
+
"engines": {
|
| 195 |
+
"node": ">=6.9.0"
|
| 196 |
+
}
|
| 197 |
+
},
|
| 198 |
+
"node_modules/@babel/helpers": {
|
| 199 |
+
"version": "7.28.4",
|
| 200 |
+
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
|
| 201 |
+
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
|
| 202 |
+
"dev": true,
|
| 203 |
+
"license": "MIT",
|
| 204 |
+
"dependencies": {
|
| 205 |
+
"@babel/template": "^7.27.2",
|
| 206 |
+
"@babel/types": "^7.28.4"
|
| 207 |
+
},
|
| 208 |
+
"engines": {
|
| 209 |
+
"node": ">=6.9.0"
|
| 210 |
+
}
|
| 211 |
+
},
|
| 212 |
+
"node_modules/@babel/parser": {
|
| 213 |
+
"version": "7.28.5",
|
| 214 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
|
| 215 |
+
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
|
| 216 |
+
"dev": true,
|
| 217 |
+
"license": "MIT",
|
| 218 |
+
"dependencies": {
|
| 219 |
+
"@babel/types": "^7.28.5"
|
| 220 |
+
},
|
| 221 |
+
"bin": {
|
| 222 |
+
"parser": "bin/babel-parser.js"
|
| 223 |
+
},
|
| 224 |
+
"engines": {
|
| 225 |
+
"node": ">=6.0.0"
|
| 226 |
+
}
|
| 227 |
+
},
|
| 228 |
+
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
| 229 |
+
"version": "7.27.1",
|
| 230 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
|
| 231 |
+
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
|
| 232 |
+
"dev": true,
|
| 233 |
+
"license": "MIT",
|
| 234 |
+
"dependencies": {
|
| 235 |
+
"@babel/helper-plugin-utils": "^7.27.1"
|
| 236 |
+
},
|
| 237 |
+
"engines": {
|
| 238 |
+
"node": ">=6.9.0"
|
| 239 |
+
},
|
| 240 |
+
"peerDependencies": {
|
| 241 |
+
"@babel/core": "^7.0.0-0"
|
| 242 |
+
}
|
| 243 |
+
},
|
| 244 |
+
"node_modules/@babel/plugin-transform-react-jsx-source": {
|
| 245 |
+
"version": "7.27.1",
|
| 246 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
|
| 247 |
+
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
|
| 248 |
+
"dev": true,
|
| 249 |
+
"license": "MIT",
|
| 250 |
+
"dependencies": {
|
| 251 |
+
"@babel/helper-plugin-utils": "^7.27.1"
|
| 252 |
+
},
|
| 253 |
+
"engines": {
|
| 254 |
+
"node": ">=6.9.0"
|
| 255 |
+
},
|
| 256 |
+
"peerDependencies": {
|
| 257 |
+
"@babel/core": "^7.0.0-0"
|
| 258 |
+
}
|
| 259 |
+
},
|
| 260 |
+
"node_modules/@babel/template": {
|
| 261 |
+
"version": "7.27.2",
|
| 262 |
+
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
| 263 |
+
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
| 264 |
+
"dev": true,
|
| 265 |
+
"license": "MIT",
|
| 266 |
+
"dependencies": {
|
| 267 |
+
"@babel/code-frame": "^7.27.1",
|
| 268 |
+
"@babel/parser": "^7.27.2",
|
| 269 |
+
"@babel/types": "^7.27.1"
|
| 270 |
+
},
|
| 271 |
+
"engines": {
|
| 272 |
+
"node": ">=6.9.0"
|
| 273 |
+
}
|
| 274 |
+
},
|
| 275 |
+
"node_modules/@babel/traverse": {
|
| 276 |
+
"version": "7.28.5",
|
| 277 |
+
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
|
| 278 |
+
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
|
| 279 |
+
"dev": true,
|
| 280 |
+
"license": "MIT",
|
| 281 |
+
"dependencies": {
|
| 282 |
+
"@babel/code-frame": "^7.27.1",
|
| 283 |
+
"@babel/generator": "^7.28.5",
|
| 284 |
+
"@babel/helper-globals": "^7.28.0",
|
| 285 |
+
"@babel/parser": "^7.28.5",
|
| 286 |
+
"@babel/template": "^7.27.2",
|
| 287 |
+
"@babel/types": "^7.28.5",
|
| 288 |
+
"debug": "^4.3.1"
|
| 289 |
+
},
|
| 290 |
+
"engines": {
|
| 291 |
+
"node": ">=6.9.0"
|
| 292 |
+
}
|
| 293 |
+
},
|
| 294 |
+
"node_modules/@babel/types": {
|
| 295 |
+
"version": "7.28.5",
|
| 296 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
|
| 297 |
+
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
|
| 298 |
+
"dev": true,
|
| 299 |
+
"license": "MIT",
|
| 300 |
+
"dependencies": {
|
| 301 |
+
"@babel/helper-string-parser": "^7.27.1",
|
| 302 |
+
"@babel/helper-validator-identifier": "^7.28.5"
|
| 303 |
+
},
|
| 304 |
+
"engines": {
|
| 305 |
+
"node": ">=6.9.0"
|
| 306 |
+
}
|
| 307 |
+
},
|
| 308 |
+
"node_modules/@emnapi/core": {
|
| 309 |
+
"version": "1.7.1",
|
| 310 |
+
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
|
| 311 |
+
"integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==",
|
| 312 |
+
"dev": true,
|
| 313 |
+
"license": "MIT",
|
| 314 |
+
"optional": true,
|
| 315 |
+
"dependencies": {
|
| 316 |
+
"@emnapi/wasi-threads": "1.1.0",
|
| 317 |
+
"tslib": "^2.4.0"
|
| 318 |
+
}
|
| 319 |
+
},
|
| 320 |
+
"node_modules/@emnapi/runtime": {
|
| 321 |
+
"version": "1.7.1",
|
| 322 |
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
|
| 323 |
+
"integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
|
| 324 |
+
"dev": true,
|
| 325 |
+
"license": "MIT",
|
| 326 |
+
"optional": true,
|
| 327 |
+
"dependencies": {
|
| 328 |
+
"tslib": "^2.4.0"
|
| 329 |
+
}
|
| 330 |
+
},
|
| 331 |
+
"node_modules/@emnapi/wasi-threads": {
|
| 332 |
+
"version": "1.1.0",
|
| 333 |
+
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
|
| 334 |
+
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
|
| 335 |
+
"dev": true,
|
| 336 |
+
"license": "MIT",
|
| 337 |
+
"optional": true,
|
| 338 |
+
"dependencies": {
|
| 339 |
+
"tslib": "^2.4.0"
|
| 340 |
+
}
|
| 341 |
+
},
|
| 342 |
+
"node_modules/@eslint-community/eslint-utils": {
|
| 343 |
+
"version": "4.9.0",
|
| 344 |
+
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
|
| 345 |
+
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
|
| 346 |
+
"dev": true,
|
| 347 |
+
"license": "MIT",
|
| 348 |
+
"dependencies": {
|
| 349 |
+
"eslint-visitor-keys": "^3.4.3"
|
| 350 |
+
},
|
| 351 |
+
"engines": {
|
| 352 |
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
| 353 |
+
},
|
| 354 |
+
"funding": {
|
| 355 |
+
"url": "https://opencollective.com/eslint"
|
| 356 |
+
},
|
| 357 |
+
"peerDependencies": {
|
| 358 |
+
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
|
| 359 |
+
}
|
| 360 |
+
},
|
| 361 |
+
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
|
| 362 |
+
"version": "3.4.3",
|
| 363 |
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
| 364 |
+
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
| 365 |
+
"dev": true,
|
| 366 |
+
"license": "Apache-2.0",
|
| 367 |
+
"engines": {
|
| 368 |
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
| 369 |
+
},
|
| 370 |
+
"funding": {
|
| 371 |
+
"url": "https://opencollective.com/eslint"
|
| 372 |
+
}
|
| 373 |
+
},
|
| 374 |
+
"node_modules/@eslint-community/regexpp": {
|
| 375 |
+
"version": "4.12.2",
|
| 376 |
+
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
|
| 377 |
+
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
|
| 378 |
+
"dev": true,
|
| 379 |
+
"license": "MIT",
|
| 380 |
+
"engines": {
|
| 381 |
+
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
| 382 |
+
}
|
| 383 |
+
},
|
| 384 |
+
"node_modules/@eslint/config-array": {
|
| 385 |
+
"version": "0.21.1",
|
| 386 |
+
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
|
| 387 |
+
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
|
| 388 |
+
"dev": true,
|
| 389 |
+
"license": "Apache-2.0",
|
| 390 |
+
"dependencies": {
|
| 391 |
+
"@eslint/object-schema": "^2.1.7",
|
| 392 |
+
"debug": "^4.3.1",
|
| 393 |
+
"minimatch": "^3.1.2"
|
| 394 |
+
},
|
| 395 |
+
"engines": {
|
| 396 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 397 |
+
}
|
| 398 |
+
},
|
| 399 |
+
"node_modules/@eslint/config-helpers": {
|
| 400 |
+
"version": "0.4.2",
|
| 401 |
+
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
|
| 402 |
+
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
|
| 403 |
+
"dev": true,
|
| 404 |
+
"license": "Apache-2.0",
|
| 405 |
+
"dependencies": {
|
| 406 |
+
"@eslint/core": "^0.17.0"
|
| 407 |
+
},
|
| 408 |
+
"engines": {
|
| 409 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 410 |
+
}
|
| 411 |
+
},
|
| 412 |
+
"node_modules/@eslint/core": {
|
| 413 |
+
"version": "0.17.0",
|
| 414 |
+
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
|
| 415 |
+
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
|
| 416 |
+
"dev": true,
|
| 417 |
+
"license": "Apache-2.0",
|
| 418 |
+
"dependencies": {
|
| 419 |
+
"@types/json-schema": "^7.0.15"
|
| 420 |
+
},
|
| 421 |
+
"engines": {
|
| 422 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 423 |
+
}
|
| 424 |
+
},
|
| 425 |
+
"node_modules/@eslint/eslintrc": {
|
| 426 |
+
"version": "3.3.1",
|
| 427 |
+
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
|
| 428 |
+
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
|
| 429 |
+
"dev": true,
|
| 430 |
+
"license": "MIT",
|
| 431 |
+
"dependencies": {
|
| 432 |
+
"ajv": "^6.12.4",
|
| 433 |
+
"debug": "^4.3.2",
|
| 434 |
+
"espree": "^10.0.1",
|
| 435 |
+
"globals": "^14.0.0",
|
| 436 |
+
"ignore": "^5.2.0",
|
| 437 |
+
"import-fresh": "^3.2.1",
|
| 438 |
+
"js-yaml": "^4.1.0",
|
| 439 |
+
"minimatch": "^3.1.2",
|
| 440 |
+
"strip-json-comments": "^3.1.1"
|
| 441 |
+
},
|
| 442 |
+
"engines": {
|
| 443 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 444 |
+
},
|
| 445 |
+
"funding": {
|
| 446 |
+
"url": "https://opencollective.com/eslint"
|
| 447 |
+
}
|
| 448 |
+
},
|
| 449 |
+
"node_modules/@eslint/eslintrc/node_modules/globals": {
|
| 450 |
+
"version": "14.0.0",
|
| 451 |
+
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
|
| 452 |
+
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
|
| 453 |
+
"dev": true,
|
| 454 |
+
"license": "MIT",
|
| 455 |
+
"engines": {
|
| 456 |
+
"node": ">=18"
|
| 457 |
+
},
|
| 458 |
+
"funding": {
|
| 459 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 460 |
+
}
|
| 461 |
+
},
|
| 462 |
+
"node_modules/@eslint/js": {
|
| 463 |
+
"version": "9.39.1",
|
| 464 |
+
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
|
| 465 |
+
"integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
|
| 466 |
+
"dev": true,
|
| 467 |
+
"license": "MIT",
|
| 468 |
+
"engines": {
|
| 469 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 470 |
+
},
|
| 471 |
+
"funding": {
|
| 472 |
+
"url": "https://eslint.org/donate"
|
| 473 |
+
}
|
| 474 |
+
},
|
| 475 |
+
"node_modules/@eslint/object-schema": {
|
| 476 |
+
"version": "2.1.7",
|
| 477 |
+
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
|
| 478 |
+
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
|
| 479 |
+
"dev": true,
|
| 480 |
+
"license": "Apache-2.0",
|
| 481 |
+
"engines": {
|
| 482 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 483 |
+
}
|
| 484 |
+
},
|
| 485 |
+
"node_modules/@eslint/plugin-kit": {
|
| 486 |
+
"version": "0.4.1",
|
| 487 |
+
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
|
| 488 |
+
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
|
| 489 |
+
"dev": true,
|
| 490 |
+
"license": "Apache-2.0",
|
| 491 |
+
"dependencies": {
|
| 492 |
+
"@eslint/core": "^0.17.0",
|
| 493 |
+
"levn": "^0.4.1"
|
| 494 |
+
},
|
| 495 |
+
"engines": {
|
| 496 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 497 |
+
}
|
| 498 |
+
},
|
| 499 |
+
"node_modules/@humanfs/core": {
|
| 500 |
+
"version": "0.19.1",
|
| 501 |
+
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
| 502 |
+
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
|
| 503 |
+
"dev": true,
|
| 504 |
+
"license": "Apache-2.0",
|
| 505 |
+
"engines": {
|
| 506 |
+
"node": ">=18.18.0"
|
| 507 |
+
}
|
| 508 |
+
},
|
| 509 |
+
"node_modules/@humanfs/node": {
|
| 510 |
+
"version": "0.16.7",
|
| 511 |
+
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
|
| 512 |
+
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
|
| 513 |
+
"dev": true,
|
| 514 |
+
"license": "Apache-2.0",
|
| 515 |
+
"dependencies": {
|
| 516 |
+
"@humanfs/core": "^0.19.1",
|
| 517 |
+
"@humanwhocodes/retry": "^0.4.0"
|
| 518 |
+
},
|
| 519 |
+
"engines": {
|
| 520 |
+
"node": ">=18.18.0"
|
| 521 |
+
}
|
| 522 |
+
},
|
| 523 |
+
"node_modules/@humanwhocodes/module-importer": {
|
| 524 |
+
"version": "1.0.1",
|
| 525 |
+
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
| 526 |
+
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
|
| 527 |
+
"dev": true,
|
| 528 |
+
"license": "Apache-2.0",
|
| 529 |
+
"engines": {
|
| 530 |
+
"node": ">=12.22"
|
| 531 |
+
},
|
| 532 |
+
"funding": {
|
| 533 |
+
"type": "github",
|
| 534 |
+
"url": "https://github.com/sponsors/nzakas"
|
| 535 |
+
}
|
| 536 |
+
},
|
| 537 |
+
"node_modules/@humanwhocodes/retry": {
|
| 538 |
+
"version": "0.4.3",
|
| 539 |
+
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
|
| 540 |
+
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
|
| 541 |
+
"dev": true,
|
| 542 |
+
"license": "Apache-2.0",
|
| 543 |
+
"engines": {
|
| 544 |
+
"node": ">=18.18"
|
| 545 |
+
},
|
| 546 |
+
"funding": {
|
| 547 |
+
"type": "github",
|
| 548 |
+
"url": "https://github.com/sponsors/nzakas"
|
| 549 |
+
}
|
| 550 |
+
},
|
| 551 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 552 |
+
"version": "0.3.13",
|
| 553 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 554 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 555 |
+
"dev": true,
|
| 556 |
+
"license": "MIT",
|
| 557 |
+
"dependencies": {
|
| 558 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 559 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 560 |
+
}
|
| 561 |
+
},
|
| 562 |
+
"node_modules/@jridgewell/remapping": {
|
| 563 |
+
"version": "2.3.5",
|
| 564 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 565 |
+
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
| 566 |
+
"dev": true,
|
| 567 |
+
"license": "MIT",
|
| 568 |
+
"dependencies": {
|
| 569 |
+
"@jridgewell/gen-mapping": "^0.3.5",
|
| 570 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 571 |
+
}
|
| 572 |
+
},
|
| 573 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 574 |
+
"version": "3.1.2",
|
| 575 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 576 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 577 |
+
"dev": true,
|
| 578 |
+
"license": "MIT",
|
| 579 |
+
"engines": {
|
| 580 |
+
"node": ">=6.0.0"
|
| 581 |
+
}
|
| 582 |
+
},
|
| 583 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 584 |
+
"version": "1.5.5",
|
| 585 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 586 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 587 |
+
"dev": true,
|
| 588 |
+
"license": "MIT"
|
| 589 |
+
},
|
| 590 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 591 |
+
"version": "0.3.31",
|
| 592 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 593 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 594 |
+
"dev": true,
|
| 595 |
+
"license": "MIT",
|
| 596 |
+
"dependencies": {
|
| 597 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 598 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 599 |
+
}
|
| 600 |
+
},
|
| 601 |
+
"node_modules/@napi-rs/wasm-runtime": {
|
| 602 |
+
"version": "1.0.7",
|
| 603 |
+
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
|
| 604 |
+
"integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
|
| 605 |
+
"dev": true,
|
| 606 |
+
"license": "MIT",
|
| 607 |
+
"optional": true,
|
| 608 |
+
"dependencies": {
|
| 609 |
+
"@emnapi/core": "^1.5.0",
|
| 610 |
+
"@emnapi/runtime": "^1.5.0",
|
| 611 |
+
"@tybys/wasm-util": "^0.10.1"
|
| 612 |
+
}
|
| 613 |
+
},
|
| 614 |
+
"node_modules/@oxc-project/runtime": {
|
| 615 |
+
"version": "0.97.0",
|
| 616 |
+
"resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.97.0.tgz",
|
| 617 |
+
"integrity": "sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==",
|
| 618 |
+
"dev": true,
|
| 619 |
+
"license": "MIT",
|
| 620 |
+
"engines": {
|
| 621 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 622 |
+
}
|
| 623 |
+
},
|
| 624 |
+
"node_modules/@oxc-project/types": {
|
| 625 |
+
"version": "0.97.0",
|
| 626 |
+
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.97.0.tgz",
|
| 627 |
+
"integrity": "sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==",
|
| 628 |
+
"dev": true,
|
| 629 |
+
"license": "MIT",
|
| 630 |
+
"funding": {
|
| 631 |
+
"url": "https://github.com/sponsors/Boshen"
|
| 632 |
+
}
|
| 633 |
+
},
|
| 634 |
+
"node_modules/@rolldown/binding-android-arm64": {
|
| 635 |
+
"version": "1.0.0-beta.50",
|
| 636 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.50.tgz",
|
| 637 |
+
"integrity": "sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==",
|
| 638 |
+
"cpu": [
|
| 639 |
+
"arm64"
|
| 640 |
+
],
|
| 641 |
+
"dev": true,
|
| 642 |
+
"license": "MIT",
|
| 643 |
+
"optional": true,
|
| 644 |
+
"os": [
|
| 645 |
+
"android"
|
| 646 |
+
],
|
| 647 |
+
"engines": {
|
| 648 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 649 |
+
}
|
| 650 |
+
},
|
| 651 |
+
"node_modules/@rolldown/binding-darwin-arm64": {
|
| 652 |
+
"version": "1.0.0-beta.50",
|
| 653 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.50.tgz",
|
| 654 |
+
"integrity": "sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==",
|
| 655 |
+
"cpu": [
|
| 656 |
+
"arm64"
|
| 657 |
+
],
|
| 658 |
+
"dev": true,
|
| 659 |
+
"license": "MIT",
|
| 660 |
+
"optional": true,
|
| 661 |
+
"os": [
|
| 662 |
+
"darwin"
|
| 663 |
+
],
|
| 664 |
+
"engines": {
|
| 665 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 666 |
+
}
|
| 667 |
+
},
|
| 668 |
+
"node_modules/@rolldown/binding-darwin-x64": {
|
| 669 |
+
"version": "1.0.0-beta.50",
|
| 670 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.50.tgz",
|
| 671 |
+
"integrity": "sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==",
|
| 672 |
+
"cpu": [
|
| 673 |
+
"x64"
|
| 674 |
+
],
|
| 675 |
+
"dev": true,
|
| 676 |
+
"license": "MIT",
|
| 677 |
+
"optional": true,
|
| 678 |
+
"os": [
|
| 679 |
+
"darwin"
|
| 680 |
+
],
|
| 681 |
+
"engines": {
|
| 682 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 683 |
+
}
|
| 684 |
+
},
|
| 685 |
+
"node_modules/@rolldown/binding-freebsd-x64": {
|
| 686 |
+
"version": "1.0.0-beta.50",
|
| 687 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.50.tgz",
|
| 688 |
+
"integrity": "sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==",
|
| 689 |
+
"cpu": [
|
| 690 |
+
"x64"
|
| 691 |
+
],
|
| 692 |
+
"dev": true,
|
| 693 |
+
"license": "MIT",
|
| 694 |
+
"optional": true,
|
| 695 |
+
"os": [
|
| 696 |
+
"freebsd"
|
| 697 |
+
],
|
| 698 |
+
"engines": {
|
| 699 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 700 |
+
}
|
| 701 |
+
},
|
| 702 |
+
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
| 703 |
+
"version": "1.0.0-beta.50",
|
| 704 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.50.tgz",
|
| 705 |
+
"integrity": "sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==",
|
| 706 |
+
"cpu": [
|
| 707 |
+
"arm"
|
| 708 |
+
],
|
| 709 |
+
"dev": true,
|
| 710 |
+
"license": "MIT",
|
| 711 |
+
"optional": true,
|
| 712 |
+
"os": [
|
| 713 |
+
"linux"
|
| 714 |
+
],
|
| 715 |
+
"engines": {
|
| 716 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 717 |
+
}
|
| 718 |
+
},
|
| 719 |
+
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
| 720 |
+
"version": "1.0.0-beta.50",
|
| 721 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.50.tgz",
|
| 722 |
+
"integrity": "sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==",
|
| 723 |
+
"cpu": [
|
| 724 |
+
"arm64"
|
| 725 |
+
],
|
| 726 |
+
"dev": true,
|
| 727 |
+
"license": "MIT",
|
| 728 |
+
"optional": true,
|
| 729 |
+
"os": [
|
| 730 |
+
"linux"
|
| 731 |
+
],
|
| 732 |
+
"engines": {
|
| 733 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 734 |
+
}
|
| 735 |
+
},
|
| 736 |
+
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
| 737 |
+
"version": "1.0.0-beta.50",
|
| 738 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.50.tgz",
|
| 739 |
+
"integrity": "sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==",
|
| 740 |
+
"cpu": [
|
| 741 |
+
"arm64"
|
| 742 |
+
],
|
| 743 |
+
"dev": true,
|
| 744 |
+
"license": "MIT",
|
| 745 |
+
"optional": true,
|
| 746 |
+
"os": [
|
| 747 |
+
"linux"
|
| 748 |
+
],
|
| 749 |
+
"engines": {
|
| 750 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 751 |
+
}
|
| 752 |
+
},
|
| 753 |
+
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
| 754 |
+
"version": "1.0.0-beta.50",
|
| 755 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.50.tgz",
|
| 756 |
+
"integrity": "sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==",
|
| 757 |
+
"cpu": [
|
| 758 |
+
"x64"
|
| 759 |
+
],
|
| 760 |
+
"dev": true,
|
| 761 |
+
"license": "MIT",
|
| 762 |
+
"optional": true,
|
| 763 |
+
"os": [
|
| 764 |
+
"linux"
|
| 765 |
+
],
|
| 766 |
+
"engines": {
|
| 767 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 768 |
+
}
|
| 769 |
+
},
|
| 770 |
+
"node_modules/@rolldown/binding-linux-x64-musl": {
|
| 771 |
+
"version": "1.0.0-beta.50",
|
| 772 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.50.tgz",
|
| 773 |
+
"integrity": "sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==",
|
| 774 |
+
"cpu": [
|
| 775 |
+
"x64"
|
| 776 |
+
],
|
| 777 |
+
"dev": true,
|
| 778 |
+
"license": "MIT",
|
| 779 |
+
"optional": true,
|
| 780 |
+
"os": [
|
| 781 |
+
"linux"
|
| 782 |
+
],
|
| 783 |
+
"engines": {
|
| 784 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 785 |
+
}
|
| 786 |
+
},
|
| 787 |
+
"node_modules/@rolldown/binding-openharmony-arm64": {
|
| 788 |
+
"version": "1.0.0-beta.50",
|
| 789 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.50.tgz",
|
| 790 |
+
"integrity": "sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==",
|
| 791 |
+
"cpu": [
|
| 792 |
+
"arm64"
|
| 793 |
+
],
|
| 794 |
+
"dev": true,
|
| 795 |
+
"license": "MIT",
|
| 796 |
+
"optional": true,
|
| 797 |
+
"os": [
|
| 798 |
+
"openharmony"
|
| 799 |
+
],
|
| 800 |
+
"engines": {
|
| 801 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 802 |
+
}
|
| 803 |
+
},
|
| 804 |
+
"node_modules/@rolldown/binding-wasm32-wasi": {
|
| 805 |
+
"version": "1.0.0-beta.50",
|
| 806 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.50.tgz",
|
| 807 |
+
"integrity": "sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==",
|
| 808 |
+
"cpu": [
|
| 809 |
+
"wasm32"
|
| 810 |
+
],
|
| 811 |
+
"dev": true,
|
| 812 |
+
"license": "MIT",
|
| 813 |
+
"optional": true,
|
| 814 |
+
"dependencies": {
|
| 815 |
+
"@napi-rs/wasm-runtime": "^1.0.7"
|
| 816 |
+
},
|
| 817 |
+
"engines": {
|
| 818 |
+
"node": ">=14.0.0"
|
| 819 |
+
}
|
| 820 |
+
},
|
| 821 |
+
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
| 822 |
+
"version": "1.0.0-beta.50",
|
| 823 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.50.tgz",
|
| 824 |
+
"integrity": "sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==",
|
| 825 |
+
"cpu": [
|
| 826 |
+
"arm64"
|
| 827 |
+
],
|
| 828 |
+
"dev": true,
|
| 829 |
+
"license": "MIT",
|
| 830 |
+
"optional": true,
|
| 831 |
+
"os": [
|
| 832 |
+
"win32"
|
| 833 |
+
],
|
| 834 |
+
"engines": {
|
| 835 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 836 |
+
}
|
| 837 |
+
},
|
| 838 |
+
"node_modules/@rolldown/binding-win32-ia32-msvc": {
|
| 839 |
+
"version": "1.0.0-beta.50",
|
| 840 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.50.tgz",
|
| 841 |
+
"integrity": "sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==",
|
| 842 |
+
"cpu": [
|
| 843 |
+
"ia32"
|
| 844 |
+
],
|
| 845 |
+
"dev": true,
|
| 846 |
+
"license": "MIT",
|
| 847 |
+
"optional": true,
|
| 848 |
+
"os": [
|
| 849 |
+
"win32"
|
| 850 |
+
],
|
| 851 |
+
"engines": {
|
| 852 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 853 |
+
}
|
| 854 |
+
},
|
| 855 |
+
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
| 856 |
+
"version": "1.0.0-beta.50",
|
| 857 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.50.tgz",
|
| 858 |
+
"integrity": "sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==",
|
| 859 |
+
"cpu": [
|
| 860 |
+
"x64"
|
| 861 |
+
],
|
| 862 |
+
"dev": true,
|
| 863 |
+
"license": "MIT",
|
| 864 |
+
"optional": true,
|
| 865 |
+
"os": [
|
| 866 |
+
"win32"
|
| 867 |
+
],
|
| 868 |
+
"engines": {
|
| 869 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 870 |
+
}
|
| 871 |
+
},
|
| 872 |
+
"node_modules/@rolldown/pluginutils": {
|
| 873 |
+
"version": "1.0.0-beta.47",
|
| 874 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz",
|
| 875 |
+
"integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==",
|
| 876 |
+
"dev": true,
|
| 877 |
+
"license": "MIT"
|
| 878 |
+
},
|
| 879 |
+
"node_modules/@tybys/wasm-util": {
|
| 880 |
+
"version": "0.10.1",
|
| 881 |
+
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
| 882 |
+
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
| 883 |
+
"dev": true,
|
| 884 |
+
"license": "MIT",
|
| 885 |
+
"optional": true,
|
| 886 |
+
"dependencies": {
|
| 887 |
+
"tslib": "^2.4.0"
|
| 888 |
+
}
|
| 889 |
+
},
|
| 890 |
+
"node_modules/@types/babel__core": {
|
| 891 |
+
"version": "7.20.5",
|
| 892 |
+
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
| 893 |
+
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
| 894 |
+
"dev": true,
|
| 895 |
+
"license": "MIT",
|
| 896 |
+
"dependencies": {
|
| 897 |
+
"@babel/parser": "^7.20.7",
|
| 898 |
+
"@babel/types": "^7.20.7",
|
| 899 |
+
"@types/babel__generator": "*",
|
| 900 |
+
"@types/babel__template": "*",
|
| 901 |
+
"@types/babel__traverse": "*"
|
| 902 |
+
}
|
| 903 |
+
},
|
| 904 |
+
"node_modules/@types/babel__generator": {
|
| 905 |
+
"version": "7.27.0",
|
| 906 |
+
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
|
| 907 |
+
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
|
| 908 |
+
"dev": true,
|
| 909 |
+
"license": "MIT",
|
| 910 |
+
"dependencies": {
|
| 911 |
+
"@babel/types": "^7.0.0"
|
| 912 |
+
}
|
| 913 |
+
},
|
| 914 |
+
"node_modules/@types/babel__template": {
|
| 915 |
+
"version": "7.4.4",
|
| 916 |
+
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
|
| 917 |
+
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
|
| 918 |
+
"dev": true,
|
| 919 |
+
"license": "MIT",
|
| 920 |
+
"dependencies": {
|
| 921 |
+
"@babel/parser": "^7.1.0",
|
| 922 |
+
"@babel/types": "^7.0.0"
|
| 923 |
+
}
|
| 924 |
+
},
|
| 925 |
+
"node_modules/@types/babel__traverse": {
|
| 926 |
+
"version": "7.28.0",
|
| 927 |
+
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
|
| 928 |
+
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
|
| 929 |
+
"dev": true,
|
| 930 |
+
"license": "MIT",
|
| 931 |
+
"dependencies": {
|
| 932 |
+
"@babel/types": "^7.28.2"
|
| 933 |
+
}
|
| 934 |
+
},
|
| 935 |
+
"node_modules/@types/estree": {
|
| 936 |
+
"version": "1.0.8",
|
| 937 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
| 938 |
+
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
| 939 |
+
"dev": true,
|
| 940 |
+
"license": "MIT"
|
| 941 |
+
},
|
| 942 |
+
"node_modules/@types/json-schema": {
|
| 943 |
+
"version": "7.0.15",
|
| 944 |
+
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
| 945 |
+
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
| 946 |
+
"dev": true,
|
| 947 |
+
"license": "MIT"
|
| 948 |
+
},
|
| 949 |
+
"node_modules/@types/react": {
|
| 950 |
+
"version": "19.2.6",
|
| 951 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz",
|
| 952 |
+
"integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==",
|
| 953 |
+
"dev": true,
|
| 954 |
+
"license": "MIT",
|
| 955 |
+
"dependencies": {
|
| 956 |
+
"csstype": "^3.2.2"
|
| 957 |
+
}
|
| 958 |
+
},
|
| 959 |
+
"node_modules/@types/react-dom": {
|
| 960 |
+
"version": "19.2.3",
|
| 961 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
| 962 |
+
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
| 963 |
+
"dev": true,
|
| 964 |
+
"license": "MIT",
|
| 965 |
+
"peerDependencies": {
|
| 966 |
+
"@types/react": "^19.2.0"
|
| 967 |
+
}
|
| 968 |
+
},
|
| 969 |
+
"node_modules/@vitejs/plugin-react": {
|
| 970 |
+
"version": "5.1.1",
|
| 971 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz",
|
| 972 |
+
"integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==",
|
| 973 |
+
"dev": true,
|
| 974 |
+
"license": "MIT",
|
| 975 |
+
"dependencies": {
|
| 976 |
+
"@babel/core": "^7.28.5",
|
| 977 |
+
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
|
| 978 |
+
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
|
| 979 |
+
"@rolldown/pluginutils": "1.0.0-beta.47",
|
| 980 |
+
"@types/babel__core": "^7.20.5",
|
| 981 |
+
"react-refresh": "^0.18.0"
|
| 982 |
+
},
|
| 983 |
+
"engines": {
|
| 984 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 985 |
+
},
|
| 986 |
+
"peerDependencies": {
|
| 987 |
+
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
| 988 |
+
}
|
| 989 |
+
},
|
| 990 |
+
"node_modules/acorn": {
|
| 991 |
+
"version": "8.15.0",
|
| 992 |
+
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
| 993 |
+
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
| 994 |
+
"dev": true,
|
| 995 |
+
"license": "MIT",
|
| 996 |
+
"bin": {
|
| 997 |
+
"acorn": "bin/acorn"
|
| 998 |
+
},
|
| 999 |
+
"engines": {
|
| 1000 |
+
"node": ">=0.4.0"
|
| 1001 |
+
}
|
| 1002 |
+
},
|
| 1003 |
+
"node_modules/acorn-jsx": {
|
| 1004 |
+
"version": "5.3.2",
|
| 1005 |
+
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
| 1006 |
+
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
| 1007 |
+
"dev": true,
|
| 1008 |
+
"license": "MIT",
|
| 1009 |
+
"peerDependencies": {
|
| 1010 |
+
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
| 1011 |
+
}
|
| 1012 |
+
},
|
| 1013 |
+
"node_modules/ajv": {
|
| 1014 |
+
"version": "6.12.6",
|
| 1015 |
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
| 1016 |
+
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
| 1017 |
+
"dev": true,
|
| 1018 |
+
"license": "MIT",
|
| 1019 |
+
"dependencies": {
|
| 1020 |
+
"fast-deep-equal": "^3.1.1",
|
| 1021 |
+
"fast-json-stable-stringify": "^2.0.0",
|
| 1022 |
+
"json-schema-traverse": "^0.4.1",
|
| 1023 |
+
"uri-js": "^4.2.2"
|
| 1024 |
+
},
|
| 1025 |
+
"funding": {
|
| 1026 |
+
"type": "github",
|
| 1027 |
+
"url": "https://github.com/sponsors/epoberezkin"
|
| 1028 |
+
}
|
| 1029 |
+
},
|
| 1030 |
+
"node_modules/ansi-styles": {
|
| 1031 |
+
"version": "4.3.0",
|
| 1032 |
+
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
| 1033 |
+
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
| 1034 |
+
"dev": true,
|
| 1035 |
+
"license": "MIT",
|
| 1036 |
+
"dependencies": {
|
| 1037 |
+
"color-convert": "^2.0.1"
|
| 1038 |
+
},
|
| 1039 |
+
"engines": {
|
| 1040 |
+
"node": ">=8"
|
| 1041 |
+
},
|
| 1042 |
+
"funding": {
|
| 1043 |
+
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
| 1044 |
+
}
|
| 1045 |
+
},
|
| 1046 |
+
"node_modules/argparse": {
|
| 1047 |
+
"version": "2.0.1",
|
| 1048 |
+
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
| 1049 |
+
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
| 1050 |
+
"dev": true,
|
| 1051 |
+
"license": "Python-2.0"
|
| 1052 |
+
},
|
| 1053 |
+
"node_modules/balanced-match": {
|
| 1054 |
+
"version": "1.0.2",
|
| 1055 |
+
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
| 1056 |
+
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
| 1057 |
+
"dev": true,
|
| 1058 |
+
"license": "MIT"
|
| 1059 |
+
},
|
| 1060 |
+
"node_modules/baseline-browser-mapping": {
|
| 1061 |
+
"version": "2.8.30",
|
| 1062 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz",
|
| 1063 |
+
"integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==",
|
| 1064 |
+
"dev": true,
|
| 1065 |
+
"license": "Apache-2.0",
|
| 1066 |
+
"bin": {
|
| 1067 |
+
"baseline-browser-mapping": "dist/cli.js"
|
| 1068 |
+
}
|
| 1069 |
+
},
|
| 1070 |
+
"node_modules/brace-expansion": {
|
| 1071 |
+
"version": "1.1.12",
|
| 1072 |
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
| 1073 |
+
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
| 1074 |
+
"dev": true,
|
| 1075 |
+
"license": "MIT",
|
| 1076 |
+
"dependencies": {
|
| 1077 |
+
"balanced-match": "^1.0.0",
|
| 1078 |
+
"concat-map": "0.0.1"
|
| 1079 |
+
}
|
| 1080 |
+
},
|
| 1081 |
+
"node_modules/browserslist": {
|
| 1082 |
+
"version": "4.28.0",
|
| 1083 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
|
| 1084 |
+
"integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
|
| 1085 |
+
"dev": true,
|
| 1086 |
+
"funding": [
|
| 1087 |
+
{
|
| 1088 |
+
"type": "opencollective",
|
| 1089 |
+
"url": "https://opencollective.com/browserslist"
|
| 1090 |
+
},
|
| 1091 |
+
{
|
| 1092 |
+
"type": "tidelift",
|
| 1093 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 1094 |
+
},
|
| 1095 |
+
{
|
| 1096 |
+
"type": "github",
|
| 1097 |
+
"url": "https://github.com/sponsors/ai"
|
| 1098 |
+
}
|
| 1099 |
+
],
|
| 1100 |
+
"license": "MIT",
|
| 1101 |
+
"dependencies": {
|
| 1102 |
+
"baseline-browser-mapping": "^2.8.25",
|
| 1103 |
+
"caniuse-lite": "^1.0.30001754",
|
| 1104 |
+
"electron-to-chromium": "^1.5.249",
|
| 1105 |
+
"node-releases": "^2.0.27",
|
| 1106 |
+
"update-browserslist-db": "^1.1.4"
|
| 1107 |
+
},
|
| 1108 |
+
"bin": {
|
| 1109 |
+
"browserslist": "cli.js"
|
| 1110 |
+
},
|
| 1111 |
+
"engines": {
|
| 1112 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1113 |
+
}
|
| 1114 |
+
},
|
| 1115 |
+
"node_modules/callsites": {
|
| 1116 |
+
"version": "3.1.0",
|
| 1117 |
+
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
| 1118 |
+
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
| 1119 |
+
"dev": true,
|
| 1120 |
+
"license": "MIT",
|
| 1121 |
+
"engines": {
|
| 1122 |
+
"node": ">=6"
|
| 1123 |
+
}
|
| 1124 |
+
},
|
| 1125 |
+
"node_modules/caniuse-lite": {
|
| 1126 |
+
"version": "1.0.30001756",
|
| 1127 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz",
|
| 1128 |
+
"integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==",
|
| 1129 |
+
"dev": true,
|
| 1130 |
+
"funding": [
|
| 1131 |
+
{
|
| 1132 |
+
"type": "opencollective",
|
| 1133 |
+
"url": "https://opencollective.com/browserslist"
|
| 1134 |
+
},
|
| 1135 |
+
{
|
| 1136 |
+
"type": "tidelift",
|
| 1137 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 1138 |
+
},
|
| 1139 |
+
{
|
| 1140 |
+
"type": "github",
|
| 1141 |
+
"url": "https://github.com/sponsors/ai"
|
| 1142 |
+
}
|
| 1143 |
+
],
|
| 1144 |
+
"license": "CC-BY-4.0"
|
| 1145 |
+
},
|
| 1146 |
+
"node_modules/chalk": {
|
| 1147 |
+
"version": "4.1.2",
|
| 1148 |
+
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
| 1149 |
+
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
| 1150 |
+
"dev": true,
|
| 1151 |
+
"license": "MIT",
|
| 1152 |
+
"dependencies": {
|
| 1153 |
+
"ansi-styles": "^4.1.0",
|
| 1154 |
+
"supports-color": "^7.1.0"
|
| 1155 |
+
},
|
| 1156 |
+
"engines": {
|
| 1157 |
+
"node": ">=10"
|
| 1158 |
+
},
|
| 1159 |
+
"funding": {
|
| 1160 |
+
"url": "https://github.com/chalk/chalk?sponsor=1"
|
| 1161 |
+
}
|
| 1162 |
+
},
|
| 1163 |
+
"node_modules/color-convert": {
|
| 1164 |
+
"version": "2.0.1",
|
| 1165 |
+
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
| 1166 |
+
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
| 1167 |
+
"dev": true,
|
| 1168 |
+
"license": "MIT",
|
| 1169 |
+
"dependencies": {
|
| 1170 |
+
"color-name": "~1.1.4"
|
| 1171 |
+
},
|
| 1172 |
+
"engines": {
|
| 1173 |
+
"node": ">=7.0.0"
|
| 1174 |
+
}
|
| 1175 |
+
},
|
| 1176 |
+
"node_modules/color-name": {
|
| 1177 |
+
"version": "1.1.4",
|
| 1178 |
+
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
| 1179 |
+
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
| 1180 |
+
"dev": true,
|
| 1181 |
+
"license": "MIT"
|
| 1182 |
+
},
|
| 1183 |
+
"node_modules/concat-map": {
|
| 1184 |
+
"version": "0.0.1",
|
| 1185 |
+
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
| 1186 |
+
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
| 1187 |
+
"dev": true,
|
| 1188 |
+
"license": "MIT"
|
| 1189 |
+
},
|
| 1190 |
+
"node_modules/convert-source-map": {
|
| 1191 |
+
"version": "2.0.0",
|
| 1192 |
+
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
| 1193 |
+
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
| 1194 |
+
"dev": true,
|
| 1195 |
+
"license": "MIT"
|
| 1196 |
+
},
|
| 1197 |
+
"node_modules/cross-spawn": {
|
| 1198 |
+
"version": "7.0.6",
|
| 1199 |
+
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
| 1200 |
+
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
| 1201 |
+
"dev": true,
|
| 1202 |
+
"license": "MIT",
|
| 1203 |
+
"dependencies": {
|
| 1204 |
+
"path-key": "^3.1.0",
|
| 1205 |
+
"shebang-command": "^2.0.0",
|
| 1206 |
+
"which": "^2.0.1"
|
| 1207 |
+
},
|
| 1208 |
+
"engines": {
|
| 1209 |
+
"node": ">= 8"
|
| 1210 |
+
}
|
| 1211 |
+
},
|
| 1212 |
+
"node_modules/csstype": {
|
| 1213 |
+
"version": "3.2.3",
|
| 1214 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1215 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1216 |
+
"dev": true,
|
| 1217 |
+
"license": "MIT"
|
| 1218 |
+
},
|
| 1219 |
+
"node_modules/debug": {
|
| 1220 |
+
"version": "4.4.3",
|
| 1221 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1222 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1223 |
+
"dev": true,
|
| 1224 |
+
"license": "MIT",
|
| 1225 |
+
"dependencies": {
|
| 1226 |
+
"ms": "^2.1.3"
|
| 1227 |
+
},
|
| 1228 |
+
"engines": {
|
| 1229 |
+
"node": ">=6.0"
|
| 1230 |
+
},
|
| 1231 |
+
"peerDependenciesMeta": {
|
| 1232 |
+
"supports-color": {
|
| 1233 |
+
"optional": true
|
| 1234 |
+
}
|
| 1235 |
+
}
|
| 1236 |
+
},
|
| 1237 |
+
"node_modules/deep-is": {
|
| 1238 |
+
"version": "0.1.4",
|
| 1239 |
+
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
| 1240 |
+
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
| 1241 |
+
"dev": true,
|
| 1242 |
+
"license": "MIT"
|
| 1243 |
+
},
|
| 1244 |
+
"node_modules/detect-libc": {
|
| 1245 |
+
"version": "2.1.2",
|
| 1246 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 1247 |
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 1248 |
+
"dev": true,
|
| 1249 |
+
"license": "Apache-2.0",
|
| 1250 |
+
"engines": {
|
| 1251 |
+
"node": ">=8"
|
| 1252 |
+
}
|
| 1253 |
+
},
|
| 1254 |
+
"node_modules/electron-to-chromium": {
|
| 1255 |
+
"version": "1.5.259",
|
| 1256 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz",
|
| 1257 |
+
"integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==",
|
| 1258 |
+
"dev": true,
|
| 1259 |
+
"license": "ISC"
|
| 1260 |
+
},
|
| 1261 |
+
"node_modules/escalade": {
|
| 1262 |
+
"version": "3.2.0",
|
| 1263 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1264 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1265 |
+
"dev": true,
|
| 1266 |
+
"license": "MIT",
|
| 1267 |
+
"engines": {
|
| 1268 |
+
"node": ">=6"
|
| 1269 |
+
}
|
| 1270 |
+
},
|
| 1271 |
+
"node_modules/escape-string-regexp": {
|
| 1272 |
+
"version": "4.0.0",
|
| 1273 |
+
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
| 1274 |
+
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
| 1275 |
+
"dev": true,
|
| 1276 |
+
"license": "MIT",
|
| 1277 |
+
"engines": {
|
| 1278 |
+
"node": ">=10"
|
| 1279 |
+
},
|
| 1280 |
+
"funding": {
|
| 1281 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1282 |
+
}
|
| 1283 |
+
},
|
| 1284 |
+
"node_modules/eslint": {
|
| 1285 |
+
"version": "9.39.1",
|
| 1286 |
+
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
|
| 1287 |
+
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
| 1288 |
+
"dev": true,
|
| 1289 |
+
"license": "MIT",
|
| 1290 |
+
"dependencies": {
|
| 1291 |
+
"@eslint-community/eslint-utils": "^4.8.0",
|
| 1292 |
+
"@eslint-community/regexpp": "^4.12.1",
|
| 1293 |
+
"@eslint/config-array": "^0.21.1",
|
| 1294 |
+
"@eslint/config-helpers": "^0.4.2",
|
| 1295 |
+
"@eslint/core": "^0.17.0",
|
| 1296 |
+
"@eslint/eslintrc": "^3.3.1",
|
| 1297 |
+
"@eslint/js": "9.39.1",
|
| 1298 |
+
"@eslint/plugin-kit": "^0.4.1",
|
| 1299 |
+
"@humanfs/node": "^0.16.6",
|
| 1300 |
+
"@humanwhocodes/module-importer": "^1.0.1",
|
| 1301 |
+
"@humanwhocodes/retry": "^0.4.2",
|
| 1302 |
+
"@types/estree": "^1.0.6",
|
| 1303 |
+
"ajv": "^6.12.4",
|
| 1304 |
+
"chalk": "^4.0.0",
|
| 1305 |
+
"cross-spawn": "^7.0.6",
|
| 1306 |
+
"debug": "^4.3.2",
|
| 1307 |
+
"escape-string-regexp": "^4.0.0",
|
| 1308 |
+
"eslint-scope": "^8.4.0",
|
| 1309 |
+
"eslint-visitor-keys": "^4.2.1",
|
| 1310 |
+
"espree": "^10.4.0",
|
| 1311 |
+
"esquery": "^1.5.0",
|
| 1312 |
+
"esutils": "^2.0.2",
|
| 1313 |
+
"fast-deep-equal": "^3.1.3",
|
| 1314 |
+
"file-entry-cache": "^8.0.0",
|
| 1315 |
+
"find-up": "^5.0.0",
|
| 1316 |
+
"glob-parent": "^6.0.2",
|
| 1317 |
+
"ignore": "^5.2.0",
|
| 1318 |
+
"imurmurhash": "^0.1.4",
|
| 1319 |
+
"is-glob": "^4.0.0",
|
| 1320 |
+
"json-stable-stringify-without-jsonify": "^1.0.1",
|
| 1321 |
+
"lodash.merge": "^4.6.2",
|
| 1322 |
+
"minimatch": "^3.1.2",
|
| 1323 |
+
"natural-compare": "^1.4.0",
|
| 1324 |
+
"optionator": "^0.9.3"
|
| 1325 |
+
},
|
| 1326 |
+
"bin": {
|
| 1327 |
+
"eslint": "bin/eslint.js"
|
| 1328 |
+
},
|
| 1329 |
+
"engines": {
|
| 1330 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 1331 |
+
},
|
| 1332 |
+
"funding": {
|
| 1333 |
+
"url": "https://eslint.org/donate"
|
| 1334 |
+
},
|
| 1335 |
+
"peerDependencies": {
|
| 1336 |
+
"jiti": "*"
|
| 1337 |
+
},
|
| 1338 |
+
"peerDependenciesMeta": {
|
| 1339 |
+
"jiti": {
|
| 1340 |
+
"optional": true
|
| 1341 |
+
}
|
| 1342 |
+
}
|
| 1343 |
+
},
|
| 1344 |
+
"node_modules/eslint-plugin-react-hooks": {
|
| 1345 |
+
"version": "7.0.1",
|
| 1346 |
+
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
|
| 1347 |
+
"integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
|
| 1348 |
+
"dev": true,
|
| 1349 |
+
"license": "MIT",
|
| 1350 |
+
"dependencies": {
|
| 1351 |
+
"@babel/core": "^7.24.4",
|
| 1352 |
+
"@babel/parser": "^7.24.4",
|
| 1353 |
+
"hermes-parser": "^0.25.1",
|
| 1354 |
+
"zod": "^3.25.0 || ^4.0.0",
|
| 1355 |
+
"zod-validation-error": "^3.5.0 || ^4.0.0"
|
| 1356 |
+
},
|
| 1357 |
+
"engines": {
|
| 1358 |
+
"node": ">=18"
|
| 1359 |
+
},
|
| 1360 |
+
"peerDependencies": {
|
| 1361 |
+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
|
| 1362 |
+
}
|
| 1363 |
+
},
|
| 1364 |
+
"node_modules/eslint-plugin-react-refresh": {
|
| 1365 |
+
"version": "0.4.24",
|
| 1366 |
+
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz",
|
| 1367 |
+
"integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==",
|
| 1368 |
+
"dev": true,
|
| 1369 |
+
"license": "MIT",
|
| 1370 |
+
"peerDependencies": {
|
| 1371 |
+
"eslint": ">=8.40"
|
| 1372 |
+
}
|
| 1373 |
+
},
|
| 1374 |
+
"node_modules/eslint-scope": {
|
| 1375 |
+
"version": "8.4.0",
|
| 1376 |
+
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
| 1377 |
+
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
| 1378 |
+
"dev": true,
|
| 1379 |
+
"license": "BSD-2-Clause",
|
| 1380 |
+
"dependencies": {
|
| 1381 |
+
"esrecurse": "^4.3.0",
|
| 1382 |
+
"estraverse": "^5.2.0"
|
| 1383 |
+
},
|
| 1384 |
+
"engines": {
|
| 1385 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 1386 |
+
},
|
| 1387 |
+
"funding": {
|
| 1388 |
+
"url": "https://opencollective.com/eslint"
|
| 1389 |
+
}
|
| 1390 |
+
},
|
| 1391 |
+
"node_modules/eslint-visitor-keys": {
|
| 1392 |
+
"version": "4.2.1",
|
| 1393 |
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
| 1394 |
+
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
| 1395 |
+
"dev": true,
|
| 1396 |
+
"license": "Apache-2.0",
|
| 1397 |
+
"engines": {
|
| 1398 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 1399 |
+
},
|
| 1400 |
+
"funding": {
|
| 1401 |
+
"url": "https://opencollective.com/eslint"
|
| 1402 |
+
}
|
| 1403 |
+
},
|
| 1404 |
+
"node_modules/espree": {
|
| 1405 |
+
"version": "10.4.0",
|
| 1406 |
+
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
| 1407 |
+
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
| 1408 |
+
"dev": true,
|
| 1409 |
+
"license": "BSD-2-Clause",
|
| 1410 |
+
"dependencies": {
|
| 1411 |
+
"acorn": "^8.15.0",
|
| 1412 |
+
"acorn-jsx": "^5.3.2",
|
| 1413 |
+
"eslint-visitor-keys": "^4.2.1"
|
| 1414 |
+
},
|
| 1415 |
+
"engines": {
|
| 1416 |
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
| 1417 |
+
},
|
| 1418 |
+
"funding": {
|
| 1419 |
+
"url": "https://opencollective.com/eslint"
|
| 1420 |
+
}
|
| 1421 |
+
},
|
| 1422 |
+
"node_modules/esquery": {
|
| 1423 |
+
"version": "1.6.0",
|
| 1424 |
+
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
| 1425 |
+
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
|
| 1426 |
+
"dev": true,
|
| 1427 |
+
"license": "BSD-3-Clause",
|
| 1428 |
+
"dependencies": {
|
| 1429 |
+
"estraverse": "^5.1.0"
|
| 1430 |
+
},
|
| 1431 |
+
"engines": {
|
| 1432 |
+
"node": ">=0.10"
|
| 1433 |
+
}
|
| 1434 |
+
},
|
| 1435 |
+
"node_modules/esrecurse": {
|
| 1436 |
+
"version": "4.3.0",
|
| 1437 |
+
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
| 1438 |
+
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
| 1439 |
+
"dev": true,
|
| 1440 |
+
"license": "BSD-2-Clause",
|
| 1441 |
+
"dependencies": {
|
| 1442 |
+
"estraverse": "^5.2.0"
|
| 1443 |
+
},
|
| 1444 |
+
"engines": {
|
| 1445 |
+
"node": ">=4.0"
|
| 1446 |
+
}
|
| 1447 |
+
},
|
| 1448 |
+
"node_modules/estraverse": {
|
| 1449 |
+
"version": "5.3.0",
|
| 1450 |
+
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
| 1451 |
+
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
| 1452 |
+
"dev": true,
|
| 1453 |
+
"license": "BSD-2-Clause",
|
| 1454 |
+
"engines": {
|
| 1455 |
+
"node": ">=4.0"
|
| 1456 |
+
}
|
| 1457 |
+
},
|
| 1458 |
+
"node_modules/esutils": {
|
| 1459 |
+
"version": "2.0.3",
|
| 1460 |
+
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
| 1461 |
+
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
| 1462 |
+
"dev": true,
|
| 1463 |
+
"license": "BSD-2-Clause",
|
| 1464 |
+
"engines": {
|
| 1465 |
+
"node": ">=0.10.0"
|
| 1466 |
+
}
|
| 1467 |
+
},
|
| 1468 |
+
"node_modules/fast-deep-equal": {
|
| 1469 |
+
"version": "3.1.3",
|
| 1470 |
+
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
| 1471 |
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
| 1472 |
+
"dev": true,
|
| 1473 |
+
"license": "MIT"
|
| 1474 |
+
},
|
| 1475 |
+
"node_modules/fast-json-stable-stringify": {
|
| 1476 |
+
"version": "2.1.0",
|
| 1477 |
+
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
| 1478 |
+
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
| 1479 |
+
"dev": true,
|
| 1480 |
+
"license": "MIT"
|
| 1481 |
+
},
|
| 1482 |
+
"node_modules/fast-levenshtein": {
|
| 1483 |
+
"version": "2.0.6",
|
| 1484 |
+
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
| 1485 |
+
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
| 1486 |
+
"dev": true,
|
| 1487 |
+
"license": "MIT"
|
| 1488 |
+
},
|
| 1489 |
+
"node_modules/fdir": {
|
| 1490 |
+
"version": "6.5.0",
|
| 1491 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1492 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1493 |
+
"dev": true,
|
| 1494 |
+
"license": "MIT",
|
| 1495 |
+
"engines": {
|
| 1496 |
+
"node": ">=12.0.0"
|
| 1497 |
+
},
|
| 1498 |
+
"peerDependencies": {
|
| 1499 |
+
"picomatch": "^3 || ^4"
|
| 1500 |
+
},
|
| 1501 |
+
"peerDependenciesMeta": {
|
| 1502 |
+
"picomatch": {
|
| 1503 |
+
"optional": true
|
| 1504 |
+
}
|
| 1505 |
+
}
|
| 1506 |
+
},
|
| 1507 |
+
"node_modules/file-entry-cache": {
|
| 1508 |
+
"version": "8.0.0",
|
| 1509 |
+
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
| 1510 |
+
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
|
| 1511 |
+
"dev": true,
|
| 1512 |
+
"license": "MIT",
|
| 1513 |
+
"dependencies": {
|
| 1514 |
+
"flat-cache": "^4.0.0"
|
| 1515 |
+
},
|
| 1516 |
+
"engines": {
|
| 1517 |
+
"node": ">=16.0.0"
|
| 1518 |
+
}
|
| 1519 |
+
},
|
| 1520 |
+
"node_modules/find-up": {
|
| 1521 |
+
"version": "5.0.0",
|
| 1522 |
+
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
| 1523 |
+
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
|
| 1524 |
+
"dev": true,
|
| 1525 |
+
"license": "MIT",
|
| 1526 |
+
"dependencies": {
|
| 1527 |
+
"locate-path": "^6.0.0",
|
| 1528 |
+
"path-exists": "^4.0.0"
|
| 1529 |
+
},
|
| 1530 |
+
"engines": {
|
| 1531 |
+
"node": ">=10"
|
| 1532 |
+
},
|
| 1533 |
+
"funding": {
|
| 1534 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1535 |
+
}
|
| 1536 |
+
},
|
| 1537 |
+
"node_modules/flat-cache": {
|
| 1538 |
+
"version": "4.0.1",
|
| 1539 |
+
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
|
| 1540 |
+
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
|
| 1541 |
+
"dev": true,
|
| 1542 |
+
"license": "MIT",
|
| 1543 |
+
"dependencies": {
|
| 1544 |
+
"flatted": "^3.2.9",
|
| 1545 |
+
"keyv": "^4.5.4"
|
| 1546 |
+
},
|
| 1547 |
+
"engines": {
|
| 1548 |
+
"node": ">=16"
|
| 1549 |
+
}
|
| 1550 |
+
},
|
| 1551 |
+
"node_modules/flatted": {
|
| 1552 |
+
"version": "3.3.3",
|
| 1553 |
+
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
| 1554 |
+
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
| 1555 |
+
"dev": true,
|
| 1556 |
+
"license": "ISC"
|
| 1557 |
+
},
|
| 1558 |
+
"node_modules/fsevents": {
|
| 1559 |
+
"version": "2.3.3",
|
| 1560 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1561 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1562 |
+
"dev": true,
|
| 1563 |
+
"hasInstallScript": true,
|
| 1564 |
+
"license": "MIT",
|
| 1565 |
+
"optional": true,
|
| 1566 |
+
"os": [
|
| 1567 |
+
"darwin"
|
| 1568 |
+
],
|
| 1569 |
+
"engines": {
|
| 1570 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1571 |
+
}
|
| 1572 |
+
},
|
| 1573 |
+
"node_modules/gensync": {
|
| 1574 |
+
"version": "1.0.0-beta.2",
|
| 1575 |
+
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
| 1576 |
+
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
| 1577 |
+
"dev": true,
|
| 1578 |
+
"license": "MIT",
|
| 1579 |
+
"engines": {
|
| 1580 |
+
"node": ">=6.9.0"
|
| 1581 |
+
}
|
| 1582 |
+
},
|
| 1583 |
+
"node_modules/glob-parent": {
|
| 1584 |
+
"version": "6.0.2",
|
| 1585 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
| 1586 |
+
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
| 1587 |
+
"dev": true,
|
| 1588 |
+
"license": "ISC",
|
| 1589 |
+
"dependencies": {
|
| 1590 |
+
"is-glob": "^4.0.3"
|
| 1591 |
+
},
|
| 1592 |
+
"engines": {
|
| 1593 |
+
"node": ">=10.13.0"
|
| 1594 |
+
}
|
| 1595 |
+
},
|
| 1596 |
+
"node_modules/globals": {
|
| 1597 |
+
"version": "16.5.0",
|
| 1598 |
+
"resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
|
| 1599 |
+
"integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
|
| 1600 |
+
"dev": true,
|
| 1601 |
+
"license": "MIT",
|
| 1602 |
+
"engines": {
|
| 1603 |
+
"node": ">=18"
|
| 1604 |
+
},
|
| 1605 |
+
"funding": {
|
| 1606 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1607 |
+
}
|
| 1608 |
+
},
|
| 1609 |
+
"node_modules/has-flag": {
|
| 1610 |
+
"version": "4.0.0",
|
| 1611 |
+
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
| 1612 |
+
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
| 1613 |
+
"dev": true,
|
| 1614 |
+
"license": "MIT",
|
| 1615 |
+
"engines": {
|
| 1616 |
+
"node": ">=8"
|
| 1617 |
+
}
|
| 1618 |
+
},
|
| 1619 |
+
"node_modules/hermes-estree": {
|
| 1620 |
+
"version": "0.25.1",
|
| 1621 |
+
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
| 1622 |
+
"integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
|
| 1623 |
+
"dev": true,
|
| 1624 |
+
"license": "MIT"
|
| 1625 |
+
},
|
| 1626 |
+
"node_modules/hermes-parser": {
|
| 1627 |
+
"version": "0.25.1",
|
| 1628 |
+
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
|
| 1629 |
+
"integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
|
| 1630 |
+
"dev": true,
|
| 1631 |
+
"license": "MIT",
|
| 1632 |
+
"dependencies": {
|
| 1633 |
+
"hermes-estree": "0.25.1"
|
| 1634 |
+
}
|
| 1635 |
+
},
|
| 1636 |
+
"node_modules/ignore": {
|
| 1637 |
+
"version": "5.3.2",
|
| 1638 |
+
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
| 1639 |
+
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
| 1640 |
+
"dev": true,
|
| 1641 |
+
"license": "MIT",
|
| 1642 |
+
"engines": {
|
| 1643 |
+
"node": ">= 4"
|
| 1644 |
+
}
|
| 1645 |
+
},
|
| 1646 |
+
"node_modules/import-fresh": {
|
| 1647 |
+
"version": "3.3.1",
|
| 1648 |
+
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
| 1649 |
+
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
| 1650 |
+
"dev": true,
|
| 1651 |
+
"license": "MIT",
|
| 1652 |
+
"dependencies": {
|
| 1653 |
+
"parent-module": "^1.0.0",
|
| 1654 |
+
"resolve-from": "^4.0.0"
|
| 1655 |
+
},
|
| 1656 |
+
"engines": {
|
| 1657 |
+
"node": ">=6"
|
| 1658 |
+
},
|
| 1659 |
+
"funding": {
|
| 1660 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1661 |
+
}
|
| 1662 |
+
},
|
| 1663 |
+
"node_modules/imurmurhash": {
|
| 1664 |
+
"version": "0.1.4",
|
| 1665 |
+
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
| 1666 |
+
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
|
| 1667 |
+
"dev": true,
|
| 1668 |
+
"license": "MIT",
|
| 1669 |
+
"engines": {
|
| 1670 |
+
"node": ">=0.8.19"
|
| 1671 |
+
}
|
| 1672 |
+
},
|
| 1673 |
+
"node_modules/is-extglob": {
|
| 1674 |
+
"version": "2.1.1",
|
| 1675 |
+
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
| 1676 |
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
| 1677 |
+
"dev": true,
|
| 1678 |
+
"license": "MIT",
|
| 1679 |
+
"engines": {
|
| 1680 |
+
"node": ">=0.10.0"
|
| 1681 |
+
}
|
| 1682 |
+
},
|
| 1683 |
+
"node_modules/is-glob": {
|
| 1684 |
+
"version": "4.0.3",
|
| 1685 |
+
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
| 1686 |
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
| 1687 |
+
"dev": true,
|
| 1688 |
+
"license": "MIT",
|
| 1689 |
+
"dependencies": {
|
| 1690 |
+
"is-extglob": "^2.1.1"
|
| 1691 |
+
},
|
| 1692 |
+
"engines": {
|
| 1693 |
+
"node": ">=0.10.0"
|
| 1694 |
+
}
|
| 1695 |
+
},
|
| 1696 |
+
"node_modules/isexe": {
|
| 1697 |
+
"version": "2.0.0",
|
| 1698 |
+
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
| 1699 |
+
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
| 1700 |
+
"dev": true,
|
| 1701 |
+
"license": "ISC"
|
| 1702 |
+
},
|
| 1703 |
+
"node_modules/js-tokens": {
|
| 1704 |
+
"version": "4.0.0",
|
| 1705 |
+
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 1706 |
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
| 1707 |
+
"dev": true,
|
| 1708 |
+
"license": "MIT"
|
| 1709 |
+
},
|
| 1710 |
+
"node_modules/js-yaml": {
|
| 1711 |
+
"version": "4.1.1",
|
| 1712 |
+
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
| 1713 |
+
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
| 1714 |
+
"dev": true,
|
| 1715 |
+
"license": "MIT",
|
| 1716 |
+
"dependencies": {
|
| 1717 |
+
"argparse": "^2.0.1"
|
| 1718 |
+
},
|
| 1719 |
+
"bin": {
|
| 1720 |
+
"js-yaml": "bin/js-yaml.js"
|
| 1721 |
+
}
|
| 1722 |
+
},
|
| 1723 |
+
"node_modules/jsesc": {
|
| 1724 |
+
"version": "3.1.0",
|
| 1725 |
+
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
| 1726 |
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
| 1727 |
+
"dev": true,
|
| 1728 |
+
"license": "MIT",
|
| 1729 |
+
"bin": {
|
| 1730 |
+
"jsesc": "bin/jsesc"
|
| 1731 |
+
},
|
| 1732 |
+
"engines": {
|
| 1733 |
+
"node": ">=6"
|
| 1734 |
+
}
|
| 1735 |
+
},
|
| 1736 |
+
"node_modules/json-buffer": {
|
| 1737 |
+
"version": "3.0.1",
|
| 1738 |
+
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
| 1739 |
+
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
| 1740 |
+
"dev": true,
|
| 1741 |
+
"license": "MIT"
|
| 1742 |
+
},
|
| 1743 |
+
"node_modules/json-schema-traverse": {
|
| 1744 |
+
"version": "0.4.1",
|
| 1745 |
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
| 1746 |
+
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
| 1747 |
+
"dev": true,
|
| 1748 |
+
"license": "MIT"
|
| 1749 |
+
},
|
| 1750 |
+
"node_modules/json-stable-stringify-without-jsonify": {
|
| 1751 |
+
"version": "1.0.1",
|
| 1752 |
+
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
| 1753 |
+
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
| 1754 |
+
"dev": true,
|
| 1755 |
+
"license": "MIT"
|
| 1756 |
+
},
|
| 1757 |
+
"node_modules/json5": {
|
| 1758 |
+
"version": "2.2.3",
|
| 1759 |
+
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
| 1760 |
+
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
| 1761 |
+
"dev": true,
|
| 1762 |
+
"license": "MIT",
|
| 1763 |
+
"bin": {
|
| 1764 |
+
"json5": "lib/cli.js"
|
| 1765 |
+
},
|
| 1766 |
+
"engines": {
|
| 1767 |
+
"node": ">=6"
|
| 1768 |
+
}
|
| 1769 |
+
},
|
| 1770 |
+
"node_modules/keyv": {
|
| 1771 |
+
"version": "4.5.4",
|
| 1772 |
+
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
| 1773 |
+
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
| 1774 |
+
"dev": true,
|
| 1775 |
+
"license": "MIT",
|
| 1776 |
+
"dependencies": {
|
| 1777 |
+
"json-buffer": "3.0.1"
|
| 1778 |
+
}
|
| 1779 |
+
},
|
| 1780 |
+
"node_modules/levn": {
|
| 1781 |
+
"version": "0.4.1",
|
| 1782 |
+
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
| 1783 |
+
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
|
| 1784 |
+
"dev": true,
|
| 1785 |
+
"license": "MIT",
|
| 1786 |
+
"dependencies": {
|
| 1787 |
+
"prelude-ls": "^1.2.1",
|
| 1788 |
+
"type-check": "~0.4.0"
|
| 1789 |
+
},
|
| 1790 |
+
"engines": {
|
| 1791 |
+
"node": ">= 0.8.0"
|
| 1792 |
+
}
|
| 1793 |
+
},
|
| 1794 |
+
"node_modules/lightningcss": {
|
| 1795 |
+
"version": "1.30.2",
|
| 1796 |
+
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
| 1797 |
+
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
| 1798 |
+
"dev": true,
|
| 1799 |
+
"license": "MPL-2.0",
|
| 1800 |
+
"dependencies": {
|
| 1801 |
+
"detect-libc": "^2.0.3"
|
| 1802 |
+
},
|
| 1803 |
+
"engines": {
|
| 1804 |
+
"node": ">= 12.0.0"
|
| 1805 |
+
},
|
| 1806 |
+
"funding": {
|
| 1807 |
+
"type": "opencollective",
|
| 1808 |
+
"url": "https://opencollective.com/parcel"
|
| 1809 |
+
},
|
| 1810 |
+
"optionalDependencies": {
|
| 1811 |
+
"lightningcss-android-arm64": "1.30.2",
|
| 1812 |
+
"lightningcss-darwin-arm64": "1.30.2",
|
| 1813 |
+
"lightningcss-darwin-x64": "1.30.2",
|
| 1814 |
+
"lightningcss-freebsd-x64": "1.30.2",
|
| 1815 |
+
"lightningcss-linux-arm-gnueabihf": "1.30.2",
|
| 1816 |
+
"lightningcss-linux-arm64-gnu": "1.30.2",
|
| 1817 |
+
"lightningcss-linux-arm64-musl": "1.30.2",
|
| 1818 |
+
"lightningcss-linux-x64-gnu": "1.30.2",
|
| 1819 |
+
"lightningcss-linux-x64-musl": "1.30.2",
|
| 1820 |
+
"lightningcss-win32-arm64-msvc": "1.30.2",
|
| 1821 |
+
"lightningcss-win32-x64-msvc": "1.30.2"
|
| 1822 |
+
}
|
| 1823 |
+
},
|
| 1824 |
+
"node_modules/lightningcss-android-arm64": {
|
| 1825 |
+
"version": "1.30.2",
|
| 1826 |
+
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
|
| 1827 |
+
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
|
| 1828 |
+
"cpu": [
|
| 1829 |
+
"arm64"
|
| 1830 |
+
],
|
| 1831 |
+
"dev": true,
|
| 1832 |
+
"license": "MPL-2.0",
|
| 1833 |
+
"optional": true,
|
| 1834 |
+
"os": [
|
| 1835 |
+
"android"
|
| 1836 |
+
],
|
| 1837 |
+
"engines": {
|
| 1838 |
+
"node": ">= 12.0.0"
|
| 1839 |
+
},
|
| 1840 |
+
"funding": {
|
| 1841 |
+
"type": "opencollective",
|
| 1842 |
+
"url": "https://opencollective.com/parcel"
|
| 1843 |
+
}
|
| 1844 |
+
},
|
| 1845 |
+
"node_modules/lightningcss-darwin-arm64": {
|
| 1846 |
+
"version": "1.30.2",
|
| 1847 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
|
| 1848 |
+
"integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
|
| 1849 |
+
"cpu": [
|
| 1850 |
+
"arm64"
|
| 1851 |
+
],
|
| 1852 |
+
"dev": true,
|
| 1853 |
+
"license": "MPL-2.0",
|
| 1854 |
+
"optional": true,
|
| 1855 |
+
"os": [
|
| 1856 |
+
"darwin"
|
| 1857 |
+
],
|
| 1858 |
+
"engines": {
|
| 1859 |
+
"node": ">= 12.0.0"
|
| 1860 |
+
},
|
| 1861 |
+
"funding": {
|
| 1862 |
+
"type": "opencollective",
|
| 1863 |
+
"url": "https://opencollective.com/parcel"
|
| 1864 |
+
}
|
| 1865 |
+
},
|
| 1866 |
+
"node_modules/lightningcss-darwin-x64": {
|
| 1867 |
+
"version": "1.30.2",
|
| 1868 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
|
| 1869 |
+
"integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
|
| 1870 |
+
"cpu": [
|
| 1871 |
+
"x64"
|
| 1872 |
+
],
|
| 1873 |
+
"dev": true,
|
| 1874 |
+
"license": "MPL-2.0",
|
| 1875 |
+
"optional": true,
|
| 1876 |
+
"os": [
|
| 1877 |
+
"darwin"
|
| 1878 |
+
],
|
| 1879 |
+
"engines": {
|
| 1880 |
+
"node": ">= 12.0.0"
|
| 1881 |
+
},
|
| 1882 |
+
"funding": {
|
| 1883 |
+
"type": "opencollective",
|
| 1884 |
+
"url": "https://opencollective.com/parcel"
|
| 1885 |
+
}
|
| 1886 |
+
},
|
| 1887 |
+
"node_modules/lightningcss-freebsd-x64": {
|
| 1888 |
+
"version": "1.30.2",
|
| 1889 |
+
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
|
| 1890 |
+
"integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
|
| 1891 |
+
"cpu": [
|
| 1892 |
+
"x64"
|
| 1893 |
+
],
|
| 1894 |
+
"dev": true,
|
| 1895 |
+
"license": "MPL-2.0",
|
| 1896 |
+
"optional": true,
|
| 1897 |
+
"os": [
|
| 1898 |
+
"freebsd"
|
| 1899 |
+
],
|
| 1900 |
+
"engines": {
|
| 1901 |
+
"node": ">= 12.0.0"
|
| 1902 |
+
},
|
| 1903 |
+
"funding": {
|
| 1904 |
+
"type": "opencollective",
|
| 1905 |
+
"url": "https://opencollective.com/parcel"
|
| 1906 |
+
}
|
| 1907 |
+
},
|
| 1908 |
+
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
| 1909 |
+
"version": "1.30.2",
|
| 1910 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
|
| 1911 |
+
"integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
|
| 1912 |
+
"cpu": [
|
| 1913 |
+
"arm"
|
| 1914 |
+
],
|
| 1915 |
+
"dev": true,
|
| 1916 |
+
"license": "MPL-2.0",
|
| 1917 |
+
"optional": true,
|
| 1918 |
+
"os": [
|
| 1919 |
+
"linux"
|
| 1920 |
+
],
|
| 1921 |
+
"engines": {
|
| 1922 |
+
"node": ">= 12.0.0"
|
| 1923 |
+
},
|
| 1924 |
+
"funding": {
|
| 1925 |
+
"type": "opencollective",
|
| 1926 |
+
"url": "https://opencollective.com/parcel"
|
| 1927 |
+
}
|
| 1928 |
+
},
|
| 1929 |
+
"node_modules/lightningcss-linux-arm64-gnu": {
|
| 1930 |
+
"version": "1.30.2",
|
| 1931 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
|
| 1932 |
+
"integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
|
| 1933 |
+
"cpu": [
|
| 1934 |
+
"arm64"
|
| 1935 |
+
],
|
| 1936 |
+
"dev": true,
|
| 1937 |
+
"license": "MPL-2.0",
|
| 1938 |
+
"optional": true,
|
| 1939 |
+
"os": [
|
| 1940 |
+
"linux"
|
| 1941 |
+
],
|
| 1942 |
+
"engines": {
|
| 1943 |
+
"node": ">= 12.0.0"
|
| 1944 |
+
},
|
| 1945 |
+
"funding": {
|
| 1946 |
+
"type": "opencollective",
|
| 1947 |
+
"url": "https://opencollective.com/parcel"
|
| 1948 |
+
}
|
| 1949 |
+
},
|
| 1950 |
+
"node_modules/lightningcss-linux-arm64-musl": {
|
| 1951 |
+
"version": "1.30.2",
|
| 1952 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
|
| 1953 |
+
"integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
|
| 1954 |
+
"cpu": [
|
| 1955 |
+
"arm64"
|
| 1956 |
+
],
|
| 1957 |
+
"dev": true,
|
| 1958 |
+
"license": "MPL-2.0",
|
| 1959 |
+
"optional": true,
|
| 1960 |
+
"os": [
|
| 1961 |
+
"linux"
|
| 1962 |
+
],
|
| 1963 |
+
"engines": {
|
| 1964 |
+
"node": ">= 12.0.0"
|
| 1965 |
+
},
|
| 1966 |
+
"funding": {
|
| 1967 |
+
"type": "opencollective",
|
| 1968 |
+
"url": "https://opencollective.com/parcel"
|
| 1969 |
+
}
|
| 1970 |
+
},
|
| 1971 |
+
"node_modules/lightningcss-linux-x64-gnu": {
|
| 1972 |
+
"version": "1.30.2",
|
| 1973 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
| 1974 |
+
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
| 1975 |
+
"cpu": [
|
| 1976 |
+
"x64"
|
| 1977 |
+
],
|
| 1978 |
+
"dev": true,
|
| 1979 |
+
"license": "MPL-2.0",
|
| 1980 |
+
"optional": true,
|
| 1981 |
+
"os": [
|
| 1982 |
+
"linux"
|
| 1983 |
+
],
|
| 1984 |
+
"engines": {
|
| 1985 |
+
"node": ">= 12.0.0"
|
| 1986 |
+
},
|
| 1987 |
+
"funding": {
|
| 1988 |
+
"type": "opencollective",
|
| 1989 |
+
"url": "https://opencollective.com/parcel"
|
| 1990 |
+
}
|
| 1991 |
+
},
|
| 1992 |
+
"node_modules/lightningcss-linux-x64-musl": {
|
| 1993 |
+
"version": "1.30.2",
|
| 1994 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
|
| 1995 |
+
"integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
|
| 1996 |
+
"cpu": [
|
| 1997 |
+
"x64"
|
| 1998 |
+
],
|
| 1999 |
+
"dev": true,
|
| 2000 |
+
"license": "MPL-2.0",
|
| 2001 |
+
"optional": true,
|
| 2002 |
+
"os": [
|
| 2003 |
+
"linux"
|
| 2004 |
+
],
|
| 2005 |
+
"engines": {
|
| 2006 |
+
"node": ">= 12.0.0"
|
| 2007 |
+
},
|
| 2008 |
+
"funding": {
|
| 2009 |
+
"type": "opencollective",
|
| 2010 |
+
"url": "https://opencollective.com/parcel"
|
| 2011 |
+
}
|
| 2012 |
+
},
|
| 2013 |
+
"node_modules/lightningcss-win32-arm64-msvc": {
|
| 2014 |
+
"version": "1.30.2",
|
| 2015 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
|
| 2016 |
+
"integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
|
| 2017 |
+
"cpu": [
|
| 2018 |
+
"arm64"
|
| 2019 |
+
],
|
| 2020 |
+
"dev": true,
|
| 2021 |
+
"license": "MPL-2.0",
|
| 2022 |
+
"optional": true,
|
| 2023 |
+
"os": [
|
| 2024 |
+
"win32"
|
| 2025 |
+
],
|
| 2026 |
+
"engines": {
|
| 2027 |
+
"node": ">= 12.0.0"
|
| 2028 |
+
},
|
| 2029 |
+
"funding": {
|
| 2030 |
+
"type": "opencollective",
|
| 2031 |
+
"url": "https://opencollective.com/parcel"
|
| 2032 |
+
}
|
| 2033 |
+
},
|
| 2034 |
+
"node_modules/lightningcss-win32-x64-msvc": {
|
| 2035 |
+
"version": "1.30.2",
|
| 2036 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
|
| 2037 |
+
"integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
|
| 2038 |
+
"cpu": [
|
| 2039 |
+
"x64"
|
| 2040 |
+
],
|
| 2041 |
+
"dev": true,
|
| 2042 |
+
"license": "MPL-2.0",
|
| 2043 |
+
"optional": true,
|
| 2044 |
+
"os": [
|
| 2045 |
+
"win32"
|
| 2046 |
+
],
|
| 2047 |
+
"engines": {
|
| 2048 |
+
"node": ">= 12.0.0"
|
| 2049 |
+
},
|
| 2050 |
+
"funding": {
|
| 2051 |
+
"type": "opencollective",
|
| 2052 |
+
"url": "https://opencollective.com/parcel"
|
| 2053 |
+
}
|
| 2054 |
+
},
|
| 2055 |
+
"node_modules/locate-path": {
|
| 2056 |
+
"version": "6.0.0",
|
| 2057 |
+
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
| 2058 |
+
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
|
| 2059 |
+
"dev": true,
|
| 2060 |
+
"license": "MIT",
|
| 2061 |
+
"dependencies": {
|
| 2062 |
+
"p-locate": "^5.0.0"
|
| 2063 |
+
},
|
| 2064 |
+
"engines": {
|
| 2065 |
+
"node": ">=10"
|
| 2066 |
+
},
|
| 2067 |
+
"funding": {
|
| 2068 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2069 |
+
}
|
| 2070 |
+
},
|
| 2071 |
+
"node_modules/lodash.merge": {
|
| 2072 |
+
"version": "4.6.2",
|
| 2073 |
+
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
| 2074 |
+
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
| 2075 |
+
"dev": true,
|
| 2076 |
+
"license": "MIT"
|
| 2077 |
+
},
|
| 2078 |
+
"node_modules/lru-cache": {
|
| 2079 |
+
"version": "5.1.1",
|
| 2080 |
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
| 2081 |
+
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
| 2082 |
+
"dev": true,
|
| 2083 |
+
"license": "ISC",
|
| 2084 |
+
"dependencies": {
|
| 2085 |
+
"yallist": "^3.0.2"
|
| 2086 |
+
}
|
| 2087 |
+
},
|
| 2088 |
+
"node_modules/minimatch": {
|
| 2089 |
+
"version": "3.1.2",
|
| 2090 |
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
| 2091 |
+
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
| 2092 |
+
"dev": true,
|
| 2093 |
+
"license": "ISC",
|
| 2094 |
+
"dependencies": {
|
| 2095 |
+
"brace-expansion": "^1.1.7"
|
| 2096 |
+
},
|
| 2097 |
+
"engines": {
|
| 2098 |
+
"node": "*"
|
| 2099 |
+
}
|
| 2100 |
+
},
|
| 2101 |
+
"node_modules/ms": {
|
| 2102 |
+
"version": "2.1.3",
|
| 2103 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 2104 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 2105 |
+
"dev": true,
|
| 2106 |
+
"license": "MIT"
|
| 2107 |
+
},
|
| 2108 |
+
"node_modules/nanoid": {
|
| 2109 |
+
"version": "3.3.11",
|
| 2110 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 2111 |
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 2112 |
+
"dev": true,
|
| 2113 |
+
"funding": [
|
| 2114 |
+
{
|
| 2115 |
+
"type": "github",
|
| 2116 |
+
"url": "https://github.com/sponsors/ai"
|
| 2117 |
+
}
|
| 2118 |
+
],
|
| 2119 |
+
"license": "MIT",
|
| 2120 |
+
"bin": {
|
| 2121 |
+
"nanoid": "bin/nanoid.cjs"
|
| 2122 |
+
},
|
| 2123 |
+
"engines": {
|
| 2124 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 2125 |
+
}
|
| 2126 |
+
},
|
| 2127 |
+
"node_modules/natural-compare": {
|
| 2128 |
+
"version": "1.4.0",
|
| 2129 |
+
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
| 2130 |
+
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
| 2131 |
+
"dev": true,
|
| 2132 |
+
"license": "MIT"
|
| 2133 |
+
},
|
| 2134 |
+
"node_modules/node-releases": {
|
| 2135 |
+
"version": "2.0.27",
|
| 2136 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
| 2137 |
+
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
| 2138 |
+
"dev": true,
|
| 2139 |
+
"license": "MIT"
|
| 2140 |
+
},
|
| 2141 |
+
"node_modules/optionator": {
|
| 2142 |
+
"version": "0.9.4",
|
| 2143 |
+
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
| 2144 |
+
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
|
| 2145 |
+
"dev": true,
|
| 2146 |
+
"license": "MIT",
|
| 2147 |
+
"dependencies": {
|
| 2148 |
+
"deep-is": "^0.1.3",
|
| 2149 |
+
"fast-levenshtein": "^2.0.6",
|
| 2150 |
+
"levn": "^0.4.1",
|
| 2151 |
+
"prelude-ls": "^1.2.1",
|
| 2152 |
+
"type-check": "^0.4.0",
|
| 2153 |
+
"word-wrap": "^1.2.5"
|
| 2154 |
+
},
|
| 2155 |
+
"engines": {
|
| 2156 |
+
"node": ">= 0.8.0"
|
| 2157 |
+
}
|
| 2158 |
+
},
|
| 2159 |
+
"node_modules/p-limit": {
|
| 2160 |
+
"version": "3.1.0",
|
| 2161 |
+
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
| 2162 |
+
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
| 2163 |
+
"dev": true,
|
| 2164 |
+
"license": "MIT",
|
| 2165 |
+
"dependencies": {
|
| 2166 |
+
"yocto-queue": "^0.1.0"
|
| 2167 |
+
},
|
| 2168 |
+
"engines": {
|
| 2169 |
+
"node": ">=10"
|
| 2170 |
+
},
|
| 2171 |
+
"funding": {
|
| 2172 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2173 |
+
}
|
| 2174 |
+
},
|
| 2175 |
+
"node_modules/p-locate": {
|
| 2176 |
+
"version": "5.0.0",
|
| 2177 |
+
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
|
| 2178 |
+
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
|
| 2179 |
+
"dev": true,
|
| 2180 |
+
"license": "MIT",
|
| 2181 |
+
"dependencies": {
|
| 2182 |
+
"p-limit": "^3.0.2"
|
| 2183 |
+
},
|
| 2184 |
+
"engines": {
|
| 2185 |
+
"node": ">=10"
|
| 2186 |
+
},
|
| 2187 |
+
"funding": {
|
| 2188 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2189 |
+
}
|
| 2190 |
+
},
|
| 2191 |
+
"node_modules/parent-module": {
|
| 2192 |
+
"version": "1.0.1",
|
| 2193 |
+
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
| 2194 |
+
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
| 2195 |
+
"dev": true,
|
| 2196 |
+
"license": "MIT",
|
| 2197 |
+
"dependencies": {
|
| 2198 |
+
"callsites": "^3.0.0"
|
| 2199 |
+
},
|
| 2200 |
+
"engines": {
|
| 2201 |
+
"node": ">=6"
|
| 2202 |
+
}
|
| 2203 |
+
},
|
| 2204 |
+
"node_modules/path-exists": {
|
| 2205 |
+
"version": "4.0.0",
|
| 2206 |
+
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
| 2207 |
+
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
| 2208 |
+
"dev": true,
|
| 2209 |
+
"license": "MIT",
|
| 2210 |
+
"engines": {
|
| 2211 |
+
"node": ">=8"
|
| 2212 |
+
}
|
| 2213 |
+
},
|
| 2214 |
+
"node_modules/path-key": {
|
| 2215 |
+
"version": "3.1.1",
|
| 2216 |
+
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
| 2217 |
+
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
| 2218 |
+
"dev": true,
|
| 2219 |
+
"license": "MIT",
|
| 2220 |
+
"engines": {
|
| 2221 |
+
"node": ">=8"
|
| 2222 |
+
}
|
| 2223 |
+
},
|
| 2224 |
+
"node_modules/picocolors": {
|
| 2225 |
+
"version": "1.1.1",
|
| 2226 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 2227 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 2228 |
+
"dev": true,
|
| 2229 |
+
"license": "ISC"
|
| 2230 |
+
},
|
| 2231 |
+
"node_modules/picomatch": {
|
| 2232 |
+
"version": "4.0.3",
|
| 2233 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
| 2234 |
+
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
| 2235 |
+
"dev": true,
|
| 2236 |
+
"license": "MIT",
|
| 2237 |
+
"engines": {
|
| 2238 |
+
"node": ">=12"
|
| 2239 |
+
},
|
| 2240 |
+
"funding": {
|
| 2241 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 2242 |
+
}
|
| 2243 |
+
},
|
| 2244 |
+
"node_modules/postcss": {
|
| 2245 |
+
"version": "8.5.6",
|
| 2246 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
| 2247 |
+
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
| 2248 |
+
"dev": true,
|
| 2249 |
+
"funding": [
|
| 2250 |
+
{
|
| 2251 |
+
"type": "opencollective",
|
| 2252 |
+
"url": "https://opencollective.com/postcss/"
|
| 2253 |
+
},
|
| 2254 |
+
{
|
| 2255 |
+
"type": "tidelift",
|
| 2256 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 2257 |
+
},
|
| 2258 |
+
{
|
| 2259 |
+
"type": "github",
|
| 2260 |
+
"url": "https://github.com/sponsors/ai"
|
| 2261 |
+
}
|
| 2262 |
+
],
|
| 2263 |
+
"license": "MIT",
|
| 2264 |
+
"dependencies": {
|
| 2265 |
+
"nanoid": "^3.3.11",
|
| 2266 |
+
"picocolors": "^1.1.1",
|
| 2267 |
+
"source-map-js": "^1.2.1"
|
| 2268 |
+
},
|
| 2269 |
+
"engines": {
|
| 2270 |
+
"node": "^10 || ^12 || >=14"
|
| 2271 |
+
}
|
| 2272 |
+
},
|
| 2273 |
+
"node_modules/prelude-ls": {
|
| 2274 |
+
"version": "1.2.1",
|
| 2275 |
+
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
| 2276 |
+
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
| 2277 |
+
"dev": true,
|
| 2278 |
+
"license": "MIT",
|
| 2279 |
+
"engines": {
|
| 2280 |
+
"node": ">= 0.8.0"
|
| 2281 |
+
}
|
| 2282 |
+
},
|
| 2283 |
+
"node_modules/punycode": {
|
| 2284 |
+
"version": "2.3.1",
|
| 2285 |
+
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
| 2286 |
+
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
| 2287 |
+
"dev": true,
|
| 2288 |
+
"license": "MIT",
|
| 2289 |
+
"engines": {
|
| 2290 |
+
"node": ">=6"
|
| 2291 |
+
}
|
| 2292 |
+
},
|
| 2293 |
+
"node_modules/react": {
|
| 2294 |
+
"version": "19.2.0",
|
| 2295 |
+
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
| 2296 |
+
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
| 2297 |
+
"license": "MIT",
|
| 2298 |
+
"engines": {
|
| 2299 |
+
"node": ">=0.10.0"
|
| 2300 |
+
}
|
| 2301 |
+
},
|
| 2302 |
+
"node_modules/react-dom": {
|
| 2303 |
+
"version": "19.2.0",
|
| 2304 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
| 2305 |
+
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
| 2306 |
+
"license": "MIT",
|
| 2307 |
+
"dependencies": {
|
| 2308 |
+
"scheduler": "^0.27.0"
|
| 2309 |
+
},
|
| 2310 |
+
"peerDependencies": {
|
| 2311 |
+
"react": "^19.2.0"
|
| 2312 |
+
}
|
| 2313 |
+
},
|
| 2314 |
+
"node_modules/react-refresh": {
|
| 2315 |
+
"version": "0.18.0",
|
| 2316 |
+
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
| 2317 |
+
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
|
| 2318 |
+
"dev": true,
|
| 2319 |
+
"license": "MIT",
|
| 2320 |
+
"engines": {
|
| 2321 |
+
"node": ">=0.10.0"
|
| 2322 |
+
}
|
| 2323 |
+
},
|
| 2324 |
+
"node_modules/resolve-from": {
|
| 2325 |
+
"version": "4.0.0",
|
| 2326 |
+
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
| 2327 |
+
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
| 2328 |
+
"dev": true,
|
| 2329 |
+
"license": "MIT",
|
| 2330 |
+
"engines": {
|
| 2331 |
+
"node": ">=4"
|
| 2332 |
+
}
|
| 2333 |
+
},
|
| 2334 |
+
"node_modules/rolldown": {
|
| 2335 |
+
"version": "1.0.0-beta.50",
|
| 2336 |
+
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.50.tgz",
|
| 2337 |
+
"integrity": "sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==",
|
| 2338 |
+
"dev": true,
|
| 2339 |
+
"license": "MIT",
|
| 2340 |
+
"dependencies": {
|
| 2341 |
+
"@oxc-project/types": "=0.97.0",
|
| 2342 |
+
"@rolldown/pluginutils": "1.0.0-beta.50"
|
| 2343 |
+
},
|
| 2344 |
+
"bin": {
|
| 2345 |
+
"rolldown": "bin/cli.mjs"
|
| 2346 |
+
},
|
| 2347 |
+
"engines": {
|
| 2348 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 2349 |
+
},
|
| 2350 |
+
"optionalDependencies": {
|
| 2351 |
+
"@rolldown/binding-android-arm64": "1.0.0-beta.50",
|
| 2352 |
+
"@rolldown/binding-darwin-arm64": "1.0.0-beta.50",
|
| 2353 |
+
"@rolldown/binding-darwin-x64": "1.0.0-beta.50",
|
| 2354 |
+
"@rolldown/binding-freebsd-x64": "1.0.0-beta.50",
|
| 2355 |
+
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.50",
|
| 2356 |
+
"@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.50",
|
| 2357 |
+
"@rolldown/binding-linux-arm64-musl": "1.0.0-beta.50",
|
| 2358 |
+
"@rolldown/binding-linux-x64-gnu": "1.0.0-beta.50",
|
| 2359 |
+
"@rolldown/binding-linux-x64-musl": "1.0.0-beta.50",
|
| 2360 |
+
"@rolldown/binding-openharmony-arm64": "1.0.0-beta.50",
|
| 2361 |
+
"@rolldown/binding-wasm32-wasi": "1.0.0-beta.50",
|
| 2362 |
+
"@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.50",
|
| 2363 |
+
"@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.50",
|
| 2364 |
+
"@rolldown/binding-win32-x64-msvc": "1.0.0-beta.50"
|
| 2365 |
+
}
|
| 2366 |
+
},
|
| 2367 |
+
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
|
| 2368 |
+
"version": "1.0.0-beta.50",
|
| 2369 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz",
|
| 2370 |
+
"integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==",
|
| 2371 |
+
"dev": true,
|
| 2372 |
+
"license": "MIT"
|
| 2373 |
+
},
|
| 2374 |
+
"node_modules/scheduler": {
|
| 2375 |
+
"version": "0.27.0",
|
| 2376 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
| 2377 |
+
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
| 2378 |
+
"license": "MIT"
|
| 2379 |
+
},
|
| 2380 |
+
"node_modules/semver": {
|
| 2381 |
+
"version": "6.3.1",
|
| 2382 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
| 2383 |
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
| 2384 |
+
"dev": true,
|
| 2385 |
+
"license": "ISC",
|
| 2386 |
+
"bin": {
|
| 2387 |
+
"semver": "bin/semver.js"
|
| 2388 |
+
}
|
| 2389 |
+
},
|
| 2390 |
+
"node_modules/shebang-command": {
|
| 2391 |
+
"version": "2.0.0",
|
| 2392 |
+
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
| 2393 |
+
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
| 2394 |
+
"dev": true,
|
| 2395 |
+
"license": "MIT",
|
| 2396 |
+
"dependencies": {
|
| 2397 |
+
"shebang-regex": "^3.0.0"
|
| 2398 |
+
},
|
| 2399 |
+
"engines": {
|
| 2400 |
+
"node": ">=8"
|
| 2401 |
+
}
|
| 2402 |
+
},
|
| 2403 |
+
"node_modules/shebang-regex": {
|
| 2404 |
+
"version": "3.0.0",
|
| 2405 |
+
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
| 2406 |
+
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
| 2407 |
+
"dev": true,
|
| 2408 |
+
"license": "MIT",
|
| 2409 |
+
"engines": {
|
| 2410 |
+
"node": ">=8"
|
| 2411 |
+
}
|
| 2412 |
+
},
|
| 2413 |
+
"node_modules/source-map-js": {
|
| 2414 |
+
"version": "1.2.1",
|
| 2415 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 2416 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 2417 |
+
"dev": true,
|
| 2418 |
+
"license": "BSD-3-Clause",
|
| 2419 |
+
"engines": {
|
| 2420 |
+
"node": ">=0.10.0"
|
| 2421 |
+
}
|
| 2422 |
+
},
|
| 2423 |
+
"node_modules/strip-json-comments": {
|
| 2424 |
+
"version": "3.1.1",
|
| 2425 |
+
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
| 2426 |
+
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
| 2427 |
+
"dev": true,
|
| 2428 |
+
"license": "MIT",
|
| 2429 |
+
"engines": {
|
| 2430 |
+
"node": ">=8"
|
| 2431 |
+
},
|
| 2432 |
+
"funding": {
|
| 2433 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2434 |
+
}
|
| 2435 |
+
},
|
| 2436 |
+
"node_modules/supports-color": {
|
| 2437 |
+
"version": "7.2.0",
|
| 2438 |
+
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
| 2439 |
+
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
| 2440 |
+
"dev": true,
|
| 2441 |
+
"license": "MIT",
|
| 2442 |
+
"dependencies": {
|
| 2443 |
+
"has-flag": "^4.0.0"
|
| 2444 |
+
},
|
| 2445 |
+
"engines": {
|
| 2446 |
+
"node": ">=8"
|
| 2447 |
+
}
|
| 2448 |
+
},
|
| 2449 |
+
"node_modules/tinyglobby": {
|
| 2450 |
+
"version": "0.2.15",
|
| 2451 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
| 2452 |
+
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
| 2453 |
+
"dev": true,
|
| 2454 |
+
"license": "MIT",
|
| 2455 |
+
"dependencies": {
|
| 2456 |
+
"fdir": "^6.5.0",
|
| 2457 |
+
"picomatch": "^4.0.3"
|
| 2458 |
+
},
|
| 2459 |
+
"engines": {
|
| 2460 |
+
"node": ">=12.0.0"
|
| 2461 |
+
},
|
| 2462 |
+
"funding": {
|
| 2463 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 2464 |
+
}
|
| 2465 |
+
},
|
| 2466 |
+
"node_modules/tslib": {
|
| 2467 |
+
"version": "2.8.1",
|
| 2468 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2469 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 2470 |
+
"dev": true,
|
| 2471 |
+
"license": "0BSD",
|
| 2472 |
+
"optional": true
|
| 2473 |
+
},
|
| 2474 |
+
"node_modules/type-check": {
|
| 2475 |
+
"version": "0.4.0",
|
| 2476 |
+
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
| 2477 |
+
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
|
| 2478 |
+
"dev": true,
|
| 2479 |
+
"license": "MIT",
|
| 2480 |
+
"dependencies": {
|
| 2481 |
+
"prelude-ls": "^1.2.1"
|
| 2482 |
+
},
|
| 2483 |
+
"engines": {
|
| 2484 |
+
"node": ">= 0.8.0"
|
| 2485 |
+
}
|
| 2486 |
+
},
|
| 2487 |
+
"node_modules/update-browserslist-db": {
|
| 2488 |
+
"version": "1.1.4",
|
| 2489 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
|
| 2490 |
+
"integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
|
| 2491 |
+
"dev": true,
|
| 2492 |
+
"funding": [
|
| 2493 |
+
{
|
| 2494 |
+
"type": "opencollective",
|
| 2495 |
+
"url": "https://opencollective.com/browserslist"
|
| 2496 |
+
},
|
| 2497 |
+
{
|
| 2498 |
+
"type": "tidelift",
|
| 2499 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 2500 |
+
},
|
| 2501 |
+
{
|
| 2502 |
+
"type": "github",
|
| 2503 |
+
"url": "https://github.com/sponsors/ai"
|
| 2504 |
+
}
|
| 2505 |
+
],
|
| 2506 |
+
"license": "MIT",
|
| 2507 |
+
"dependencies": {
|
| 2508 |
+
"escalade": "^3.2.0",
|
| 2509 |
+
"picocolors": "^1.1.1"
|
| 2510 |
+
},
|
| 2511 |
+
"bin": {
|
| 2512 |
+
"update-browserslist-db": "cli.js"
|
| 2513 |
+
},
|
| 2514 |
+
"peerDependencies": {
|
| 2515 |
+
"browserslist": ">= 4.21.0"
|
| 2516 |
+
}
|
| 2517 |
+
},
|
| 2518 |
+
"node_modules/uri-js": {
|
| 2519 |
+
"version": "4.4.1",
|
| 2520 |
+
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
| 2521 |
+
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
| 2522 |
+
"dev": true,
|
| 2523 |
+
"license": "BSD-2-Clause",
|
| 2524 |
+
"dependencies": {
|
| 2525 |
+
"punycode": "^2.1.0"
|
| 2526 |
+
}
|
| 2527 |
+
},
|
| 2528 |
+
"node_modules/vite": {
|
| 2529 |
+
"name": "rolldown-vite",
|
| 2530 |
+
"version": "7.2.5",
|
| 2531 |
+
"resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.2.5.tgz",
|
| 2532 |
+
"integrity": "sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==",
|
| 2533 |
+
"dev": true,
|
| 2534 |
+
"license": "MIT",
|
| 2535 |
+
"dependencies": {
|
| 2536 |
+
"@oxc-project/runtime": "0.97.0",
|
| 2537 |
+
"fdir": "^6.5.0",
|
| 2538 |
+
"lightningcss": "^1.30.2",
|
| 2539 |
+
"picomatch": "^4.0.3",
|
| 2540 |
+
"postcss": "^8.5.6",
|
| 2541 |
+
"rolldown": "1.0.0-beta.50",
|
| 2542 |
+
"tinyglobby": "^0.2.15"
|
| 2543 |
+
},
|
| 2544 |
+
"bin": {
|
| 2545 |
+
"vite": "bin/vite.js"
|
| 2546 |
+
},
|
| 2547 |
+
"engines": {
|
| 2548 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 2549 |
+
},
|
| 2550 |
+
"funding": {
|
| 2551 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 2552 |
+
},
|
| 2553 |
+
"optionalDependencies": {
|
| 2554 |
+
"fsevents": "~2.3.3"
|
| 2555 |
+
},
|
| 2556 |
+
"peerDependencies": {
|
| 2557 |
+
"@types/node": "^20.19.0 || >=22.12.0",
|
| 2558 |
+
"esbuild": "^0.25.0",
|
| 2559 |
+
"jiti": ">=1.21.0",
|
| 2560 |
+
"less": "^4.0.0",
|
| 2561 |
+
"sass": "^1.70.0",
|
| 2562 |
+
"sass-embedded": "^1.70.0",
|
| 2563 |
+
"stylus": ">=0.54.8",
|
| 2564 |
+
"sugarss": "^5.0.0",
|
| 2565 |
+
"terser": "^5.16.0",
|
| 2566 |
+
"tsx": "^4.8.1",
|
| 2567 |
+
"yaml": "^2.4.2"
|
| 2568 |
+
},
|
| 2569 |
+
"peerDependenciesMeta": {
|
| 2570 |
+
"@types/node": {
|
| 2571 |
+
"optional": true
|
| 2572 |
+
},
|
| 2573 |
+
"esbuild": {
|
| 2574 |
+
"optional": true
|
| 2575 |
+
},
|
| 2576 |
+
"jiti": {
|
| 2577 |
+
"optional": true
|
| 2578 |
+
},
|
| 2579 |
+
"less": {
|
| 2580 |
+
"optional": true
|
| 2581 |
+
},
|
| 2582 |
+
"sass": {
|
| 2583 |
+
"optional": true
|
| 2584 |
+
},
|
| 2585 |
+
"sass-embedded": {
|
| 2586 |
+
"optional": true
|
| 2587 |
+
},
|
| 2588 |
+
"stylus": {
|
| 2589 |
+
"optional": true
|
| 2590 |
+
},
|
| 2591 |
+
"sugarss": {
|
| 2592 |
+
"optional": true
|
| 2593 |
+
},
|
| 2594 |
+
"terser": {
|
| 2595 |
+
"optional": true
|
| 2596 |
+
},
|
| 2597 |
+
"tsx": {
|
| 2598 |
+
"optional": true
|
| 2599 |
+
},
|
| 2600 |
+
"yaml": {
|
| 2601 |
+
"optional": true
|
| 2602 |
+
}
|
| 2603 |
+
}
|
| 2604 |
+
},
|
| 2605 |
+
"node_modules/which": {
|
| 2606 |
+
"version": "2.0.2",
|
| 2607 |
+
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
| 2608 |
+
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
| 2609 |
+
"dev": true,
|
| 2610 |
+
"license": "ISC",
|
| 2611 |
+
"dependencies": {
|
| 2612 |
+
"isexe": "^2.0.0"
|
| 2613 |
+
},
|
| 2614 |
+
"bin": {
|
| 2615 |
+
"node-which": "bin/node-which"
|
| 2616 |
+
},
|
| 2617 |
+
"engines": {
|
| 2618 |
+
"node": ">= 8"
|
| 2619 |
+
}
|
| 2620 |
+
},
|
| 2621 |
+
"node_modules/word-wrap": {
|
| 2622 |
+
"version": "1.2.5",
|
| 2623 |
+
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
| 2624 |
+
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
| 2625 |
+
"dev": true,
|
| 2626 |
+
"license": "MIT",
|
| 2627 |
+
"engines": {
|
| 2628 |
+
"node": ">=0.10.0"
|
| 2629 |
+
}
|
| 2630 |
+
},
|
| 2631 |
+
"node_modules/yallist": {
|
| 2632 |
+
"version": "3.1.1",
|
| 2633 |
+
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
| 2634 |
+
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 2635 |
+
"dev": true,
|
| 2636 |
+
"license": "ISC"
|
| 2637 |
+
},
|
| 2638 |
+
"node_modules/yocto-queue": {
|
| 2639 |
+
"version": "0.1.0",
|
| 2640 |
+
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
| 2641 |
+
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
| 2642 |
+
"dev": true,
|
| 2643 |
+
"license": "MIT",
|
| 2644 |
+
"engines": {
|
| 2645 |
+
"node": ">=10"
|
| 2646 |
+
},
|
| 2647 |
+
"funding": {
|
| 2648 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2649 |
+
}
|
| 2650 |
+
},
|
| 2651 |
+
"node_modules/zod": {
|
| 2652 |
+
"version": "4.1.12",
|
| 2653 |
+
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
|
| 2654 |
+
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
| 2655 |
+
"dev": true,
|
| 2656 |
+
"license": "MIT",
|
| 2657 |
+
"funding": {
|
| 2658 |
+
"url": "https://github.com/sponsors/colinhacks"
|
| 2659 |
+
}
|
| 2660 |
+
},
|
| 2661 |
+
"node_modules/zod-validation-error": {
|
| 2662 |
+
"version": "4.0.2",
|
| 2663 |
+
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
|
| 2664 |
+
"integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
|
| 2665 |
+
"dev": true,
|
| 2666 |
+
"license": "MIT",
|
| 2667 |
+
"engines": {
|
| 2668 |
+
"node": ">=18.0.0"
|
| 2669 |
+
},
|
| 2670 |
+
"peerDependencies": {
|
| 2671 |
+
"zod": "^3.25.0 || ^4.0.0"
|
| 2672 |
+
}
|
| 2673 |
+
}
|
| 2674 |
+
}
|
| 2675 |
+
}
|
frontend/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"lint": "eslint .",
|
| 10 |
+
"preview": "vite preview"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"react": "^19.2.0",
|
| 14 |
+
"react-dom": "^19.2.0"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@eslint/js": "^9.39.1",
|
| 18 |
+
"@types/react": "^19.2.5",
|
| 19 |
+
"@types/react-dom": "^19.2.3",
|
| 20 |
+
"@vitejs/plugin-react": "^5.1.1",
|
| 21 |
+
"eslint": "^9.39.1",
|
| 22 |
+
"eslint-plugin-react-hooks": "^7.0.1",
|
| 23 |
+
"eslint-plugin-react-refresh": "^0.4.24",
|
| 24 |
+
"globals": "^16.5.0",
|
| 25 |
+
"vite": "npm:rolldown-vite@7.2.5"
|
| 26 |
+
},
|
| 27 |
+
"overrides": {
|
| 28 |
+
"vite": "npm:rolldown-vite@7.2.5"
|
| 29 |
+
}
|
| 30 |
+
}
|
frontend/public/vite.svg
ADDED
|
|
frontend/src/App.css
ADDED
|
@@ -0,0 +1,885 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--bg-app: #0f172a;
|
| 3 |
+
--bg-sidebar: #1e293b;
|
| 4 |
+
--bg-panel: #1e293b;
|
| 5 |
+
--bg-input: #334155;
|
| 6 |
+
--border-color: #334155;
|
| 7 |
+
--primary: #3b82f6;
|
| 8 |
+
--primary-hover: #2563eb;
|
| 9 |
+
--text-main: #f8fafc;
|
| 10 |
+
--text-muted: #94a3b8;
|
| 11 |
+
--accent-gradient: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
| 12 |
+
|
| 13 |
+
--radius-lg: 12px;
|
| 14 |
+
--radius-md: 8px;
|
| 15 |
+
--radius-sm: 4px;
|
| 16 |
+
|
| 17 |
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
@keyframes glow-rotate {
|
| 21 |
+
0% {
|
| 22 |
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4),
|
| 23 |
+
0 0 25px rgba(59, 130, 246, 0.6),
|
| 24 |
+
0 0 50px rgba(59, 130, 246, 0.3),
|
| 25 |
+
-10px 0 30px rgba(139, 92, 246, 0.4),
|
| 26 |
+
var(--shadow-lg);
|
| 27 |
+
}
|
| 28 |
+
25% {
|
| 29 |
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4),
|
| 30 |
+
0 0 25px rgba(59, 130, 246, 0.6),
|
| 31 |
+
0 0 50px rgba(59, 130, 246, 0.3),
|
| 32 |
+
0 -10px 30px rgba(139, 92, 246, 0.4),
|
| 33 |
+
var(--shadow-lg);
|
| 34 |
+
}
|
| 35 |
+
50% {
|
| 36 |
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4),
|
| 37 |
+
0 0 25px rgba(59, 130, 246, 0.6),
|
| 38 |
+
0 0 50px rgba(59, 130, 246, 0.3),
|
| 39 |
+
10px 0 30px rgba(139, 92, 246, 0.4),
|
| 40 |
+
var(--shadow-lg);
|
| 41 |
+
}
|
| 42 |
+
75% {
|
| 43 |
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4),
|
| 44 |
+
0 0 25px rgba(59, 130, 246, 0.6),
|
| 45 |
+
0 0 50px rgba(59, 130, 246, 0.3),
|
| 46 |
+
0 10px 30px rgba(139, 92, 246, 0.4),
|
| 47 |
+
var(--shadow-lg);
|
| 48 |
+
}
|
| 49 |
+
100% {
|
| 50 |
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4),
|
| 51 |
+
0 0 25px rgba(59, 130, 246, 0.6),
|
| 52 |
+
0 0 50px rgba(59, 130, 246, 0.3),
|
| 53 |
+
-10px 0 30px rgba(139, 92, 246, 0.4),
|
| 54 |
+
var(--shadow-lg);
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
* {
|
| 59 |
+
box-sizing: border-box;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
body {
|
| 63 |
+
margin: 0;
|
| 64 |
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
| 65 |
+
background-color: var(--bg-app);
|
| 66 |
+
color: var(--text-main);
|
| 67 |
+
-webkit-font-smoothing: antialiased;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
#root {
|
| 71 |
+
height: 100vh;
|
| 72 |
+
display: flex;
|
| 73 |
+
overflow: hidden;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/* Layout */
|
| 77 |
+
.app-shell {
|
| 78 |
+
display: grid;
|
| 79 |
+
grid-template-columns: 280px 1fr;
|
| 80 |
+
width: 100%;
|
| 81 |
+
height: 100vh;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
/* Sidebar */
|
| 85 |
+
.sidebar {
|
| 86 |
+
background-color: var(--bg-sidebar);
|
| 87 |
+
border-right: 1px solid var(--border-color);
|
| 88 |
+
padding: 1.5rem;
|
| 89 |
+
display: flex;
|
| 90 |
+
flex-direction: column;
|
| 91 |
+
gap: 2rem;
|
| 92 |
+
overflow-y: auto;
|
| 93 |
+
height: 100%;
|
| 94 |
+
min-height: 0;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.brand {
|
| 98 |
+
display: flex;
|
| 99 |
+
align-items: center;
|
| 100 |
+
gap: 0.75rem;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.brand h1 {
|
| 104 |
+
font-size: 1.25rem;
|
| 105 |
+
font-weight: 700;
|
| 106 |
+
margin: 0;
|
| 107 |
+
background: var(--accent-gradient);
|
| 108 |
+
-webkit-background-clip: text;
|
| 109 |
+
-webkit-text-fill-color: transparent;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.sidebar-section h3 {
|
| 113 |
+
font-size: 0.75rem;
|
| 114 |
+
text-transform: uppercase;
|
| 115 |
+
letter-spacing: 0.05em;
|
| 116 |
+
color: var(--text-muted);
|
| 117 |
+
margin-bottom: 1rem;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
/* Dataset Controls */
|
| 121 |
+
.dataset-control {
|
| 122 |
+
display: flex;
|
| 123 |
+
flex-direction: column;
|
| 124 |
+
gap: 1rem;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.input-group {
|
| 128 |
+
display: flex;
|
| 129 |
+
flex-direction: column;
|
| 130 |
+
gap: 0.5rem;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.input-group label {
|
| 134 |
+
font-size: 0.875rem;
|
| 135 |
+
color: var(--text-muted);
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.input-group input {
|
| 139 |
+
background: var(--bg-app);
|
| 140 |
+
border: 1px solid var(--border-color);
|
| 141 |
+
color: var(--text-main);
|
| 142 |
+
padding: 0.5rem;
|
| 143 |
+
border-radius: var(--radius-md);
|
| 144 |
+
font-size: 0.875rem;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
.btn-secondary {
|
| 148 |
+
background: transparent;
|
| 149 |
+
border: 1px solid var(--border-color);
|
| 150 |
+
color: var(--text-main);
|
| 151 |
+
padding: 0.5rem;
|
| 152 |
+
border-radius: var(--radius-md);
|
| 153 |
+
cursor: pointer;
|
| 154 |
+
font-size: 0.875rem;
|
| 155 |
+
transition: all 0.2s;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.btn-secondary:hover:not(:disabled) {
|
| 159 |
+
background: var(--bg-input);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.btn-primary {
|
| 163 |
+
background: var(--primary);
|
| 164 |
+
border: none;
|
| 165 |
+
color: white;
|
| 166 |
+
padding: 0.5rem 1rem;
|
| 167 |
+
border-radius: var(--radius-md);
|
| 168 |
+
cursor: pointer;
|
| 169 |
+
font-weight: 500;
|
| 170 |
+
transition: background 0.2s;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.btn-primary:hover:not(:disabled) {
|
| 174 |
+
background: var(--primary-hover);
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.upload-label {
|
| 178 |
+
display: block;
|
| 179 |
+
text-align: center;
|
| 180 |
+
background: var(--bg-input);
|
| 181 |
+
border: 1px dashed var(--text-muted);
|
| 182 |
+
padding: 1rem;
|
| 183 |
+
border-radius: var(--radius-md);
|
| 184 |
+
cursor: pointer;
|
| 185 |
+
font-size: 0.875rem;
|
| 186 |
+
transition: all 0.2s;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
.upload-label:hover {
|
| 190 |
+
border-color: var(--primary);
|
| 191 |
+
color: var(--primary);
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.upload-label input {
|
| 195 |
+
display: none;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.dataset-list {
|
| 199 |
+
list-style: none;
|
| 200 |
+
padding: 0;
|
| 201 |
+
margin: 0;
|
| 202 |
+
display: flex;
|
| 203 |
+
flex-direction: column;
|
| 204 |
+
gap: 0.5rem;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.dataset-item {
|
| 208 |
+
display: flex;
|
| 209 |
+
justify-content: space-between;
|
| 210 |
+
align-items: center;
|
| 211 |
+
padding: 0.5rem;
|
| 212 |
+
background: var(--bg-app);
|
| 213 |
+
border-radius: var(--radius-md);
|
| 214 |
+
font-size: 0.8rem;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
.dataset-item strong {
|
| 218 |
+
color: var(--primary);
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
/* Main Content */
|
| 222 |
+
.main-content {
|
| 223 |
+
display: grid;
|
| 224 |
+
grid-template-columns: 1fr 400px;
|
| 225 |
+
overflow: hidden;
|
| 226 |
+
height: 100%;
|
| 227 |
+
min-height: 0;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
/* Chat Area */
|
| 231 |
+
.chat-section {
|
| 232 |
+
display: flex;
|
| 233 |
+
flex-direction: column;
|
| 234 |
+
height: 100%;
|
| 235 |
+
min-height: 0;
|
| 236 |
+
position: relative;
|
| 237 |
+
overflow: hidden;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.chat-header {
|
| 241 |
+
padding: 1rem 2rem;
|
| 242 |
+
border-bottom: 1px solid var(--border-color);
|
| 243 |
+
display: flex;
|
| 244 |
+
justify-content: space-between;
|
| 245 |
+
align-items: center;
|
| 246 |
+
background: rgba(15, 23, 42, 0.8);
|
| 247 |
+
backdrop-filter: blur(8px);
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
.status-indicator {
|
| 251 |
+
display: flex;
|
| 252 |
+
align-items: center;
|
| 253 |
+
gap: 0.5rem;
|
| 254 |
+
font-size: 0.875rem;
|
| 255 |
+
color: var(--text-muted);
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
.dot {
|
| 259 |
+
width: 8px;
|
| 260 |
+
height: 8px;
|
| 261 |
+
border-radius: 50%;
|
| 262 |
+
background-color: #22c55e;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.dot.busy {
|
| 266 |
+
background-color: #eab308;
|
| 267 |
+
animation: pulse 1.5s infinite;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
@keyframes pulse {
|
| 271 |
+
0% { opacity: 1; }
|
| 272 |
+
50% { opacity: 0.5; }
|
| 273 |
+
100% { opacity: 1; }
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
.messages-container {
|
| 277 |
+
flex: 1;
|
| 278 |
+
overflow-y: auto;
|
| 279 |
+
padding: 2rem;
|
| 280 |
+
display: flex;
|
| 281 |
+
flex-direction: column;
|
| 282 |
+
gap: 1.5rem;
|
| 283 |
+
min-height: 0;
|
| 284 |
+
scroll-behavior: smooth;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
.welcome-screen {
|
| 288 |
+
display: flex;
|
| 289 |
+
flex-direction: column;
|
| 290 |
+
align-items: center;
|
| 291 |
+
justify-content: center;
|
| 292 |
+
height: 100%;
|
| 293 |
+
text-align: center;
|
| 294 |
+
color: var(--text-muted);
|
| 295 |
+
max-width: 600px;
|
| 296 |
+
margin: 0 auto;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
.welcome-screen h2 {
|
| 300 |
+
color: var(--text-main);
|
| 301 |
+
font-size: 2rem;
|
| 302 |
+
margin-bottom: 1rem;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.quick-prompts {
|
| 306 |
+
display: grid;
|
| 307 |
+
grid-template-columns: repeat(2, 1fr);
|
| 308 |
+
gap: 1rem;
|
| 309 |
+
margin-top: 2rem;
|
| 310 |
+
width: 100%;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
.prompt-card {
|
| 314 |
+
background: var(--bg-panel);
|
| 315 |
+
border: 1px solid var(--border-color);
|
| 316 |
+
padding: 1rem;
|
| 317 |
+
border-radius: var(--radius-lg);
|
| 318 |
+
text-align: left;
|
| 319 |
+
cursor: pointer;
|
| 320 |
+
transition: all 0.2s;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
.prompt-card:hover {
|
| 324 |
+
border-color: var(--primary);
|
| 325 |
+
transform: translateY(-2px);
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
/* Messages */
|
| 329 |
+
.message {
|
| 330 |
+
display: flex;
|
| 331 |
+
gap: 1rem;
|
| 332 |
+
max-width: 800px;
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
.message.user {
|
| 336 |
+
flex-direction: row-reverse;
|
| 337 |
+
align-self: flex-end;
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
.avatar {
|
| 341 |
+
width: 32px;
|
| 342 |
+
height: 32px;
|
| 343 |
+
border-radius: 50%;
|
| 344 |
+
display: flex;
|
| 345 |
+
align-items: center;
|
| 346 |
+
justify-content: center;
|
| 347 |
+
font-size: 1.2rem;
|
| 348 |
+
flex-shrink: 0;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.message.agent .avatar {
|
| 352 |
+
background: var(--primary);
|
| 353 |
+
color: white;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
.message.user .avatar {
|
| 357 |
+
background: var(--text-muted);
|
| 358 |
+
color: var(--bg-app);
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
.message-content {
|
| 362 |
+
background: var(--bg-panel);
|
| 363 |
+
padding: 1rem 1.5rem;
|
| 364 |
+
border-radius: var(--radius-lg);
|
| 365 |
+
border-top-left-radius: 2px;
|
| 366 |
+
line-height: 1.6;
|
| 367 |
+
font-size: 0.95rem;
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
.message.user .message-content {
|
| 371 |
+
background: var(--primary);
|
| 372 |
+
color: white;
|
| 373 |
+
border-top-left-radius: var(--radius-lg);
|
| 374 |
+
border-top-right-radius: 2px;
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
.input-area {
|
| 378 |
+
padding: 2rem;
|
| 379 |
+
background: linear-gradient(to top, var(--bg-app) 80%, transparent);
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
.input-wrapper {
|
| 383 |
+
max-width: 1500px;
|
| 384 |
+
margin: 0 auto;
|
| 385 |
+
position: relative;
|
| 386 |
+
}
|
| 387 |
+
.input-shell {
|
| 388 |
+
display: flex;
|
| 389 |
+
align-items: flex-end;
|
| 390 |
+
gap: 1rem;
|
| 391 |
+
background: var(--bg-panel);
|
| 392 |
+
border: 2px solid var(--border-color);
|
| 393 |
+
padding: 1.5rem 1.5rem 1.5rem 2rem;
|
| 394 |
+
border-radius: 999px;
|
| 395 |
+
box-shadow: var(--shadow-lg);
|
| 396 |
+
transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
.input-shell:focus-within {
|
| 400 |
+
border-color: var(--primary);
|
| 401 |
+
animation: glow-rotate 3s ease-in-out infinite;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
.input-wrapper textarea {
|
| 405 |
+
flex: 1;
|
| 406 |
+
width: 100%;
|
| 407 |
+
background: transparent;
|
| 408 |
+
border: none;
|
| 409 |
+
padding: 0;
|
| 410 |
+
border-radius: 0;
|
| 411 |
+
color: var(--text-main);
|
| 412 |
+
font-size: 1.05rem;
|
| 413 |
+
resize: none;
|
| 414 |
+
min-height: 50px;
|
| 415 |
+
line-height: 1.6;
|
| 416 |
+
overflow: hidden;
|
| 417 |
+
transition: border-color 0.2s, box-shadow 0.2s;
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
.input-wrapper textarea:focus {
|
| 421 |
+
outline: none;
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
.send-btn {
|
| 425 |
+
flex-shrink: 0;
|
| 426 |
+
border: none;
|
| 427 |
+
background: linear-gradient(135deg, #60a5fa, #8b5cf6);
|
| 428 |
+
width: 80px;
|
| 429 |
+
height: 56px;
|
| 430 |
+
border-radius: 999px;
|
| 431 |
+
cursor: pointer;
|
| 432 |
+
display: flex;
|
| 433 |
+
align-items: center;
|
| 434 |
+
justify-content: center;
|
| 435 |
+
color: #fff;
|
| 436 |
+
box-shadow: 0 10px 25px rgba(99, 102, 241, 0.35);
|
| 437 |
+
transition: transform 0.2s, box-shadow 0.2s, opacity 0.2s;
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
.send-btn svg {
|
| 441 |
+
width: 26px;
|
| 442 |
+
height: 26px;
|
| 443 |
+
stroke: currentColor;
|
| 444 |
+
fill: none;
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
.send-btn:hover:not(:disabled) {
|
| 448 |
+
transform: translateX(2px) scale(1.02);
|
| 449 |
+
box-shadow: 0 12px 28px rgba(99, 102, 241, 0.5);
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
.send-btn:disabled {
|
| 453 |
+
opacity: 0.6;
|
| 454 |
+
cursor: not-allowed;
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
/* Results Panel */
|
| 458 |
+
.results-section {
|
| 459 |
+
background: var(--bg-sidebar);
|
| 460 |
+
border-left: 1px solid var(--border-color);
|
| 461 |
+
display: flex;
|
| 462 |
+
flex-direction: column;
|
| 463 |
+
height: 100%;
|
| 464 |
+
min-height: 0;
|
| 465 |
+
overflow: hidden;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
.results-header {
|
| 469 |
+
padding: 1rem;
|
| 470 |
+
border-bottom: 1px solid var(--border-color);
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
.results-header h2 {
|
| 474 |
+
margin: 0;
|
| 475 |
+
font-size: 1rem;
|
| 476 |
+
font-weight: 600;
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
.results-content {
|
| 480 |
+
flex: 1;
|
| 481 |
+
overflow-y: auto;
|
| 482 |
+
padding: 1rem;
|
| 483 |
+
display: flex;
|
| 484 |
+
flex-direction: column;
|
| 485 |
+
gap: 1rem;
|
| 486 |
+
min-height: 0;
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
.result-card {
|
| 490 |
+
background: var(--bg-app);
|
| 491 |
+
border: 1px solid var(--border-color);
|
| 492 |
+
border-radius: var(--radius-md);
|
| 493 |
+
padding: 1rem;
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
.card-header {
|
| 497 |
+
display: flex;
|
| 498 |
+
justify-content: space-between;
|
| 499 |
+
align-items: center;
|
| 500 |
+
margin-bottom: 0.75rem;
|
| 501 |
+
font-size: 0.8rem;
|
| 502 |
+
text-transform: uppercase;
|
| 503 |
+
letter-spacing: 0.05em;
|
| 504 |
+
color: var(--text-muted);
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
.badge {
|
| 508 |
+
background: rgba(59, 130, 246, 0.1);
|
| 509 |
+
color: var(--primary);
|
| 510 |
+
padding: 0.2rem 0.5rem;
|
| 511 |
+
border-radius: 4px;
|
| 512 |
+
font-size: 0.7rem;
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
.trend-grid {
|
| 516 |
+
display: grid;
|
| 517 |
+
grid-template-columns: repeat(3, 1fr);
|
| 518 |
+
gap: 0.5rem;
|
| 519 |
+
text-align: center;
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
.trend-grid div {
|
| 523 |
+
display: flex;
|
| 524 |
+
flex-direction: column;
|
| 525 |
+
gap: 0.25rem;
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
.trend-grid span {
|
| 529 |
+
font-size: 0.75rem;
|
| 530 |
+
color: var(--text-muted);
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
.trend-grid strong {
|
| 534 |
+
font-size: 1rem;
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
.anomaly-list {
|
| 538 |
+
list-style: none;
|
| 539 |
+
padding: 0;
|
| 540 |
+
margin: 0;
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
.anomaly-list li {
|
| 544 |
+
display: flex;
|
| 545 |
+
justify-content: space-between;
|
| 546 |
+
padding: 0.5rem 0;
|
| 547 |
+
border-bottom: 1px solid var(--border-color);
|
| 548 |
+
font-size: 0.875rem;
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
.anomaly-list li:last-child {
|
| 552 |
+
border-bottom: none;
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
code {
|
| 556 |
+
display: block;
|
| 557 |
+
background: #000;
|
| 558 |
+
padding: 0.75rem;
|
| 559 |
+
border-radius: var(--radius-sm);
|
| 560 |
+
font-family: 'Fira Code', monospace;
|
| 561 |
+
font-size: 0.8rem;
|
| 562 |
+
overflow-x: auto;
|
| 563 |
+
color: #a5b4fc;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
.viz-container img {
|
| 567 |
+
width: 100%;
|
| 568 |
+
border-radius: var(--radius-sm);
|
| 569 |
+
border: 1px solid var(--border-color);
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
.table-container {
|
| 573 |
+
overflow-x: auto;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
table {
|
| 577 |
+
width: 100%;
|
| 578 |
+
border-collapse: collapse;
|
| 579 |
+
font-size: 0.8rem;
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
th, td {
|
| 583 |
+
padding: 0.5rem;
|
| 584 |
+
text-align: left;
|
| 585 |
+
border-bottom: 1px solid var(--border-color);
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
th {
|
| 589 |
+
color: var(--text-muted);
|
| 590 |
+
font-weight: 500;
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
.download-btn {
|
| 594 |
+
display: block;
|
| 595 |
+
width: 100%;
|
| 596 |
+
text-align: center;
|
| 597 |
+
background: var(--bg-input);
|
| 598 |
+
color: var(--text-main);
|
| 599 |
+
text-decoration: none;
|
| 600 |
+
padding: 0.75rem;
|
| 601 |
+
border-radius: var(--radius-md);
|
| 602 |
+
font-size: 0.875rem;
|
| 603 |
+
transition: background 0.2s;
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
.download-btn:hover {
|
| 607 |
+
background: var(--border-color);
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
.empty-state-results {
|
| 611 |
+
text-align: center;
|
| 612 |
+
color: var(--text-muted);
|
| 613 |
+
padding: 2rem;
|
| 614 |
+
font-size: 0.9rem;
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
/* Responsive */
|
| 618 |
+
@media (max-width: 1024px) {
|
| 619 |
+
.main-content {
|
| 620 |
+
grid-template-columns: 1fr;
|
| 621 |
+
}
|
| 622 |
+
.results-section {
|
| 623 |
+
display: none; /* Or make it a modal/drawer */
|
| 624 |
+
}
|
| 625 |
+
.results-section.active {
|
| 626 |
+
display: flex;
|
| 627 |
+
position: fixed;
|
| 628 |
+
inset: 0;
|
| 629 |
+
z-index: 50;
|
| 630 |
+
}
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
.viz-preview img {
|
| 634 |
+
width: 100%;
|
| 635 |
+
display: block;
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
.chart-summary {
|
| 639 |
+
margin-top: 0.75rem;
|
| 640 |
+
font-size: 0.9rem;
|
| 641 |
+
color: rgba(255, 255, 255, 0.78);
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
.report-chip {
|
| 645 |
+
display: inline-flex;
|
| 646 |
+
align-items: center;
|
| 647 |
+
gap: 0.4rem;
|
| 648 |
+
margin-top: 0.75rem;
|
| 649 |
+
padding: 0.4rem 0.9rem;
|
| 650 |
+
border-radius: 999px;
|
| 651 |
+
text-decoration: none;
|
| 652 |
+
color: #1b1d34;
|
| 653 |
+
background: #9ef4c5;
|
| 654 |
+
font-weight: 600;
|
| 655 |
+
}
|
| 656 |
+
|
| 657 |
+
.diagnostic-chips {
|
| 658 |
+
display: flex;
|
| 659 |
+
flex-wrap: wrap;
|
| 660 |
+
gap: 0.4rem;
|
| 661 |
+
margin-top: 0.6rem;
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
.diagnostic-chip {
|
| 665 |
+
font-size: 0.75rem;
|
| 666 |
+
border-radius: 999px;
|
| 667 |
+
padding: 0.25rem 0.7rem;
|
| 668 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 669 |
+
background: rgba(255, 255, 255, 0.08);
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
+
.input-area {
|
| 673 |
+
margin-top: 1.5rem;
|
| 674 |
+
display: flex;
|
| 675 |
+
gap: 0.75rem;
|
| 676 |
+
}
|
| 677 |
+
|
| 678 |
+
.input-area input {
|
| 679 |
+
flex: 1;
|
| 680 |
+
border-radius: 999px;
|
| 681 |
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
| 682 |
+
background: rgba(255, 255, 255, 0.04);
|
| 683 |
+
color: inherit;
|
| 684 |
+
padding: 0.95rem 1.4rem;
|
| 685 |
+
font-size: 1.05rem;
|
| 686 |
+
}
|
| 687 |
+
|
| 688 |
+
.input-area input:focus {
|
| 689 |
+
outline: none;
|
| 690 |
+
border-color: rgba(122, 131, 255, 0.6);
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
.input-area button {
|
| 694 |
+
border: none;
|
| 695 |
+
border-radius: 999px;
|
| 696 |
+
padding: 0.95rem 1.8rem;
|
| 697 |
+
background: linear-gradient(135deg, #7a83ff, #5cd4f4);
|
| 698 |
+
color: #05070f;
|
| 699 |
+
font-weight: 600;
|
| 700 |
+
cursor: pointer;
|
| 701 |
+
transition: transform 0.2s ease;
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
.input-area button:disabled {
|
| 705 |
+
opacity: 0.5;
|
| 706 |
+
cursor: not-allowed;
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
.input-area button:not(:disabled):hover {
|
| 710 |
+
transform: translateY(-1px);
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
.error-text {
|
| 714 |
+
margin-top: 0.75rem;
|
| 715 |
+
color: #ffb4b4;
|
| 716 |
+
font-size: 0.9rem;
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
.result-stack {
|
| 720 |
+
display: flex;
|
| 721 |
+
flex-direction: column;
|
| 722 |
+
gap: 1.25rem;
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
.insight-card {
|
| 726 |
+
background: rgba(158, 244, 197, 0.08);
|
| 727 |
+
border: 1px solid rgba(158, 244, 197, 0.4);
|
| 728 |
+
border-radius: 16px;
|
| 729 |
+
padding: 1rem 1.2rem;
|
| 730 |
+
color: #d4ffe7;
|
| 731 |
+
}
|
| 732 |
+
|
| 733 |
+
.meta-card {
|
| 734 |
+
border-radius: 16px;
|
| 735 |
+
padding: 1rem 1.2rem;
|
| 736 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 737 |
+
background: rgba(255, 255, 255, 0.03);
|
| 738 |
+
}
|
| 739 |
+
|
| 740 |
+
.trend-grid {
|
| 741 |
+
display: grid;
|
| 742 |
+
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
| 743 |
+
gap: 0.75rem;
|
| 744 |
+
margin-top: 0.75rem;
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
.trend-grid span {
|
| 748 |
+
display: block;
|
| 749 |
+
font-size: 0.75rem;
|
| 750 |
+
text-transform: uppercase;
|
| 751 |
+
letter-spacing: 0.08em;
|
| 752 |
+
color: rgba(255, 255, 255, 0.5);
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
.trend-grid strong {
|
| 756 |
+
display: block;
|
| 757 |
+
font-size: 1.1rem;
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
.anomaly-list {
|
| 761 |
+
list-style: none;
|
| 762 |
+
margin: 0.75rem 0 0;
|
| 763 |
+
padding: 0;
|
| 764 |
+
display: flex;
|
| 765 |
+
flex-direction: column;
|
| 766 |
+
gap: 0.35rem;
|
| 767 |
+
}
|
| 768 |
+
|
| 769 |
+
.anomaly-list li {
|
| 770 |
+
display: flex;
|
| 771 |
+
gap: 0.5rem;
|
| 772 |
+
flex-wrap: wrap;
|
| 773 |
+
font-size: 0.85rem;
|
| 774 |
+
color: rgba(255, 255, 255, 0.85);
|
| 775 |
+
}
|
| 776 |
+
|
| 777 |
+
.anomaly-list li strong {
|
| 778 |
+
color: #ffffff;
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
+
.meta-head {
|
| 782 |
+
display: flex;
|
| 783 |
+
justify-content: space-between;
|
| 784 |
+
font-size: 0.85rem;
|
| 785 |
+
color: rgba(255, 255, 255, 0.65);
|
| 786 |
+
text-transform: uppercase;
|
| 787 |
+
letter-spacing: 0.08em;
|
| 788 |
+
}
|
| 789 |
+
|
| 790 |
+
.badge {
|
| 791 |
+
padding: 0.1rem 0.6rem;
|
| 792 |
+
border-radius: 999px;
|
| 793 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
.meta-card code {
|
| 797 |
+
display: block;
|
| 798 |
+
margin-top: 0.5rem;
|
| 799 |
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
| 800 |
+
color: #9bd0ff;
|
| 801 |
+
white-space: pre-wrap;
|
| 802 |
+
}
|
| 803 |
+
|
| 804 |
+
.data-preview {
|
| 805 |
+
border-radius: 18px;
|
| 806 |
+
border: 1px solid rgba(255, 255, 255, 0.09);
|
| 807 |
+
background: rgba(1, 2, 15, 0.4);
|
| 808 |
+
padding: 1rem;
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
.table-wrapper {
|
| 812 |
+
overflow-x: auto;
|
| 813 |
+
margin-top: 0.75rem;
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
table {
|
| 817 |
+
width: 100%;
|
| 818 |
+
border-collapse: collapse;
|
| 819 |
+
font-size: 0.9rem;
|
| 820 |
+
}
|
| 821 |
+
|
| 822 |
+
th,
|
| 823 |
+
td {
|
| 824 |
+
padding: 0.55rem 0.75rem;
|
| 825 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
| 826 |
+
text-align: left;
|
| 827 |
+
}
|
| 828 |
+
|
| 829 |
+
th {
|
| 830 |
+
font-weight: 600;
|
| 831 |
+
color: rgba(255, 255, 255, 0.7);
|
| 832 |
+
}
|
| 833 |
+
|
| 834 |
+
.viz-card {
|
| 835 |
+
border-radius: 18px;
|
| 836 |
+
border: 1px solid rgba(255, 255, 255, 0.09);
|
| 837 |
+
background: rgba(255, 255, 255, 0.03);
|
| 838 |
+
padding: 1rem;
|
| 839 |
+
}
|
| 840 |
+
|
| 841 |
+
.viz-card img {
|
| 842 |
+
width: 100%;
|
| 843 |
+
border-radius: 12px;
|
| 844 |
+
}
|
| 845 |
+
|
| 846 |
+
.report-button {
|
| 847 |
+
display: inline-flex;
|
| 848 |
+
justify-content: center;
|
| 849 |
+
padding: 0.9rem 1.5rem;
|
| 850 |
+
border-radius: 12px;
|
| 851 |
+
text-decoration: none;
|
| 852 |
+
font-weight: 600;
|
| 853 |
+
background: linear-gradient(135deg, #4ade80, #22d3ee);
|
| 854 |
+
color: #041019;
|
| 855 |
+
}
|
| 856 |
+
|
| 857 |
+
.empty-state {
|
| 858 |
+
padding: 2rem;
|
| 859 |
+
text-align: center;
|
| 860 |
+
color: rgba(255, 255, 255, 0.6);
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
@media (max-width: 1100px) {
|
| 864 |
+
.dashboard {
|
| 865 |
+
grid-template-columns: 1fr;
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
.messages-area {
|
| 869 |
+
max-height: 45vh;
|
| 870 |
+
}
|
| 871 |
+
}
|
| 872 |
+
|
| 873 |
+
@media (max-width: 600px) {
|
| 874 |
+
.app-shell {
|
| 875 |
+
padding: 2rem 1rem 3rem;
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
+
.panel {
|
| 879 |
+
padding: 1.25rem;
|
| 880 |
+
}
|
| 881 |
+
|
| 882 |
+
.bubble {
|
| 883 |
+
max-width: 100%;
|
| 884 |
+
}
|
| 885 |
+
}
|
frontend/src/App.jsx
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useRef, useEffect } from 'react'
|
| 2 |
+
import './App.css'
|
| 3 |
+
|
| 4 |
+
const QUICK_PROMPTS = [
|
| 5 |
+
'What were total sales by category?',
|
| 6 |
+
'Compare revenue by region last quarter',
|
| 7 |
+
'Show monthly sales trend for 2023',
|
| 8 |
+
'Top products by revenue'
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
const DEFAULT_AGENT_MESSAGE = {
|
| 12 |
+
type: 'agent',
|
| 13 |
+
content: 'Hi! Ask me anything about your business data and I will generate SQL, visuals, and insights for you.'
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
function App() {
|
| 17 |
+
const sessionIdRef = useRef(null)
|
| 18 |
+
if (!sessionIdRef.current) {
|
| 19 |
+
sessionIdRef.current = crypto?.randomUUID ? crypto.randomUUID() : `session-${Date.now()}`
|
| 20 |
+
}
|
| 21 |
+
const sessionId = sessionIdRef.current
|
| 22 |
+
|
| 23 |
+
const [messages, setMessages] = useState([DEFAULT_AGENT_MESSAGE])
|
| 24 |
+
const [input, setInput] = useState('')
|
| 25 |
+
const [loading, setLoading] = useState(false)
|
| 26 |
+
const [activeResult, setActiveResult] = useState(null)
|
| 27 |
+
const [error, setError] = useState(null)
|
| 28 |
+
const [datasetCatalog, setDatasetCatalog] = useState([])
|
| 29 |
+
const [catalogLoading, setCatalogLoading] = useState(false)
|
| 30 |
+
const [tableName, setTableName] = useState('sales')
|
| 31 |
+
const [uploading, setUploading] = useState(false)
|
| 32 |
+
const [uploadError, setUploadError] = useState(null)
|
| 33 |
+
const [clearing, setClearing] = useState(false)
|
| 34 |
+
const messagesEndRef = useRef(null)
|
| 35 |
+
const inputRef = useRef(null)
|
| 36 |
+
|
| 37 |
+
const [datasetPreview, setDatasetPreview] = useState(null)
|
| 38 |
+
|
| 39 |
+
const dataPreview = activeResult?.data ?? []
|
| 40 |
+
const previewColumns = dataPreview.length ? Object.keys(dataPreview[0]) : []
|
| 41 |
+
const previewRows = dataPreview.slice(0, 5)
|
| 42 |
+
|
| 43 |
+
const scrollToBottom = () => {
|
| 44 |
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
useEffect(() => {
|
| 48 |
+
scrollToBottom()
|
| 49 |
+
}, [messages])
|
| 50 |
+
|
| 51 |
+
useEffect(() => {
|
| 52 |
+
if (!inputRef.current) return
|
| 53 |
+
const el = inputRef.current
|
| 54 |
+
el.style.height = 'auto'
|
| 55 |
+
const nextHeight = Math.min(el.scrollHeight, 160)
|
| 56 |
+
el.style.height = `${nextHeight}px`
|
| 57 |
+
}, [input])
|
| 58 |
+
|
| 59 |
+
const fetchDatasetCatalog = async () => {
|
| 60 |
+
setCatalogLoading(true)
|
| 61 |
+
try {
|
| 62 |
+
const res = await fetch('http://localhost:8000/api/datasets')
|
| 63 |
+
const payload = await res.json()
|
| 64 |
+
setDatasetCatalog(payload.tables || [])
|
| 65 |
+
} catch (err) {
|
| 66 |
+
console.error('Failed to fetch dataset catalog', err)
|
| 67 |
+
} finally {
|
| 68 |
+
setCatalogLoading(false)
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
useEffect(() => {
|
| 73 |
+
fetchDatasetCatalog()
|
| 74 |
+
}, [])
|
| 75 |
+
|
| 76 |
+
const sendQuery = async (question) => {
|
| 77 |
+
const trimmed = question.trim()
|
| 78 |
+
if (!trimmed) return
|
| 79 |
+
|
| 80 |
+
const userMessage = { type: 'user', content: trimmed }
|
| 81 |
+
setMessages((prev) => [...prev, userMessage])
|
| 82 |
+
setInput('')
|
| 83 |
+
setLoading(true)
|
| 84 |
+
setError(null)
|
| 85 |
+
|
| 86 |
+
try {
|
| 87 |
+
const response = await fetch('http://localhost:8000/api/analyze', {
|
| 88 |
+
method: 'POST',
|
| 89 |
+
headers: {
|
| 90 |
+
'Content-Type': 'application/json'
|
| 91 |
+
},
|
| 92 |
+
body: JSON.stringify({ query: trimmed, session_id: sessionId })
|
| 93 |
+
})
|
| 94 |
+
|
| 95 |
+
const data = await response.json()
|
| 96 |
+
|
| 97 |
+
if (!response.ok || data.error) {
|
| 98 |
+
const message = data.error || 'Something went wrong.'
|
| 99 |
+
setError(message)
|
| 100 |
+
setMessages((prev) => [...prev, { type: 'agent', content: `⚠️ ${message}` }])
|
| 101 |
+
return
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
const agentPayload = {
|
| 105 |
+
type: 'agent',
|
| 106 |
+
content: data.insights || 'Analysis complete.',
|
| 107 |
+
sql: data.sql_query,
|
| 108 |
+
visualization: data.visualization_url,
|
| 109 |
+
chartSummary: data.visualization_summary,
|
| 110 |
+
trendSummary: data.trend_analysis?.summary,
|
| 111 |
+
anomalySummary: data.anomaly_analysis?.summary,
|
| 112 |
+
report: data.report_url
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
setMessages((prev) => [...prev, agentPayload])
|
| 116 |
+
setActiveResult(data)
|
| 117 |
+
} catch (err) {
|
| 118 |
+
const fallback = `Network error: ${err.message}`
|
| 119 |
+
setError(fallback)
|
| 120 |
+
setMessages((prev) => [...prev, { type: 'agent', content: fallback }])
|
| 121 |
+
} finally {
|
| 122 |
+
setLoading(false)
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
const handleSubmit = (e) => {
|
| 127 |
+
e.preventDefault()
|
| 128 |
+
if (loading) return
|
| 129 |
+
sendQuery(input)
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
const handlePromptClick = (prompt) => {
|
| 133 |
+
if (loading) return
|
| 134 |
+
setInput(prompt)
|
| 135 |
+
sendQuery(prompt)
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
const handleDatasetUpload = async (event) => {
|
| 139 |
+
const file = event.target.files?.[0]
|
| 140 |
+
if (!file || uploading) return
|
| 141 |
+
|
| 142 |
+
const formData = new FormData()
|
| 143 |
+
formData.append('file', file)
|
| 144 |
+
formData.append('table_name', tableName || 'sales')
|
| 145 |
+
|
| 146 |
+
setUploading(true)
|
| 147 |
+
setUploadError(null)
|
| 148 |
+
|
| 149 |
+
try {
|
| 150 |
+
const response = await fetch('http://localhost:8000/api/upload-csv', {
|
| 151 |
+
method: 'POST',
|
| 152 |
+
body: formData
|
| 153 |
+
})
|
| 154 |
+
|
| 155 |
+
const data = await response.json()
|
| 156 |
+
|
| 157 |
+
if (!response.ok || data.status !== 'success') {
|
| 158 |
+
throw new Error(data.detail || 'Upload failed')
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
if (data.preview) {
|
| 162 |
+
setDatasetPreview({
|
| 163 |
+
table: data.table,
|
| 164 |
+
columns: data.columns,
|
| 165 |
+
rows: data.preview
|
| 166 |
+
})
|
| 167 |
+
setActiveResult(null)
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
fetchDatasetCatalog()
|
| 171 |
+
} catch (err) {
|
| 172 |
+
setUploadError(err.message)
|
| 173 |
+
} finally {
|
| 174 |
+
setUploading(false)
|
| 175 |
+
event.target.value = ''
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
const handleClearChat = async () => {
|
| 180 |
+
if (loading || clearing) return
|
| 181 |
+
|
| 182 |
+
setClearing(true)
|
| 183 |
+
setMessages([DEFAULT_AGENT_MESSAGE])
|
| 184 |
+
setActiveResult(null)
|
| 185 |
+
setError(null)
|
| 186 |
+
|
| 187 |
+
try {
|
| 188 |
+
await fetch('http://localhost:8000/api/session/reset', {
|
| 189 |
+
method: 'POST',
|
| 190 |
+
headers: { 'Content-Type': 'application/json' },
|
| 191 |
+
body: JSON.stringify({ session_id: sessionId })
|
| 192 |
+
})
|
| 193 |
+
} catch (err) {
|
| 194 |
+
console.error('Failed to reset session', err)
|
| 195 |
+
} finally {
|
| 196 |
+
setClearing(false)
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
return (
|
| 201 |
+
<div className="app-shell">
|
| 202 |
+
<aside className="sidebar">
|
| 203 |
+
<div className="brand">
|
| 204 |
+
<h1>InsightPilot</h1>
|
| 205 |
+
</div>
|
| 206 |
+
|
| 207 |
+
<div className="sidebar-section">
|
| 208 |
+
<h3>Dataset Control</h3>
|
| 209 |
+
<div className="dataset-control">
|
| 210 |
+
<div className="input-group">
|
| 211 |
+
<label>Target Table</label>
|
| 212 |
+
<input
|
| 213 |
+
type="text"
|
| 214 |
+
value={tableName}
|
| 215 |
+
onChange={(e) => setTableName(e.target.value)}
|
| 216 |
+
placeholder="sales"
|
| 217 |
+
/>
|
| 218 |
+
</div>
|
| 219 |
+
|
| 220 |
+
<label className="upload-label">
|
| 221 |
+
{uploading ? 'Uploading...' : 'Click to Upload CSV'}
|
| 222 |
+
<input type="file" accept=".csv" onChange={handleDatasetUpload} disabled={uploading} />
|
| 223 |
+
</label>
|
| 224 |
+
{uploadError && <p className="error-text">{uploadError}</p>}
|
| 225 |
+
|
| 226 |
+
<button className="btn-secondary" onClick={fetchDatasetCatalog} disabled={catalogLoading}>
|
| 227 |
+
{catalogLoading ? 'Refreshing...' : 'Refresh Catalog'}
|
| 228 |
+
</button>
|
| 229 |
+
</div>
|
| 230 |
+
</div>
|
| 231 |
+
|
| 232 |
+
<div className="sidebar-section">
|
| 233 |
+
<h3>Available Tables</h3>
|
| 234 |
+
<ul className="dataset-list">
|
| 235 |
+
{datasetCatalog.length === 0 ? (
|
| 236 |
+
<li className="dataset-item">No tables found</li>
|
| 237 |
+
) : (
|
| 238 |
+
datasetCatalog.map((table) => (
|
| 239 |
+
<li key={table.table} className="dataset-item">
|
| 240 |
+
<strong>{table.table}</strong>
|
| 241 |
+
<span>{table.rows?.toLocaleString()} rows</span>
|
| 242 |
+
</li>
|
| 243 |
+
))
|
| 244 |
+
)}
|
| 245 |
+
</ul>
|
| 246 |
+
</div>
|
| 247 |
+
</aside>
|
| 248 |
+
|
| 249 |
+
<main className="main-content">
|
| 250 |
+
<section className="chat-section">
|
| 251 |
+
<header className="chat-header">
|
| 252 |
+
<div className="status-indicator">
|
| 253 |
+
<span className={`dot ${loading ? 'busy' : ''}`}></span>
|
| 254 |
+
{loading ? 'Agent is thinking...' : 'Agent is ready'}
|
| 255 |
+
</div>
|
| 256 |
+
<button
|
| 257 |
+
className="btn-secondary"
|
| 258 |
+
onClick={handleClearChat}
|
| 259 |
+
disabled={loading || clearing || messages.length <= 1}
|
| 260 |
+
>
|
| 261 |
+
{clearing ? 'Clearing...' : 'Clear Chat'}
|
| 262 |
+
</button>
|
| 263 |
+
</header>
|
| 264 |
+
|
| 265 |
+
<div className="messages-container">
|
| 266 |
+
{messages.length === 1 ? (
|
| 267 |
+
<div className="welcome-screen">
|
| 268 |
+
<h2>Autonomous Data Analyst</h2>
|
| 269 |
+
<p>Ask natural-language questions about your warehouse. The agent will plan the query, validate results, draw charts, and package everything in a PDF.</p>
|
| 270 |
+
<div className="quick-prompts">
|
| 271 |
+
{QUICK_PROMPTS.map((prompt) => (
|
| 272 |
+
<button key={prompt} className="prompt-card" onClick={() => handlePromptClick(prompt)}>
|
| 273 |
+
{prompt}
|
| 274 |
+
</button>
|
| 275 |
+
))}
|
| 276 |
+
</div>
|
| 277 |
+
</div>
|
| 278 |
+
) : (
|
| 279 |
+
messages.map((msg, index) => (
|
| 280 |
+
<div key={index} className={`message ${msg.type}`}>
|
| 281 |
+
<div className="avatar">
|
| 282 |
+
{msg.type === 'agent' ? '🤖' : '👤'}
|
| 283 |
+
</div>
|
| 284 |
+
<div className="message-content">
|
| 285 |
+
<p>{msg.content}</p>
|
| 286 |
+
{msg.sql && (
|
| 287 |
+
<div className="sql-snippet">
|
| 288 |
+
<code>{msg.sql}</code>
|
| 289 |
+
</div>
|
| 290 |
+
)}
|
| 291 |
+
{(msg.trendSummary || msg.anomalySummary) && (
|
| 292 |
+
<div className="diagnostic-chips">
|
| 293 |
+
{msg.trendSummary && <span className="badge">Trend: {msg.trendSummary}</span>}
|
| 294 |
+
{msg.anomalySummary && <span className="badge">Anomaly: {msg.anomalySummary}</span>}
|
| 295 |
+
</div>
|
| 296 |
+
)}
|
| 297 |
+
</div>
|
| 298 |
+
</div>
|
| 299 |
+
))
|
| 300 |
+
)}
|
| 301 |
+
<div ref={messagesEndRef} />
|
| 302 |
+
</div>
|
| 303 |
+
|
| 304 |
+
<div className="input-area">
|
| 305 |
+
<form className="input-wrapper" onSubmit={handleSubmit}>
|
| 306 |
+
<div className="input-shell">
|
| 307 |
+
<textarea
|
| 308 |
+
ref={inputRef}
|
| 309 |
+
rows={1}
|
| 310 |
+
value={input}
|
| 311 |
+
onChange={(e) => setInput(e.target.value)}
|
| 312 |
+
onKeyDown={(e) => {
|
| 313 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 314 |
+
e.preventDefault()
|
| 315 |
+
if (!loading) {
|
| 316 |
+
sendQuery(input)
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
}}
|
| 320 |
+
placeholder="Ask anything about your data..."
|
| 321 |
+
disabled={loading}
|
| 322 |
+
/>
|
| 323 |
+
<button type="submit" className="send-btn" disabled={loading}>
|
| 324 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
| 325 |
+
<path d="M22 2L11 13"></path>
|
| 326 |
+
<path d="M22 2L15 22L11 13L2 9L22 2Z"></path>
|
| 327 |
+
</svg>
|
| 328 |
+
</button>
|
| 329 |
+
</div>
|
| 330 |
+
</form>
|
| 331 |
+
</div>
|
| 332 |
+
</section>
|
| 333 |
+
|
| 334 |
+
<section className="results-section">
|
| 335 |
+
<div className="results-header">
|
| 336 |
+
<h2>Analysis Results</h2>
|
| 337 |
+
</div>
|
| 338 |
+
<div className="results-content">
|
| 339 |
+
{datasetPreview && !activeResult && (
|
| 340 |
+
<div className="result-card">
|
| 341 |
+
<div className="card-header">
|
| 342 |
+
<span>Uploaded Data Preview: {datasetPreview.table}</span>
|
| 343 |
+
</div>
|
| 344 |
+
<div className="table-container">
|
| 345 |
+
<table>
|
| 346 |
+
<thead>
|
| 347 |
+
<tr>
|
| 348 |
+
{datasetPreview.columns.map(col => <th key={col}>{col}</th>)}
|
| 349 |
+
</tr>
|
| 350 |
+
</thead>
|
| 351 |
+
<tbody>
|
| 352 |
+
{datasetPreview.rows.map((row, i) => (
|
| 353 |
+
<tr key={i}>
|
| 354 |
+
{datasetPreview.columns.map(col => <td key={col}>{row[col]}</td>)}
|
| 355 |
+
</tr>
|
| 356 |
+
))}
|
| 357 |
+
</tbody>
|
| 358 |
+
</table>
|
| 359 |
+
</div>
|
| 360 |
+
</div>
|
| 361 |
+
)}
|
| 362 |
+
|
| 363 |
+
{activeResult ? (
|
| 364 |
+
<>
|
| 365 |
+
<div className="result-card">
|
| 366 |
+
<div className="card-header">
|
| 367 |
+
<span>Insight</span>
|
| 368 |
+
</div>
|
| 369 |
+
<p>{activeResult.insights}</p>
|
| 370 |
+
</div>
|
| 371 |
+
|
| 372 |
+
{activeResult.visualization_url && (
|
| 373 |
+
<div className="result-card">
|
| 374 |
+
<div className="card-header">
|
| 375 |
+
<span>Visualization</span>
|
| 376 |
+
</div>
|
| 377 |
+
<div className="viz-container">
|
| 378 |
+
<img src={`http://localhost:8000${activeResult.visualization_url}`} alt="Visualization" />
|
| 379 |
+
</div>
|
| 380 |
+
</div>
|
| 381 |
+
)}
|
| 382 |
+
|
| 383 |
+
{activeResult.trend_analysis && (
|
| 384 |
+
<div className="result-card">
|
| 385 |
+
<div className="card-header">
|
| 386 |
+
<span>Trend Analysis</span>
|
| 387 |
+
<span className="badge">Diagnostic</span>
|
| 388 |
+
</div>
|
| 389 |
+
<p>{activeResult.trend_analysis.summary}</p>
|
| 390 |
+
<div className="trend-grid">
|
| 391 |
+
<div>
|
| 392 |
+
<span>Start</span>
|
| 393 |
+
<strong>{Number.isFinite(activeResult.trend_analysis.start) ? activeResult.trend_analysis.start.toFixed(2) : '-'}</strong>
|
| 394 |
+
</div>
|
| 395 |
+
<div>
|
| 396 |
+
<span>End</span>
|
| 397 |
+
<strong>{Number.isFinite(activeResult.trend_analysis.end) ? activeResult.trend_analysis.end.toFixed(2) : '-'}</strong>
|
| 398 |
+
</div>
|
| 399 |
+
<div>
|
| 400 |
+
<span>Change</span>
|
| 401 |
+
<strong>{activeResult.trend_analysis.change_pct?.toFixed(1)}%</strong>
|
| 402 |
+
</div>
|
| 403 |
+
</div>
|
| 404 |
+
</div>
|
| 405 |
+
)}
|
| 406 |
+
|
| 407 |
+
{activeResult.anomaly_analysis && (
|
| 408 |
+
<div className="result-card">
|
| 409 |
+
<div className="card-header">
|
| 410 |
+
<span>Anomalies</span>
|
| 411 |
+
<span className="badge">Diagnostic</span>
|
| 412 |
+
</div>
|
| 413 |
+
<p>{activeResult.anomaly_analysis.summary}</p>
|
| 414 |
+
<ul className="anomaly-list">
|
| 415 |
+
{activeResult.anomaly_analysis.anomalies?.slice(0, 3).map((a, i) => (
|
| 416 |
+
<li key={i}>
|
| 417 |
+
<span>{a.period}</span>
|
| 418 |
+
<strong>z={a.z_score?.toFixed(2)}</strong>
|
| 419 |
+
</li>
|
| 420 |
+
))}
|
| 421 |
+
</ul>
|
| 422 |
+
</div>
|
| 423 |
+
)}
|
| 424 |
+
|
| 425 |
+
{activeResult.forecast_analysis && (
|
| 426 |
+
<div className="result-card">
|
| 427 |
+
<div className="card-header">
|
| 428 |
+
<span>Forecast</span>
|
| 429 |
+
<span className="badge">Predictive</span>
|
| 430 |
+
</div>
|
| 431 |
+
<p>{activeResult.forecast_analysis.summary}</p>
|
| 432 |
+
{activeResult.forecast_analysis.method && (
|
| 433 |
+
<div style={{marginTop: '0.5rem', fontSize: '0.85rem', color: '#8899aa'}}>
|
| 434 |
+
Method: {activeResult.forecast_analysis.method}
|
| 435 |
+
</div>
|
| 436 |
+
)}
|
| 437 |
+
{activeResult.forecast_analysis.forecasts && (
|
| 438 |
+
<div className="table-container" style={{marginTop: '1rem'}}>
|
| 439 |
+
<table>
|
| 440 |
+
<thead>
|
| 441 |
+
<tr>
|
| 442 |
+
<th>Period</th>
|
| 443 |
+
<th>Forecast</th>
|
| 444 |
+
<th>Lower Bound</th>
|
| 445 |
+
<th>Upper Bound</th>
|
| 446 |
+
</tr>
|
| 447 |
+
</thead>
|
| 448 |
+
<tbody>
|
| 449 |
+
{activeResult.forecast_analysis.forecasts.map((f, i) => (
|
| 450 |
+
<tr key={i}>
|
| 451 |
+
<td>{f.period}</td>
|
| 452 |
+
<td>{Number.isFinite(f.value) ? f.value.toFixed(2) : '-'}</td>
|
| 453 |
+
<td>{Number.isFinite(f.lower_bound) ? f.lower_bound.toFixed(2) : '-'}</td>
|
| 454 |
+
<td>{Number.isFinite(f.upper_bound) ? f.upper_bound.toFixed(2) : '-'}</td>
|
| 455 |
+
</tr>
|
| 456 |
+
))}
|
| 457 |
+
</tbody>
|
| 458 |
+
</table>
|
| 459 |
+
</div>
|
| 460 |
+
)}
|
| 461 |
+
</div>
|
| 462 |
+
)}
|
| 463 |
+
|
| 464 |
+
{activeResult.statistical_tests && (
|
| 465 |
+
<div className="result-card">
|
| 466 |
+
<div className="card-header">
|
| 467 |
+
<span>Statistical Tests</span>
|
| 468 |
+
<span className="badge">Diagnostic</span>
|
| 469 |
+
</div>
|
| 470 |
+
<p>{activeResult.statistical_tests.summary}</p>
|
| 471 |
+
<div style={{marginTop: '1rem'}}>
|
| 472 |
+
{activeResult.statistical_tests.tests && Object.entries(activeResult.statistical_tests.tests).map(([testName, testData]) => (
|
| 473 |
+
<div key={testName} style={{marginBottom: '1rem', padding: '0.75rem', background: '#1e2430', borderRadius: '6px'}}>
|
| 474 |
+
<div style={{fontWeight: '600', marginBottom: '0.5rem', textTransform: 'capitalize'}}>
|
| 475 |
+
{testName.replace(/_/g, ' ')}
|
| 476 |
+
</div>
|
| 477 |
+
<div style={{fontSize: '0.9rem', color: '#8899aa'}}>
|
| 478 |
+
{testData.summary || testData.test || '-'}
|
| 479 |
+
</div>
|
| 480 |
+
{testData.p_value !== undefined && (
|
| 481 |
+
<div style={{marginTop: '0.5rem', fontSize: '0.85rem'}}>
|
| 482 |
+
<span style={{color: '#8899aa'}}>p-value: </span>
|
| 483 |
+
<span style={{fontFamily: 'monospace', color: testData.significant || testData.is_stationary === false ? '#ff6b6b' : '#51cf66'}}>
|
| 484 |
+
{Number.isFinite(testData.p_value) ? testData.p_value.toFixed(4) : '-'}
|
| 485 |
+
</span>
|
| 486 |
+
{testData.significant !== undefined && (
|
| 487 |
+
<span style={{marginLeft: '0.75rem', color: testData.significant ? '#ff6b6b' : '#51cf66'}}>
|
| 488 |
+
{testData.significant ? '✓ Significant' : '○ Not Significant'}
|
| 489 |
+
</span>
|
| 490 |
+
)}
|
| 491 |
+
{testData.is_stationary !== undefined && (
|
| 492 |
+
<span style={{marginLeft: '0.75rem', color: testData.is_stationary ? '#51cf66' : '#ff6b6b'}}>
|
| 493 |
+
{testData.is_stationary ? '✓ Stationary' : '○ Non-Stationary'}
|
| 494 |
+
</span>
|
| 495 |
+
)}
|
| 496 |
+
</div>
|
| 497 |
+
)}
|
| 498 |
+
</div>
|
| 499 |
+
))}
|
| 500 |
+
</div>
|
| 501 |
+
</div>
|
| 502 |
+
)}
|
| 503 |
+
|
| 504 |
+
{previewColumns.length > 0 && (
|
| 505 |
+
<div className="result-card">
|
| 506 |
+
<div className="card-header">
|
| 507 |
+
<span>Data Preview</span>
|
| 508 |
+
</div>
|
| 509 |
+
<div className="table-container">
|
| 510 |
+
<table>
|
| 511 |
+
<thead>
|
| 512 |
+
<tr>
|
| 513 |
+
{previewColumns.map(col => <th key={col}>{col}</th>)}
|
| 514 |
+
</tr>
|
| 515 |
+
</thead>
|
| 516 |
+
<tbody>
|
| 517 |
+
{previewRows.map((row, i) => (
|
| 518 |
+
<tr key={i}>
|
| 519 |
+
{previewColumns.map(col => <td key={col}>{row[col]}</td>)}
|
| 520 |
+
</tr>
|
| 521 |
+
))}
|
| 522 |
+
</tbody>
|
| 523 |
+
</table>
|
| 524 |
+
</div>
|
| 525 |
+
</div>
|
| 526 |
+
)}
|
| 527 |
+
|
| 528 |
+
{activeResult.report_url && (
|
| 529 |
+
<a href={`http://localhost:8000${activeResult.report_url}`} target="_blank" rel="noreferrer" className="download-btn">
|
| 530 |
+
Download PDF Report
|
| 531 |
+
</a>
|
| 532 |
+
)}
|
| 533 |
+
</>
|
| 534 |
+
) : (
|
| 535 |
+
<div className="empty-state-results">
|
| 536 |
+
<p>Run a query to see detailed analysis, charts, and data previews here.</p>
|
| 537 |
+
</div>
|
| 538 |
+
)}
|
| 539 |
+
</div>
|
| 540 |
+
</section>
|
| 541 |
+
</main>
|
| 542 |
+
</div>
|
| 543 |
+
)
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
export default App
|
frontend/src/assets/react.svg
ADDED
|
|
frontend/src/index.css
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
| 3 |
+
line-height: 1.5;
|
| 4 |
+
font-weight: 400;
|
| 5 |
+
color-scheme: dark;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
body {
|
| 9 |
+
margin: 0;
|
| 10 |
+
padding: 0;
|
| 11 |
+
min-height: 100vh;
|
| 12 |
+
width: 100%;
|
| 13 |
+
}
|
frontend/src/main.jsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { StrictMode } from 'react'
|
| 2 |
+
import { createRoot } from 'react-dom/client'
|
| 3 |
+
import './index.css'
|
| 4 |
+
import App from './App.jsx'
|
| 5 |
+
|
| 6 |
+
createRoot(document.getElementById('root')).render(
|
| 7 |
+
<StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</StrictMode>,
|
| 10 |
+
)
|
frontend/vite.config.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from 'vite'
|
| 2 |
+
import react from '@vitejs/plugin-react'
|
| 3 |
+
|
| 4 |
+
// https://vite.dev/config/
|
| 5 |
+
export default defineConfig({
|
| 6 |
+
plugins: [react()],
|
| 7 |
+
})
|
requirements.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python dependencies from backend
|
| 2 |
+
fastapi==0.109.0
|
| 3 |
+
uvicorn[standard]==0.27.0
|
| 4 |
+
langgraph==0.0.20
|
| 5 |
+
langchain==0.1.0
|
| 6 |
+
langchain-groq==0.0.1
|
| 7 |
+
langchain-community==0.0.13
|
| 8 |
+
sqlalchemy==2.0.25
|
| 9 |
+
pandas==2.1.4
|
| 10 |
+
numpy==1.26.3
|
| 11 |
+
scipy==1.11.4
|
| 12 |
+
statsmodels==0.14.1
|
| 13 |
+
matplotlib==3.8.2
|
| 14 |
+
reportlab==4.0.8
|
| 15 |
+
python-dotenv==1.0.0
|
| 16 |
+
pydantic==2.5.3
|
| 17 |
+
requests==2.31.0
|
| 18 |
+
python-multipart==0.0.6
|
| 19 |
+
|
| 20 |
+
# Gradio for HF Spaces UI
|
| 21 |
+
gradio==4.16.0
|
| 22 |
+
|
| 23 |
+
# Additional helpful packages
|
| 24 |
+
aiofiles==23.2.1
|
setup_hf_deployment.sh
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Quick setup script for HF Spaces deployment
|
| 3 |
+
|
| 4 |
+
echo "🚀 Setting up InsightPilot for Hugging Face Spaces deployment"
|
| 5 |
+
echo ""
|
| 6 |
+
|
| 7 |
+
# Check if we should build frontend
|
| 8 |
+
read -p "Build React frontend? (y/n - takes a few minutes): " build_frontend
|
| 9 |
+
|
| 10 |
+
if [ "$build_frontend" = "y" ] || [ "$build_frontend" = "Y" ]; then
|
| 11 |
+
echo "📦 Building frontend..."
|
| 12 |
+
cd frontend
|
| 13 |
+
npm install
|
| 14 |
+
npm run build
|
| 15 |
+
cd ..
|
| 16 |
+
echo "✅ Frontend built successfully!"
|
| 17 |
+
else
|
| 18 |
+
echo "⏭️ Skipping frontend build (app will work without it)"
|
| 19 |
+
fi
|
| 20 |
+
|
| 21 |
+
echo ""
|
| 22 |
+
echo "📝 Next steps:"
|
| 23 |
+
echo ""
|
| 24 |
+
echo "1. Create a new Space on Hugging Face:"
|
| 25 |
+
echo " → https://huggingface.co/new-space"
|
| 26 |
+
echo " → Choose SDK: Gradio"
|
| 27 |
+
echo " → Choose Hardware: CPU basic (free)"
|
| 28 |
+
echo ""
|
| 29 |
+
echo "2. Clone your new Space:"
|
| 30 |
+
echo " git clone https://huggingface.co/spaces/YOUR_USERNAME/SPACE_NAME"
|
| 31 |
+
echo ""
|
| 32 |
+
echo "3. Copy files to your Space:"
|
| 33 |
+
echo " cp app.py YOUR_SPACE/"
|
| 34 |
+
echo " cp requirements.txt YOUR_SPACE/"
|
| 35 |
+
echo " cp README_HF.md YOUR_SPACE/README.md"
|
| 36 |
+
echo " cp -r backend YOUR_SPACE/"
|
| 37 |
+
echo " cp -r data YOUR_SPACE/"
|
| 38 |
+
if [ "$build_frontend" = "y" ] || [ "$build_frontend" = "Y" ]; then
|
| 39 |
+
echo " cp -r frontend/dist YOUR_SPACE/frontend/dist"
|
| 40 |
+
fi
|
| 41 |
+
echo ""
|
| 42 |
+
echo "4. Push to Hugging Face:"
|
| 43 |
+
echo " cd YOUR_SPACE"
|
| 44 |
+
echo " git add ."
|
| 45 |
+
echo " git commit -m 'Initial deployment'"
|
| 46 |
+
echo " git push"
|
| 47 |
+
echo ""
|
| 48 |
+
echo "5. Add your GROQ_API_KEY in Space Settings → Repository secrets"
|
| 49 |
+
echo ""
|
| 50 |
+
echo "📖 Full deployment guide: See DEPLOYMENT.md"
|
| 51 |
+
echo ""
|
| 52 |
+
echo "✨ Done! Ready to deploy to Hugging Face Spaces!"
|