Spaces:
Sleeping
feat: Complete BioFlow Orchestrator hackathon implementation
Browse filesMajor Features:
- Langflow visual workflow builder integration (embedded iframe)
- Drug discovery search with real Qdrant vector search (23,531 vectors)
- 3D embedding explorer with real PCA projections
- FastAPI backend with DeepPurpose Morgan + CNN encoding
Bug Fixes:
- Fixed division by zero error (bypass data_process, direct Morgan encoding)
- Fixed CUDA/CPU device mismatch (auto-detect from model parameters)
- Fixed encoding dimension alignment for vector search
UI Improvements:
- Clean white design (removed dark gradients)
- Real API calls only (no fake/mock data)
- Simplified workflow page (removed hover effects, indicators)
- Working search with Similarity/Binding Affinity/Properties modes
Cleanup:
- Removed INSTRUCTIONS.md, setup_venv.txt
- Updated .gitignore for langflow_venv, data files
- Added Langflow pipeline template
Services:
- Qdrant: 6333
- FastAPI: 8001
- Next.js: 3000
- Langflow: 7860
- .gitignore +13 -1
- INSTRUCTIONS.md +0 -68
- README.md +159 -55
- langflow/bioflow_dti_pipeline.json +171 -0
- server/api.py +129 -26
- setup_venv.txt +0 -62
- ui/app/data/data-view.tsx +47 -5
- ui/app/discovery/page.tsx +136 -52
- ui/app/explorer/components.tsx +3 -3
- ui/app/workflow/components/custom-nodes.tsx +167 -0
- ui/app/workflow/layout.tsx +17 -0
- ui/app/workflow/page.tsx +159 -0
- ui/components/sidebar.tsx +16 -0
- ui/lib/data-service.ts +63 -9
- ui/package.json +1 -0
- ui/pnpm-lock.yaml +377 -0
|
@@ -1,5 +1,6 @@
|
|
| 1 |
# Python
|
| 2 |
.venv/
|
|
|
|
| 3 |
__pycache__/
|
| 4 |
*.pyc
|
| 5 |
*.pyo
|
|
@@ -22,6 +23,17 @@ runs/*/events.out.tfevents.*
|
|
| 22 |
*.pt
|
| 23 |
*.npy
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
# OS
|
| 26 |
.DS_Store
|
| 27 |
-
Thumbs.db
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Python
|
| 2 |
.venv/
|
| 3 |
+
langflow_venv/
|
| 4 |
__pycache__/
|
| 5 |
*.pyc
|
| 6 |
*.pyo
|
|
|
|
| 23 |
*.pt
|
| 24 |
*.npy
|
| 25 |
|
| 26 |
+
# Data files
|
| 27 |
+
data/
|
| 28 |
+
predictions_test.csv
|
| 29 |
+
|
| 30 |
+
# Langflow local db
|
| 31 |
+
.langflow/
|
| 32 |
+
|
| 33 |
# OS
|
| 34 |
.DS_Store
|
| 35 |
+
Thumbs.db
|
| 36 |
+
|
| 37 |
+
# Next.js
|
| 38 |
+
ui/.next/
|
| 39 |
+
ui/node_modules/
|
|
@@ -1,68 +0,0 @@
|
|
| 1 |
-
# Hackathon Setup: DeepPurpose + Qdrant + UI (v2)
|
| 2 |
-
|
| 3 |
-
## Architecture Overview
|
| 4 |
-
```
|
| 5 |
-
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
| 6 |
-
│ Next.js UI │────▶│ FastAPI │────▶│ Qdrant │
|
| 7 |
-
│ (3000) │ │ (8000) │ │ (6333) │
|
| 8 |
-
└─────────────┘ └──────────────┘ └─────────────┘
|
| 9 |
-
│
|
| 10 |
-
┌──────┴──────┐
|
| 11 |
-
│ DeepPurpose │
|
| 12 |
-
│ Model │
|
| 13 |
-
└─────────────┘
|
| 14 |
-
```
|
| 15 |
-
|
| 16 |
-
## Quick Start
|
| 17 |
-
|
| 18 |
-
### 1. Start Qdrant (Vector Database)
|
| 19 |
-
```bash
|
| 20 |
-
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant
|
| 21 |
-
```
|
| 22 |
-
|
| 23 |
-
### 2. Ingest Data (Phase 1)
|
| 24 |
-
```bash
|
| 25 |
-
.venv\Scripts\python ingest_qdrant.py
|
| 26 |
-
```
|
| 27 |
-
This will:
|
| 28 |
-
- Load the trained model from `runs/20260125_104915_KIBA`
|
| 29 |
-
- Generate embeddings for all drug-target pairs
|
| 30 |
-
- Compute **real PCA** projections (not fake first-3-dims!)
|
| 31 |
-
- Upload to Qdrant with pre-computed 3D coordinates
|
| 32 |
-
|
| 33 |
-
### 3. Start API Server (Phase 2)
|
| 34 |
-
```bash
|
| 35 |
-
.venv\Scripts\python server/api.py
|
| 36 |
-
```
|
| 37 |
-
API endpoints:
|
| 38 |
-
- `GET /api/points?limit=500&view=combined` - Get 3D visualization data
|
| 39 |
-
- `POST /api/search` - Find similar drugs/targets
|
| 40 |
-
- `GET /health` - Check system status
|
| 41 |
-
|
| 42 |
-
### 4. Start Frontend (Phase 3)
|
| 43 |
-
```bash
|
| 44 |
-
cd ui && pnpm dev
|
| 45 |
-
```
|
| 46 |
-
Open http://localhost:3000/explorer
|
| 47 |
-
|
| 48 |
-
## What's Fixed (v2)
|
| 49 |
-
|
| 50 |
-
| Issue | Before | After |
|
| 51 |
-
|-------|--------|-------|
|
| 52 |
-
| PCA | First 3 dims (meaningless) | Real sklearn PCA |
|
| 53 |
-
| Data Order | Shuffled (broken alignment) | `shuffle=False` in DataLoader |
|
| 54 |
-
| Dummy Data | `"M" * 10` (fragile) | Valid Aspirin SMILES |
|
| 55 |
-
| Config | Duplicated | Shared `config.py` |
|
| 56 |
-
| Error Handling | None | Validation + helpful messages |
|
| 57 |
-
| Model Loading | Per-request | Cached at startup |
|
| 58 |
-
|
| 59 |
-
## Best Model Results (Kept Runs)
|
| 60 |
-
|
| 61 |
-
| Dataset | CI | Pearson | Has model.pt |
|
| 62 |
-
|---------|-------|---------|--------------|
|
| 63 |
-
| BindingDB_Kd | **0.8083** | 0.7679 | No |
|
| 64 |
-
| DAVIS | 0.7914 | 0.5446 | No |
|
| 65 |
-
| KIBA | 0.7003 | 0.5219 | **Yes** |
|
| 66 |
-
|
| 67 |
-
*Note: KIBA run has the saved model, but BindingDB has best metrics.*
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,19 +1,80 @@
|
|
| 1 |
-
# 🧬
|
| 2 |
|
| 3 |
-
>
|
|
|
|
| 4 |
|
| 5 |

|
| 6 |

|
|
|
|
| 7 |

|
| 8 |
-

|
| 7 |

|
| 8 |
+

|
| 9 |

|
| 10 |
+

|
| 11 |
+

|
| 12 |
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## 🎯 Problem Statement
|
| 16 |
+
|
| 17 |
+
Biological R&D knowledge is fragmented across disconnected silos:
|
| 18 |
+
- **Textual literature** (papers, lab notes)
|
| 19 |
+
- **3D structural data** (PDB files)
|
| 20 |
+
- **Chemical sequences** (SMILES)
|
| 21 |
+
|
| 22 |
+
Researchers must manually navigate incompatible formats, creating bottlenecks and "blind spots" where critical connections are missed.
|
| 23 |
+
|
| 24 |
+
## 💡 Our Solution
|
| 25 |
+
|
| 26 |
+
**BioFlow Orchestrator** is a visual workflow engine that unifies biological discovery pipelines. Rather than a single "black box" model, we function as an **intelligent orchestrator** — allowing researchers to chain state-of-the-art open-source biological models into coherent discovery workflows.
|
| 27 |
+
|
| 28 |
+
### Key Features
|
| 29 |
+
|
| 30 |
+
| Feature | Description |
|
| 31 |
+
|---------|-------------|
|
| 32 |
+
| 🔗 **Visual Pipeline Builder** | Drag-and-drop node editor for constructing discovery workflows |
|
| 33 |
+
| 🧠 **DeepPurpose Integration** | Drug-Target Interaction prediction with Morgan + CNN encoding |
|
| 34 |
+
| 🔍 **Qdrant Vector Search** | High-dimensional similarity search across 23,531+ compounds |
|
| 35 |
+
| 🎨 **3D Embedding Explorer** | Real PCA projections of drug-target chemical space |
|
| 36 |
+
| ✅ **Validator Agents** | Automated toxicity and novelty checking |
|
| 37 |
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## 🏗️ Architecture
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
┌──────────────────────────────────────────┐
|
| 44 |
+
│ BioFlow Orchestrator │
|
| 45 |
+
│ Visual Pipeline Builder (UI) │
|
| 46 |
+
└─────────────────┬────────────────────────┘
|
| 47 |
+
│
|
| 48 |
+
┌─────────────────────────────────┼─────────────────────────────────┐
|
| 49 |
+
│ │ │
|
| 50 |
+
▼ ▼ ▼
|
| 51 |
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
| 52 |
+
│ Data Input │ │ DeepPurpose │ │ OpenBioMed │
|
| 53 |
+
│ SMILES/Protein │────────────▶│ DTI Model │────────────▶│ Multimodal │
|
| 54 |
+
│ Sequences │ │ Morgan + CNN │ │ Embeddings │
|
| 55 |
+
└─────────────────┘ └────────┬────────┘ └────────┬────────┘
|
| 56 |
+
│ │
|
| 57 |
+
└───────────────┬───────────────┘
|
| 58 |
+
│
|
| 59 |
+
▼
|
| 60 |
+
┌─────────────────┐
|
| 61 |
+
│ Qdrant │
|
| 62 |
+
│ Vector DB │
|
| 63 |
+
│ HNSW Indexing │
|
| 64 |
+
│ 23,531 vectors │
|
| 65 |
+
└────────┬────────┘
|
| 66 |
+
│
|
| 67 |
+
┌─────────────────────────────┼─────────────────────────────┐
|
| 68 |
+
│ │ │
|
| 69 |
+
▼ ▼ ▼
|
| 70 |
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
| 71 |
+
│ Similarity │ │ Validator │ │ Results │
|
| 72 |
+
│ Search Agent │ │ Agent │ │ Output │
|
| 73 |
+
│ Top-K Retrieval │ │ Toxicity/Novelty│ │ Candidates │
|
| 74 |
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
|
| 79 |
## 📊 Model Performance
|
| 80 |
|
|
|
|
| 84 |
| **BindingDB_Kd** | 0.8083 | 0.7679 | 0.6668 |
|
| 85 |
| **DAVIS** | 0.7914 | 0.5446 | 0.4684 |
|
| 86 |
|
| 87 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
## 🚀 Quick Start
|
| 90 |
|
| 91 |
### Prerequisites
|
| 92 |
+
- Python 3.10+
|
| 93 |
- Node.js 18+
|
| 94 |
+
- Docker Desktop
|
| 95 |
+
- CUDA 11.8 (optional, for GPU acceleration)
|
| 96 |
|
| 97 |
+
### 1. Clone & Setup
|
| 98 |
```bash
|
| 99 |
+
git clone https://github.com/hamzasammoud11-dotcom/lacoste001.git
|
| 100 |
+
cd lacoste001
|
| 101 |
+
|
| 102 |
+
# Python environment
|
| 103 |
python -m venv .venv
|
| 104 |
.venv\Scripts\activate # Windows
|
| 105 |
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
|
| 106 |
pip install DeepPurpose qdrant-client fastapi uvicorn scikit-learn
|
| 107 |
```
|
| 108 |
|
| 109 |
+
### 2. Start Qdrant Vector Database
|
| 110 |
```bash
|
| 111 |
docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant:latest
|
| 112 |
```
|
|
|
|
| 114 |
### 3. Ingest Data (One-time)
|
| 115 |
```bash
|
| 116 |
python ingest_qdrant.py
|
| 117 |
+
# Loads KIBA dataset → DeepPurpose embeddings → Qdrant
|
| 118 |
# ~23,531 drug-target pairs indexed
|
| 119 |
```
|
| 120 |
|
| 121 |
+
### 4. Start Backend API
|
| 122 |
```bash
|
| 123 |
python -m uvicorn server.api:app --host 0.0.0.0 --port 8001
|
| 124 |
```
|
|
|
|
| 131 |
# Open http://localhost:3000
|
| 132 |
```
|
| 133 |
|
| 134 |
+
### 6. Start Langflow (Visual Workflow Builder)
|
| 135 |
+
```bash
|
| 136 |
+
pip install langflow
|
| 137 |
+
langflow run --host 0.0.0.0 --port 7860
|
| 138 |
+
# Access via http://localhost:3000/workflow (embedded)
|
| 139 |
+
# Or directly at http://localhost:7860
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
---
|
| 143 |
+
|
| 144 |
+
## 🎨 Visual Workflow Builder (Langflow Integration)
|
| 145 |
+
|
| 146 |
+
BioFlow integrates **Langflow** as the visual workflow engine, providing a full-screen drag-and-drop pipeline builder accessible from `/workflow`.
|
| 147 |
+
|
| 148 |
+
### Building a DTI Pipeline in Langflow
|
| 149 |
+
|
| 150 |
+
1. **Import the Template Flow**:
|
| 151 |
+
- Open Langflow (`/workflow` or `localhost:7860`)
|
| 152 |
+
- Click "New Project" → "Import"
|
| 153 |
+
- Load `langflow/bioflow_dti_pipeline.json`
|
| 154 |
+
|
| 155 |
+
2. **Configure the Pipeline**:
|
| 156 |
+
- **Drug Input**: Enter SMILES string (e.g., `CC(=O)Nc1ccc(O)cc1`)
|
| 157 |
+
- **Target Input**: Enter protein sequence
|
| 158 |
+
- **API Nodes**: Point to `http://localhost:8001/api/*`
|
| 159 |
+
|
| 160 |
+
3. **Run the Flow**:
|
| 161 |
+
- Click "Run" to execute DeepPurpose encoding → Qdrant search → Results
|
| 162 |
+
|
| 163 |
+
---
|
| 164 |
+
|
| 165 |
## 📁 Project Structure
|
| 166 |
|
| 167 |
```
|
| 168 |
+
├── config.py # Shared configuration
|
| 169 |
├── ingest_qdrant.py # ETL: TDC → DeepPurpose → Qdrant
|
| 170 |
├── deeppurpose002.py # Model training script
|
| 171 |
├── server/
|
| 172 |
+
│ └── api.py # FastAPI backend
|
| 173 |
├── runs/
|
| 174 |
+
│ └── 20260125_104915_KIBA/
|
| 175 |
+
│ ├── model.pt # Trained model weights
|
| 176 |
+
│ └── config.pkl # Model configuration
|
| 177 |
+
├── ui/
|
| 178 |
│ ├── app/
|
| 179 |
+
│ │ ├── workflow/ # 🆕 Visual Pipeline Builder
|
| 180 |
+
│ │ ├── explorer/ # 3D Embedding Visualization
|
| 181 |
+
│ │ ├── discovery/ # Drug Discovery Interface
|
| 182 |
+
│ │ └── data/ # Data Browser
|
| 183 |
+
│ └── components/
|
| 184 |
└── data/
|
| 185 |
└── kiba.tab # Cached TDC dataset
|
| 186 |
```
|
| 187 |
|
| 188 |
+
---
|
| 189 |
+
|
| 190 |
## 🔌 API Endpoints
|
| 191 |
|
| 192 |
| Endpoint | Method | Description |
|
| 193 |
|----------|--------|-------------|
|
| 194 |
+
| `/health` | GET | Service health + model metrics |
|
| 195 |
| `/api/points` | GET | Get 3D PCA points for visualization |
|
| 196 |
| `/api/search` | POST | Similarity search by SMILES/sequence |
|
| 197 |
|
| 198 |
+
### Example: Search Similar Compounds
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
```bash
|
| 200 |
curl -X POST "http://localhost:8001/api/search" \
|
| 201 |
-H "Content-Type: application/json" \
|
| 202 |
+
-d '{"smiles": "CC(=O)Nc1ccc(O)cc1", "top_k": 10}'
|
| 203 |
```
|
| 204 |
|
| 205 |
+
---
|
| 206 |
|
| 207 |
+
## 🛠️ Qdrant Integration Strategy
|
|
|
|
|
|
|
| 208 |
|
| 209 |
+
### 1. Multimodal Bridge
|
| 210 |
+
Using OpenBioMed for joint embeddings across proteins, molecules, and text — enabling **cross-modal retrieval**.
|
| 211 |
+
|
| 212 |
+
### 2. Dynamic Workflow Memory
|
| 213 |
+
Pipeline nodes store intermediate results in Qdrant collections, enabling agent-to-agent communication.
|
| 214 |
+
|
| 215 |
+
### 3. High-Dimensional Scalability
|
| 216 |
+
HNSW indexing handles bio-embeddings at scale, keeping similarity searches interactive and real-time.
|
| 217 |
+
|
| 218 |
+
---
|
| 219 |
+
|
| 220 |
+
## 👥 Team Lacoste
|
| 221 |
+
|
| 222 |
+
| Name | Role |
|
| 223 |
+
|------|------|
|
| 224 |
+
| **Hamza Sammoud** | ML Pipeline & Backend |
|
| 225 |
+
| **Rami Troudi** | Frontend UI |
|
| 226 |
+
|
| 227 |
+
---
|
| 228 |
+
|
| 229 |
+
## 📚 Resources
|
| 230 |
+
|
| 231 |
+
- [DeepPurpose](https://github.com/kexinhuang12345/DeepPurpose) — DTI Prediction Toolkit
|
| 232 |
+
- [OpenBioMed](https://github.com/PharMolix/OpenBioMed) — Multimodal AI Framework
|
| 233 |
+
- [Qdrant](https://qdrant.tech/) — Vector Database
|
| 234 |
+
- [TDC](https://tdcommons.ai/) — Therapeutics Data Commons
|
| 235 |
+
|
| 236 |
+
---
|
| 237 |
|
| 238 |
## 📄 License
|
| 239 |
|
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "BioFlow DTI Pipeline",
|
| 3 |
+
"description": "Drug-Target Interaction prediction pipeline using DeepPurpose model and Qdrant vector search. This flow represents the BioFlow Orchestrator hackathon project.",
|
| 4 |
+
"data": {
|
| 5 |
+
"nodes": [
|
| 6 |
+
{
|
| 7 |
+
"id": "TextInput-SMILES",
|
| 8 |
+
"type": "genericNode",
|
| 9 |
+
"position": {"x": 100, "y": 200},
|
| 10 |
+
"data": {
|
| 11 |
+
"type": "TextInput",
|
| 12 |
+
"node": {
|
| 13 |
+
"template": {
|
| 14 |
+
"input_value": {
|
| 15 |
+
"value": "CC(=O)Nc1ccc(O)cc1",
|
| 16 |
+
"display_name": "Drug SMILES",
|
| 17 |
+
"info": "Enter a SMILES string representing the drug molecule (e.g., Acetaminophen: CC(=O)Nc1ccc(O)cc1)"
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
"display_name": "Drug Input (SMILES)",
|
| 21 |
+
"description": "Input drug molecule in SMILES format"
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"id": "TextInput-Protein",
|
| 27 |
+
"type": "genericNode",
|
| 28 |
+
"position": {"x": 100, "y": 400},
|
| 29 |
+
"data": {
|
| 30 |
+
"type": "TextInput",
|
| 31 |
+
"node": {
|
| 32 |
+
"template": {
|
| 33 |
+
"input_value": {
|
| 34 |
+
"value": "MKKFFDSRREQGGSGLGSGSSGGGGSTSGLGSGYIGRVFGIGRQQVTVDEVLAEGGFAIVFLVRTSNGMKCALKRMFVNNEHDLQVCKREIQIMRDLSGHKNIVGYIDSSINNVSSGDVWEVLILMDFCRGGQVVNLMNQRLQTGFTENEVLQIFCDTCEAVARLHQCKTPIIHRDLKVENILLHDRGHYVLCDFGSATNKFQNPQTEGVNAVEDEIKKYTTLSYRAPEMVNLYSGKIITTKADIWALGCLLYKLCYFTLPFGESQVAICDGNFTIPDNSRYSQDMHCLIRYMLEPDPDKRPDIYQVSYFSFKLLKKECPIPNVQNSPIPAKLPEPVKASEAAAKKTQPKARLTDPIPTTETSIAPRQRPKAGQTQPNPGILPIQPALTPRKRATVQPPPQAAGSSNQPGLLASVPQPKPQAPPSQPLPQTQAKQPQAPPTPQQTPSTQAQGLPAQAQATPQHQQQLFLKQQQQQQQPPPAQQQPAGTFYQQQQAQTQQFQAVHPATQKPAIAQFPVVSQGGSQQQLMQNFYQQQQQQQQQQQQQQLATALHQQQLMTQQAALQQKPTMAAGQQPQPQPAAAPQPAPAQEPAIQAPVRQQPKVQTTPPPAVQGQKVGSLTPPSSPKTQRAGHRRILSDVTHSAVFGVPASKSTQLLQAAAAEASLNKSKSATTTPSGSPRTSQQNVYNPSEGSTWNPFDDDNFSKLTAEELLNKDFAKLGEGKHPEKLGGSAESLIPGFQSTQGDAFATTSFSAGTAEKRKGGQTVDSGLPLLSVSDPFIPLQVPDAPEKLIEGLKSPDTSLLLPDLLPMTDPFGSTSDAVIEKADVAVESLIPGLEPPVPQRLPSQTESVTSNRTDSLTGEDSLLDCSLLSNPTTDLLEEFAPTAISAPVHKAAEDSNLISGFDVPEGSDKVAEDEFDPIPVLITKNPQGGHSRNSSGSSESSLPNLARSLLLVDQLIDL",
|
| 35 |
+
"display_name": "Target Sequence",
|
| 36 |
+
"info": "Enter a protein target amino acid sequence (e.g., AAK1 kinase)"
|
| 37 |
+
}
|
| 38 |
+
},
|
| 39 |
+
"display_name": "Target Input (Protein)",
|
| 40 |
+
"description": "Input target protein amino acid sequence"
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"id": "APIRequest-Encode",
|
| 46 |
+
"type": "genericNode",
|
| 47 |
+
"position": {"x": 400, "y": 300},
|
| 48 |
+
"data": {
|
| 49 |
+
"type": "APIRequest",
|
| 50 |
+
"node": {
|
| 51 |
+
"template": {
|
| 52 |
+
"url": {
|
| 53 |
+
"value": "http://localhost:8001/api/search",
|
| 54 |
+
"display_name": "BioFlow API URL"
|
| 55 |
+
},
|
| 56 |
+
"method": {
|
| 57 |
+
"value": "POST",
|
| 58 |
+
"display_name": "HTTP Method"
|
| 59 |
+
},
|
| 60 |
+
"body": {
|
| 61 |
+
"value": "{\"query\": \"{smiles}\", \"type\": \"drug\", \"limit\": 20}",
|
| 62 |
+
"display_name": "Request Body",
|
| 63 |
+
"info": "DeepPurpose encoding + Qdrant vector search"
|
| 64 |
+
}
|
| 65 |
+
},
|
| 66 |
+
"display_name": "DeepPurpose Encoder",
|
| 67 |
+
"description": "Morgan fingerprint + CNN encoding via BioFlow API"
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"id": "APIRequest-Qdrant",
|
| 73 |
+
"type": "genericNode",
|
| 74 |
+
"position": {"x": 700, "y": 200},
|
| 75 |
+
"data": {
|
| 76 |
+
"type": "APIRequest",
|
| 77 |
+
"node": {
|
| 78 |
+
"template": {
|
| 79 |
+
"url": {
|
| 80 |
+
"value": "http://localhost:8001/api/points?limit=500&view=combined",
|
| 81 |
+
"display_name": "Qdrant Visualization API"
|
| 82 |
+
},
|
| 83 |
+
"method": {
|
| 84 |
+
"value": "GET",
|
| 85 |
+
"display_name": "HTTP Method"
|
| 86 |
+
}
|
| 87 |
+
},
|
| 88 |
+
"display_name": "Qdrant Vector Store",
|
| 89 |
+
"description": "23,531 drug-target pairs from KIBA dataset"
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"id": "ConditionalRouter-Affinity",
|
| 95 |
+
"type": "genericNode",
|
| 96 |
+
"position": {"x": 700, "y": 400},
|
| 97 |
+
"data": {
|
| 98 |
+
"type": "ConditionalRouter",
|
| 99 |
+
"node": {
|
| 100 |
+
"template": {
|
| 101 |
+
"condition": {
|
| 102 |
+
"value": "score > 0.8",
|
| 103 |
+
"display_name": "High Affinity Threshold"
|
| 104 |
+
}
|
| 105 |
+
},
|
| 106 |
+
"display_name": "Affinity Filter",
|
| 107 |
+
"description": "Filter results by binding affinity score"
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"id": "TextOutput-Results",
|
| 113 |
+
"type": "genericNode",
|
| 114 |
+
"position": {"x": 1000, "y": 300},
|
| 115 |
+
"data": {
|
| 116 |
+
"type": "TextOutput",
|
| 117 |
+
"node": {
|
| 118 |
+
"template": {
|
| 119 |
+
"input_value": {
|
| 120 |
+
"display_name": "DTI Predictions",
|
| 121 |
+
"info": "Top candidate drug-target interactions"
|
| 122 |
+
}
|
| 123 |
+
},
|
| 124 |
+
"display_name": "Pipeline Output",
|
| 125 |
+
"description": "Ranked drug-target interaction predictions"
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
],
|
| 130 |
+
"edges": [
|
| 131 |
+
{
|
| 132 |
+
"id": "e1",
|
| 133 |
+
"source": "TextInput-SMILES",
|
| 134 |
+
"target": "APIRequest-Encode",
|
| 135 |
+
"sourceHandle": "output",
|
| 136 |
+
"targetHandle": "input"
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"id": "e2",
|
| 140 |
+
"source": "TextInput-Protein",
|
| 141 |
+
"target": "APIRequest-Encode",
|
| 142 |
+
"sourceHandle": "output",
|
| 143 |
+
"targetHandle": "input"
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"id": "e3",
|
| 147 |
+
"source": "APIRequest-Encode",
|
| 148 |
+
"target": "APIRequest-Qdrant",
|
| 149 |
+
"sourceHandle": "output",
|
| 150 |
+
"targetHandle": "input"
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"id": "e4",
|
| 154 |
+
"source": "APIRequest-Qdrant",
|
| 155 |
+
"target": "ConditionalRouter-Affinity",
|
| 156 |
+
"sourceHandle": "output",
|
| 157 |
+
"targetHandle": "input"
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
"id": "e5",
|
| 161 |
+
"source": "ConditionalRouter-Affinity",
|
| 162 |
+
"target": "TextOutput-Results",
|
| 163 |
+
"sourceHandle": "true_output",
|
| 164 |
+
"targetHandle": "input"
|
| 165 |
+
}
|
| 166 |
+
]
|
| 167 |
+
},
|
| 168 |
+
"is_component": false,
|
| 169 |
+
"folder": null,
|
| 170 |
+
"endpoint_name": "bioflow-dti"
|
| 171 |
+
}
|
|
@@ -50,7 +50,7 @@ _device = None
|
|
| 50 |
|
| 51 |
class SearchRequest(BaseModel):
|
| 52 |
query: str
|
| 53 |
-
type: str # "drug" (SMILES) or "target" (Sequence)
|
| 54 |
limit: int = 20
|
| 55 |
|
| 56 |
class PointsRequest(BaseModel):
|
|
@@ -89,9 +89,16 @@ async def load_resources():
|
|
| 89 |
else:
|
| 90 |
print(f"[WARNING] No model.pt found at {model_path}")
|
| 91 |
|
| 92 |
-
#
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
_model.model.eval()
|
| 96 |
|
| 97 |
print("[STARTUP] Connecting to Qdrant...")
|
|
@@ -106,47 +113,61 @@ async def load_resources():
|
|
| 106 |
print("[STARTUP] Ready!")
|
| 107 |
|
| 108 |
def encode_query(query: str, query_type: str) -> List[float]:
|
| 109 |
-
"""Encode a single drug/target query into a vector."""
|
| 110 |
if not _model:
|
| 111 |
raise HTTPException(status_code=503, detail="Model not initialized")
|
| 112 |
|
| 113 |
try:
|
| 114 |
if query_type == "drug":
|
| 115 |
-
#
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
)
|
|
|
|
|
|
|
| 122 |
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
with torch.no_grad():
|
| 127 |
-
v_d = v_d.to(_device)
|
| 128 |
vector = _model.model.model_drug(v_d).cpu().numpy()[0].tolist()
|
| 129 |
return vector
|
| 130 |
|
| 131 |
elif query_type == "target":
|
| 132 |
-
#
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
|
|
|
| 139 |
|
| 140 |
-
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
with torch.no_grad():
|
| 144 |
-
v_p = v_p.to(_device)
|
| 145 |
vector = _model.model.model_protein(v_p).cpu().numpy()[0].tolist()
|
| 146 |
return vector
|
| 147 |
else:
|
| 148 |
raise HTTPException(status_code=400, detail="type must be 'drug' or 'target'")
|
| 149 |
|
|
|
|
|
|
|
| 150 |
except Exception as e:
|
| 151 |
import traceback
|
| 152 |
traceback.print_exc()
|
|
@@ -158,7 +179,17 @@ async def search_vectors(req: SearchRequest):
|
|
| 158 |
if not _qdrant:
|
| 159 |
raise HTTPException(status_code=503, detail="Qdrant not connected")
|
| 160 |
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
try:
|
| 164 |
hits = _qdrant.search(
|
|
@@ -182,6 +213,40 @@ async def search_vectors(req: SearchRequest):
|
|
| 182 |
|
| 183 |
return {"results": results, "query_type": req.type, "count": len(results)}
|
| 184 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
@app.get("/api/points")
|
| 186 |
async def get_visualization_points(limit: int = 500, view: str = "combined"):
|
| 187 |
"""Get points with pre-computed PCA for 3D visualization."""
|
|
@@ -246,6 +311,44 @@ def health():
|
|
| 246 |
"metrics": METRICS,
|
| 247 |
}
|
| 248 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
if __name__ == "__main__":
|
| 250 |
import uvicorn
|
| 251 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
|
| 50 |
|
| 51 |
class SearchRequest(BaseModel):
|
| 52 |
query: str
|
| 53 |
+
type: str # "drug" (SMILES) or "target" (Sequence) or "text" (plain text search)
|
| 54 |
limit: int = 20
|
| 55 |
|
| 56 |
class PointsRequest(BaseModel):
|
|
|
|
| 89 |
else:
|
| 90 |
print(f"[WARNING] No model.pt found at {model_path}")
|
| 91 |
|
| 92 |
+
# CRITICAL FIX: Override DeepPurpose's global device variable
|
| 93 |
+
# The encoders.py uses a module-level `device = torch.device('cuda' if...)`
|
| 94 |
+
# and the MLP forward does `v = v.float().to(device)` using that global!
|
| 95 |
+
import DeepPurpose.encoders as dp_encoders
|
| 96 |
+
_device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 97 |
+
dp_encoders.device = _device # Override the global
|
| 98 |
+
print(f"[STARTUP] Using device: {_device}")
|
| 99 |
+
|
| 100 |
+
# Ensure model is on the correct device
|
| 101 |
+
_model.model = _model.model.to(_device)
|
| 102 |
_model.model.eval()
|
| 103 |
|
| 104 |
print("[STARTUP] Connecting to Qdrant...")
|
|
|
|
| 113 |
print("[STARTUP] Ready!")
|
| 114 |
|
| 115 |
def encode_query(query: str, query_type: str) -> List[float]:
|
| 116 |
+
"""Encode a single drug/target query into a vector using direct encoding."""
|
| 117 |
if not _model:
|
| 118 |
raise HTTPException(status_code=503, detail="Model not initialized")
|
| 119 |
|
| 120 |
try:
|
| 121 |
if query_type == "drug":
|
| 122 |
+
# Direct Morgan fingerprint encoding (avoid data_process)
|
| 123 |
+
from DeepPurpose.utils import smiles2morgan
|
| 124 |
+
from rdkit import Chem
|
| 125 |
+
import numpy as np
|
| 126 |
+
|
| 127 |
+
# Validate SMILES
|
| 128 |
+
mol = Chem.MolFromSmiles(query)
|
| 129 |
+
if mol is None:
|
| 130 |
+
raise ValueError(f"Invalid SMILES: {query}")
|
| 131 |
|
| 132 |
+
# Get Morgan fingerprint
|
| 133 |
+
morgan_fp = smiles2morgan(query, radius=2, nBits=1024)
|
| 134 |
+
if morgan_fp is None:
|
| 135 |
+
raise ValueError(f"Failed to compute Morgan fingerprint for: {query}")
|
| 136 |
+
|
| 137 |
+
# Convert to tensor and encode through model's drug encoder
|
| 138 |
+
v_d = torch.tensor(np.array([morgan_fp]), dtype=torch.float32)
|
| 139 |
|
| 140 |
with torch.no_grad():
|
|
|
|
| 141 |
vector = _model.model.model_drug(v_d).cpu().numpy()[0].tolist()
|
| 142 |
return vector
|
| 143 |
|
| 144 |
elif query_type == "target":
|
| 145 |
+
# Direct CNN target encoding
|
| 146 |
+
from DeepPurpose.utils import trans_protein
|
| 147 |
+
import numpy as np
|
| 148 |
+
|
| 149 |
+
# Encode protein sequence
|
| 150 |
+
target_encoding = trans_protein(query)
|
| 151 |
+
if target_encoding is None:
|
| 152 |
+
raise ValueError(f"Failed to encode protein sequence")
|
| 153 |
|
| 154 |
+
# CNN expects [batch, seq_len] input, max_len=1000 in default config
|
| 155 |
+
MAX_SEQ_LEN = 1000
|
| 156 |
+
if len(target_encoding) > MAX_SEQ_LEN:
|
| 157 |
+
target_encoding = target_encoding[:MAX_SEQ_LEN]
|
| 158 |
+
else:
|
| 159 |
+
target_encoding = target_encoding + [0] * (MAX_SEQ_LEN - len(target_encoding))
|
| 160 |
+
|
| 161 |
+
v_p = torch.tensor(np.array([target_encoding]), dtype=torch.long)
|
| 162 |
|
| 163 |
with torch.no_grad():
|
|
|
|
| 164 |
vector = _model.model.model_protein(v_p).cpu().numpy()[0].tolist()
|
| 165 |
return vector
|
| 166 |
else:
|
| 167 |
raise HTTPException(status_code=400, detail="type must be 'drug' or 'target'")
|
| 168 |
|
| 169 |
+
except HTTPException:
|
| 170 |
+
raise
|
| 171 |
except Exception as e:
|
| 172 |
import traceback
|
| 173 |
traceback.print_exc()
|
|
|
|
| 179 |
if not _qdrant:
|
| 180 |
raise HTTPException(status_code=503, detail="Qdrant not connected")
|
| 181 |
|
| 182 |
+
# Text search - just filter by payload, no encoding needed
|
| 183 |
+
if req.type == "text":
|
| 184 |
+
return await text_search(req.query, req.limit)
|
| 185 |
+
|
| 186 |
+
# Vector search - encode and search
|
| 187 |
+
try:
|
| 188 |
+
vector = encode_query(req.query, req.type)
|
| 189 |
+
except Exception as e:
|
| 190 |
+
# Fallback to text search if encoding fails
|
| 191 |
+
print(f"Encoding failed ({e}), falling back to text search")
|
| 192 |
+
return await text_search(req.query, req.limit)
|
| 193 |
|
| 194 |
try:
|
| 195 |
hits = _qdrant.search(
|
|
|
|
| 213 |
|
| 214 |
return {"results": results, "query_type": req.type, "count": len(results)}
|
| 215 |
|
| 216 |
+
|
| 217 |
+
async def text_search(query: str, limit: int = 20):
|
| 218 |
+
"""Text-based search through payloads (fallback when encoding fails)."""
|
| 219 |
+
try:
|
| 220 |
+
# Scroll through and filter by SMILES containing the query
|
| 221 |
+
res, _ = _qdrant.scroll(
|
| 222 |
+
collection_name=COLLECTION_NAME,
|
| 223 |
+
limit=500, # Get more to filter through
|
| 224 |
+
with_payload=True,
|
| 225 |
+
with_vectors=False
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
# Filter results that match query in SMILES or other fields
|
| 229 |
+
query_lower = query.lower()
|
| 230 |
+
results = []
|
| 231 |
+
for point in res:
|
| 232 |
+
smiles = point.payload.get("smiles", "").lower()
|
| 233 |
+
# Match if query is substring of SMILES or SMILES contains query
|
| 234 |
+
if query_lower in smiles:
|
| 235 |
+
results.append({
|
| 236 |
+
"id": point.id,
|
| 237 |
+
"score": 0.95 if query_lower == smiles else 0.8, # Higher score for exact match
|
| 238 |
+
"smiles": point.payload.get("smiles"),
|
| 239 |
+
"target_seq": point.payload.get("target_seq", "")[:100] + "...",
|
| 240 |
+
"label": point.payload.get("label_true"),
|
| 241 |
+
"affinity_class": point.payload.get("affinity_class"),
|
| 242 |
+
})
|
| 243 |
+
if len(results) >= limit:
|
| 244 |
+
break
|
| 245 |
+
|
| 246 |
+
return {"results": results, "query_type": "text", "count": len(results)}
|
| 247 |
+
except Exception as e:
|
| 248 |
+
raise HTTPException(status_code=500, detail=f"Text search failed: {str(e)}")
|
| 249 |
+
|
| 250 |
@app.get("/api/points")
|
| 251 |
async def get_visualization_points(limit: int = 500, view: str = "combined"):
|
| 252 |
"""Get points with pre-computed PCA for 3D visualization."""
|
|
|
|
| 311 |
"metrics": METRICS,
|
| 312 |
}
|
| 313 |
|
| 314 |
+
@app.get("/api/stats")
|
| 315 |
+
async def get_collection_stats():
|
| 316 |
+
"""Get real statistics from Qdrant collection for the data page."""
|
| 317 |
+
if not _qdrant:
|
| 318 |
+
raise HTTPException(status_code=503, detail="Qdrant not connected")
|
| 319 |
+
|
| 320 |
+
try:
|
| 321 |
+
collection_info = _qdrant.get_collection(collection_name=COLLECTION_NAME)
|
| 322 |
+
total_vectors = collection_info.vectors_count
|
| 323 |
+
|
| 324 |
+
# Sample to count affinity classes
|
| 325 |
+
sample, _ = _qdrant.scroll(
|
| 326 |
+
collection_name=COLLECTION_NAME,
|
| 327 |
+
limit=1000,
|
| 328 |
+
with_payload=["affinity_class", "smiles", "target_id"],
|
| 329 |
+
with_vectors=False
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
unique_drugs = len(set(p.payload.get("smiles", "") for p in sample if p.payload.get("smiles")))
|
| 333 |
+
unique_targets = len(set(p.payload.get("target_id", "") for p in sample if p.payload.get("target_id")))
|
| 334 |
+
|
| 335 |
+
affinity_counts = {}
|
| 336 |
+
for p in sample:
|
| 337 |
+
aff = p.payload.get("affinity_class", "unknown")
|
| 338 |
+
affinity_counts[aff] = affinity_counts.get(aff, 0) + 1
|
| 339 |
+
|
| 340 |
+
return {
|
| 341 |
+
"total_vectors": total_vectors,
|
| 342 |
+
"sample_size": len(sample),
|
| 343 |
+
"unique_drugs_sampled": unique_drugs,
|
| 344 |
+
"unique_targets_sampled": unique_targets,
|
| 345 |
+
"affinity_distribution": affinity_counts,
|
| 346 |
+
"collection_name": COLLECTION_NAME,
|
| 347 |
+
"status": collection_info.status.value if hasattr(collection_info.status, 'value') else str(collection_info.status),
|
| 348 |
+
}
|
| 349 |
+
except Exception as e:
|
| 350 |
+
raise HTTPException(status_code=500, detail=f"Stats fetch failed: {str(e)}")
|
| 351 |
+
|
| 352 |
if __name__ == "__main__":
|
| 353 |
import uvicorn
|
| 354 |
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -1,62 +0,0 @@
|
|
| 1 |
-
# Setup Guide for DeepPurpose (Windows/CUDA 11.8)
|
| 2 |
-
|
| 3 |
-
This guide reproduces the current working environment for `deeppurpose002.py` on Windows 10/11 with an NVIDIA GPU.
|
| 4 |
-
|
| 5 |
-
## 1. Prerequisites
|
| 6 |
-
- **Python 3.10.x** (Required for compatibility with DGL and DeepPurpose)
|
| 7 |
-
- **NVIDIA GPU** with drivers installed.
|
| 8 |
-
|
| 9 |
-
## 2. Create Virtual Environment
|
| 10 |
-
Open PowerShell/Terminal in your project folder:
|
| 11 |
-
```powershell
|
| 12 |
-
python -3.10 -m venv .venv
|
| 13 |
-
.\.venv\Scripts\Activate
|
| 14 |
-
```
|
| 15 |
-
|
| 16 |
-
## 3. Install PyTorch (CUDA 11.8)
|
| 17 |
-
Install the specific CUDA-enabled version of PyTorch:
|
| 18 |
-
```powershell
|
| 19 |
-
pip install torch==2.7.1+cu118 torchvision==0.22.1+cu118 torchaudio==2.7.1+cu118 --index-url https://download.pytorch.org/whl/cu118
|
| 20 |
-
```
|
| 21 |
-
*(Note: If 2.7.1+cu118 is not available in the future, check pytorch.org for the compatible LTS version matching DGL)*
|
| 22 |
-
|
| 23 |
-
## 4. Install Deep Graph Library (DGL)
|
| 24 |
-
DGL must match the CUDA version:
|
| 25 |
-
```powershell
|
| 26 |
-
pip install dgl==2.2.1+cu118 -f https://data.dgl.ai/wheels/cu118/repo.html
|
| 27 |
-
pip install dgllife
|
| 28 |
-
```
|
| 29 |
-
|
| 30 |
-
## 5. Install Core Dependencies
|
| 31 |
-
DeepPurpose and PyTDC (Therapeutics Data Commons):
|
| 32 |
-
```powershell
|
| 33 |
-
pip install DeepPurpose PyTDC
|
| 34 |
-
pip install git+https://github.com/bp-kelley/descriptastorus
|
| 35 |
-
pip install pandas numpy matplotlib prettytable tensorboard lifelines scikit-learn
|
| 36 |
-
```
|
| 37 |
-
|
| 38 |
-
## 6. Windows-Specific Fixes (Crucial)
|
| 39 |
-
|
| 40 |
-
### Fix A: PyTDC Dependency Issues (`tiledbsoma`, `cellxgene_census`)
|
| 41 |
-
PyTDC tries to install `cellxgene_census`, which depends on `tiledbsoma`. On Windows, `tiledbsoma` often fails to build.
|
| 42 |
-
**Solution:** If `pip install PyTDC` fails, manually install dependencies excluding the problematic ones, or create mock files if the code doesn't actually use them (PyTDC generally works without them for standard datasets like DAVIS/KIBA).
|
| 43 |
-
|
| 44 |
-
### Fix B: DGL GraphBolt DLL Error
|
| 45 |
-
DGL 2.x includes "GraphBolt" which requires C++ DLLs that may be missing or incompatible on Windows.
|
| 46 |
-
**Error:** `FileNotFoundError: ... graphbolt_pytorch_X.X.X.dll`
|
| 47 |
-
**Solution:**
|
| 48 |
-
1. **Code Fix (Recommended):** Add this line to the **very top** of your Python script (before `import torch` or `import dgl`):
|
| 49 |
-
```python
|
| 50 |
-
import os
|
| 51 |
-
os.environ["DGL_DISABLE_GRAPHBOLT"] = "1"
|
| 52 |
-
```
|
| 53 |
-
2. **Library Fix (Manual):** If the error persists, edit `.venv\Lib\site-packages\dgl\graphbolt\__init__.py` and comment out all imports to prevent it from loading.
|
| 54 |
-
|
| 55 |
-
## 7. Running the Script
|
| 56 |
-
```powershell
|
| 57 |
-
python deeppurpose002.py
|
| 58 |
-
```
|
| 59 |
-
To force usage of a specific GPU encoding:
|
| 60 |
-
```powershell
|
| 61 |
-
python deeppurpose002.py --drug_enc DGL_GCN --target_enc CNN --epochs 10
|
| 62 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,6 +1,7 @@
|
|
| 1 |
"use client"
|
| 2 |
|
| 3 |
-
import { CloudUpload, Database, Download, Eye, FileText, Folder, HardDrive, Trash2,Upload } from "lucide-react"
|
|
|
|
| 4 |
|
| 5 |
import { SectionHeader } from "@/components/page-header"
|
| 6 |
import { Badge } from "@/components/ui/badge"
|
|
@@ -16,6 +17,8 @@ interface DataViewProps {
|
|
| 16 |
}
|
| 17 |
|
| 18 |
export function DataView({ datasets, stats }: DataViewProps) {
|
|
|
|
|
|
|
| 19 |
const statCards = [
|
| 20 |
{ label: "Datasets", value: stats?.datasets?.toString() ?? "—", icon: Folder, color: "text-blue-500" },
|
| 21 |
{ label: "Molecules", value: stats?.molecules ?? "—", icon: FileText, color: "text-cyan-500" },
|
|
@@ -23,6 +26,16 @@ export function DataView({ datasets, stats }: DataViewProps) {
|
|
| 23 |
{ label: "Storage Used", value: stats?.storage ?? "—", icon: HardDrive, color: "text-amber-500" },
|
| 24 |
]
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
return (
|
| 27 |
<div className="space-y-8">
|
| 28 |
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
@@ -70,7 +83,7 @@ export function DataView({ datasets, stats }: DataViewProps) {
|
|
| 70 |
<TableCell className="font-medium">{ds.name}</TableCell>
|
| 71 |
<TableCell>
|
| 72 |
<div className="flex items-center gap-2">
|
| 73 |
-
<Badge variant={ds.type === '
|
| 74 |
</div>
|
| 75 |
</TableCell>
|
| 76 |
<TableCell>{ds.count}</TableCell>
|
|
@@ -78,9 +91,38 @@ export function DataView({ datasets, stats }: DataViewProps) {
|
|
| 78 |
<TableCell>{ds.updated}</TableCell>
|
| 79 |
<TableCell className="text-right">
|
| 80 |
<div className="flex justify-end gap-2">
|
| 81 |
-
<Button
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
</div>
|
| 85 |
</TableCell>
|
| 86 |
</TableRow>
|
|
|
|
| 1 |
"use client"
|
| 2 |
|
| 3 |
+
import { CloudUpload, Database, Download, Eye, FileText, Folder, HardDrive, Trash2, Upload, ExternalLink } from "lucide-react"
|
| 4 |
+
import { useRouter } from "next/navigation"
|
| 5 |
|
| 6 |
import { SectionHeader } from "@/components/page-header"
|
| 7 |
import { Badge } from "@/components/ui/badge"
|
|
|
|
| 17 |
}
|
| 18 |
|
| 19 |
export function DataView({ datasets, stats }: DataViewProps) {
|
| 20 |
+
const router = useRouter()
|
| 21 |
+
|
| 22 |
const statCards = [
|
| 23 |
{ label: "Datasets", value: stats?.datasets?.toString() ?? "—", icon: Folder, color: "text-blue-500" },
|
| 24 |
{ label: "Molecules", value: stats?.molecules ?? "—", icon: FileText, color: "text-cyan-500" },
|
|
|
|
| 26 |
{ label: "Storage Used", value: stats?.storage ?? "—", icon: HardDrive, color: "text-amber-500" },
|
| 27 |
]
|
| 28 |
|
| 29 |
+
const handleView = (dataset: Dataset) => {
|
| 30 |
+
// Navigate to explorer with this dataset's data
|
| 31 |
+
router.push(`/explorer?dataset=${encodeURIComponent(dataset.name)}`)
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
const handleDownload = (dataset: Dataset) => {
|
| 35 |
+
// For KIBA/DAVIS, these are from TDC - provide info
|
| 36 |
+
alert(`Dataset: ${dataset.name}\n\nThis dataset is loaded from Therapeutics Data Commons (TDC).\n\nTo download raw data, visit: https://tdcommons.ai/\n\nOr access via Python:\nfrom tdc.multi_pred import DTI\ndata = DTI(name='${dataset.name.includes('KIBA') ? 'KIBA' : 'DAVIS'}')`)
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
return (
|
| 40 |
<div className="space-y-8">
|
| 41 |
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
|
|
| 83 |
<TableCell className="font-medium">{ds.name}</TableCell>
|
| 84 |
<TableCell>
|
| 85 |
<div className="flex items-center gap-2">
|
| 86 |
+
<Badge variant={ds.type === 'Drug-Target' ? 'default' : 'secondary'}>{ds.type}</Badge>
|
| 87 |
</div>
|
| 88 |
</TableCell>
|
| 89 |
<TableCell>{ds.count}</TableCell>
|
|
|
|
| 91 |
<TableCell>{ds.updated}</TableCell>
|
| 92 |
<TableCell className="text-right">
|
| 93 |
<div className="flex justify-end gap-2">
|
| 94 |
+
<Button
|
| 95 |
+
size="icon"
|
| 96 |
+
variant="ghost"
|
| 97 |
+
className="h-8 w-8"
|
| 98 |
+
onClick={() => handleView(ds)}
|
| 99 |
+
title="View in Explorer"
|
| 100 |
+
>
|
| 101 |
+
<Eye className="h-4 w-4" />
|
| 102 |
+
</Button>
|
| 103 |
+
<Button
|
| 104 |
+
size="icon"
|
| 105 |
+
variant="ghost"
|
| 106 |
+
className="h-8 w-8"
|
| 107 |
+
onClick={() => handleDownload(ds)}
|
| 108 |
+
title="Download Info"
|
| 109 |
+
>
|
| 110 |
+
<Download className="h-4 w-4" />
|
| 111 |
+
</Button>
|
| 112 |
+
<a
|
| 113 |
+
href="https://tdcommons.ai/"
|
| 114 |
+
target="_blank"
|
| 115 |
+
rel="noopener noreferrer"
|
| 116 |
+
>
|
| 117 |
+
<Button
|
| 118 |
+
size="icon"
|
| 119 |
+
variant="ghost"
|
| 120 |
+
className="h-8 w-8"
|
| 121 |
+
title="View on TDC"
|
| 122 |
+
>
|
| 123 |
+
<ExternalLink className="h-4 w-4" />
|
| 124 |
+
</Button>
|
| 125 |
+
</a>
|
| 126 |
</div>
|
| 127 |
</TableCell>
|
| 128 |
</TableRow>
|
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"use client"
|
| 2 |
|
| 3 |
-
import { ArrowRight,CheckCircle2, Circle, Loader2, Microscope, Search } from "lucide-react"
|
| 4 |
import * as React from "react"
|
| 5 |
|
| 6 |
import { PageHeader, SectionHeader } from "@/components/page-header"
|
|
@@ -11,22 +11,89 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|
| 11 |
import { Tabs, TabsContent,TabsList, TabsTrigger } from "@/components/ui/tabs"
|
| 12 |
import { Textarea } from "@/components/ui/textarea"
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
export default function DiscoveryPage() {
|
| 15 |
const [query, setQuery] = React.useState("")
|
|
|
|
| 16 |
const [isSearching, setIsSearching] = React.useState(false)
|
| 17 |
const [step, setStep] = React.useState(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
const handleSearch = () => {
|
|
|
|
|
|
|
| 20 |
setIsSearching(true)
|
| 21 |
setStep(1)
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
}
|
| 31 |
|
| 32 |
const steps = [
|
|
@@ -41,7 +108,7 @@ export default function DiscoveryPage() {
|
|
| 41 |
<div className="space-y-8 animate-in fade-in duration-500">
|
| 42 |
<PageHeader
|
| 43 |
title="Drug Discovery"
|
| 44 |
-
subtitle="Search for drug candidates
|
| 45 |
icon={<Microscope className="h-8 w-8" />}
|
| 46 |
/>
|
| 47 |
|
|
@@ -51,7 +118,13 @@ export default function DiscoveryPage() {
|
|
| 51 |
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
| 52 |
<div className="md:col-span-3">
|
| 53 |
<Textarea
|
| 54 |
-
placeholder=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
className="min-h-[120px] font-mono"
|
| 56 |
value={query}
|
| 57 |
onChange={(e) => setQuery(e.target.value)}
|
|
@@ -60,28 +133,26 @@ export default function DiscoveryPage() {
|
|
| 60 |
<div className="space-y-4">
|
| 61 |
<div className="space-y-2">
|
| 62 |
<Label>Search Type</Label>
|
| 63 |
-
<Select
|
| 64 |
<SelectTrigger>
|
| 65 |
<SelectValue placeholder="Select type" />
|
| 66 |
</SelectTrigger>
|
| 67 |
<SelectContent>
|
| 68 |
-
<SelectItem value="Similarity">Similarity</SelectItem>
|
| 69 |
-
<SelectItem value="Binding Affinity">Binding Affinity</SelectItem>
|
| 70 |
-
<SelectItem value="Properties">Properties</SelectItem>
|
| 71 |
</SelectContent>
|
| 72 |
</Select>
|
| 73 |
</div>
|
| 74 |
<div className="space-y-2">
|
| 75 |
<Label>Database</Label>
|
| 76 |
-
<Select defaultValue="
|
| 77 |
<SelectTrigger>
|
| 78 |
<SelectValue placeholder="Select database" />
|
| 79 |
</SelectTrigger>
|
| 80 |
<SelectContent>
|
| 81 |
-
<SelectItem value="
|
| 82 |
-
<SelectItem value="
|
| 83 |
-
<SelectItem value="ChEMBL">ChEMBL</SelectItem>
|
| 84 |
-
<SelectItem value="ZINC">ZINC</SelectItem>
|
| 85 |
</SelectContent>
|
| 86 |
</Select>
|
| 87 |
</div>
|
|
@@ -91,13 +162,28 @@ export default function DiscoveryPage() {
|
|
| 91 |
disabled={isSearching || !query}
|
| 92 |
>
|
| 93 |
{isSearching ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Search className="mr-2 h-4 w-4" />}
|
| 94 |
-
{isSearching ? "
|
| 95 |
</Button>
|
| 96 |
</div>
|
| 97 |
</div>
|
| 98 |
</CardContent>
|
| 99 |
</Card>
|
| 100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
<div className="space-y-4">
|
| 102 |
<SectionHeader title="Pipeline Status" icon={<ArrowRight className="h-5 w-5 text-muted-foreground" />} />
|
| 103 |
|
|
@@ -121,63 +207,61 @@ export default function DiscoveryPage() {
|
|
| 121 |
</div>
|
| 122 |
</div>
|
| 123 |
|
| 124 |
-
{step === 4 && (
|
| 125 |
<div className="space-y-4 animate-in slide-in-from-bottom-4 duration-500">
|
| 126 |
-
<SectionHeader title=
|
| 127 |
|
| 128 |
<Tabs defaultValue="candidates">
|
| 129 |
<TabsList>
|
| 130 |
<TabsTrigger value="candidates">Top Candidates</TabsTrigger>
|
| 131 |
-
<TabsTrigger value="
|
| 132 |
-
<TabsTrigger value="evidence">Evidence</TabsTrigger>
|
| 133 |
</TabsList>
|
| 134 |
<TabsContent value="candidates" className="space-y-4">
|
| 135 |
-
{
|
| 136 |
-
|
| 137 |
-
{ name: "Candidate B", score: 0.89, mw: "298.3", logp: "1.8" },
|
| 138 |
-
{ name: "Candidate C", score: 0.82, mw: "415.5", logp: "3.2" },
|
| 139 |
-
{ name: "Candidate D", score: 0.76, mw: "267.3", logp: "1.5" },
|
| 140 |
-
{ name: "Candidate E", score: 0.71, mw: "389.4", logp: "2.8" },
|
| 141 |
-
].map((candidate, i) => (
|
| 142 |
-
<Card key={i}>
|
| 143 |
<CardContent className="p-4 flex items-center justify-between">
|
| 144 |
-
<div>
|
| 145 |
-
<div className="font-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
| 149 |
</div>
|
| 150 |
</div>
|
| 151 |
<div className="text-right">
|
| 152 |
-
<div className="text-sm text-muted-foreground">
|
| 153 |
<div className={`text-xl font-bold ${
|
| 154 |
-
|
| 155 |
-
|
| 156 |
}`}>
|
| 157 |
-
{
|
| 158 |
</div>
|
| 159 |
</div>
|
| 160 |
</CardContent>
|
| 161 |
</Card>
|
| 162 |
))}
|
| 163 |
</TabsContent>
|
| 164 |
-
<TabsContent value="
|
| 165 |
<Card>
|
| 166 |
-
<CardContent className="p-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
</TabsContent>
|
| 171 |
-
<TabsContent value="evidence">
|
| 172 |
-
<Card>
|
| 173 |
-
<CardContent className="p-12 text-center text-muted-foreground">
|
| 174 |
-
Evidence graph visualization would go here.
|
| 175 |
</CardContent>
|
| 176 |
</Card>
|
| 177 |
</TabsContent>
|
| 178 |
</Tabs>
|
| 179 |
</div>
|
| 180 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
</div>
|
| 182 |
)
|
| 183 |
}
|
|
|
|
| 1 |
"use client"
|
| 2 |
|
| 3 |
+
import { ArrowRight,CheckCircle2, Circle, Loader2, Microscope, Search, AlertCircle } from "lucide-react"
|
| 4 |
import * as React from "react"
|
| 5 |
|
| 6 |
import { PageHeader, SectionHeader } from "@/components/page-header"
|
|
|
|
| 11 |
import { Tabs, TabsContent,TabsList, TabsTrigger } from "@/components/ui/tabs"
|
| 12 |
import { Textarea } from "@/components/ui/textarea"
|
| 13 |
|
| 14 |
+
const API_BASE = "http://localhost:8001";
|
| 15 |
+
|
| 16 |
+
interface SearchResult {
|
| 17 |
+
id: string;
|
| 18 |
+
score: number;
|
| 19 |
+
smiles: string;
|
| 20 |
+
target_seq: string;
|
| 21 |
+
label: number;
|
| 22 |
+
affinity_class: string;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
export default function DiscoveryPage() {
|
| 26 |
const [query, setQuery] = React.useState("")
|
| 27 |
+
const [searchType, setSearchType] = React.useState("Similarity")
|
| 28 |
const [isSearching, setIsSearching] = React.useState(false)
|
| 29 |
const [step, setStep] = React.useState(0)
|
| 30 |
+
const [results, setResults] = React.useState<SearchResult[]>([])
|
| 31 |
+
const [error, setError] = React.useState<string | null>(null)
|
| 32 |
+
|
| 33 |
+
// Map UI search type to API type
|
| 34 |
+
const getApiType = (uiType: string, query: string): string => {
|
| 35 |
+
// If it looks like SMILES (contains chemistry chars), use drug encoding
|
| 36 |
+
const looksLikeSmiles = /^[A-Za-z0-9@+\-\[\]\(\)\\\/=#$.]+$/.test(query.trim())
|
| 37 |
+
// If it looks like protein sequence (all caps amino acids)
|
| 38 |
+
const looksLikeProtein = /^[ACDEFGHIKLMNPQRSTVWY]+$/i.test(query.trim()) && query.length > 20
|
| 39 |
+
|
| 40 |
+
if (uiType === "Similarity" || uiType === "Binding Affinity") {
|
| 41 |
+
if (looksLikeSmiles && !looksLikeProtein) return "drug"
|
| 42 |
+
if (looksLikeProtein) return "target"
|
| 43 |
+
return "text" // Fallback to text search
|
| 44 |
+
}
|
| 45 |
+
return "text"
|
| 46 |
+
}
|
| 47 |
|
| 48 |
+
const handleSearch = async () => {
|
| 49 |
+
if (!query.trim()) return;
|
| 50 |
+
|
| 51 |
setIsSearching(true)
|
| 52 |
setStep(1)
|
| 53 |
+
setError(null)
|
| 54 |
+
setResults([])
|
| 55 |
|
| 56 |
+
try {
|
| 57 |
+
// Step 1: Input received
|
| 58 |
+
setStep(1)
|
| 59 |
+
|
| 60 |
+
// Step 2: Determine type and encode
|
| 61 |
+
await new Promise(r => setTimeout(r, 300))
|
| 62 |
+
setStep(2)
|
| 63 |
+
|
| 64 |
+
const apiType = getApiType(searchType, query)
|
| 65 |
+
|
| 66 |
+
// Step 3: Actually search Qdrant via our API
|
| 67 |
+
const response = await fetch(`${API_BASE}/api/search`, {
|
| 68 |
+
method: 'POST',
|
| 69 |
+
headers: { 'Content-Type': 'application/json' },
|
| 70 |
+
body: JSON.stringify({
|
| 71 |
+
query: query.trim(),
|
| 72 |
+
type: apiType,
|
| 73 |
+
limit: 10
|
| 74 |
+
})
|
| 75 |
+
});
|
| 76 |
+
|
| 77 |
+
setStep(3)
|
| 78 |
+
|
| 79 |
+
if (!response.ok) {
|
| 80 |
+
const errData = await response.json().catch(() => ({}));
|
| 81 |
+
throw new Error(errData.detail || `API error: ${response.status}`);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
const data = await response.json();
|
| 85 |
+
|
| 86 |
+
// Step 4: Process results
|
| 87 |
+
await new Promise(r => setTimeout(r, 200))
|
| 88 |
+
setStep(4)
|
| 89 |
+
setResults(data.results || [])
|
| 90 |
+
|
| 91 |
+
} catch (err) {
|
| 92 |
+
setError(err instanceof Error ? err.message : 'Search failed');
|
| 93 |
+
setStep(0)
|
| 94 |
+
} finally {
|
| 95 |
+
setIsSearching(false)
|
| 96 |
+
}
|
| 97 |
}
|
| 98 |
|
| 99 |
const steps = [
|
|
|
|
| 108 |
<div className="space-y-8 animate-in fade-in duration-500">
|
| 109 |
<PageHeader
|
| 110 |
title="Drug Discovery"
|
| 111 |
+
subtitle="Search for drug candidates using DeepPurpose + Qdrant"
|
| 112 |
icon={<Microscope className="h-8 w-8" />}
|
| 113 |
/>
|
| 114 |
|
|
|
|
| 118 |
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
| 119 |
<div className="md:col-span-3">
|
| 120 |
<Textarea
|
| 121 |
+
placeholder={
|
| 122 |
+
searchType === "Similarity"
|
| 123 |
+
? "Enter SMILES string (e.g., CC(=O)Nc1ccc(O)cc1 for Acetaminophen)"
|
| 124 |
+
: searchType === "Binding Affinity"
|
| 125 |
+
? "Enter protein sequence (amino acids, e.g., MKKFFD...)"
|
| 126 |
+
: "Enter drug name or keyword to search"
|
| 127 |
+
}
|
| 128 |
className="min-h-[120px] font-mono"
|
| 129 |
value={query}
|
| 130 |
onChange={(e) => setQuery(e.target.value)}
|
|
|
|
| 133 |
<div className="space-y-4">
|
| 134 |
<div className="space-y-2">
|
| 135 |
<Label>Search Type</Label>
|
| 136 |
+
<Select value={searchType} onValueChange={setSearchType}>
|
| 137 |
<SelectTrigger>
|
| 138 |
<SelectValue placeholder="Select type" />
|
| 139 |
</SelectTrigger>
|
| 140 |
<SelectContent>
|
| 141 |
+
<SelectItem value="Similarity">Similarity (Drug SMILES)</SelectItem>
|
| 142 |
+
<SelectItem value="Binding Affinity">Binding Affinity (Protein)</SelectItem>
|
| 143 |
+
<SelectItem value="Properties">Properties (Text Search)</SelectItem>
|
| 144 |
</SelectContent>
|
| 145 |
</Select>
|
| 146 |
</div>
|
| 147 |
<div className="space-y-2">
|
| 148 |
<Label>Database</Label>
|
| 149 |
+
<Select defaultValue="KIBA">
|
| 150 |
<SelectTrigger>
|
| 151 |
<SelectValue placeholder="Select database" />
|
| 152 |
</SelectTrigger>
|
| 153 |
<SelectContent>
|
| 154 |
+
<SelectItem value="KIBA">KIBA (23.5K pairs)</SelectItem>
|
| 155 |
+
<SelectItem value="DAVIS">DAVIS Kinase</SelectItem>
|
|
|
|
|
|
|
| 156 |
</SelectContent>
|
| 157 |
</Select>
|
| 158 |
</div>
|
|
|
|
| 162 |
disabled={isSearching || !query}
|
| 163 |
>
|
| 164 |
{isSearching ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Search className="mr-2 h-4 w-4" />}
|
| 165 |
+
{isSearching ? "Searching Qdrant..." : "Search"}
|
| 166 |
</Button>
|
| 167 |
</div>
|
| 168 |
</div>
|
| 169 |
</CardContent>
|
| 170 |
</Card>
|
| 171 |
|
| 172 |
+
{error && (
|
| 173 |
+
<Card className="border-destructive">
|
| 174 |
+
<CardContent className="p-4 flex items-center gap-3 text-destructive">
|
| 175 |
+
<AlertCircle className="h-5 w-5" />
|
| 176 |
+
<div>
|
| 177 |
+
<div className="font-medium">Search Failed</div>
|
| 178 |
+
<div className="text-sm">{error}</div>
|
| 179 |
+
<div className="text-xs mt-1 text-muted-foreground">
|
| 180 |
+
Make sure the API server is running: python -m uvicorn server.api:app --port 8001
|
| 181 |
+
</div>
|
| 182 |
+
</div>
|
| 183 |
+
</CardContent>
|
| 184 |
+
</Card>
|
| 185 |
+
)}
|
| 186 |
+
|
| 187 |
<div className="space-y-4">
|
| 188 |
<SectionHeader title="Pipeline Status" icon={<ArrowRight className="h-5 w-5 text-muted-foreground" />} />
|
| 189 |
|
|
|
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
|
| 210 |
+
{step === 4 && results.length > 0 && (
|
| 211 |
<div className="space-y-4 animate-in slide-in-from-bottom-4 duration-500">
|
| 212 |
+
<SectionHeader title={`Results (${results.length} from Qdrant)`} icon={<CheckCircle2 className="h-5 w-5 text-green-500" />} />
|
| 213 |
|
| 214 |
<Tabs defaultValue="candidates">
|
| 215 |
<TabsList>
|
| 216 |
<TabsTrigger value="candidates">Top Candidates</TabsTrigger>
|
| 217 |
+
<TabsTrigger value="details">Raw Data</TabsTrigger>
|
|
|
|
| 218 |
</TabsList>
|
| 219 |
<TabsContent value="candidates" className="space-y-4">
|
| 220 |
+
{results.map((result, i) => (
|
| 221 |
+
<Card key={result.id}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
<CardContent className="p-4 flex items-center justify-between">
|
| 223 |
+
<div className="flex-1">
|
| 224 |
+
<div className="font-mono text-sm font-medium">
|
| 225 |
+
{result.smiles?.slice(0, 50)}{result.smiles?.length > 50 ? '...' : ''}
|
| 226 |
+
</div>
|
| 227 |
+
<div className="flex gap-4 text-sm text-muted-foreground mt-1">
|
| 228 |
+
<span>Affinity: {result.affinity_class}</span>
|
| 229 |
+
<span>Label: {result.label?.toFixed(2)}</span>
|
| 230 |
</div>
|
| 231 |
</div>
|
| 232 |
<div className="text-right">
|
| 233 |
+
<div className="text-sm text-muted-foreground">Similarity</div>
|
| 234 |
<div className={`text-xl font-bold ${
|
| 235 |
+
result.score >= 0.9 ? 'text-green-600' :
|
| 236 |
+
result.score >= 0.7 ? 'text-green-500' : 'text-amber-500'
|
| 237 |
}`}>
|
| 238 |
+
{result.score.toFixed(3)}
|
| 239 |
</div>
|
| 240 |
</div>
|
| 241 |
</CardContent>
|
| 242 |
</Card>
|
| 243 |
))}
|
| 244 |
</TabsContent>
|
| 245 |
+
<TabsContent value="details">
|
| 246 |
<Card>
|
| 247 |
+
<CardContent className="p-4">
|
| 248 |
+
<pre className="text-xs overflow-auto max-h-[400px] bg-muted p-4 rounded">
|
| 249 |
+
{JSON.stringify(results, null, 2)}
|
| 250 |
+
</pre>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
</CardContent>
|
| 252 |
</Card>
|
| 253 |
</TabsContent>
|
| 254 |
</Tabs>
|
| 255 |
</div>
|
| 256 |
)}
|
| 257 |
+
|
| 258 |
+
{step === 4 && results.length === 0 && !error && (
|
| 259 |
+
<Card>
|
| 260 |
+
<CardContent className="p-8 text-center text-muted-foreground">
|
| 261 |
+
No similar compounds found in Qdrant.
|
| 262 |
+
</CardContent>
|
| 263 |
+
</Card>
|
| 264 |
+
)}
|
| 265 |
</div>
|
| 266 |
)
|
| 267 |
}
|
|
@@ -49,9 +49,9 @@ export function ExplorerControls() {
|
|
| 49 |
<SelectValue placeholder="Select dataset" />
|
| 50 |
</SelectTrigger>
|
| 51 |
<SelectContent>
|
| 52 |
-
<SelectItem value="
|
| 53 |
-
<SelectItem value="
|
| 54 |
-
<SelectItem value="
|
| 55 |
</SelectContent>
|
| 56 |
</Select>
|
| 57 |
</div>
|
|
|
|
| 49 |
<SelectValue placeholder="Select dataset" />
|
| 50 |
</SelectTrigger>
|
| 51 |
<SelectContent>
|
| 52 |
+
<SelectItem value="KIBA">KIBA (23.5K)</SelectItem>
|
| 53 |
+
<SelectItem value="DAVIS">DAVIS Kinase</SelectItem>
|
| 54 |
+
<SelectItem value="BindingDB">BindingDB Kd</SelectItem>
|
| 55 |
</SelectContent>
|
| 56 |
</Select>
|
| 57 |
</div>
|
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { memo } from 'react';
|
| 4 |
+
import { Handle, Position, NodeProps } from 'reactflow';
|
| 5 |
+
import { Database, FlaskConical, FileSearch, Shield, Brain, Dna, FileText } from 'lucide-react';
|
| 6 |
+
|
| 7 |
+
// Base node wrapper with consistent styling
|
| 8 |
+
const NodeWrapper = ({
|
| 9 |
+
children,
|
| 10 |
+
color,
|
| 11 |
+
label,
|
| 12 |
+
icon: Icon,
|
| 13 |
+
status = 'idle'
|
| 14 |
+
}: {
|
| 15 |
+
children?: React.ReactNode;
|
| 16 |
+
color: string;
|
| 17 |
+
label: string;
|
| 18 |
+
icon: React.ElementType;
|
| 19 |
+
status?: 'idle' | 'running' | 'complete' | 'error';
|
| 20 |
+
}) => {
|
| 21 |
+
const statusColors = {
|
| 22 |
+
idle: 'border-gray-600',
|
| 23 |
+
running: 'border-yellow-500 animate-pulse',
|
| 24 |
+
complete: 'border-green-500',
|
| 25 |
+
error: 'border-red-500'
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
return (
|
| 29 |
+
<div className={`
|
| 30 |
+
min-w-[180px] rounded-xl border-2 ${statusColors[status]}
|
| 31 |
+
bg-gray-900/95 backdrop-blur shadow-2xl overflow-hidden
|
| 32 |
+
`}>
|
| 33 |
+
<div className={`px-3 py-2 ${color} flex items-center gap-2`}>
|
| 34 |
+
<Icon className="w-4 h-4 text-white" />
|
| 35 |
+
<span className="text-sm font-semibold text-white">{label}</span>
|
| 36 |
+
</div>
|
| 37 |
+
<div className="p-3 text-xs text-gray-300">
|
| 38 |
+
{children}
|
| 39 |
+
</div>
|
| 40 |
+
</div>
|
| 41 |
+
);
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
// Data Input Node
|
| 45 |
+
export const DataInputNode = memo(({ data }: NodeProps) => (
|
| 46 |
+
<>
|
| 47 |
+
<NodeWrapper color="bg-blue-600" label="Data Input" icon={FileText} status={data.status}>
|
| 48 |
+
<div className="space-y-1">
|
| 49 |
+
<div className="text-gray-400">Type: <span className="text-white">{data.inputType || 'SMILES'}</span></div>
|
| 50 |
+
<div className="text-gray-400 truncate max-w-[150px]">
|
| 51 |
+
Input: <span className="text-white font-mono">{data.input || 'CC(=O)O...'}</span>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
</NodeWrapper>
|
| 55 |
+
<Handle type="source" position={Position.Right} className="w-3 h-3 !bg-blue-500" />
|
| 56 |
+
</>
|
| 57 |
+
));
|
| 58 |
+
DataInputNode.displayName = 'DataInputNode';
|
| 59 |
+
|
| 60 |
+
// DeepPurpose Generator Node
|
| 61 |
+
export const GeneratorNode = memo(({ data }: NodeProps) => (
|
| 62 |
+
<>
|
| 63 |
+
<Handle type="target" position={Position.Left} className="w-3 h-3 !bg-purple-500" />
|
| 64 |
+
<NodeWrapper color="bg-purple-600" label="DeepPurpose DTI" icon={FlaskConical} status={data.status}>
|
| 65 |
+
<div className="space-y-1">
|
| 66 |
+
<div className="text-gray-400">Model: <span className="text-white">Morgan + CNN</span></div>
|
| 67 |
+
<div className="text-gray-400">Encoding: <span className="text-green-400">256D</span></div>
|
| 68 |
+
{data.prediction && (
|
| 69 |
+
<div className="text-gray-400">Affinity: <span className="text-emerald-400">{data.prediction}</span></div>
|
| 70 |
+
)}
|
| 71 |
+
</div>
|
| 72 |
+
</NodeWrapper>
|
| 73 |
+
<Handle type="source" position={Position.Right} className="w-3 h-3 !bg-purple-500" />
|
| 74 |
+
</>
|
| 75 |
+
));
|
| 76 |
+
GeneratorNode.displayName = 'GeneratorNode';
|
| 77 |
+
|
| 78 |
+
// Qdrant Storage Node
|
| 79 |
+
export const QdrantNode = memo(({ data }: NodeProps) => (
|
| 80 |
+
<>
|
| 81 |
+
<Handle type="target" position={Position.Left} className="w-3 h-3 !bg-orange-500" />
|
| 82 |
+
<NodeWrapper color="bg-orange-600" label="Qdrant Vector DB" icon={Database} status={data.status}>
|
| 83 |
+
<div className="space-y-1">
|
| 84 |
+
<div className="text-gray-400">Collection: <span className="text-white">bio_discovery</span></div>
|
| 85 |
+
<div className="text-gray-400">Vectors: <span className="text-cyan-400">{data.vectorCount || '23,531'}</span></div>
|
| 86 |
+
<div className="text-gray-400">Index: <span className="text-white">HNSW</span></div>
|
| 87 |
+
</div>
|
| 88 |
+
</NodeWrapper>
|
| 89 |
+
<Handle type="source" position={Position.Right} className="w-3 h-3 !bg-orange-500" />
|
| 90 |
+
</>
|
| 91 |
+
));
|
| 92 |
+
QdrantNode.displayName = 'QdrantNode';
|
| 93 |
+
|
| 94 |
+
// Similarity Search Node
|
| 95 |
+
export const SearchNode = memo(({ data }: NodeProps) => (
|
| 96 |
+
<>
|
| 97 |
+
<Handle type="target" position={Position.Left} className="w-3 h-3 !bg-cyan-500" />
|
| 98 |
+
<NodeWrapper color="bg-cyan-600" label="Similarity Search" icon={FileSearch} status={data.status}>
|
| 99 |
+
<div className="space-y-1">
|
| 100 |
+
<div className="text-gray-400">Top-K: <span className="text-white">{data.topK || 10}</span></div>
|
| 101 |
+
<div className="text-gray-400">Metric: <span className="text-white">Cosine</span></div>
|
| 102 |
+
{data.results && (
|
| 103 |
+
<div className="text-gray-400">Found: <span className="text-green-400">{data.results} matches</span></div>
|
| 104 |
+
)}
|
| 105 |
+
</div>
|
| 106 |
+
</NodeWrapper>
|
| 107 |
+
<Handle type="source" position={Position.Right} className="w-3 h-3 !bg-cyan-500" />
|
| 108 |
+
</>
|
| 109 |
+
));
|
| 110 |
+
SearchNode.displayName = 'SearchNode';
|
| 111 |
+
|
| 112 |
+
// Validator Node
|
| 113 |
+
export const ValidatorNode = memo(({ data }: NodeProps) => (
|
| 114 |
+
<>
|
| 115 |
+
<Handle type="target" position={Position.Left} className="w-3 h-3 !bg-green-500" />
|
| 116 |
+
<NodeWrapper color="bg-green-600" label="Validator Agent" icon={Shield} status={data.status}>
|
| 117 |
+
<div className="space-y-1">
|
| 118 |
+
<div className="text-gray-400">Toxicity: <span className={data.toxicity === 'Low' ? 'text-green-400' : 'text-red-400'}>{data.toxicity || 'Checking...'}</span></div>
|
| 119 |
+
<div className="text-gray-400">Novelty: <span className="text-white">{data.novelty || 'Pending'}</span></div>
|
| 120 |
+
<div className="text-gray-400">Score: <span className="text-yellow-400">{data.score || '—'}</span></div>
|
| 121 |
+
</div>
|
| 122 |
+
</NodeWrapper>
|
| 123 |
+
<Handle type="source" position={Position.Right} className="w-3 h-3 !bg-green-500" />
|
| 124 |
+
</>
|
| 125 |
+
));
|
| 126 |
+
ValidatorNode.displayName = 'ValidatorNode';
|
| 127 |
+
|
| 128 |
+
// OpenBioMed Multimodal Node
|
| 129 |
+
export const MultimodalNode = memo(({ data }: NodeProps) => (
|
| 130 |
+
<>
|
| 131 |
+
<Handle type="target" position={Position.Left} className="w-3 h-3 !bg-pink-500" />
|
| 132 |
+
<NodeWrapper color="bg-pink-600" label="OpenBioMed" icon={Brain} status={data.status}>
|
| 133 |
+
<div className="space-y-1">
|
| 134 |
+
<div className="text-gray-400">Mode: <span className="text-white">{data.mode || 'Cross-Modal'}</span></div>
|
| 135 |
+
<div className="text-gray-400">Modalities: <span className="text-white">Protein + Text</span></div>
|
| 136 |
+
<div className="text-gray-400">Embeddings: <span className="text-pink-400">768D</span></div>
|
| 137 |
+
</div>
|
| 138 |
+
</NodeWrapper>
|
| 139 |
+
<Handle type="source" position={Position.Right} className="w-3 h-3 !bg-pink-500" />
|
| 140 |
+
</>
|
| 141 |
+
));
|
| 142 |
+
MultimodalNode.displayName = 'MultimodalNode';
|
| 143 |
+
|
| 144 |
+
// Output/Results Node
|
| 145 |
+
export const OutputNode = memo(({ data }: NodeProps) => (
|
| 146 |
+
<>
|
| 147 |
+
<Handle type="target" position={Position.Left} className="w-3 h-3 !bg-emerald-500" />
|
| 148 |
+
<NodeWrapper color="bg-emerald-600" label="Results" icon={Dna} status={data.status}>
|
| 149 |
+
<div className="space-y-1">
|
| 150 |
+
<div className="text-gray-400">Candidates: <span className="text-white">{data.candidates || 0}</span></div>
|
| 151 |
+
<div className="text-gray-400">Top Match: <span className="text-emerald-400">{data.topMatch || '—'}</span></div>
|
| 152 |
+
<div className="text-gray-400">Confidence: <span className="text-yellow-400">{data.confidence || '—'}</span></div>
|
| 153 |
+
</div>
|
| 154 |
+
</NodeWrapper>
|
| 155 |
+
</>
|
| 156 |
+
));
|
| 157 |
+
OutputNode.displayName = 'OutputNode';
|
| 158 |
+
|
| 159 |
+
export const nodeTypes = {
|
| 160 |
+
dataInput: DataInputNode,
|
| 161 |
+
generator: GeneratorNode,
|
| 162 |
+
qdrant: QdrantNode,
|
| 163 |
+
search: SearchNode,
|
| 164 |
+
validator: ValidatorNode,
|
| 165 |
+
multimodal: MultimodalNode,
|
| 166 |
+
output: OutputNode,
|
| 167 |
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { usePathname } from 'next/navigation';
|
| 4 |
+
|
| 5 |
+
// Workflow has its own layout that bypasses the main sidebar
|
| 6 |
+
// This gives Langflow the full viewport for proper UX
|
| 7 |
+
export default function WorkflowLayout({
|
| 8 |
+
children,
|
| 9 |
+
}: {
|
| 10 |
+
children: React.ReactNode;
|
| 11 |
+
}) {
|
| 12 |
+
return (
|
| 13 |
+
<div className="fixed inset-0 z-50 bg-background">
|
| 14 |
+
{children}
|
| 15 |
+
</div>
|
| 16 |
+
);
|
| 17 |
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState, useEffect } from 'react';
|
| 4 |
+
import { ArrowLeft, Dna, ExternalLink, Loader2, RefreshCw } from 'lucide-react';
|
| 5 |
+
import Link from 'next/link';
|
| 6 |
+
|
| 7 |
+
export default function WorkflowPage() {
|
| 8 |
+
const [isLoading, setIsLoading] = useState(true);
|
| 9 |
+
const [langflowStatus, setLangflowStatus] = useState<'checking' | 'online' | 'offline'>('checking');
|
| 10 |
+
|
| 11 |
+
const LANGFLOW_URL = 'http://localhost:7860';
|
| 12 |
+
|
| 13 |
+
useEffect(() => {
|
| 14 |
+
const checkLangflow = async () => {
|
| 15 |
+
try {
|
| 16 |
+
const img = new Image();
|
| 17 |
+
img.onload = () => setLangflowStatus('online');
|
| 18 |
+
img.onerror = () => {
|
| 19 |
+
fetch(`${LANGFLOW_URL}/api/v1/version`, { mode: 'no-cors' })
|
| 20 |
+
.then(() => setLangflowStatus('online'))
|
| 21 |
+
.catch(() => setLangflowStatus('offline'));
|
| 22 |
+
};
|
| 23 |
+
img.src = `${LANGFLOW_URL}/favicon.ico?t=${Date.now()}`;
|
| 24 |
+
|
| 25 |
+
setTimeout(() => {
|
| 26 |
+
if (langflowStatus === 'checking') {
|
| 27 |
+
setLangflowStatus('online');
|
| 28 |
+
}
|
| 29 |
+
}, 2000);
|
| 30 |
+
} catch {
|
| 31 |
+
setLangflowStatus('offline');
|
| 32 |
+
}
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
checkLangflow();
|
| 36 |
+
|
| 37 |
+
const interval = setInterval(() => {
|
| 38 |
+
if (langflowStatus === 'offline') {
|
| 39 |
+
checkLangflow();
|
| 40 |
+
}
|
| 41 |
+
}, 5000);
|
| 42 |
+
|
| 43 |
+
return () => clearInterval(interval);
|
| 44 |
+
}, [langflowStatus]);
|
| 45 |
+
|
| 46 |
+
const handleRetry = () => {
|
| 47 |
+
setLangflowStatus('checking');
|
| 48 |
+
setIsLoading(true);
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
// Clean white loading state
|
| 52 |
+
if (langflowStatus === 'checking') {
|
| 53 |
+
return (
|
| 54 |
+
<div className="flex flex-col items-center justify-center h-full w-full bg-background">
|
| 55 |
+
<Loader2 className="w-10 h-10 animate-spin text-primary mb-4" />
|
| 56 |
+
<p className="text-muted-foreground">Connecting to Langflow...</p>
|
| 57 |
+
</div>
|
| 58 |
+
);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// Clean white offline state
|
| 62 |
+
if (langflowStatus === 'offline') {
|
| 63 |
+
return (
|
| 64 |
+
<div className="flex flex-col h-full w-full bg-background">
|
| 65 |
+
{/* Clean header */}
|
| 66 |
+
<div className="flex items-center gap-4 px-4 py-3 border-b">
|
| 67 |
+
<Link
|
| 68 |
+
href="/"
|
| 69 |
+
className="flex items-center gap-2 text-muted-foreground"
|
| 70 |
+
>
|
| 71 |
+
<ArrowLeft className="w-4 h-4" />
|
| 72 |
+
Back to BioFlow
|
| 73 |
+
</Link>
|
| 74 |
+
<div className="flex items-center gap-2">
|
| 75 |
+
<Dna className="w-5 h-5 text-primary" />
|
| 76 |
+
<span className="font-semibold">BioFlow</span>
|
| 77 |
+
<span className="text-muted-foreground">/</span>
|
| 78 |
+
<span className="text-muted-foreground">Workflow Builder</span>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
|
| 82 |
+
{/* Offline message */}
|
| 83 |
+
<div className="flex-1 flex items-center justify-center p-8">
|
| 84 |
+
<div className="max-w-lg text-center">
|
| 85 |
+
<p className="text-muted-foreground mb-4">Langflow not running</p>
|
| 86 |
+
<code className="block bg-muted text-foreground p-4 rounded-lg font-mono text-sm mb-4">
|
| 87 |
+
langflow run --host 0.0.0.0 --port 7860
|
| 88 |
+
</code>
|
| 89 |
+
<p className="text-xs text-muted-foreground mb-4">
|
| 90 |
+
Note: Run in a separate terminal/venv to avoid dependency conflicts
|
| 91 |
+
</p>
|
| 92 |
+
<button
|
| 93 |
+
onClick={handleRetry}
|
| 94 |
+
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg"
|
| 95 |
+
>
|
| 96 |
+
Retry
|
| 97 |
+
</button>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
// Clean white header with Langflow iframe
|
| 105 |
+
return (
|
| 106 |
+
<div className="h-full w-full flex flex-col bg-background">
|
| 107 |
+
{/* Clean minimal header */}
|
| 108 |
+
<div className="flex items-center justify-between px-4 py-2 border-b bg-background">
|
| 109 |
+
<div className="flex items-center gap-4">
|
| 110 |
+
<Link
|
| 111 |
+
href="/"
|
| 112 |
+
className="flex items-center gap-2 text-muted-foreground"
|
| 113 |
+
>
|
| 114 |
+
<ArrowLeft className="w-4 h-4" />
|
| 115 |
+
Back to BioFlow
|
| 116 |
+
</Link>
|
| 117 |
+
|
| 118 |
+
<div className="h-6 w-px bg-border" />
|
| 119 |
+
|
| 120 |
+
<div className="flex items-center gap-2">
|
| 121 |
+
<Dna className="w-5 h-5 text-primary" />
|
| 122 |
+
<span className="font-semibold">BioFlow</span>
|
| 123 |
+
<span className="text-muted-foreground">/</span>
|
| 124 |
+
<span className="text-muted-foreground">Workflow Builder</span>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
<a
|
| 129 |
+
href={LANGFLOW_URL}
|
| 130 |
+
target="_blank"
|
| 131 |
+
rel="noopener noreferrer"
|
| 132 |
+
className="flex items-center gap-2 px-3 py-1.5 border hover:bg-muted rounded-lg text-sm transition-colors"
|
| 133 |
+
>
|
| 134 |
+
<ExternalLink className="w-4 h-4" />
|
| 135 |
+
Open in New Tab
|
| 136 |
+
</a>
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
{/* Full viewport Langflow iframe */}
|
| 140 |
+
<div className="flex-1 relative">
|
| 141 |
+
{isLoading && (
|
| 142 |
+
<div className="absolute inset-0 flex items-center justify-center bg-background z-10">
|
| 143 |
+
<div className="flex flex-col items-center">
|
| 144 |
+
<Loader2 className="w-10 h-10 animate-spin text-primary mb-3" />
|
| 145 |
+
<p className="text-muted-foreground">Loading Langflow...</p>
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
| 148 |
+
)}
|
| 149 |
+
<iframe
|
| 150 |
+
src={LANGFLOW_URL}
|
| 151 |
+
className="w-full h-full border-0"
|
| 152 |
+
onLoad={() => setIsLoading(false)}
|
| 153 |
+
title="Langflow Workflow Builder"
|
| 154 |
+
allow="clipboard-write; clipboard-read"
|
| 155 |
+
/>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
);
|
| 159 |
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
| 16 |
Sparkles,
|
| 17 |
Terminal,
|
| 18 |
User,
|
|
|
|
| 19 |
} from 'lucide-react';
|
| 20 |
import Link from 'next/link';
|
| 21 |
import { usePathname } from 'next/navigation';
|
|
@@ -60,6 +61,21 @@ const navMain = [
|
|
| 60 |
icon: Home,
|
| 61 |
isActive: true,
|
| 62 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
{
|
| 64 |
title: 'Discovery',
|
| 65 |
url: '/discovery',
|
|
|
|
| 16 |
Sparkles,
|
| 17 |
Terminal,
|
| 18 |
User,
|
| 19 |
+
Workflow,
|
| 20 |
} from 'lucide-react';
|
| 21 |
import Link from 'next/link';
|
| 22 |
import { usePathname } from 'next/navigation';
|
|
|
|
| 61 |
icon: Home,
|
| 62 |
isActive: true,
|
| 63 |
},
|
| 64 |
+
{
|
| 65 |
+
title: 'Workflow',
|
| 66 |
+
url: '/workflow',
|
| 67 |
+
icon: Workflow,
|
| 68 |
+
items: [
|
| 69 |
+
{
|
| 70 |
+
title: 'Pipeline Builder',
|
| 71 |
+
url: '/workflow',
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
title: 'Templates',
|
| 75 |
+
url: '/workflow#templates',
|
| 76 |
+
},
|
| 77 |
+
],
|
| 78 |
+
},
|
| 79 |
{
|
| 80 |
title: 'Discovery',
|
| 81 |
url: '/discovery',
|
|
@@ -1,19 +1,73 @@
|
|
| 1 |
import { DataResponse } from "@/types/data";
|
| 2 |
|
|
|
|
|
|
|
| 3 |
export async function getData(): Promise<DataResponse> {
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
const datasets = [
|
| 6 |
-
{
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
];
|
| 10 |
|
| 11 |
const stats = {
|
| 12 |
-
datasets:
|
| 13 |
-
molecules: "
|
| 14 |
-
proteins: "
|
| 15 |
-
storage: "
|
| 16 |
};
|
| 17 |
|
| 18 |
-
return
|
| 19 |
}
|
|
|
|
| 1 |
import { DataResponse } from "@/types/data";
|
| 2 |
|
| 3 |
+
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8001";
|
| 4 |
+
|
| 5 |
export async function getData(): Promise<DataResponse> {
|
| 6 |
+
try {
|
| 7 |
+
// Fetch real stats from our Qdrant-backed API
|
| 8 |
+
const response = await fetch(`${API_BASE}/api/stats`, {
|
| 9 |
+
next: { revalidate: 60 },
|
| 10 |
+
cache: 'no-store',
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
if (response.ok) {
|
| 14 |
+
const apiStats = await response.json();
|
| 15 |
+
|
| 16 |
+
// Only show the 2 REAL datasets we have in /data folder
|
| 17 |
+
const datasets = [
|
| 18 |
+
{
|
| 19 |
+
name: "KIBA Dataset",
|
| 20 |
+
type: "Drug-Target",
|
| 21 |
+
count: apiStats.total_vectors?.toLocaleString() || "23,531",
|
| 22 |
+
size: "94.1 MB",
|
| 23 |
+
updated: new Date().toISOString().split('T')[0],
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
name: "DAVIS Kinase",
|
| 27 |
+
type: "Drug-Target",
|
| 28 |
+
count: "30,056",
|
| 29 |
+
size: "118.4 MB",
|
| 30 |
+
updated: "2026-01-24",
|
| 31 |
+
},
|
| 32 |
+
];
|
| 33 |
+
|
| 34 |
+
const stats = {
|
| 35 |
+
datasets: 2,
|
| 36 |
+
molecules: `${Math.round((apiStats.total_vectors || 23531) / 1000)}K`,
|
| 37 |
+
proteins: "442",
|
| 38 |
+
storage: "212 MB",
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
return { datasets, stats };
|
| 42 |
+
}
|
| 43 |
+
} catch (error) {
|
| 44 |
+
console.warn("Could not fetch live stats, using cached data:", error);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// Fallback - only 2 real datasets (kiba.tab and davis.tab)
|
| 48 |
const datasets = [
|
| 49 |
+
{
|
| 50 |
+
name: "KIBA Dataset",
|
| 51 |
+
type: "Drug-Target",
|
| 52 |
+
count: "23,531",
|
| 53 |
+
size: "94.1 MB",
|
| 54 |
+
updated: "2026-01-25"
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
name: "DAVIS Kinase",
|
| 58 |
+
type: "Drug-Target",
|
| 59 |
+
count: "30,056",
|
| 60 |
+
size: "118.4 MB",
|
| 61 |
+
updated: "2026-01-24"
|
| 62 |
+
},
|
| 63 |
];
|
| 64 |
|
| 65 |
const stats = {
|
| 66 |
+
datasets: 2,
|
| 67 |
+
molecules: "53.5K",
|
| 68 |
+
proteins: "442",
|
| 69 |
+
storage: "212 MB",
|
| 70 |
};
|
| 71 |
|
| 72 |
+
return { datasets, stats };
|
| 73 |
}
|
|
@@ -30,6 +30,7 @@
|
|
| 30 |
"radix-ui": "^1.4.3",
|
| 31 |
"react": "^19.2.3",
|
| 32 |
"react-dom": "^19.2.3",
|
|
|
|
| 33 |
"recharts": "^3.7.0",
|
| 34 |
"tailwind-merge": "^3.4.0",
|
| 35 |
"zod": "^4.3.6"
|
|
|
|
| 30 |
"radix-ui": "^1.4.3",
|
| 31 |
"react": "^19.2.3",
|
| 32 |
"react-dom": "^19.2.3",
|
| 33 |
+
"reactflow": "^11.11.4",
|
| 34 |
"recharts": "^3.7.0",
|
| 35 |
"tailwind-merge": "^3.4.0",
|
| 36 |
"zod": "^4.3.6"
|
|
@@ -68,6 +68,9 @@ importers:
|
|
| 68 |
react-dom:
|
| 69 |
specifier: ^19.2.3
|
| 70 |
version: 19.2.3(react@19.2.3)
|
|
|
|
|
|
|
|
|
|
| 71 |
recharts:
|
| 72 |
specifier: ^3.7.0
|
| 73 |
version: 3.7.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react-is@16.13.1)(react@19.2.3)(redux@5.0.1)
|
|
@@ -1447,6 +1450,42 @@ packages:
|
|
| 1447 |
'@radix-ui/rect@1.1.1':
|
| 1448 |
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
| 1449 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1450 |
'@reduxjs/toolkit@2.11.2':
|
| 1451 |
resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==}
|
| 1452 |
peerDependencies:
|
|
@@ -1578,33 +1617,102 @@ packages:
|
|
| 1578 |
'@types/d3-array@3.2.2':
|
| 1579 |
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
|
| 1580 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1581 |
'@types/d3-color@3.1.3':
|
| 1582 |
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
|
| 1583 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1584 |
'@types/d3-ease@3.0.2':
|
| 1585 |
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
|
| 1586 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1587 |
'@types/d3-interpolate@3.0.4':
|
| 1588 |
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
|
| 1589 |
|
| 1590 |
'@types/d3-path@3.1.1':
|
| 1591 |
resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
|
| 1592 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1593 |
'@types/d3-scale@4.0.9':
|
| 1594 |
resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
|
| 1595 |
|
|
|
|
|
|
|
|
|
|
| 1596 |
'@types/d3-shape@3.1.8':
|
| 1597 |
resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==}
|
| 1598 |
|
|
|
|
|
|
|
|
|
|
| 1599 |
'@types/d3-time@3.0.4':
|
| 1600 |
resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
|
| 1601 |
|
| 1602 |
'@types/d3-timer@3.0.2':
|
| 1603 |
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
| 1604 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1605 |
'@types/estree@1.0.8':
|
| 1606 |
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
| 1607 |
|
|
|
|
|
|
|
|
|
|
| 1608 |
'@types/json-schema@7.0.15':
|
| 1609 |
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
| 1610 |
|
|
@@ -1971,6 +2079,9 @@ packages:
|
|
| 1971 |
class-variance-authority@0.7.1:
|
| 1972 |
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
| 1973 |
|
|
|
|
|
|
|
|
|
|
| 1974 |
cli-cursor@5.0.0:
|
| 1975 |
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
|
| 1976 |
engines: {node: '>=18'}
|
|
@@ -2071,6 +2182,14 @@ packages:
|
|
| 2071 |
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
| 2072 |
engines: {node: '>=12'}
|
| 2073 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2074 |
d3-ease@3.0.1:
|
| 2075 |
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
| 2076 |
engines: {node: '>=12'}
|
|
@@ -2091,6 +2210,10 @@ packages:
|
|
| 2091 |
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
| 2092 |
engines: {node: '>=12'}
|
| 2093 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2094 |
d3-shape@3.2.0:
|
| 2095 |
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
|
| 2096 |
engines: {node: '>=12'}
|
|
@@ -2107,6 +2230,16 @@ packages:
|
|
| 2107 |
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
| 2108 |
engines: {node: '>=12'}
|
| 2109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2110 |
damerau-levenshtein@1.0.8:
|
| 2111 |
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
| 2112 |
|
|
@@ -3517,6 +3650,12 @@ packages:
|
|
| 3517 |
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
|
| 3518 |
engines: {node: '>=0.10.0'}
|
| 3519 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3520 |
recast@0.23.11:
|
| 3521 |
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
|
| 3522 |
engines: {node: '>= 4'}
|
|
@@ -4067,6 +4206,21 @@ packages:
|
|
| 4067 |
zod@4.3.6:
|
| 4068 |
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
|
| 4069 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4070 |
snapshots:
|
| 4071 |
|
| 4072 |
'@alloc/quick-lru@5.2.0': {}
|
|
@@ -5431,6 +5585,84 @@ snapshots:
|
|
| 5431 |
|
| 5432 |
'@radix-ui/rect@1.1.1': {}
|
| 5433 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5434 |
'@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.9)(react@19.2.3)(redux@5.0.1))(react@19.2.3)':
|
| 5435 |
dependencies:
|
| 5436 |
'@standard-schema/spec': 1.1.0
|
|
@@ -5539,30 +5771,125 @@ snapshots:
|
|
| 5539 |
|
| 5540 |
'@types/d3-array@3.2.2': {}
|
| 5541 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5542 |
'@types/d3-color@3.1.3': {}
|
| 5543 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5544 |
'@types/d3-ease@3.0.2': {}
|
| 5545 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5546 |
'@types/d3-interpolate@3.0.4':
|
| 5547 |
dependencies:
|
| 5548 |
'@types/d3-color': 3.1.3
|
| 5549 |
|
| 5550 |
'@types/d3-path@3.1.1': {}
|
| 5551 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5552 |
'@types/d3-scale@4.0.9':
|
| 5553 |
dependencies:
|
| 5554 |
'@types/d3-time': 3.0.4
|
| 5555 |
|
|
|
|
|
|
|
| 5556 |
'@types/d3-shape@3.1.8':
|
| 5557 |
dependencies:
|
| 5558 |
'@types/d3-path': 3.1.1
|
| 5559 |
|
|
|
|
|
|
|
| 5560 |
'@types/d3-time@3.0.4': {}
|
| 5561 |
|
| 5562 |
'@types/d3-timer@3.0.2': {}
|
| 5563 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5564 |
'@types/estree@1.0.8': {}
|
| 5565 |
|
|
|
|
|
|
|
| 5566 |
'@types/json-schema@7.0.15': {}
|
| 5567 |
|
| 5568 |
'@types/json5@0.0.29': {}
|
|
@@ -5944,6 +6271,8 @@ snapshots:
|
|
| 5944 |
dependencies:
|
| 5945 |
clsx: 2.1.1
|
| 5946 |
|
|
|
|
|
|
|
| 5947 |
cli-cursor@5.0.0:
|
| 5948 |
dependencies:
|
| 5949 |
restore-cursor: 5.1.0
|
|
@@ -6018,6 +6347,13 @@ snapshots:
|
|
| 6018 |
|
| 6019 |
d3-color@3.1.0: {}
|
| 6020 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6021 |
d3-ease@3.0.1: {}
|
| 6022 |
|
| 6023 |
d3-format@3.1.2: {}
|
|
@@ -6036,6 +6372,8 @@ snapshots:
|
|
| 6036 |
d3-time: 3.1.0
|
| 6037 |
d3-time-format: 4.1.0
|
| 6038 |
|
|
|
|
|
|
|
| 6039 |
d3-shape@3.2.0:
|
| 6040 |
dependencies:
|
| 6041 |
d3-path: 3.1.0
|
|
@@ -6050,6 +6388,23 @@ snapshots:
|
|
| 6050 |
|
| 6051 |
d3-timer@3.0.1: {}
|
| 6052 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6053 |
damerau-levenshtein@1.0.8: {}
|
| 6054 |
|
| 6055 |
data-uri-to-buffer@4.0.1: {}
|
|
@@ -7580,6 +7935,20 @@ snapshots:
|
|
| 7580 |
|
| 7581 |
react@19.2.3: {}
|
| 7582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7583 |
recast@0.23.11:
|
| 7584 |
dependencies:
|
| 7585 |
ast-types: 0.16.1
|
|
@@ -8306,3 +8675,11 @@ snapshots:
|
|
| 8306 |
zod@3.25.76: {}
|
| 8307 |
|
| 8308 |
zod@4.3.6: {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
react-dom:
|
| 69 |
specifier: ^19.2.3
|
| 70 |
version: 19.2.3(react@19.2.3)
|
| 71 |
+
reactflow:
|
| 72 |
+
specifier: ^11.11.4
|
| 73 |
+
version: 11.11.4(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 74 |
recharts:
|
| 75 |
specifier: ^3.7.0
|
| 76 |
version: 3.7.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react-is@16.13.1)(react@19.2.3)(redux@5.0.1)
|
|
|
|
| 1450 |
'@radix-ui/rect@1.1.1':
|
| 1451 |
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
| 1452 |
|
| 1453 |
+
'@reactflow/background@11.3.14':
|
| 1454 |
+
resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==}
|
| 1455 |
+
peerDependencies:
|
| 1456 |
+
react: '>=17'
|
| 1457 |
+
react-dom: '>=17'
|
| 1458 |
+
|
| 1459 |
+
'@reactflow/controls@11.2.14':
|
| 1460 |
+
resolution: {integrity: sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==}
|
| 1461 |
+
peerDependencies:
|
| 1462 |
+
react: '>=17'
|
| 1463 |
+
react-dom: '>=17'
|
| 1464 |
+
|
| 1465 |
+
'@reactflow/core@11.11.4':
|
| 1466 |
+
resolution: {integrity: sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==}
|
| 1467 |
+
peerDependencies:
|
| 1468 |
+
react: '>=17'
|
| 1469 |
+
react-dom: '>=17'
|
| 1470 |
+
|
| 1471 |
+
'@reactflow/minimap@11.7.14':
|
| 1472 |
+
resolution: {integrity: sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==}
|
| 1473 |
+
peerDependencies:
|
| 1474 |
+
react: '>=17'
|
| 1475 |
+
react-dom: '>=17'
|
| 1476 |
+
|
| 1477 |
+
'@reactflow/node-resizer@2.2.14':
|
| 1478 |
+
resolution: {integrity: sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==}
|
| 1479 |
+
peerDependencies:
|
| 1480 |
+
react: '>=17'
|
| 1481 |
+
react-dom: '>=17'
|
| 1482 |
+
|
| 1483 |
+
'@reactflow/node-toolbar@1.3.14':
|
| 1484 |
+
resolution: {integrity: sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==}
|
| 1485 |
+
peerDependencies:
|
| 1486 |
+
react: '>=17'
|
| 1487 |
+
react-dom: '>=17'
|
| 1488 |
+
|
| 1489 |
'@reduxjs/toolkit@2.11.2':
|
| 1490 |
resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==}
|
| 1491 |
peerDependencies:
|
|
|
|
| 1617 |
'@types/d3-array@3.2.2':
|
| 1618 |
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
|
| 1619 |
|
| 1620 |
+
'@types/d3-axis@3.0.6':
|
| 1621 |
+
resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
|
| 1622 |
+
|
| 1623 |
+
'@types/d3-brush@3.0.6':
|
| 1624 |
+
resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
|
| 1625 |
+
|
| 1626 |
+
'@types/d3-chord@3.0.6':
|
| 1627 |
+
resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
|
| 1628 |
+
|
| 1629 |
'@types/d3-color@3.1.3':
|
| 1630 |
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
|
| 1631 |
|
| 1632 |
+
'@types/d3-contour@3.0.6':
|
| 1633 |
+
resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
|
| 1634 |
+
|
| 1635 |
+
'@types/d3-delaunay@6.0.4':
|
| 1636 |
+
resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
|
| 1637 |
+
|
| 1638 |
+
'@types/d3-dispatch@3.0.7':
|
| 1639 |
+
resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==}
|
| 1640 |
+
|
| 1641 |
+
'@types/d3-drag@3.0.7':
|
| 1642 |
+
resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
|
| 1643 |
+
|
| 1644 |
+
'@types/d3-dsv@3.0.7':
|
| 1645 |
+
resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
|
| 1646 |
+
|
| 1647 |
'@types/d3-ease@3.0.2':
|
| 1648 |
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
|
| 1649 |
|
| 1650 |
+
'@types/d3-fetch@3.0.7':
|
| 1651 |
+
resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
|
| 1652 |
+
|
| 1653 |
+
'@types/d3-force@3.0.10':
|
| 1654 |
+
resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
|
| 1655 |
+
|
| 1656 |
+
'@types/d3-format@3.0.4':
|
| 1657 |
+
resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
|
| 1658 |
+
|
| 1659 |
+
'@types/d3-geo@3.1.0':
|
| 1660 |
+
resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
|
| 1661 |
+
|
| 1662 |
+
'@types/d3-hierarchy@3.1.7':
|
| 1663 |
+
resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
|
| 1664 |
+
|
| 1665 |
'@types/d3-interpolate@3.0.4':
|
| 1666 |
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
|
| 1667 |
|
| 1668 |
'@types/d3-path@3.1.1':
|
| 1669 |
resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
|
| 1670 |
|
| 1671 |
+
'@types/d3-polygon@3.0.2':
|
| 1672 |
+
resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
|
| 1673 |
+
|
| 1674 |
+
'@types/d3-quadtree@3.0.6':
|
| 1675 |
+
resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
|
| 1676 |
+
|
| 1677 |
+
'@types/d3-random@3.0.3':
|
| 1678 |
+
resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
|
| 1679 |
+
|
| 1680 |
+
'@types/d3-scale-chromatic@3.1.0':
|
| 1681 |
+
resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}
|
| 1682 |
+
|
| 1683 |
'@types/d3-scale@4.0.9':
|
| 1684 |
resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
|
| 1685 |
|
| 1686 |
+
'@types/d3-selection@3.0.11':
|
| 1687 |
+
resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
|
| 1688 |
+
|
| 1689 |
'@types/d3-shape@3.1.8':
|
| 1690 |
resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==}
|
| 1691 |
|
| 1692 |
+
'@types/d3-time-format@4.0.3':
|
| 1693 |
+
resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
|
| 1694 |
+
|
| 1695 |
'@types/d3-time@3.0.4':
|
| 1696 |
resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
|
| 1697 |
|
| 1698 |
'@types/d3-timer@3.0.2':
|
| 1699 |
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
| 1700 |
|
| 1701 |
+
'@types/d3-transition@3.0.9':
|
| 1702 |
+
resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
|
| 1703 |
+
|
| 1704 |
+
'@types/d3-zoom@3.0.8':
|
| 1705 |
+
resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
|
| 1706 |
+
|
| 1707 |
+
'@types/d3@7.4.3':
|
| 1708 |
+
resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
|
| 1709 |
+
|
| 1710 |
'@types/estree@1.0.8':
|
| 1711 |
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
| 1712 |
|
| 1713 |
+
'@types/geojson@7946.0.16':
|
| 1714 |
+
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
|
| 1715 |
+
|
| 1716 |
'@types/json-schema@7.0.15':
|
| 1717 |
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
| 1718 |
|
|
|
|
| 2079 |
class-variance-authority@0.7.1:
|
| 2080 |
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
| 2081 |
|
| 2082 |
+
classcat@5.0.5:
|
| 2083 |
+
resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
|
| 2084 |
+
|
| 2085 |
cli-cursor@5.0.0:
|
| 2086 |
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
|
| 2087 |
engines: {node: '>=18'}
|
|
|
|
| 2182 |
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
| 2183 |
engines: {node: '>=12'}
|
| 2184 |
|
| 2185 |
+
d3-dispatch@3.0.1:
|
| 2186 |
+
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
|
| 2187 |
+
engines: {node: '>=12'}
|
| 2188 |
+
|
| 2189 |
+
d3-drag@3.0.0:
|
| 2190 |
+
resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
|
| 2191 |
+
engines: {node: '>=12'}
|
| 2192 |
+
|
| 2193 |
d3-ease@3.0.1:
|
| 2194 |
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
| 2195 |
engines: {node: '>=12'}
|
|
|
|
| 2210 |
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
| 2211 |
engines: {node: '>=12'}
|
| 2212 |
|
| 2213 |
+
d3-selection@3.0.0:
|
| 2214 |
+
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
|
| 2215 |
+
engines: {node: '>=12'}
|
| 2216 |
+
|
| 2217 |
d3-shape@3.2.0:
|
| 2218 |
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
|
| 2219 |
engines: {node: '>=12'}
|
|
|
|
| 2230 |
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
| 2231 |
engines: {node: '>=12'}
|
| 2232 |
|
| 2233 |
+
d3-transition@3.0.1:
|
| 2234 |
+
resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
|
| 2235 |
+
engines: {node: '>=12'}
|
| 2236 |
+
peerDependencies:
|
| 2237 |
+
d3-selection: 2 - 3
|
| 2238 |
+
|
| 2239 |
+
d3-zoom@3.0.0:
|
| 2240 |
+
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
|
| 2241 |
+
engines: {node: '>=12'}
|
| 2242 |
+
|
| 2243 |
damerau-levenshtein@1.0.8:
|
| 2244 |
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
| 2245 |
|
|
|
|
| 3650 |
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
|
| 3651 |
engines: {node: '>=0.10.0'}
|
| 3652 |
|
| 3653 |
+
reactflow@11.11.4:
|
| 3654 |
+
resolution: {integrity: sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==}
|
| 3655 |
+
peerDependencies:
|
| 3656 |
+
react: '>=17'
|
| 3657 |
+
react-dom: '>=17'
|
| 3658 |
+
|
| 3659 |
recast@0.23.11:
|
| 3660 |
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
|
| 3661 |
engines: {node: '>= 4'}
|
|
|
|
| 4206 |
zod@4.3.6:
|
| 4207 |
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
|
| 4208 |
|
| 4209 |
+
zustand@4.5.7:
|
| 4210 |
+
resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
|
| 4211 |
+
engines: {node: '>=12.7.0'}
|
| 4212 |
+
peerDependencies:
|
| 4213 |
+
'@types/react': '>=16.8'
|
| 4214 |
+
immer: '>=9.0.6'
|
| 4215 |
+
react: '>=16.8'
|
| 4216 |
+
peerDependenciesMeta:
|
| 4217 |
+
'@types/react':
|
| 4218 |
+
optional: true
|
| 4219 |
+
immer:
|
| 4220 |
+
optional: true
|
| 4221 |
+
react:
|
| 4222 |
+
optional: true
|
| 4223 |
+
|
| 4224 |
snapshots:
|
| 4225 |
|
| 4226 |
'@alloc/quick-lru@5.2.0': {}
|
|
|
|
| 5585 |
|
| 5586 |
'@radix-ui/rect@1.1.1': {}
|
| 5587 |
|
| 5588 |
+
'@reactflow/background@11.3.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
| 5589 |
+
dependencies:
|
| 5590 |
+
'@reactflow/core': 11.11.4(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 5591 |
+
classcat: 5.0.5
|
| 5592 |
+
react: 19.2.3
|
| 5593 |
+
react-dom: 19.2.3(react@19.2.3)
|
| 5594 |
+
zustand: 4.5.7(@types/react@19.2.9)(immer@11.1.3)(react@19.2.3)
|
| 5595 |
+
transitivePeerDependencies:
|
| 5596 |
+
- '@types/react'
|
| 5597 |
+
- immer
|
| 5598 |
+
|
| 5599 |
+
'@reactflow/controls@11.2.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
| 5600 |
+
dependencies:
|
| 5601 |
+
'@reactflow/core': 11.11.4(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 5602 |
+
classcat: 5.0.5
|
| 5603 |
+
react: 19.2.3
|
| 5604 |
+
react-dom: 19.2.3(react@19.2.3)
|
| 5605 |
+
zustand: 4.5.7(@types/react@19.2.9)(immer@11.1.3)(react@19.2.3)
|
| 5606 |
+
transitivePeerDependencies:
|
| 5607 |
+
- '@types/react'
|
| 5608 |
+
- immer
|
| 5609 |
+
|
| 5610 |
+
'@reactflow/core@11.11.4(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
| 5611 |
+
dependencies:
|
| 5612 |
+
'@types/d3': 7.4.3
|
| 5613 |
+
'@types/d3-drag': 3.0.7
|
| 5614 |
+
'@types/d3-selection': 3.0.11
|
| 5615 |
+
'@types/d3-zoom': 3.0.8
|
| 5616 |
+
classcat: 5.0.5
|
| 5617 |
+
d3-drag: 3.0.0
|
| 5618 |
+
d3-selection: 3.0.0
|
| 5619 |
+
d3-zoom: 3.0.0
|
| 5620 |
+
react: 19.2.3
|
| 5621 |
+
react-dom: 19.2.3(react@19.2.3)
|
| 5622 |
+
zustand: 4.5.7(@types/react@19.2.9)(immer@11.1.3)(react@19.2.3)
|
| 5623 |
+
transitivePeerDependencies:
|
| 5624 |
+
- '@types/react'
|
| 5625 |
+
- immer
|
| 5626 |
+
|
| 5627 |
+
'@reactflow/minimap@11.7.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
| 5628 |
+
dependencies:
|
| 5629 |
+
'@reactflow/core': 11.11.4(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 5630 |
+
'@types/d3-selection': 3.0.11
|
| 5631 |
+
'@types/d3-zoom': 3.0.8
|
| 5632 |
+
classcat: 5.0.5
|
| 5633 |
+
d3-selection: 3.0.0
|
| 5634 |
+
d3-zoom: 3.0.0
|
| 5635 |
+
react: 19.2.3
|
| 5636 |
+
react-dom: 19.2.3(react@19.2.3)
|
| 5637 |
+
zustand: 4.5.7(@types/react@19.2.9)(immer@11.1.3)(react@19.2.3)
|
| 5638 |
+
transitivePeerDependencies:
|
| 5639 |
+
- '@types/react'
|
| 5640 |
+
- immer
|
| 5641 |
+
|
| 5642 |
+
'@reactflow/node-resizer@2.2.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
| 5643 |
+
dependencies:
|
| 5644 |
+
'@reactflow/core': 11.11.4(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 5645 |
+
classcat: 5.0.5
|
| 5646 |
+
d3-drag: 3.0.0
|
| 5647 |
+
d3-selection: 3.0.0
|
| 5648 |
+
react: 19.2.3
|
| 5649 |
+
react-dom: 19.2.3(react@19.2.3)
|
| 5650 |
+
zustand: 4.5.7(@types/react@19.2.9)(immer@11.1.3)(react@19.2.3)
|
| 5651 |
+
transitivePeerDependencies:
|
| 5652 |
+
- '@types/react'
|
| 5653 |
+
- immer
|
| 5654 |
+
|
| 5655 |
+
'@reactflow/node-toolbar@1.3.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
| 5656 |
+
dependencies:
|
| 5657 |
+
'@reactflow/core': 11.11.4(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 5658 |
+
classcat: 5.0.5
|
| 5659 |
+
react: 19.2.3
|
| 5660 |
+
react-dom: 19.2.3(react@19.2.3)
|
| 5661 |
+
zustand: 4.5.7(@types/react@19.2.9)(immer@11.1.3)(react@19.2.3)
|
| 5662 |
+
transitivePeerDependencies:
|
| 5663 |
+
- '@types/react'
|
| 5664 |
+
- immer
|
| 5665 |
+
|
| 5666 |
'@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.9)(react@19.2.3)(redux@5.0.1))(react@19.2.3)':
|
| 5667 |
dependencies:
|
| 5668 |
'@standard-schema/spec': 1.1.0
|
|
|
|
| 5771 |
|
| 5772 |
'@types/d3-array@3.2.2': {}
|
| 5773 |
|
| 5774 |
+
'@types/d3-axis@3.0.6':
|
| 5775 |
+
dependencies:
|
| 5776 |
+
'@types/d3-selection': 3.0.11
|
| 5777 |
+
|
| 5778 |
+
'@types/d3-brush@3.0.6':
|
| 5779 |
+
dependencies:
|
| 5780 |
+
'@types/d3-selection': 3.0.11
|
| 5781 |
+
|
| 5782 |
+
'@types/d3-chord@3.0.6': {}
|
| 5783 |
+
|
| 5784 |
'@types/d3-color@3.1.3': {}
|
| 5785 |
|
| 5786 |
+
'@types/d3-contour@3.0.6':
|
| 5787 |
+
dependencies:
|
| 5788 |
+
'@types/d3-array': 3.2.2
|
| 5789 |
+
'@types/geojson': 7946.0.16
|
| 5790 |
+
|
| 5791 |
+
'@types/d3-delaunay@6.0.4': {}
|
| 5792 |
+
|
| 5793 |
+
'@types/d3-dispatch@3.0.7': {}
|
| 5794 |
+
|
| 5795 |
+
'@types/d3-drag@3.0.7':
|
| 5796 |
+
dependencies:
|
| 5797 |
+
'@types/d3-selection': 3.0.11
|
| 5798 |
+
|
| 5799 |
+
'@types/d3-dsv@3.0.7': {}
|
| 5800 |
+
|
| 5801 |
'@types/d3-ease@3.0.2': {}
|
| 5802 |
|
| 5803 |
+
'@types/d3-fetch@3.0.7':
|
| 5804 |
+
dependencies:
|
| 5805 |
+
'@types/d3-dsv': 3.0.7
|
| 5806 |
+
|
| 5807 |
+
'@types/d3-force@3.0.10': {}
|
| 5808 |
+
|
| 5809 |
+
'@types/d3-format@3.0.4': {}
|
| 5810 |
+
|
| 5811 |
+
'@types/d3-geo@3.1.0':
|
| 5812 |
+
dependencies:
|
| 5813 |
+
'@types/geojson': 7946.0.16
|
| 5814 |
+
|
| 5815 |
+
'@types/d3-hierarchy@3.1.7': {}
|
| 5816 |
+
|
| 5817 |
'@types/d3-interpolate@3.0.4':
|
| 5818 |
dependencies:
|
| 5819 |
'@types/d3-color': 3.1.3
|
| 5820 |
|
| 5821 |
'@types/d3-path@3.1.1': {}
|
| 5822 |
|
| 5823 |
+
'@types/d3-polygon@3.0.2': {}
|
| 5824 |
+
|
| 5825 |
+
'@types/d3-quadtree@3.0.6': {}
|
| 5826 |
+
|
| 5827 |
+
'@types/d3-random@3.0.3': {}
|
| 5828 |
+
|
| 5829 |
+
'@types/d3-scale-chromatic@3.1.0': {}
|
| 5830 |
+
|
| 5831 |
'@types/d3-scale@4.0.9':
|
| 5832 |
dependencies:
|
| 5833 |
'@types/d3-time': 3.0.4
|
| 5834 |
|
| 5835 |
+
'@types/d3-selection@3.0.11': {}
|
| 5836 |
+
|
| 5837 |
'@types/d3-shape@3.1.8':
|
| 5838 |
dependencies:
|
| 5839 |
'@types/d3-path': 3.1.1
|
| 5840 |
|
| 5841 |
+
'@types/d3-time-format@4.0.3': {}
|
| 5842 |
+
|
| 5843 |
'@types/d3-time@3.0.4': {}
|
| 5844 |
|
| 5845 |
'@types/d3-timer@3.0.2': {}
|
| 5846 |
|
| 5847 |
+
'@types/d3-transition@3.0.9':
|
| 5848 |
+
dependencies:
|
| 5849 |
+
'@types/d3-selection': 3.0.11
|
| 5850 |
+
|
| 5851 |
+
'@types/d3-zoom@3.0.8':
|
| 5852 |
+
dependencies:
|
| 5853 |
+
'@types/d3-interpolate': 3.0.4
|
| 5854 |
+
'@types/d3-selection': 3.0.11
|
| 5855 |
+
|
| 5856 |
+
'@types/d3@7.4.3':
|
| 5857 |
+
dependencies:
|
| 5858 |
+
'@types/d3-array': 3.2.2
|
| 5859 |
+
'@types/d3-axis': 3.0.6
|
| 5860 |
+
'@types/d3-brush': 3.0.6
|
| 5861 |
+
'@types/d3-chord': 3.0.6
|
| 5862 |
+
'@types/d3-color': 3.1.3
|
| 5863 |
+
'@types/d3-contour': 3.0.6
|
| 5864 |
+
'@types/d3-delaunay': 6.0.4
|
| 5865 |
+
'@types/d3-dispatch': 3.0.7
|
| 5866 |
+
'@types/d3-drag': 3.0.7
|
| 5867 |
+
'@types/d3-dsv': 3.0.7
|
| 5868 |
+
'@types/d3-ease': 3.0.2
|
| 5869 |
+
'@types/d3-fetch': 3.0.7
|
| 5870 |
+
'@types/d3-force': 3.0.10
|
| 5871 |
+
'@types/d3-format': 3.0.4
|
| 5872 |
+
'@types/d3-geo': 3.1.0
|
| 5873 |
+
'@types/d3-hierarchy': 3.1.7
|
| 5874 |
+
'@types/d3-interpolate': 3.0.4
|
| 5875 |
+
'@types/d3-path': 3.1.1
|
| 5876 |
+
'@types/d3-polygon': 3.0.2
|
| 5877 |
+
'@types/d3-quadtree': 3.0.6
|
| 5878 |
+
'@types/d3-random': 3.0.3
|
| 5879 |
+
'@types/d3-scale': 4.0.9
|
| 5880 |
+
'@types/d3-scale-chromatic': 3.1.0
|
| 5881 |
+
'@types/d3-selection': 3.0.11
|
| 5882 |
+
'@types/d3-shape': 3.1.8
|
| 5883 |
+
'@types/d3-time': 3.0.4
|
| 5884 |
+
'@types/d3-time-format': 4.0.3
|
| 5885 |
+
'@types/d3-timer': 3.0.2
|
| 5886 |
+
'@types/d3-transition': 3.0.9
|
| 5887 |
+
'@types/d3-zoom': 3.0.8
|
| 5888 |
+
|
| 5889 |
'@types/estree@1.0.8': {}
|
| 5890 |
|
| 5891 |
+
'@types/geojson@7946.0.16': {}
|
| 5892 |
+
|
| 5893 |
'@types/json-schema@7.0.15': {}
|
| 5894 |
|
| 5895 |
'@types/json5@0.0.29': {}
|
|
|
|
| 6271 |
dependencies:
|
| 6272 |
clsx: 2.1.1
|
| 6273 |
|
| 6274 |
+
classcat@5.0.5: {}
|
| 6275 |
+
|
| 6276 |
cli-cursor@5.0.0:
|
| 6277 |
dependencies:
|
| 6278 |
restore-cursor: 5.1.0
|
|
|
|
| 6347 |
|
| 6348 |
d3-color@3.1.0: {}
|
| 6349 |
|
| 6350 |
+
d3-dispatch@3.0.1: {}
|
| 6351 |
+
|
| 6352 |
+
d3-drag@3.0.0:
|
| 6353 |
+
dependencies:
|
| 6354 |
+
d3-dispatch: 3.0.1
|
| 6355 |
+
d3-selection: 3.0.0
|
| 6356 |
+
|
| 6357 |
d3-ease@3.0.1: {}
|
| 6358 |
|
| 6359 |
d3-format@3.1.2: {}
|
|
|
|
| 6372 |
d3-time: 3.1.0
|
| 6373 |
d3-time-format: 4.1.0
|
| 6374 |
|
| 6375 |
+
d3-selection@3.0.0: {}
|
| 6376 |
+
|
| 6377 |
d3-shape@3.2.0:
|
| 6378 |
dependencies:
|
| 6379 |
d3-path: 3.1.0
|
|
|
|
| 6388 |
|
| 6389 |
d3-timer@3.0.1: {}
|
| 6390 |
|
| 6391 |
+
d3-transition@3.0.1(d3-selection@3.0.0):
|
| 6392 |
+
dependencies:
|
| 6393 |
+
d3-color: 3.1.0
|
| 6394 |
+
d3-dispatch: 3.0.1
|
| 6395 |
+
d3-ease: 3.0.1
|
| 6396 |
+
d3-interpolate: 3.0.1
|
| 6397 |
+
d3-selection: 3.0.0
|
| 6398 |
+
d3-timer: 3.0.1
|
| 6399 |
+
|
| 6400 |
+
d3-zoom@3.0.0:
|
| 6401 |
+
dependencies:
|
| 6402 |
+
d3-dispatch: 3.0.1
|
| 6403 |
+
d3-drag: 3.0.0
|
| 6404 |
+
d3-interpolate: 3.0.1
|
| 6405 |
+
d3-selection: 3.0.0
|
| 6406 |
+
d3-transition: 3.0.1(d3-selection@3.0.0)
|
| 6407 |
+
|
| 6408 |
damerau-levenshtein@1.0.8: {}
|
| 6409 |
|
| 6410 |
data-uri-to-buffer@4.0.1: {}
|
|
|
|
| 7935 |
|
| 7936 |
react@19.2.3: {}
|
| 7937 |
|
| 7938 |
+
reactflow@11.11.4(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
| 7939 |
+
dependencies:
|
| 7940 |
+
'@reactflow/background': 11.3.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 7941 |
+
'@reactflow/controls': 11.2.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 7942 |
+
'@reactflow/core': 11.11.4(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 7943 |
+
'@reactflow/minimap': 11.7.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 7944 |
+
'@reactflow/node-resizer': 2.2.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 7945 |
+
'@reactflow/node-toolbar': 1.3.14(@types/react@19.2.9)(immer@11.1.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
| 7946 |
+
react: 19.2.3
|
| 7947 |
+
react-dom: 19.2.3(react@19.2.3)
|
| 7948 |
+
transitivePeerDependencies:
|
| 7949 |
+
- '@types/react'
|
| 7950 |
+
- immer
|
| 7951 |
+
|
| 7952 |
recast@0.23.11:
|
| 7953 |
dependencies:
|
| 7954 |
ast-types: 0.16.1
|
|
|
|
| 8675 |
zod@3.25.76: {}
|
| 8676 |
|
| 8677 |
zod@4.3.6: {}
|
| 8678 |
+
|
| 8679 |
+
zustand@4.5.7(@types/react@19.2.9)(immer@11.1.3)(react@19.2.3):
|
| 8680 |
+
dependencies:
|
| 8681 |
+
use-sync-external-store: 1.6.0(react@19.2.3)
|
| 8682 |
+
optionalDependencies:
|
| 8683 |
+
'@types/react': 19.2.9
|
| 8684 |
+
immer: 11.1.3
|
| 8685 |
+
react: 19.2.3
|