feat: MAANG-grade Studio UI (Tailwind + Lucide icons + confetti) and Hugging Face Docker deployment ready
Browse files- .dockerignore +9 -0
- Dockerfile +61 -0
- README.md +83 -45
- diffweave/bridge/app.py +9 -2
- studio/package-lock.json +982 -1
- studio/package.json +5 -0
- studio/postcss.config.js +6 -0
- studio/src/App.jsx +118 -81
- studio/src/components/AuditLog.jsx +124 -50
- studio/src/components/IngestionStaging.jsx +123 -121
- studio/src/components/KnowledgeBase.jsx +160 -93
- studio/src/components/KnowledgeGraph.jsx +300 -144
- studio/src/components/Navbar.jsx +150 -86
- studio/src/components/PRReviewDeck.jsx +240 -139
- studio/src/components/PolicyRulesLab.jsx +178 -157
- studio/src/components/SemanticDiffViewer.jsx +345 -201
- studio/tailwind.config.js +28 -0
.dockerignore
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules/
|
| 2 |
+
.git/
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.pyc
|
| 5 |
+
*.pyo
|
| 6 |
+
.env
|
| 7 |
+
.diffweave/credentials.json
|
| 8 |
+
.diffweave/config.json
|
| 9 |
+
storage/
|
Dockerfile
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ===========================================================================
|
| 2 |
+
# DiffWeave — Hugging Face Space Dockerfile (Multi-stage Build)
|
| 3 |
+
# ===========================================================================
|
| 4 |
+
|
| 5 |
+
# Stage 1: Build React 18 Studio Frontend
|
| 6 |
+
FROM node:18-bullseye-slim AS frontend-builder
|
| 7 |
+
WORKDIR /app/studio
|
| 8 |
+
COPY studio/package*.json ./
|
| 9 |
+
RUN npm ci
|
| 10 |
+
COPY studio ./
|
| 11 |
+
RUN npm run build
|
| 12 |
+
|
| 13 |
+
# Stage 2: Production Python Runtime
|
| 14 |
+
FROM python:3.10-slim
|
| 15 |
+
|
| 16 |
+
WORKDIR /app
|
| 17 |
+
|
| 18 |
+
# Install system dependencies
|
| 19 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 20 |
+
curl \
|
| 21 |
+
git \
|
| 22 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 23 |
+
|
| 24 |
+
# Install Python backend dependencies
|
| 25 |
+
COPY pyproject.toml ./
|
| 26 |
+
RUN pip install --no-cache-dir \
|
| 27 |
+
fastapi==0.115.0 \
|
| 28 |
+
uvicorn==0.30.6 \
|
| 29 |
+
pydantic==2.8.2 \
|
| 30 |
+
httpx==0.27.2 \
|
| 31 |
+
typer==0.12.5 \
|
| 32 |
+
rich==13.8.0 \
|
| 33 |
+
python-multipart==0.0.9
|
| 34 |
+
|
| 35 |
+
# Copy DiffWeave application package
|
| 36 |
+
COPY diffweave ./diffweave
|
| 37 |
+
COPY sample_test_docs ./sample_test_docs
|
| 38 |
+
COPY README.md ./
|
| 39 |
+
COPY pyproject.toml ./
|
| 40 |
+
|
| 41 |
+
# Copy compiled frontend from Stage 1
|
| 42 |
+
COPY --from=frontend-builder /app/studio/dist ./studio/dist
|
| 43 |
+
|
| 44 |
+
# Set up non-root user (Hugging Face Spaces requirement: UID 1000)
|
| 45 |
+
RUN useradd -m -u 1000 user
|
| 46 |
+
RUN chown -R user:user /app
|
| 47 |
+
USER user
|
| 48 |
+
ENV HOME=/home/user \
|
| 49 |
+
PATH=/home/user/.local/bin:$PATH \
|
| 50 |
+
PYTHONPATH=/app \
|
| 51 |
+
PORT=7860 \
|
| 52 |
+
HOST=0.0.0.0
|
| 53 |
+
|
| 54 |
+
EXPOSE 7860
|
| 55 |
+
|
| 56 |
+
# Health check
|
| 57 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 58 |
+
CMD curl -f http://localhost:7860/healthz || exit 1
|
| 59 |
+
|
| 60 |
+
# Launch FastAPI Bridge (Serves Studio Web UI + MCP APIs on port 7860)
|
| 61 |
+
CMD ["uvicorn", "diffweave.bridge.app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,17 +1,28 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
> **A Git-like developer platform for AI document intelligence, turning unstructured document changes into semantic commits, pull requests, and 3-way fact diffs.**
|
| 4 |
|
| 5 |
-
DiffWeave is built on top of the **DocWeave** document intelligence engine via the **Model Context Protocol (MCP)**. Where DocWeave acts as the deep extraction and reconciliation engine (
|
| 6 |
|
| 7 |
-
1. **dw (Command Line Interface)**: A high-velocity, Git-modeled CLI for staging documents, inspecting semantic diffs, validating CI policies, and committing approved facts to the Master Knowledge Register.
|
| 8 |
-
2. **DiffWeave Studio (Web UI)**: A
|
| 9 |
|
| 10 |
---
|
| 11 |
|
| 12 |
## Architecture Overview
|
| 13 |
|
| 14 |
-
`
|
| 15 |
+---------------------------------------+
|
| 16 |
| DiffWeave Ecosystem |
|
| 17 |
+-------------------+-------------------+
|
|
@@ -20,7 +31,7 @@ DiffWeave is built on top of the **DocWeave** document intelligence engine via t
|
|
| 20 |
| |
|
| 21 |
+----------v----------+ +----------v----------+
|
| 22 |
| DiffWeave CLI | | DiffWeave Studio |
|
| 23 |
-
| (dw) | | (React 18 + Vite) |
|
| 24 |
+----------+----------+ +----------+----------+
|
| 25 |
| |
|
| 26 |
| +----------v----------+
|
|
@@ -32,7 +43,7 @@ DiffWeave is built on top of the **DocWeave** document intelligence engine via t
|
|
| 32 |
|
|
| 33 |
+-------------------v-------------------+
|
| 34 |
| DiffWeave MCP Client |
|
| 35 |
-
| (Stdio / In-Process
|
| 36 |
+-------------------+-------------------+
|
| 37 |
|
|
| 38 |
+-------------------v-------------------+
|
|
@@ -46,7 +57,7 @@ DiffWeave is built on top of the **DocWeave** document intelligence engine via t
|
|
| 46 |
| Extraction Engine | | Neon PostgreSQL |
|
| 47 |
| & Embedding Models | | Master Knowledge |
|
| 48 |
+---------------------+ +---------------------+
|
| 49 |
-
`
|
| 50 |
|
| 51 |
---
|
| 52 |
|
|
@@ -54,34 +65,37 @@ DiffWeave is built on top of the **DocWeave** document intelligence engine via t
|
|
| 54 |
|
| 55 |
### 1. Requirements
|
| 56 |
- Python 3.10+
|
| 57 |
-
- Node.js 18+ (for Studio UI)
|
| 58 |
-
- DocWeave engine backend
|
| 59 |
|
| 60 |
### 2. CLI Setup
|
| 61 |
Run diagnostic checks to verify connectivity to DocWeave:
|
| 62 |
|
| 63 |
-
`
|
| 64 |
# Verify system and MCP health
|
| 65 |
.\dw.bat doctor
|
| 66 |
|
|
|
|
|
|
|
|
|
|
| 67 |
# Initialize or bind your workspace
|
| 68 |
.\dw.bat init --workspace 4314fb04-95be-41a2-bef4-50bf27c9c363
|
| 69 |
|
| 70 |
# Check workspace status
|
| 71 |
.\dw.bat status
|
| 72 |
-
`
|
| 73 |
|
| 74 |
-
### 3. Stage & Ingest Documents (dw add)
|
| 75 |
Stage a document into the ingestion pipeline:
|
| 76 |
|
| 77 |
-
`
|
| 78 |
.\dw.bat add sample_test_docs\doc1_clinical_protocol_baseline.txt
|
| 79 |
-
`
|
| 80 |
|
| 81 |
### 4. Semantic Diffs & PR Reviews
|
| 82 |
Once DocWeave completes proposal extraction:
|
| 83 |
|
| 84 |
-
`
|
| 85 |
# Inspect pending proposals (PRs)
|
| 86 |
.\dw.bat proposals
|
| 87 |
|
|
@@ -96,7 +110,7 @@ Once DocWeave completes proposal extraction:
|
|
| 96 |
|
| 97 |
# Or commit all approved proposals
|
| 98 |
.\dw.bat commit --all-approved
|
| 99 |
-
`
|
| 100 |
|
| 101 |
---
|
| 102 |
|
|
@@ -104,23 +118,42 @@ Once DocWeave completes proposal extraction:
|
|
| 104 |
|
| 105 |
DiffWeave Studio offers a rich visual dashboard for team review:
|
| 106 |
|
| 107 |
-
- **3-Way Semantic Fact Diff**: Side-by-side view showing baseline truth, proposed extractions, and conflict highlights.
|
| 108 |
-
- **Knowledge PR Deck**: One-click approvals, rejections, or archival with confidence scores and
|
| 109 |
-
- **Interactive Knowledge Graph**: SVG-based topological visualization of entities, relationships, and confidence thresholds.
|
| 110 |
-
- **Policy Rules Lab**:
|
| 111 |
-
- **Master Knowledge Base**: Searchable register of all committed facts with
|
|
|
|
| 112 |
|
| 113 |
### Running Studio
|
| 114 |
-
`
|
| 115 |
-
#
|
| 116 |
python -m diffweave.bridge.app
|
| 117 |
|
| 118 |
-
#
|
| 119 |
cd studio
|
| 120 |
npm run dev
|
| 121 |
-
`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
---
|
| 126 |
|
|
@@ -128,37 +161,42 @@ Visit http://localhost:8000 (or http://localhost:5173 if running Vite dev server
|
|
| 128 |
|
| 129 |
| Command | Description |
|
| 130 |
|---|---|
|
| 131 |
-
| dw
|
| 132 |
-
| dw
|
| 133 |
-
| dw
|
| 134 |
-
| dw
|
| 135 |
-
| dw
|
| 136 |
-
| dw
|
| 137 |
-
| dw
|
| 138 |
-
| dw
|
| 139 |
-
| dw
|
| 140 |
-
| dw
|
| 141 |
-
| dw
|
| 142 |
-
| dw
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
---
|
| 145 |
|
| 146 |
## Directory Structure
|
| 147 |
|
| 148 |
-
`
|
| 149 |
DiffWeave/
|
|
|
|
|
|
|
| 150 |
├── diffweave/
|
| 151 |
│ ├── cli/ # dw command line tool (Click / Typer + Rich)
|
| 152 |
│ ├���─ mcp/ # MCP client adapter & DocWeave integration
|
| 153 |
│ └── bridge/ # FastAPI bridge server for Studio Web UI
|
| 154 |
-
├── studio/ # React 18 + TailwindCSS + Vite Web UI
|
| 155 |
│ ├── src/
|
| 156 |
│ │ ├── components/ # SemanticDiffViewer, PRReviewDeck, Graph, etc.
|
| 157 |
-
│ │ ├── api/ # Studio REST client
|
| 158 |
│ │ └── App.jsx # Navigation, Workspace Switcher, State
|
| 159 |
-
│ └── dist/ # Production bundle (
|
| 160 |
├── sample_test_docs/ # Example documents for clinical trial protocol diffing
|
| 161 |
├── dw.bat # Windows batch launcher for instant CLI access
|
| 162 |
├── pyproject.toml # Python package configuration
|
| 163 |
-
└── README.md # Platform documentation
|
| 164 |
-
`
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: DiffWeave
|
| 3 |
+
emoji: 🧶
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# DiffWeave 🧶 — GitHub for Document Knowledge
|
| 13 |
|
| 14 |
> **A Git-like developer platform for AI document intelligence, turning unstructured document changes into semantic commits, pull requests, and 3-way fact diffs.**
|
| 15 |
|
| 16 |
+
DiffWeave is built on top of the **DocWeave** document intelligence engine via the **Model Context Protocol (MCP)**. Where DocWeave acts as the deep extraction and reconciliation engine ("Git plumbing"), **DiffWeave** serves as the developer-facing platform ("GitHub porcelain") comprising:
|
| 17 |
|
| 18 |
+
1. **`dw` (Command Line Interface)**: A high-velocity, Git-modeled CLI for staging documents, inspecting semantic diffs, validating CI policies, and committing approved facts to the Master Knowledge Register.
|
| 19 |
+
2. **DiffWeave Studio (Web UI)**: A MAANG-grade visual interface with a 3-way Semantic Fact Diff viewer, Knowledge PR Review deck, interactive Knowledge Graph topology, and Policy Rules lab.
|
| 20 |
|
| 21 |
---
|
| 22 |
|
| 23 |
## Architecture Overview
|
| 24 |
|
| 25 |
+
```
|
| 26 |
+---------------------------------------+
|
| 27 |
| DiffWeave Ecosystem |
|
| 28 |
+-------------------+-------------------+
|
|
|
|
| 31 |
| |
|
| 32 |
+----------v----------+ +----------v----------+
|
| 33 |
| DiffWeave CLI | | DiffWeave Studio |
|
| 34 |
+
| (`dw`) | | (React 18 + Vite) |
|
| 35 |
+----------+----------+ +----------+----------+
|
| 36 |
| |
|
| 37 |
| +----------v----------+
|
|
|
|
| 43 |
|
|
| 44 |
+-------------------v-------------------+
|
| 45 |
| DiffWeave MCP Client |
|
| 46 |
+
| (Stdio / Remote HTTPS / In-Process) |
|
| 47 |
+-------------------+-------------------+
|
| 48 |
|
|
| 49 |
+-------------------v-------------------+
|
|
|
|
| 57 |
| Extraction Engine | | Neon PostgreSQL |
|
| 58 |
| & Embedding Models | | Master Knowledge |
|
| 59 |
+---------------------+ +---------------------+
|
| 60 |
+
```
|
| 61 |
|
| 62 |
---
|
| 63 |
|
|
|
|
| 65 |
|
| 66 |
### 1. Requirements
|
| 67 |
- Python 3.10+
|
| 68 |
+
- Node.js 18+ (for Studio UI development)
|
| 69 |
+
- DocWeave engine backend (or run in embedded standalone mode)
|
| 70 |
|
| 71 |
### 2. CLI Setup
|
| 72 |
Run diagnostic checks to verify connectivity to DocWeave:
|
| 73 |
|
| 74 |
+
```bash
|
| 75 |
# Verify system and MCP health
|
| 76 |
.\dw.bat doctor
|
| 77 |
|
| 78 |
+
# Authenticate session (or use 1-click demo login)
|
| 79 |
+
.\dw.bat login --demo
|
| 80 |
+
|
| 81 |
# Initialize or bind your workspace
|
| 82 |
.\dw.bat init --workspace 4314fb04-95be-41a2-bef4-50bf27c9c363
|
| 83 |
|
| 84 |
# Check workspace status
|
| 85 |
.\dw.bat status
|
| 86 |
+
```
|
| 87 |
|
| 88 |
+
### 3. Stage & Ingest Documents (`dw add`)
|
| 89 |
Stage a document into the ingestion pipeline:
|
| 90 |
|
| 91 |
+
```bash
|
| 92 |
.\dw.bat add sample_test_docs\doc1_clinical_protocol_baseline.txt
|
| 93 |
+
```
|
| 94 |
|
| 95 |
### 4. Semantic Diffs & PR Reviews
|
| 96 |
Once DocWeave completes proposal extraction:
|
| 97 |
|
| 98 |
+
```bash
|
| 99 |
# Inspect pending proposals (PRs)
|
| 100 |
.\dw.bat proposals
|
| 101 |
|
|
|
|
| 110 |
|
| 111 |
# Or commit all approved proposals
|
| 112 |
.\dw.bat commit --all-approved
|
| 113 |
+
```
|
| 114 |
|
| 115 |
---
|
| 116 |
|
|
|
|
| 118 |
|
| 119 |
DiffWeave Studio offers a rich visual dashboard for team review:
|
| 120 |
|
| 121 |
+
- **3-Way Semantic Fact Diff**: Side-by-side view showing baseline truth, proposed extractions, and conflict highlights with word-level delta tags.
|
| 122 |
+
- **Knowledge PR Deck**: One-click approvals, rejections, or archival with confidence scores, reviewer notes, and celebratory confetti.
|
| 123 |
+
- **Interactive Knowledge Graph**: SVG-based topological visualization of entities, relationships, and confidence thresholds with zoom/pan and node inspector drawer.
|
| 124 |
+
- **Policy Rules Lab**: Policy-as-Code engine with dry-run CI linter suite.
|
| 125 |
+
- **Master Knowledge Base**: Searchable register of all committed facts with JSON export and instant copy.
|
| 126 |
+
- **Git Audit Trail**: Cryptographically verifiable commit timeline with verification badges.
|
| 127 |
|
| 128 |
### Running Studio
|
| 129 |
+
```bash
|
| 130 |
+
# Full-Stack Bridge Server (Serves UI + API on port 7860 or 8000)
|
| 131 |
python -m diffweave.bridge.app
|
| 132 |
|
| 133 |
+
# Vite Development Server (with hot-module reloading)
|
| 134 |
cd studio
|
| 135 |
npm run dev
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
Visit `http://localhost:7860` (or `http://localhost:5174` for Vite dev server).
|
| 139 |
+
|
| 140 |
+
---
|
| 141 |
+
|
| 142 |
+
## Hugging Face Spaces Deployment
|
| 143 |
+
|
| 144 |
+
DiffWeave is pre-configured for instant **1-click Docker deployment** on Hugging Face Spaces:
|
| 145 |
|
| 146 |
+
1. Create a new Space on [Hugging Face Spaces](https://huggingface.co/new-space).
|
| 147 |
+
2. Select **Docker** as the Space SDK.
|
| 148 |
+
3. Push or sync this repository to your Hugging Face Space git remote:
|
| 149 |
+
```bash
|
| 150 |
+
git remote add hf https://huggingface.co/spaces/<your-username>/DiffWeave
|
| 151 |
+
git push hf main
|
| 152 |
+
```
|
| 153 |
+
4. Configure Secrets / Environment Variables (optional, for remote DocWeave connection):
|
| 154 |
+
- `DOCWEAVE_MCP_URL`: `https://<your-docweave-space>.hf.space`
|
| 155 |
+
- `DOCWEAVE_API_KEY`: `<your_access_token>`
|
| 156 |
+
5. Your full-stack DiffWeave Studio will automatically build and launch on port `7860`!
|
| 157 |
|
| 158 |
---
|
| 159 |
|
|
|
|
| 161 |
|
| 162 |
| Command | Description |
|
| 163 |
|---|---|
|
| 164 |
+
| `dw login` | Authenticate with DocWeave via email/password, demo, or API token |
|
| 165 |
+
| `dw logout` | Log out and revoke stored session credentials |
|
| 166 |
+
| `dw whoami` | Display active user identity, token preview, and bound workspace |
|
| 167 |
+
| `dw init` | Initialize `.diffweave/` context or bind to existing workspace |
|
| 168 |
+
| `dw add <path>` | Stage and trigger document ingestion workflow |
|
| 169 |
+
| `dw status` | Display workspace summary, tracked documents, and pending reviews |
|
| 170 |
+
| `dw proposals` | List and inspect extracted knowledge proposals |
|
| 171 |
+
| `dw diff` | Render 3-way semantic fact diffs with confidence scoring |
|
| 172 |
+
| `dw validate` | Execute workspace policy rules and display pass/warn/fail lint table |
|
| 173 |
+
| `dw review` | Interactive terminal wizard to approve, reject, or archive proposals |
|
| 174 |
+
| `dw commit` | Promote approved proposals into Master Knowledge Register |
|
| 175 |
+
| `dw log` | Chronological audit trail of document updates and state transitions |
|
| 176 |
+
| `dw search <query>` | Hybrid search across committed knowledge entities |
|
| 177 |
+
| `dw rules` | List, add, or delete automated validation rules |
|
| 178 |
+
| `dw doctor` | Diagnose MCP connectivity, environment, and tool coverage |
|
| 179 |
|
| 180 |
---
|
| 181 |
|
| 182 |
## Directory Structure
|
| 183 |
|
| 184 |
+
```
|
| 185 |
DiffWeave/
|
| 186 |
+
├── Dockerfile # Multi-stage production build for Hugging Face Spaces
|
| 187 |
+
├── .dockerignore # Optimized Docker build context
|
| 188 |
├── diffweave/
|
| 189 |
│ ├── cli/ # dw command line tool (Click / Typer + Rich)
|
| 190 |
│ ├���─ mcp/ # MCP client adapter & DocWeave integration
|
| 191 |
│ └── bridge/ # FastAPI bridge server for Studio Web UI
|
| 192 |
+
├── studio/ # React 18 + TailwindCSS + Lucide Icons + Vite Web UI
|
| 193 |
│ ├── src/
|
| 194 |
│ │ ├── components/ # SemanticDiffViewer, PRReviewDeck, Graph, etc.
|
| 195 |
+
│ │ ├── api/ # Studio REST client with Bearer auth
|
| 196 |
│ │ └── App.jsx # Navigation, Workspace Switcher, State
|
| 197 |
+
│ └── dist/ # Production bundle (compiled and ready to serve)
|
| 198 |
├── sample_test_docs/ # Example documents for clinical trial protocol diffing
|
| 199 |
├── dw.bat # Windows batch launcher for instant CLI access
|
| 200 |
├── pyproject.toml # Python package configuration
|
| 201 |
+
└── README.md # Platform documentation with Hugging Face metadata
|
| 202 |
+
```
|
diffweave/bridge/app.py
CHANGED
|
@@ -399,12 +399,18 @@ async def get_activity_feed_endpoint(
|
|
| 399 |
# Static Studio UI Hosting (if built)
|
| 400 |
# ---------------------------------------------------------------------------
|
| 401 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
studio_dist_candidates = [
|
|
|
|
|
|
|
| 403 |
Path(__file__).parent.parent.parent / "studio" / "dist",
|
| 404 |
Path(__file__).parent.parent / "studio" / "dist",
|
| 405 |
Path("C:/Users/Adhi/Desktop/DiffWeave/studio/dist"),
|
| 406 |
]
|
| 407 |
-
|
| 408 |
for dist_path in studio_dist_candidates:
|
| 409 |
if dist_path.exists() and (dist_path / "index.html").exists():
|
| 410 |
from fastapi.staticfiles import StaticFiles
|
|
@@ -414,4 +420,5 @@ for dist_path in studio_dist_candidates:
|
|
| 414 |
|
| 415 |
if __name__ == "__main__":
|
| 416 |
import uvicorn
|
| 417 |
-
|
|
|
|
|
|
| 399 |
# Static Studio UI Hosting (if built)
|
| 400 |
# ---------------------------------------------------------------------------
|
| 401 |
|
| 402 |
+
@app.get("/healthz")
|
| 403 |
+
async def healthz():
|
| 404 |
+
return {"status": "ok", "service": "diffweave", "platform": "huggingface-space"}
|
| 405 |
+
|
| 406 |
+
|
| 407 |
studio_dist_candidates = [
|
| 408 |
+
Path("/app/studio/dist"),
|
| 409 |
+
Path("studio/dist"),
|
| 410 |
Path(__file__).parent.parent.parent / "studio" / "dist",
|
| 411 |
Path(__file__).parent.parent / "studio" / "dist",
|
| 412 |
Path("C:/Users/Adhi/Desktop/DiffWeave/studio/dist"),
|
| 413 |
]
|
|
|
|
| 414 |
for dist_path in studio_dist_candidates:
|
| 415 |
if dist_path.exists() and (dist_path / "index.html").exists():
|
| 416 |
from fastapi.staticfiles import StaticFiles
|
|
|
|
| 420 |
|
| 421 |
if __name__ == "__main__":
|
| 422 |
import uvicorn
|
| 423 |
+
port = int(os.environ.get("PORT", 7860))
|
| 424 |
+
uvicorn.run("diffweave.bridge.app:app", host="0.0.0.0", port=port, reload=False)
|
studio/package-lock.json
CHANGED
|
@@ -8,14 +8,32 @@
|
|
| 8 |
"name": "diffweave-studio",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
|
|
|
|
|
|
| 11 |
"react": "^18.2.0",
|
| 12 |
"react-dom": "^18.2.0"
|
| 13 |
},
|
| 14 |
"devDependencies": {
|
| 15 |
"@vitejs/plugin-react": "^4.2.0",
|
|
|
|
|
|
|
|
|
|
| 16 |
"vite": "^5.0.0"
|
| 17 |
}
|
| 18 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
"node_modules/@babel/code-frame": {
|
| 20 |
"version": "7.29.7",
|
| 21 |
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
|
@@ -759,6 +777,44 @@
|
|
| 759 |
"node": "^22.20 || ^24.12 || >=25"
|
| 760 |
}
|
| 761 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
"node_modules/@rolldown/pluginutils": {
|
| 763 |
"version": "1.0.0-beta.27",
|
| 764 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
|
@@ -1228,6 +1284,71 @@
|
|
| 1228 |
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
| 1229 |
}
|
| 1230 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1231 |
"node_modules/baseline-browser-mapping": {
|
| 1232 |
"version": "2.11.27",
|
| 1233 |
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz",
|
|
@@ -1241,6 +1362,32 @@
|
|
| 1241 |
"node": ">=6.0.0"
|
| 1242 |
}
|
| 1243 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1244 |
"node_modules/browserslist": {
|
| 1245 |
"version": "4.29.3",
|
| 1246 |
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz",
|
|
@@ -1275,6 +1422,16 @@
|
|
| 1275 |
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1276 |
}
|
| 1277 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1278 |
"node_modules/caniuse-lite": {
|
| 1279 |
"version": "1.0.30001814",
|
| 1280 |
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz",
|
|
@@ -1296,6 +1453,64 @@
|
|
| 1296 |
],
|
| 1297 |
"license": "CC-BY-4.0"
|
| 1298 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1299 |
"node_modules/convert-source-map": {
|
| 1300 |
"version": "2.0.0",
|
| 1301 |
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
|
@@ -1303,6 +1518,19 @@
|
|
| 1303 |
"dev": true,
|
| 1304 |
"license": "MIT"
|
| 1305 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1306 |
"node_modules/debug": {
|
| 1307 |
"version": "4.4.3",
|
| 1308 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
|
@@ -1321,6 +1549,20 @@
|
|
| 1321 |
}
|
| 1322 |
}
|
| 1323 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1324 |
"node_modules/electron-to-chromium": {
|
| 1325 |
"version": "1.5.444",
|
| 1326 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.444.tgz",
|
|
@@ -1328,6 +1570,16 @@
|
|
| 1328 |
"dev": true,
|
| 1329 |
"license": "ISC"
|
| 1330 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1331 |
"node_modules/esbuild": {
|
| 1332 |
"version": "0.21.5",
|
| 1333 |
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
|
@@ -1377,6 +1629,73 @@
|
|
| 1377 |
"node": ">=6"
|
| 1378 |
}
|
| 1379 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1380 |
"node_modules/fsevents": {
|
| 1381 |
"version": "2.3.3",
|
| 1382 |
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
|
@@ -1392,6 +1711,16 @@
|
|
| 1392 |
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1393 |
}
|
| 1394 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1395 |
"node_modules/gensync": {
|
| 1396 |
"version": "1.0.0-beta.2",
|
| 1397 |
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
|
@@ -1402,6 +1731,104 @@
|
|
| 1402 |
"node": ">=6.9.0"
|
| 1403 |
}
|
| 1404 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1405 |
"node_modules/js-tokens": {
|
| 1406 |
"version": "4.0.0",
|
| 1407 |
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
@@ -1434,6 +1861,23 @@
|
|
| 1434 |
"node": ">=6"
|
| 1435 |
}
|
| 1436 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1437 |
"node_modules/loose-envify": {
|
| 1438 |
"version": "1.4.0",
|
| 1439 |
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
@@ -1456,6 +1900,45 @@
|
|
| 1456 |
"yallist": "^3.0.2"
|
| 1457 |
}
|
| 1458 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1459 |
"node_modules/ms": {
|
| 1460 |
"version": "2.1.3",
|
| 1461 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
|
@@ -1463,6 +1946,18 @@
|
|
| 1463 |
"dev": true,
|
| 1464 |
"license": "MIT"
|
| 1465 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1466 |
"node_modules/nanoid": {
|
| 1467 |
"version": "3.3.19",
|
| 1468 |
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
|
|
@@ -1492,13 +1987,73 @@
|
|
| 1492 |
"node": ">=18"
|
| 1493 |
}
|
| 1494 |
},
|
| 1495 |
-
"node_modules/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1496 |
"version": "1.1.1",
|
| 1497 |
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1498 |
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1499 |
"dev": true,
|
| 1500 |
"license": "ISC"
|
| 1501 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1502 |
"node_modules/postcss": {
|
| 1503 |
"version": "8.5.28",
|
| 1504 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
|
|
@@ -1528,6 +2083,167 @@
|
|
| 1528 |
"node": "^10 || ^12 || >=14"
|
| 1529 |
}
|
| 1530 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1531 |
"node_modules/react": {
|
| 1532 |
"version": "18.3.1",
|
| 1533 |
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
|
@@ -1563,6 +2279,59 @@
|
|
| 1563 |
"node": ">=0.10.0"
|
| 1564 |
}
|
| 1565 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1566 |
"node_modules/rollup": {
|
| 1567 |
"version": "4.63.6",
|
| 1568 |
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.6.tgz",
|
|
@@ -1609,6 +2378,30 @@
|
|
| 1609 |
"fsevents": "~2.3.2"
|
| 1610 |
}
|
| 1611 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1612 |
"node_modules/scheduler": {
|
| 1613 |
"version": "0.23.2",
|
| 1614 |
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
|
@@ -1638,6 +2431,171 @@
|
|
| 1638 |
"node": ">=0.10.0"
|
| 1639 |
}
|
| 1640 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1641 |
"node_modules/update-browserslist-db": {
|
| 1642 |
"version": "1.3.3",
|
| 1643 |
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
|
|
@@ -1669,6 +2627,13 @@
|
|
| 1669 |
"browserslist": ">= 4.21.0"
|
| 1670 |
}
|
| 1671 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1672 |
"node_modules/vite": {
|
| 1673 |
"version": "5.4.21",
|
| 1674 |
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
|
@@ -1735,6 +2700,22 @@
|
|
| 1735 |
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 1736 |
"dev": true,
|
| 1737 |
"license": "ISC"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1738 |
}
|
| 1739 |
}
|
| 1740 |
}
|
|
|
|
| 8 |
"name": "diffweave-studio",
|
| 9 |
"version": "0.1.0",
|
| 10 |
"dependencies": {
|
| 11 |
+
"canvas-confetti": "^1.9.4",
|
| 12 |
+
"lucide-react": "^1.49.0",
|
| 13 |
"react": "^18.2.0",
|
| 14 |
"react-dom": "^18.2.0"
|
| 15 |
},
|
| 16 |
"devDependencies": {
|
| 17 |
"@vitejs/plugin-react": "^4.2.0",
|
| 18 |
+
"autoprefixer": "^10.6.1",
|
| 19 |
+
"postcss": "^8.5.28",
|
| 20 |
+
"tailwindcss": "^3.4.1",
|
| 21 |
"vite": "^5.0.0"
|
| 22 |
}
|
| 23 |
},
|
| 24 |
+
"node_modules/@alloc/quick-lru": {
|
| 25 |
+
"version": "5.3.0",
|
| 26 |
+
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz",
|
| 27 |
+
"integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==",
|
| 28 |
+
"dev": true,
|
| 29 |
+
"license": "MIT",
|
| 30 |
+
"engines": {
|
| 31 |
+
"node": ">=10"
|
| 32 |
+
},
|
| 33 |
+
"funding": {
|
| 34 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
"node_modules/@babel/code-frame": {
|
| 38 |
"version": "7.29.7",
|
| 39 |
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
|
|
|
| 777 |
"node": "^22.20 || ^24.12 || >=25"
|
| 778 |
}
|
| 779 |
},
|
| 780 |
+
"node_modules/@nodelib/fs.scandir": {
|
| 781 |
+
"version": "2.1.5",
|
| 782 |
+
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
| 783 |
+
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
| 784 |
+
"dev": true,
|
| 785 |
+
"license": "MIT",
|
| 786 |
+
"dependencies": {
|
| 787 |
+
"@nodelib/fs.stat": "2.0.5",
|
| 788 |
+
"run-parallel": "^1.1.9"
|
| 789 |
+
},
|
| 790 |
+
"engines": {
|
| 791 |
+
"node": ">= 8"
|
| 792 |
+
}
|
| 793 |
+
},
|
| 794 |
+
"node_modules/@nodelib/fs.stat": {
|
| 795 |
+
"version": "2.0.5",
|
| 796 |
+
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
| 797 |
+
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
| 798 |
+
"dev": true,
|
| 799 |
+
"license": "MIT",
|
| 800 |
+
"engines": {
|
| 801 |
+
"node": ">= 8"
|
| 802 |
+
}
|
| 803 |
+
},
|
| 804 |
+
"node_modules/@nodelib/fs.walk": {
|
| 805 |
+
"version": "1.2.8",
|
| 806 |
+
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
| 807 |
+
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
| 808 |
+
"dev": true,
|
| 809 |
+
"license": "MIT",
|
| 810 |
+
"dependencies": {
|
| 811 |
+
"@nodelib/fs.scandir": "2.1.5",
|
| 812 |
+
"fastq": "^1.6.0"
|
| 813 |
+
},
|
| 814 |
+
"engines": {
|
| 815 |
+
"node": ">= 8"
|
| 816 |
+
}
|
| 817 |
+
},
|
| 818 |
"node_modules/@rolldown/pluginutils": {
|
| 819 |
"version": "1.0.0-beta.27",
|
| 820 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
|
|
|
| 1284 |
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
| 1285 |
}
|
| 1286 |
},
|
| 1287 |
+
"node_modules/any-promise": {
|
| 1288 |
+
"version": "1.3.0",
|
| 1289 |
+
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
| 1290 |
+
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
| 1291 |
+
"dev": true,
|
| 1292 |
+
"license": "MIT"
|
| 1293 |
+
},
|
| 1294 |
+
"node_modules/anymatch": {
|
| 1295 |
+
"version": "3.1.3",
|
| 1296 |
+
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
| 1297 |
+
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
| 1298 |
+
"dev": true,
|
| 1299 |
+
"license": "ISC",
|
| 1300 |
+
"dependencies": {
|
| 1301 |
+
"normalize-path": "^3.0.0",
|
| 1302 |
+
"picomatch": "^2.0.4"
|
| 1303 |
+
},
|
| 1304 |
+
"engines": {
|
| 1305 |
+
"node": ">= 8"
|
| 1306 |
+
}
|
| 1307 |
+
},
|
| 1308 |
+
"node_modules/arg": {
|
| 1309 |
+
"version": "5.0.2",
|
| 1310 |
+
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
| 1311 |
+
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
| 1312 |
+
"dev": true,
|
| 1313 |
+
"license": "MIT"
|
| 1314 |
+
},
|
| 1315 |
+
"node_modules/autoprefixer": {
|
| 1316 |
+
"version": "10.6.1",
|
| 1317 |
+
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.1.tgz",
|
| 1318 |
+
"integrity": "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==",
|
| 1319 |
+
"dev": true,
|
| 1320 |
+
"funding": [
|
| 1321 |
+
{
|
| 1322 |
+
"type": "opencollective",
|
| 1323 |
+
"url": "https://opencollective.com/postcss/"
|
| 1324 |
+
},
|
| 1325 |
+
{
|
| 1326 |
+
"type": "tidelift",
|
| 1327 |
+
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
| 1328 |
+
},
|
| 1329 |
+
{
|
| 1330 |
+
"type": "github",
|
| 1331 |
+
"url": "https://github.com/sponsors/ai"
|
| 1332 |
+
}
|
| 1333 |
+
],
|
| 1334 |
+
"license": "MIT",
|
| 1335 |
+
"dependencies": {
|
| 1336 |
+
"browserslist": "^4.28.9",
|
| 1337 |
+
"caniuse-lite": "^1.0.30001810",
|
| 1338 |
+
"fraction.js": "^5.3.4",
|
| 1339 |
+
"picocolors": "^1.1.1",
|
| 1340 |
+
"postcss-value-parser": "^4.2.0"
|
| 1341 |
+
},
|
| 1342 |
+
"bin": {
|
| 1343 |
+
"autoprefixer": "bin/autoprefixer"
|
| 1344 |
+
},
|
| 1345 |
+
"engines": {
|
| 1346 |
+
"node": "^10 || ^12 || >=14"
|
| 1347 |
+
},
|
| 1348 |
+
"peerDependencies": {
|
| 1349 |
+
"postcss": "^8.1.0"
|
| 1350 |
+
}
|
| 1351 |
+
},
|
| 1352 |
"node_modules/baseline-browser-mapping": {
|
| 1353 |
"version": "2.11.27",
|
| 1354 |
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz",
|
|
|
|
| 1362 |
"node": ">=6.0.0"
|
| 1363 |
}
|
| 1364 |
},
|
| 1365 |
+
"node_modules/binary-extensions": {
|
| 1366 |
+
"version": "2.3.0",
|
| 1367 |
+
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
| 1368 |
+
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
| 1369 |
+
"dev": true,
|
| 1370 |
+
"license": "MIT",
|
| 1371 |
+
"engines": {
|
| 1372 |
+
"node": ">=8"
|
| 1373 |
+
},
|
| 1374 |
+
"funding": {
|
| 1375 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1376 |
+
}
|
| 1377 |
+
},
|
| 1378 |
+
"node_modules/braces": {
|
| 1379 |
+
"version": "3.0.3",
|
| 1380 |
+
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
| 1381 |
+
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
| 1382 |
+
"dev": true,
|
| 1383 |
+
"license": "MIT",
|
| 1384 |
+
"dependencies": {
|
| 1385 |
+
"fill-range": "^7.1.1"
|
| 1386 |
+
},
|
| 1387 |
+
"engines": {
|
| 1388 |
+
"node": ">=8"
|
| 1389 |
+
}
|
| 1390 |
+
},
|
| 1391 |
"node_modules/browserslist": {
|
| 1392 |
"version": "4.29.3",
|
| 1393 |
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz",
|
|
|
|
| 1422 |
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1423 |
}
|
| 1424 |
},
|
| 1425 |
+
"node_modules/camelcase-css": {
|
| 1426 |
+
"version": "2.0.1",
|
| 1427 |
+
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
| 1428 |
+
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
| 1429 |
+
"dev": true,
|
| 1430 |
+
"license": "MIT",
|
| 1431 |
+
"engines": {
|
| 1432 |
+
"node": ">= 6"
|
| 1433 |
+
}
|
| 1434 |
+
},
|
| 1435 |
"node_modules/caniuse-lite": {
|
| 1436 |
"version": "1.0.30001814",
|
| 1437 |
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz",
|
|
|
|
| 1453 |
],
|
| 1454 |
"license": "CC-BY-4.0"
|
| 1455 |
},
|
| 1456 |
+
"node_modules/canvas-confetti": {
|
| 1457 |
+
"version": "1.9.4",
|
| 1458 |
+
"resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.4.tgz",
|
| 1459 |
+
"integrity": "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==",
|
| 1460 |
+
"license": "ISC",
|
| 1461 |
+
"funding": {
|
| 1462 |
+
"type": "donate",
|
| 1463 |
+
"url": "https://www.paypal.me/kirilvatev"
|
| 1464 |
+
}
|
| 1465 |
+
},
|
| 1466 |
+
"node_modules/chokidar": {
|
| 1467 |
+
"version": "3.6.0",
|
| 1468 |
+
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
| 1469 |
+
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
| 1470 |
+
"dev": true,
|
| 1471 |
+
"license": "MIT",
|
| 1472 |
+
"dependencies": {
|
| 1473 |
+
"anymatch": "~3.1.2",
|
| 1474 |
+
"braces": "~3.0.2",
|
| 1475 |
+
"glob-parent": "~5.1.2",
|
| 1476 |
+
"is-binary-path": "~2.1.0",
|
| 1477 |
+
"is-glob": "~4.0.1",
|
| 1478 |
+
"normalize-path": "~3.0.0",
|
| 1479 |
+
"readdirp": "~3.6.0"
|
| 1480 |
+
},
|
| 1481 |
+
"engines": {
|
| 1482 |
+
"node": ">= 8.10.0"
|
| 1483 |
+
},
|
| 1484 |
+
"funding": {
|
| 1485 |
+
"url": "https://paulmillr.com/funding/"
|
| 1486 |
+
},
|
| 1487 |
+
"optionalDependencies": {
|
| 1488 |
+
"fsevents": "~2.3.2"
|
| 1489 |
+
}
|
| 1490 |
+
},
|
| 1491 |
+
"node_modules/chokidar/node_modules/glob-parent": {
|
| 1492 |
+
"version": "5.1.2",
|
| 1493 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
| 1494 |
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
| 1495 |
+
"dev": true,
|
| 1496 |
+
"license": "ISC",
|
| 1497 |
+
"dependencies": {
|
| 1498 |
+
"is-glob": "^4.0.1"
|
| 1499 |
+
},
|
| 1500 |
+
"engines": {
|
| 1501 |
+
"node": ">= 6"
|
| 1502 |
+
}
|
| 1503 |
+
},
|
| 1504 |
+
"node_modules/commander": {
|
| 1505 |
+
"version": "4.1.1",
|
| 1506 |
+
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
| 1507 |
+
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
| 1508 |
+
"dev": true,
|
| 1509 |
+
"license": "MIT",
|
| 1510 |
+
"engines": {
|
| 1511 |
+
"node": ">= 6"
|
| 1512 |
+
}
|
| 1513 |
+
},
|
| 1514 |
"node_modules/convert-source-map": {
|
| 1515 |
"version": "2.0.0",
|
| 1516 |
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
|
|
|
| 1518 |
"dev": true,
|
| 1519 |
"license": "MIT"
|
| 1520 |
},
|
| 1521 |
+
"node_modules/cssesc": {
|
| 1522 |
+
"version": "3.0.0",
|
| 1523 |
+
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
| 1524 |
+
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
| 1525 |
+
"dev": true,
|
| 1526 |
+
"license": "MIT",
|
| 1527 |
+
"bin": {
|
| 1528 |
+
"cssesc": "bin/cssesc"
|
| 1529 |
+
},
|
| 1530 |
+
"engines": {
|
| 1531 |
+
"node": ">=4"
|
| 1532 |
+
}
|
| 1533 |
+
},
|
| 1534 |
"node_modules/debug": {
|
| 1535 |
"version": "4.4.3",
|
| 1536 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
|
|
|
| 1549 |
}
|
| 1550 |
}
|
| 1551 |
},
|
| 1552 |
+
"node_modules/didyoumean": {
|
| 1553 |
+
"version": "1.2.2",
|
| 1554 |
+
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
| 1555 |
+
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
| 1556 |
+
"dev": true,
|
| 1557 |
+
"license": "Apache-2.0"
|
| 1558 |
+
},
|
| 1559 |
+
"node_modules/dlv": {
|
| 1560 |
+
"version": "1.1.3",
|
| 1561 |
+
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
| 1562 |
+
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
| 1563 |
+
"dev": true,
|
| 1564 |
+
"license": "MIT"
|
| 1565 |
+
},
|
| 1566 |
"node_modules/electron-to-chromium": {
|
| 1567 |
"version": "1.5.444",
|
| 1568 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.444.tgz",
|
|
|
|
| 1570 |
"dev": true,
|
| 1571 |
"license": "ISC"
|
| 1572 |
},
|
| 1573 |
+
"node_modules/es-errors": {
|
| 1574 |
+
"version": "1.3.0",
|
| 1575 |
+
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 1576 |
+
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
| 1577 |
+
"dev": true,
|
| 1578 |
+
"license": "MIT",
|
| 1579 |
+
"engines": {
|
| 1580 |
+
"node": ">= 0.4"
|
| 1581 |
+
}
|
| 1582 |
+
},
|
| 1583 |
"node_modules/esbuild": {
|
| 1584 |
"version": "0.21.5",
|
| 1585 |
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
|
|
|
| 1629 |
"node": ">=6"
|
| 1630 |
}
|
| 1631 |
},
|
| 1632 |
+
"node_modules/fast-glob": {
|
| 1633 |
+
"version": "3.3.3",
|
| 1634 |
+
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
| 1635 |
+
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
| 1636 |
+
"dev": true,
|
| 1637 |
+
"license": "MIT",
|
| 1638 |
+
"dependencies": {
|
| 1639 |
+
"@nodelib/fs.stat": "^2.0.2",
|
| 1640 |
+
"@nodelib/fs.walk": "^1.2.3",
|
| 1641 |
+
"glob-parent": "^5.1.2",
|
| 1642 |
+
"merge2": "^1.3.0",
|
| 1643 |
+
"micromatch": "^4.0.8"
|
| 1644 |
+
},
|
| 1645 |
+
"engines": {
|
| 1646 |
+
"node": ">=8.6.0"
|
| 1647 |
+
}
|
| 1648 |
+
},
|
| 1649 |
+
"node_modules/fast-glob/node_modules/glob-parent": {
|
| 1650 |
+
"version": "5.1.2",
|
| 1651 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
| 1652 |
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
| 1653 |
+
"dev": true,
|
| 1654 |
+
"license": "ISC",
|
| 1655 |
+
"dependencies": {
|
| 1656 |
+
"is-glob": "^4.0.1"
|
| 1657 |
+
},
|
| 1658 |
+
"engines": {
|
| 1659 |
+
"node": ">= 6"
|
| 1660 |
+
}
|
| 1661 |
+
},
|
| 1662 |
+
"node_modules/fastq": {
|
| 1663 |
+
"version": "1.20.3",
|
| 1664 |
+
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz",
|
| 1665 |
+
"integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==",
|
| 1666 |
+
"dev": true,
|
| 1667 |
+
"license": "ISC",
|
| 1668 |
+
"dependencies": {
|
| 1669 |
+
"reusify": "^1.0.4"
|
| 1670 |
+
}
|
| 1671 |
+
},
|
| 1672 |
+
"node_modules/fill-range": {
|
| 1673 |
+
"version": "7.1.1",
|
| 1674 |
+
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
| 1675 |
+
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
| 1676 |
+
"dev": true,
|
| 1677 |
+
"license": "MIT",
|
| 1678 |
+
"dependencies": {
|
| 1679 |
+
"to-regex-range": "^5.0.1"
|
| 1680 |
+
},
|
| 1681 |
+
"engines": {
|
| 1682 |
+
"node": ">=8"
|
| 1683 |
+
}
|
| 1684 |
+
},
|
| 1685 |
+
"node_modules/fraction.js": {
|
| 1686 |
+
"version": "5.3.4",
|
| 1687 |
+
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
| 1688 |
+
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
| 1689 |
+
"dev": true,
|
| 1690 |
+
"license": "MIT",
|
| 1691 |
+
"engines": {
|
| 1692 |
+
"node": "*"
|
| 1693 |
+
},
|
| 1694 |
+
"funding": {
|
| 1695 |
+
"type": "github",
|
| 1696 |
+
"url": "https://github.com/sponsors/rawify"
|
| 1697 |
+
}
|
| 1698 |
+
},
|
| 1699 |
"node_modules/fsevents": {
|
| 1700 |
"version": "2.3.3",
|
| 1701 |
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
|
|
|
| 1711 |
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1712 |
}
|
| 1713 |
},
|
| 1714 |
+
"node_modules/function-bind": {
|
| 1715 |
+
"version": "1.1.2",
|
| 1716 |
+
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 1717 |
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 1718 |
+
"dev": true,
|
| 1719 |
+
"license": "MIT",
|
| 1720 |
+
"funding": {
|
| 1721 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1722 |
+
}
|
| 1723 |
+
},
|
| 1724 |
"node_modules/gensync": {
|
| 1725 |
"version": "1.0.0-beta.2",
|
| 1726 |
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
|
|
|
| 1731 |
"node": ">=6.9.0"
|
| 1732 |
}
|
| 1733 |
},
|
| 1734 |
+
"node_modules/glob-parent": {
|
| 1735 |
+
"version": "6.0.2",
|
| 1736 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
| 1737 |
+
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
| 1738 |
+
"dev": true,
|
| 1739 |
+
"license": "ISC",
|
| 1740 |
+
"dependencies": {
|
| 1741 |
+
"is-glob": "^4.0.3"
|
| 1742 |
+
},
|
| 1743 |
+
"engines": {
|
| 1744 |
+
"node": ">=10.13.0"
|
| 1745 |
+
}
|
| 1746 |
+
},
|
| 1747 |
+
"node_modules/hasown": {
|
| 1748 |
+
"version": "2.0.4",
|
| 1749 |
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
| 1750 |
+
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
| 1751 |
+
"dev": true,
|
| 1752 |
+
"license": "MIT",
|
| 1753 |
+
"dependencies": {
|
| 1754 |
+
"function-bind": "^1.1.2"
|
| 1755 |
+
},
|
| 1756 |
+
"engines": {
|
| 1757 |
+
"node": ">= 0.4"
|
| 1758 |
+
}
|
| 1759 |
+
},
|
| 1760 |
+
"node_modules/is-binary-path": {
|
| 1761 |
+
"version": "2.1.0",
|
| 1762 |
+
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
| 1763 |
+
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
| 1764 |
+
"dev": true,
|
| 1765 |
+
"license": "MIT",
|
| 1766 |
+
"dependencies": {
|
| 1767 |
+
"binary-extensions": "^2.0.0"
|
| 1768 |
+
},
|
| 1769 |
+
"engines": {
|
| 1770 |
+
"node": ">=8"
|
| 1771 |
+
}
|
| 1772 |
+
},
|
| 1773 |
+
"node_modules/is-core-module": {
|
| 1774 |
+
"version": "2.17.0",
|
| 1775 |
+
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz",
|
| 1776 |
+
"integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==",
|
| 1777 |
+
"dev": true,
|
| 1778 |
+
"license": "MIT",
|
| 1779 |
+
"dependencies": {
|
| 1780 |
+
"hasown": "^2.0.4"
|
| 1781 |
+
},
|
| 1782 |
+
"engines": {
|
| 1783 |
+
"node": ">= 0.4"
|
| 1784 |
+
},
|
| 1785 |
+
"funding": {
|
| 1786 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1787 |
+
}
|
| 1788 |
+
},
|
| 1789 |
+
"node_modules/is-extglob": {
|
| 1790 |
+
"version": "2.1.1",
|
| 1791 |
+
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
| 1792 |
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
| 1793 |
+
"dev": true,
|
| 1794 |
+
"license": "MIT",
|
| 1795 |
+
"engines": {
|
| 1796 |
+
"node": ">=0.10.0"
|
| 1797 |
+
}
|
| 1798 |
+
},
|
| 1799 |
+
"node_modules/is-glob": {
|
| 1800 |
+
"version": "4.0.3",
|
| 1801 |
+
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
| 1802 |
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
| 1803 |
+
"dev": true,
|
| 1804 |
+
"license": "MIT",
|
| 1805 |
+
"dependencies": {
|
| 1806 |
+
"is-extglob": "^2.1.1"
|
| 1807 |
+
},
|
| 1808 |
+
"engines": {
|
| 1809 |
+
"node": ">=0.10.0"
|
| 1810 |
+
}
|
| 1811 |
+
},
|
| 1812 |
+
"node_modules/is-number": {
|
| 1813 |
+
"version": "7.0.0",
|
| 1814 |
+
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
| 1815 |
+
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
| 1816 |
+
"dev": true,
|
| 1817 |
+
"license": "MIT",
|
| 1818 |
+
"engines": {
|
| 1819 |
+
"node": ">=0.12.0"
|
| 1820 |
+
}
|
| 1821 |
+
},
|
| 1822 |
+
"node_modules/jiti": {
|
| 1823 |
+
"version": "1.21.7",
|
| 1824 |
+
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
| 1825 |
+
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
| 1826 |
+
"dev": true,
|
| 1827 |
+
"license": "MIT",
|
| 1828 |
+
"bin": {
|
| 1829 |
+
"jiti": "bin/jiti.js"
|
| 1830 |
+
}
|
| 1831 |
+
},
|
| 1832 |
"node_modules/js-tokens": {
|
| 1833 |
"version": "4.0.0",
|
| 1834 |
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
|
|
| 1861 |
"node": ">=6"
|
| 1862 |
}
|
| 1863 |
},
|
| 1864 |
+
"node_modules/lilconfig": {
|
| 1865 |
+
"version": "2.1.0",
|
| 1866 |
+
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
| 1867 |
+
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
|
| 1868 |
+
"dev": true,
|
| 1869 |
+
"license": "MIT",
|
| 1870 |
+
"engines": {
|
| 1871 |
+
"node": ">=10"
|
| 1872 |
+
}
|
| 1873 |
+
},
|
| 1874 |
+
"node_modules/lines-and-columns": {
|
| 1875 |
+
"version": "1.2.4",
|
| 1876 |
+
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
| 1877 |
+
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
| 1878 |
+
"dev": true,
|
| 1879 |
+
"license": "MIT"
|
| 1880 |
+
},
|
| 1881 |
"node_modules/loose-envify": {
|
| 1882 |
"version": "1.4.0",
|
| 1883 |
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
|
|
| 1900 |
"yallist": "^3.0.2"
|
| 1901 |
}
|
| 1902 |
},
|
| 1903 |
+
"node_modules/lucide-react": {
|
| 1904 |
+
"version": "1.49.0",
|
| 1905 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.49.0.tgz",
|
| 1906 |
+
"integrity": "sha512-ApfaNM4/7jBrVopqLJ84Jz8TMQ1Q1JeZ1PmKh25aeS3E1FTVcMkSh1bEQB7dfzdIrwXRP8p3z4AJjTIHHb5IIA==",
|
| 1907 |
+
"license": "ISC",
|
| 1908 |
+
"peerDependencies": {
|
| 1909 |
+
"@types/react": "*",
|
| 1910 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1911 |
+
},
|
| 1912 |
+
"peerDependenciesMeta": {
|
| 1913 |
+
"@types/react": {
|
| 1914 |
+
"optional": true
|
| 1915 |
+
}
|
| 1916 |
+
}
|
| 1917 |
+
},
|
| 1918 |
+
"node_modules/merge2": {
|
| 1919 |
+
"version": "1.4.1",
|
| 1920 |
+
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
| 1921 |
+
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
| 1922 |
+
"dev": true,
|
| 1923 |
+
"license": "MIT",
|
| 1924 |
+
"engines": {
|
| 1925 |
+
"node": ">= 8"
|
| 1926 |
+
}
|
| 1927 |
+
},
|
| 1928 |
+
"node_modules/micromatch": {
|
| 1929 |
+
"version": "4.0.8",
|
| 1930 |
+
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
| 1931 |
+
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
| 1932 |
+
"dev": true,
|
| 1933 |
+
"license": "MIT",
|
| 1934 |
+
"dependencies": {
|
| 1935 |
+
"braces": "^3.0.3",
|
| 1936 |
+
"picomatch": "^2.3.1"
|
| 1937 |
+
},
|
| 1938 |
+
"engines": {
|
| 1939 |
+
"node": ">=8.6"
|
| 1940 |
+
}
|
| 1941 |
+
},
|
| 1942 |
"node_modules/ms": {
|
| 1943 |
"version": "2.1.3",
|
| 1944 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
|
|
|
| 1946 |
"dev": true,
|
| 1947 |
"license": "MIT"
|
| 1948 |
},
|
| 1949 |
+
"node_modules/mz": {
|
| 1950 |
+
"version": "2.7.0",
|
| 1951 |
+
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
| 1952 |
+
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
| 1953 |
+
"dev": true,
|
| 1954 |
+
"license": "MIT",
|
| 1955 |
+
"dependencies": {
|
| 1956 |
+
"any-promise": "^1.0.0",
|
| 1957 |
+
"object-assign": "^4.0.1",
|
| 1958 |
+
"thenify-all": "^1.0.0"
|
| 1959 |
+
}
|
| 1960 |
+
},
|
| 1961 |
"node_modules/nanoid": {
|
| 1962 |
"version": "3.3.19",
|
| 1963 |
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
|
|
|
|
| 1987 |
"node": ">=18"
|
| 1988 |
}
|
| 1989 |
},
|
| 1990 |
+
"node_modules/normalize-path": {
|
| 1991 |
+
"version": "3.0.0",
|
| 1992 |
+
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
| 1993 |
+
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
| 1994 |
+
"dev": true,
|
| 1995 |
+
"license": "MIT",
|
| 1996 |
+
"engines": {
|
| 1997 |
+
"node": ">=0.10.0"
|
| 1998 |
+
}
|
| 1999 |
+
},
|
| 2000 |
+
"node_modules/object-assign": {
|
| 2001 |
+
"version": "4.1.1",
|
| 2002 |
+
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 2003 |
+
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
| 2004 |
+
"dev": true,
|
| 2005 |
+
"license": "MIT",
|
| 2006 |
+
"engines": {
|
| 2007 |
+
"node": ">=0.10.0"
|
| 2008 |
+
}
|
| 2009 |
+
},
|
| 2010 |
+
"node_modules/object-hash": {
|
| 2011 |
+
"version": "3.0.0",
|
| 2012 |
+
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
| 2013 |
+
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
| 2014 |
+
"dev": true,
|
| 2015 |
+
"license": "MIT",
|
| 2016 |
+
"engines": {
|
| 2017 |
+
"node": ">= 6"
|
| 2018 |
+
}
|
| 2019 |
+
},
|
| 2020 |
+
"node_modules/path-parse": {
|
| 2021 |
+
"version": "1.0.7",
|
| 2022 |
+
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
| 2023 |
+
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
| 2024 |
+
"dev": true,
|
| 2025 |
+
"license": "MIT"
|
| 2026 |
+
},
|
| 2027 |
+
"node_modules/picocolors": {
|
| 2028 |
"version": "1.1.1",
|
| 2029 |
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 2030 |
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 2031 |
"dev": true,
|
| 2032 |
"license": "ISC"
|
| 2033 |
},
|
| 2034 |
+
"node_modules/picomatch": {
|
| 2035 |
+
"version": "2.3.2",
|
| 2036 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
| 2037 |
+
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
| 2038 |
+
"dev": true,
|
| 2039 |
+
"license": "MIT",
|
| 2040 |
+
"engines": {
|
| 2041 |
+
"node": ">=8.6"
|
| 2042 |
+
},
|
| 2043 |
+
"funding": {
|
| 2044 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 2045 |
+
}
|
| 2046 |
+
},
|
| 2047 |
+
"node_modules/pirates": {
|
| 2048 |
+
"version": "4.0.7",
|
| 2049 |
+
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
| 2050 |
+
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
| 2051 |
+
"dev": true,
|
| 2052 |
+
"license": "MIT",
|
| 2053 |
+
"engines": {
|
| 2054 |
+
"node": ">= 6"
|
| 2055 |
+
}
|
| 2056 |
+
},
|
| 2057 |
"node_modules/postcss": {
|
| 2058 |
"version": "8.5.28",
|
| 2059 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
|
|
|
|
| 2083 |
"node": "^10 || ^12 || >=14"
|
| 2084 |
}
|
| 2085 |
},
|
| 2086 |
+
"node_modules/postcss-import": {
|
| 2087 |
+
"version": "15.1.0",
|
| 2088 |
+
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
| 2089 |
+
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
| 2090 |
+
"dev": true,
|
| 2091 |
+
"license": "MIT",
|
| 2092 |
+
"dependencies": {
|
| 2093 |
+
"postcss-value-parser": "^4.0.0",
|
| 2094 |
+
"read-cache": "^1.0.0",
|
| 2095 |
+
"resolve": "^1.1.7"
|
| 2096 |
+
},
|
| 2097 |
+
"engines": {
|
| 2098 |
+
"node": ">=14.0.0"
|
| 2099 |
+
},
|
| 2100 |
+
"peerDependencies": {
|
| 2101 |
+
"postcss": "^8.0.0"
|
| 2102 |
+
}
|
| 2103 |
+
},
|
| 2104 |
+
"node_modules/postcss-js": {
|
| 2105 |
+
"version": "4.1.0",
|
| 2106 |
+
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
| 2107 |
+
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
| 2108 |
+
"dev": true,
|
| 2109 |
+
"funding": [
|
| 2110 |
+
{
|
| 2111 |
+
"type": "opencollective",
|
| 2112 |
+
"url": "https://opencollective.com/postcss/"
|
| 2113 |
+
},
|
| 2114 |
+
{
|
| 2115 |
+
"type": "github",
|
| 2116 |
+
"url": "https://github.com/sponsors/ai"
|
| 2117 |
+
}
|
| 2118 |
+
],
|
| 2119 |
+
"license": "MIT",
|
| 2120 |
+
"dependencies": {
|
| 2121 |
+
"camelcase-css": "^2.0.1"
|
| 2122 |
+
},
|
| 2123 |
+
"engines": {
|
| 2124 |
+
"node": "^12 || ^14 || >= 16"
|
| 2125 |
+
},
|
| 2126 |
+
"peerDependencies": {
|
| 2127 |
+
"postcss": "^8.4.21"
|
| 2128 |
+
}
|
| 2129 |
+
},
|
| 2130 |
+
"node_modules/postcss-load-config": {
|
| 2131 |
+
"version": "4.0.2",
|
| 2132 |
+
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
| 2133 |
+
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
| 2134 |
+
"dev": true,
|
| 2135 |
+
"funding": [
|
| 2136 |
+
{
|
| 2137 |
+
"type": "opencollective",
|
| 2138 |
+
"url": "https://opencollective.com/postcss/"
|
| 2139 |
+
},
|
| 2140 |
+
{
|
| 2141 |
+
"type": "github",
|
| 2142 |
+
"url": "https://github.com/sponsors/ai"
|
| 2143 |
+
}
|
| 2144 |
+
],
|
| 2145 |
+
"license": "MIT",
|
| 2146 |
+
"dependencies": {
|
| 2147 |
+
"lilconfig": "^3.0.0",
|
| 2148 |
+
"yaml": "^2.3.4"
|
| 2149 |
+
},
|
| 2150 |
+
"engines": {
|
| 2151 |
+
"node": ">= 14"
|
| 2152 |
+
},
|
| 2153 |
+
"peerDependencies": {
|
| 2154 |
+
"postcss": ">=8.0.9",
|
| 2155 |
+
"ts-node": ">=9.0.0"
|
| 2156 |
+
},
|
| 2157 |
+
"peerDependenciesMeta": {
|
| 2158 |
+
"postcss": {
|
| 2159 |
+
"optional": true
|
| 2160 |
+
},
|
| 2161 |
+
"ts-node": {
|
| 2162 |
+
"optional": true
|
| 2163 |
+
}
|
| 2164 |
+
}
|
| 2165 |
+
},
|
| 2166 |
+
"node_modules/postcss-load-config/node_modules/lilconfig": {
|
| 2167 |
+
"version": "3.1.3",
|
| 2168 |
+
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
| 2169 |
+
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
| 2170 |
+
"dev": true,
|
| 2171 |
+
"license": "MIT",
|
| 2172 |
+
"engines": {
|
| 2173 |
+
"node": ">=14"
|
| 2174 |
+
},
|
| 2175 |
+
"funding": {
|
| 2176 |
+
"url": "https://github.com/sponsors/antonk52"
|
| 2177 |
+
}
|
| 2178 |
+
},
|
| 2179 |
+
"node_modules/postcss-nested": {
|
| 2180 |
+
"version": "6.2.0",
|
| 2181 |
+
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
| 2182 |
+
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
| 2183 |
+
"dev": true,
|
| 2184 |
+
"funding": [
|
| 2185 |
+
{
|
| 2186 |
+
"type": "opencollective",
|
| 2187 |
+
"url": "https://opencollective.com/postcss/"
|
| 2188 |
+
},
|
| 2189 |
+
{
|
| 2190 |
+
"type": "github",
|
| 2191 |
+
"url": "https://github.com/sponsors/ai"
|
| 2192 |
+
}
|
| 2193 |
+
],
|
| 2194 |
+
"license": "MIT",
|
| 2195 |
+
"dependencies": {
|
| 2196 |
+
"postcss-selector-parser": "^6.1.1"
|
| 2197 |
+
},
|
| 2198 |
+
"engines": {
|
| 2199 |
+
"node": ">=12.0"
|
| 2200 |
+
},
|
| 2201 |
+
"peerDependencies": {
|
| 2202 |
+
"postcss": "^8.2.14"
|
| 2203 |
+
}
|
| 2204 |
+
},
|
| 2205 |
+
"node_modules/postcss-selector-parser": {
|
| 2206 |
+
"version": "6.1.4",
|
| 2207 |
+
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz",
|
| 2208 |
+
"integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==",
|
| 2209 |
+
"dev": true,
|
| 2210 |
+
"license": "MIT",
|
| 2211 |
+
"dependencies": {
|
| 2212 |
+
"cssesc": "^3.0.0",
|
| 2213 |
+
"util-deprecate": "^1.0.2"
|
| 2214 |
+
},
|
| 2215 |
+
"engines": {
|
| 2216 |
+
"node": ">=4"
|
| 2217 |
+
}
|
| 2218 |
+
},
|
| 2219 |
+
"node_modules/postcss-value-parser": {
|
| 2220 |
+
"version": "4.2.0",
|
| 2221 |
+
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
| 2222 |
+
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
| 2223 |
+
"dev": true,
|
| 2224 |
+
"license": "MIT"
|
| 2225 |
+
},
|
| 2226 |
+
"node_modules/queue-microtask": {
|
| 2227 |
+
"version": "1.2.3",
|
| 2228 |
+
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
| 2229 |
+
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
| 2230 |
+
"dev": true,
|
| 2231 |
+
"funding": [
|
| 2232 |
+
{
|
| 2233 |
+
"type": "github",
|
| 2234 |
+
"url": "https://github.com/sponsors/feross"
|
| 2235 |
+
},
|
| 2236 |
+
{
|
| 2237 |
+
"type": "patreon",
|
| 2238 |
+
"url": "https://www.patreon.com/feross"
|
| 2239 |
+
},
|
| 2240 |
+
{
|
| 2241 |
+
"type": "consulting",
|
| 2242 |
+
"url": "https://feross.org/support"
|
| 2243 |
+
}
|
| 2244 |
+
],
|
| 2245 |
+
"license": "MIT"
|
| 2246 |
+
},
|
| 2247 |
"node_modules/react": {
|
| 2248 |
"version": "18.3.1",
|
| 2249 |
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
|
|
|
| 2279 |
"node": ">=0.10.0"
|
| 2280 |
}
|
| 2281 |
},
|
| 2282 |
+
"node_modules/read-cache": {
|
| 2283 |
+
"version": "1.0.2",
|
| 2284 |
+
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz",
|
| 2285 |
+
"integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==",
|
| 2286 |
+
"dev": true,
|
| 2287 |
+
"license": "MIT"
|
| 2288 |
+
},
|
| 2289 |
+
"node_modules/readdirp": {
|
| 2290 |
+
"version": "3.6.0",
|
| 2291 |
+
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
| 2292 |
+
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
| 2293 |
+
"dev": true,
|
| 2294 |
+
"license": "MIT",
|
| 2295 |
+
"dependencies": {
|
| 2296 |
+
"picomatch": "^2.2.1"
|
| 2297 |
+
},
|
| 2298 |
+
"engines": {
|
| 2299 |
+
"node": ">=8.10.0"
|
| 2300 |
+
}
|
| 2301 |
+
},
|
| 2302 |
+
"node_modules/resolve": {
|
| 2303 |
+
"version": "1.22.12",
|
| 2304 |
+
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
|
| 2305 |
+
"integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
|
| 2306 |
+
"dev": true,
|
| 2307 |
+
"license": "MIT",
|
| 2308 |
+
"dependencies": {
|
| 2309 |
+
"es-errors": "^1.3.0",
|
| 2310 |
+
"is-core-module": "^2.16.1",
|
| 2311 |
+
"path-parse": "^1.0.7",
|
| 2312 |
+
"supports-preserve-symlinks-flag": "^1.0.0"
|
| 2313 |
+
},
|
| 2314 |
+
"bin": {
|
| 2315 |
+
"resolve": "bin/resolve"
|
| 2316 |
+
},
|
| 2317 |
+
"engines": {
|
| 2318 |
+
"node": ">= 0.4"
|
| 2319 |
+
},
|
| 2320 |
+
"funding": {
|
| 2321 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2322 |
+
}
|
| 2323 |
+
},
|
| 2324 |
+
"node_modules/reusify": {
|
| 2325 |
+
"version": "1.1.0",
|
| 2326 |
+
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
| 2327 |
+
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
| 2328 |
+
"dev": true,
|
| 2329 |
+
"license": "MIT",
|
| 2330 |
+
"engines": {
|
| 2331 |
+
"iojs": ">=1.0.0",
|
| 2332 |
+
"node": ">=0.10.0"
|
| 2333 |
+
}
|
| 2334 |
+
},
|
| 2335 |
"node_modules/rollup": {
|
| 2336 |
"version": "4.63.6",
|
| 2337 |
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.6.tgz",
|
|
|
|
| 2378 |
"fsevents": "~2.3.2"
|
| 2379 |
}
|
| 2380 |
},
|
| 2381 |
+
"node_modules/run-parallel": {
|
| 2382 |
+
"version": "1.2.0",
|
| 2383 |
+
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
| 2384 |
+
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
| 2385 |
+
"dev": true,
|
| 2386 |
+
"funding": [
|
| 2387 |
+
{
|
| 2388 |
+
"type": "github",
|
| 2389 |
+
"url": "https://github.com/sponsors/feross"
|
| 2390 |
+
},
|
| 2391 |
+
{
|
| 2392 |
+
"type": "patreon",
|
| 2393 |
+
"url": "https://www.patreon.com/feross"
|
| 2394 |
+
},
|
| 2395 |
+
{
|
| 2396 |
+
"type": "consulting",
|
| 2397 |
+
"url": "https://feross.org/support"
|
| 2398 |
+
}
|
| 2399 |
+
],
|
| 2400 |
+
"license": "MIT",
|
| 2401 |
+
"dependencies": {
|
| 2402 |
+
"queue-microtask": "^1.2.2"
|
| 2403 |
+
}
|
| 2404 |
+
},
|
| 2405 |
"node_modules/scheduler": {
|
| 2406 |
"version": "0.23.2",
|
| 2407 |
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
|
|
|
| 2431 |
"node": ">=0.10.0"
|
| 2432 |
}
|
| 2433 |
},
|
| 2434 |
+
"node_modules/sucrase": {
|
| 2435 |
+
"version": "3.35.1",
|
| 2436 |
+
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
|
| 2437 |
+
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
|
| 2438 |
+
"dev": true,
|
| 2439 |
+
"license": "MIT",
|
| 2440 |
+
"dependencies": {
|
| 2441 |
+
"@jridgewell/gen-mapping": "^0.3.2",
|
| 2442 |
+
"commander": "^4.0.0",
|
| 2443 |
+
"lines-and-columns": "^1.1.6",
|
| 2444 |
+
"mz": "^2.7.0",
|
| 2445 |
+
"pirates": "^4.0.1",
|
| 2446 |
+
"tinyglobby": "^0.2.11",
|
| 2447 |
+
"ts-interface-checker": "^0.1.9"
|
| 2448 |
+
},
|
| 2449 |
+
"bin": {
|
| 2450 |
+
"sucrase": "bin/sucrase",
|
| 2451 |
+
"sucrase-node": "bin/sucrase-node"
|
| 2452 |
+
},
|
| 2453 |
+
"engines": {
|
| 2454 |
+
"node": ">=16 || 14 >=14.17"
|
| 2455 |
+
}
|
| 2456 |
+
},
|
| 2457 |
+
"node_modules/supports-preserve-symlinks-flag": {
|
| 2458 |
+
"version": "1.0.0",
|
| 2459 |
+
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
| 2460 |
+
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
| 2461 |
+
"dev": true,
|
| 2462 |
+
"license": "MIT",
|
| 2463 |
+
"engines": {
|
| 2464 |
+
"node": ">= 0.4"
|
| 2465 |
+
},
|
| 2466 |
+
"funding": {
|
| 2467 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2468 |
+
}
|
| 2469 |
+
},
|
| 2470 |
+
"node_modules/tailwindcss": {
|
| 2471 |
+
"version": "3.4.1",
|
| 2472 |
+
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
|
| 2473 |
+
"integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==",
|
| 2474 |
+
"dev": true,
|
| 2475 |
+
"license": "MIT",
|
| 2476 |
+
"dependencies": {
|
| 2477 |
+
"@alloc/quick-lru": "^5.2.0",
|
| 2478 |
+
"arg": "^5.0.2",
|
| 2479 |
+
"chokidar": "^3.5.3",
|
| 2480 |
+
"didyoumean": "^1.2.2",
|
| 2481 |
+
"dlv": "^1.1.3",
|
| 2482 |
+
"fast-glob": "^3.3.0",
|
| 2483 |
+
"glob-parent": "^6.0.2",
|
| 2484 |
+
"is-glob": "^4.0.3",
|
| 2485 |
+
"jiti": "^1.19.1",
|
| 2486 |
+
"lilconfig": "^2.1.0",
|
| 2487 |
+
"micromatch": "^4.0.5",
|
| 2488 |
+
"normalize-path": "^3.0.0",
|
| 2489 |
+
"object-hash": "^3.0.0",
|
| 2490 |
+
"picocolors": "^1.0.0",
|
| 2491 |
+
"postcss": "^8.4.23",
|
| 2492 |
+
"postcss-import": "^15.1.0",
|
| 2493 |
+
"postcss-js": "^4.0.1",
|
| 2494 |
+
"postcss-load-config": "^4.0.1",
|
| 2495 |
+
"postcss-nested": "^6.0.1",
|
| 2496 |
+
"postcss-selector-parser": "^6.0.11",
|
| 2497 |
+
"resolve": "^1.22.2",
|
| 2498 |
+
"sucrase": "^3.32.0"
|
| 2499 |
+
},
|
| 2500 |
+
"bin": {
|
| 2501 |
+
"tailwind": "lib/cli.js",
|
| 2502 |
+
"tailwindcss": "lib/cli.js"
|
| 2503 |
+
},
|
| 2504 |
+
"engines": {
|
| 2505 |
+
"node": ">=14.0.0"
|
| 2506 |
+
}
|
| 2507 |
+
},
|
| 2508 |
+
"node_modules/thenify": {
|
| 2509 |
+
"version": "3.3.1",
|
| 2510 |
+
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
| 2511 |
+
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
| 2512 |
+
"dev": true,
|
| 2513 |
+
"license": "MIT",
|
| 2514 |
+
"dependencies": {
|
| 2515 |
+
"any-promise": "^1.0.0"
|
| 2516 |
+
}
|
| 2517 |
+
},
|
| 2518 |
+
"node_modules/thenify-all": {
|
| 2519 |
+
"version": "1.6.0",
|
| 2520 |
+
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
| 2521 |
+
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
| 2522 |
+
"dev": true,
|
| 2523 |
+
"license": "MIT",
|
| 2524 |
+
"dependencies": {
|
| 2525 |
+
"thenify": ">= 3.1.0 < 4"
|
| 2526 |
+
},
|
| 2527 |
+
"engines": {
|
| 2528 |
+
"node": ">=0.8"
|
| 2529 |
+
}
|
| 2530 |
+
},
|
| 2531 |
+
"node_modules/tinyglobby": {
|
| 2532 |
+
"version": "0.2.17",
|
| 2533 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
| 2534 |
+
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
| 2535 |
+
"dev": true,
|
| 2536 |
+
"license": "MIT",
|
| 2537 |
+
"dependencies": {
|
| 2538 |
+
"fdir": "^6.5.0",
|
| 2539 |
+
"picomatch": "^4.0.4"
|
| 2540 |
+
},
|
| 2541 |
+
"engines": {
|
| 2542 |
+
"node": ">=12.0.0"
|
| 2543 |
+
},
|
| 2544 |
+
"funding": {
|
| 2545 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 2546 |
+
}
|
| 2547 |
+
},
|
| 2548 |
+
"node_modules/tinyglobby/node_modules/fdir": {
|
| 2549 |
+
"version": "6.5.0",
|
| 2550 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 2551 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 2552 |
+
"dev": true,
|
| 2553 |
+
"license": "MIT",
|
| 2554 |
+
"engines": {
|
| 2555 |
+
"node": ">=12.0.0"
|
| 2556 |
+
},
|
| 2557 |
+
"peerDependencies": {
|
| 2558 |
+
"picomatch": "^3 || ^4"
|
| 2559 |
+
},
|
| 2560 |
+
"peerDependenciesMeta": {
|
| 2561 |
+
"picomatch": {
|
| 2562 |
+
"optional": true
|
| 2563 |
+
}
|
| 2564 |
+
}
|
| 2565 |
+
},
|
| 2566 |
+
"node_modules/tinyglobby/node_modules/picomatch": {
|
| 2567 |
+
"version": "4.0.7",
|
| 2568 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
|
| 2569 |
+
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
|
| 2570 |
+
"dev": true,
|
| 2571 |
+
"license": "MIT",
|
| 2572 |
+
"engines": {
|
| 2573 |
+
"node": ">=12"
|
| 2574 |
+
},
|
| 2575 |
+
"funding": {
|
| 2576 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 2577 |
+
}
|
| 2578 |
+
},
|
| 2579 |
+
"node_modules/to-regex-range": {
|
| 2580 |
+
"version": "5.0.1",
|
| 2581 |
+
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
| 2582 |
+
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
| 2583 |
+
"dev": true,
|
| 2584 |
+
"license": "MIT",
|
| 2585 |
+
"dependencies": {
|
| 2586 |
+
"is-number": "^7.0.0"
|
| 2587 |
+
},
|
| 2588 |
+
"engines": {
|
| 2589 |
+
"node": ">=8.0"
|
| 2590 |
+
}
|
| 2591 |
+
},
|
| 2592 |
+
"node_modules/ts-interface-checker": {
|
| 2593 |
+
"version": "0.1.13",
|
| 2594 |
+
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
| 2595 |
+
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
| 2596 |
+
"dev": true,
|
| 2597 |
+
"license": "Apache-2.0"
|
| 2598 |
+
},
|
| 2599 |
"node_modules/update-browserslist-db": {
|
| 2600 |
"version": "1.3.3",
|
| 2601 |
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
|
|
|
|
| 2627 |
"browserslist": ">= 4.21.0"
|
| 2628 |
}
|
| 2629 |
},
|
| 2630 |
+
"node_modules/util-deprecate": {
|
| 2631 |
+
"version": "1.0.2",
|
| 2632 |
+
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
| 2633 |
+
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
| 2634 |
+
"dev": true,
|
| 2635 |
+
"license": "MIT"
|
| 2636 |
+
},
|
| 2637 |
"node_modules/vite": {
|
| 2638 |
"version": "5.4.21",
|
| 2639 |
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
|
|
|
| 2700 |
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 2701 |
"dev": true,
|
| 2702 |
"license": "ISC"
|
| 2703 |
+
},
|
| 2704 |
+
"node_modules/yaml": {
|
| 2705 |
+
"version": "2.9.1",
|
| 2706 |
+
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz",
|
| 2707 |
+
"integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==",
|
| 2708 |
+
"dev": true,
|
| 2709 |
+
"license": "ISC",
|
| 2710 |
+
"bin": {
|
| 2711 |
+
"yaml": "bin.mjs"
|
| 2712 |
+
},
|
| 2713 |
+
"engines": {
|
| 2714 |
+
"node": ">= 14.6"
|
| 2715 |
+
},
|
| 2716 |
+
"funding": {
|
| 2717 |
+
"url": "https://github.com/sponsors/eemeli"
|
| 2718 |
+
}
|
| 2719 |
}
|
| 2720 |
}
|
| 2721 |
}
|
studio/package.json
CHANGED
|
@@ -8,11 +8,16 @@
|
|
| 8 |
"preview": "vite preview"
|
| 9 |
},
|
| 10 |
"dependencies": {
|
|
|
|
|
|
|
| 11 |
"react": "^18.2.0",
|
| 12 |
"react-dom": "^18.2.0"
|
| 13 |
},
|
| 14 |
"devDependencies": {
|
| 15 |
"@vitejs/plugin-react": "^4.2.0",
|
|
|
|
|
|
|
|
|
|
| 16 |
"vite": "^5.0.0"
|
| 17 |
}
|
| 18 |
}
|
|
|
|
| 8 |
"preview": "vite preview"
|
| 9 |
},
|
| 10 |
"dependencies": {
|
| 11 |
+
"canvas-confetti": "^1.9.4",
|
| 12 |
+
"lucide-react": "^1.49.0",
|
| 13 |
"react": "^18.2.0",
|
| 14 |
"react-dom": "^18.2.0"
|
| 15 |
},
|
| 16 |
"devDependencies": {
|
| 17 |
"@vitejs/plugin-react": "^4.2.0",
|
| 18 |
+
"autoprefixer": "^10.6.1",
|
| 19 |
+
"postcss": "^8.5.28",
|
| 20 |
+
"tailwindcss": "^3.4.1",
|
| 21 |
"vite": "^5.0.0"
|
| 22 |
}
|
| 23 |
}
|
studio/postcss.config.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default {
|
| 2 |
+
plugins: {
|
| 3 |
+
tailwindcss: {},
|
| 4 |
+
autoprefixer: {},
|
| 5 |
+
},
|
| 6 |
+
}
|
studio/src/App.jsx
CHANGED
|
@@ -8,6 +8,7 @@ import KnowledgeGraph from './components/KnowledgeGraph';
|
|
| 8 |
import KnowledgeBase from './components/KnowledgeBase';
|
| 9 |
import PolicyRulesLab from './components/PolicyRulesLab';
|
| 10 |
import AuditLog from './components/AuditLog';
|
|
|
|
| 11 |
|
| 12 |
export default function App() {
|
| 13 |
const [workspaces, setWorkspaces] = useState([]);
|
|
@@ -22,37 +23,38 @@ export default function App() {
|
|
| 22 |
const [graphData, setGraphData] = useState(null);
|
| 23 |
const [knowledgeItems, setKnowledgeItems] = useState([]);
|
| 24 |
const [rules, setRules] = useState([]);
|
| 25 |
-
const [activity, setActivity] = useState(
|
| 26 |
-
const [validationResult, setValidationResult] = useState(null);
|
| 27 |
|
| 28 |
const [loading, setLoading] = useState(false);
|
|
|
|
| 29 |
const [toast, setToast] = useState(null);
|
| 30 |
|
| 31 |
const showToast = (message, type = 'success') => {
|
| 32 |
setToast({ message, type });
|
| 33 |
-
setTimeout(() => setToast(null),
|
| 34 |
};
|
| 35 |
|
| 36 |
-
// Initial load: workspaces
|
| 37 |
useEffect(() => {
|
| 38 |
async function init() {
|
| 39 |
try {
|
| 40 |
const wsList = await api.getWorkspaces();
|
| 41 |
setWorkspaces(wsList);
|
| 42 |
-
if (wsList.length > 0) {
|
| 43 |
setCurrentWorkspace(wsList[0]);
|
| 44 |
}
|
| 45 |
} catch (err) {
|
| 46 |
-
showToast(`
|
| 47 |
}
|
| 48 |
}
|
| 49 |
init();
|
| 50 |
}, []);
|
| 51 |
|
| 52 |
-
// Fetch workspace details
|
| 53 |
const refreshData = useCallback(async () => {
|
| 54 |
if (!currentWorkspace?.id) return;
|
| 55 |
const wsId = currentWorkspace.id;
|
|
|
|
| 56 |
|
| 57 |
try {
|
| 58 |
if (activeTab === 'diff') {
|
|
@@ -60,29 +62,37 @@ export default function App() {
|
|
| 60 |
setDiff(diffRes);
|
| 61 |
} else if (activeTab === 'prs') {
|
| 62 |
const prsRes = await api.getProposals(wsId);
|
| 63 |
-
setProposals(prsRes);
|
| 64 |
} else if (activeTab === 'staging') {
|
| 65 |
const docsRes = await api.getDocuments(wsId);
|
| 66 |
-
setDocuments(docsRes);
|
| 67 |
} else if (activeTab === 'graph') {
|
| 68 |
const graphRes = await api.getKnowledgeGraph(wsId);
|
| 69 |
setGraphData(graphRes);
|
| 70 |
} else if (activeTab === 'knowledge') {
|
| 71 |
-
const kRes = await api.
|
| 72 |
-
setKnowledgeItems(kRes);
|
| 73 |
} else if (activeTab === 'rules') {
|
| 74 |
const rulesRes = await api.getRules(wsId);
|
| 75 |
-
setRules(rulesRes);
|
| 76 |
} else if (activeTab === 'audit') {
|
| 77 |
-
const actRes = await api.
|
| 78 |
-
setActivity(actRes);
|
| 79 |
}
|
| 80 |
|
| 81 |
-
//
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
} catch (err) {
|
| 85 |
console.error('Error refreshing workspace data:', err);
|
|
|
|
|
|
|
| 86 |
}
|
| 87 |
}, [currentWorkspace, activeTab]);
|
| 88 |
|
|
@@ -94,24 +104,18 @@ export default function App() {
|
|
| 94 |
const handleCreateWorkspace = async (name, desc) => {
|
| 95 |
try {
|
| 96 |
const newWs = await api.createWorkspace(name, desc);
|
| 97 |
-
setWorkspaces([...
|
| 98 |
setCurrentWorkspace(newWs);
|
| 99 |
-
showToast(`Workspace "${name}"
|
| 100 |
} catch (err) {
|
| 101 |
showToast(`Create failed: ${err.message}`, 'error');
|
| 102 |
}
|
| 103 |
};
|
| 104 |
|
| 105 |
-
const handleReview = async (proposalId, decision, comments =
|
| 106 |
try {
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
showToast(`Proposal approved! Commit ID: ${res.commit_id?.slice(0, 8)}`);
|
| 110 |
-
} else if (decision === 'REJECTED') {
|
| 111 |
-
showToast('Proposal rejected.', 'info');
|
| 112 |
-
} else {
|
| 113 |
-
showToast('Proposal stashed for later.', 'info');
|
| 114 |
-
}
|
| 115 |
refreshData();
|
| 116 |
} catch (err) {
|
| 117 |
showToast(`Review failed: ${err.message}`, 'error');
|
|
@@ -120,7 +124,7 @@ export default function App() {
|
|
| 120 |
|
| 121 |
const handleBatchReview = async (decision) => {
|
| 122 |
try {
|
| 123 |
-
const res = await api.
|
| 124 |
showToast(`Batch ${decision.toLowerCase()} processed (${res.processed_count} items)!`);
|
| 125 |
refreshData();
|
| 126 |
} catch (err) {
|
|
@@ -131,7 +135,7 @@ export default function App() {
|
|
| 131 |
const handleUpload = async (file) => {
|
| 132 |
try {
|
| 133 |
const res = await api.uploadDocument(currentWorkspace.id, file);
|
| 134 |
-
showToast(`
|
| 135 |
refreshData();
|
| 136 |
return res;
|
| 137 |
} catch (err) {
|
|
@@ -140,56 +144,40 @@ export default function App() {
|
|
| 140 |
}
|
| 141 |
};
|
| 142 |
|
| 143 |
-
const
|
| 144 |
try {
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
showToast('Rule disabled.');
|
| 148 |
-
} else {
|
| 149 |
-
await api.enableRule(ruleId);
|
| 150 |
-
showToast('Rule enabled.');
|
| 151 |
-
}
|
| 152 |
refreshData();
|
| 153 |
} catch (err) {
|
| 154 |
-
showToast(`Rule
|
| 155 |
}
|
| 156 |
};
|
| 157 |
|
| 158 |
-
const
|
| 159 |
try {
|
| 160 |
-
await api.
|
| 161 |
-
showToast(
|
| 162 |
refreshData();
|
| 163 |
} catch (err) {
|
| 164 |
-
showToast(`Rule
|
| 165 |
}
|
| 166 |
};
|
| 167 |
|
| 168 |
-
const
|
| 169 |
try {
|
| 170 |
-
const valRes = await api.
|
| 171 |
-
|
| 172 |
-
|
| 173 |
} catch (err) {
|
| 174 |
showToast(`Validation check failed: ${err.message}`, 'error');
|
| 175 |
-
|
| 176 |
-
};
|
| 177 |
-
|
| 178 |
-
const handleSearchKnowledge = async (query) => {
|
| 179 |
-
try {
|
| 180 |
-
setLoading(true);
|
| 181 |
-
const res = await api.searchKnowledge(currentWorkspace.id, query);
|
| 182 |
-
setKnowledgeItems(res);
|
| 183 |
-
showToast(`Found ${res.length} matches for "${query}"`);
|
| 184 |
-
} catch (err) {
|
| 185 |
-
showToast(`Search failed: ${err.message}`, 'error');
|
| 186 |
-
} finally {
|
| 187 |
-
setLoading(false);
|
| 188 |
}
|
| 189 |
};
|
| 190 |
|
| 191 |
return (
|
| 192 |
-
<div className="min-h-screen bg-[#
|
|
|
|
| 193 |
<Navbar
|
| 194 |
workspaces={workspaces}
|
| 195 |
currentWorkspace={currentWorkspace}
|
|
@@ -200,54 +188,103 @@ export default function App() {
|
|
| 200 |
stats={stats}
|
| 201 |
/>
|
| 202 |
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
{activeTab === 'diff' && (
|
| 205 |
-
<SemanticDiffViewer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
)}
|
|
|
|
| 207 |
{activeTab === 'prs' && (
|
| 208 |
<PRReviewDeck
|
| 209 |
proposals={proposals}
|
| 210 |
-
|
| 211 |
onBatchReview={handleBatchReview}
|
| 212 |
loading={loading}
|
| 213 |
/>
|
| 214 |
)}
|
|
|
|
| 215 |
{activeTab === 'staging' && (
|
| 216 |
-
<IngestionStaging
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
)}
|
|
|
|
| 218 |
{activeTab === 'graph' && (
|
| 219 |
<KnowledgeGraph graphData={graphData} loading={loading} />
|
| 220 |
)}
|
|
|
|
| 221 |
{activeTab === 'knowledge' && (
|
| 222 |
-
<KnowledgeBase
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
)}
|
|
|
|
| 224 |
{activeTab === 'rules' && (
|
| 225 |
<PolicyRulesLab
|
| 226 |
rules={rules}
|
| 227 |
-
|
| 228 |
onCreateRule={handleCreateRule}
|
| 229 |
-
|
| 230 |
-
|
|
|
|
| 231 |
/>
|
| 232 |
)}
|
|
|
|
| 233 |
{activeTab === 'audit' && (
|
| 234 |
<AuditLog activityFeed={activity} loading={loading} />
|
| 235 |
)}
|
| 236 |
</main>
|
| 237 |
|
| 238 |
-
{/* Toast Notification */}
|
| 239 |
{toast && (
|
| 240 |
-
<div
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
</div>
|
| 252 |
)}
|
| 253 |
</div>
|
|
|
|
| 8 |
import KnowledgeBase from './components/KnowledgeBase';
|
| 9 |
import PolicyRulesLab from './components/PolicyRulesLab';
|
| 10 |
import AuditLog from './components/AuditLog';
|
| 11 |
+
import { RefreshCw, CheckCircle2, AlertCircle, Sparkles } from 'lucide-react';
|
| 12 |
|
| 13 |
export default function App() {
|
| 14 |
const [workspaces, setWorkspaces] = useState([]);
|
|
|
|
| 23 |
const [graphData, setGraphData] = useState(null);
|
| 24 |
const [knowledgeItems, setKnowledgeItems] = useState([]);
|
| 25 |
const [rules, setRules] = useState([]);
|
| 26 |
+
const [activity, setActivity] = useState([]);
|
|
|
|
| 27 |
|
| 28 |
const [loading, setLoading] = useState(false);
|
| 29 |
+
const [refreshing, setRefreshing] = useState(false);
|
| 30 |
const [toast, setToast] = useState(null);
|
| 31 |
|
| 32 |
const showToast = (message, type = 'success') => {
|
| 33 |
setToast({ message, type });
|
| 34 |
+
setTimeout(() => setToast(null), 3500);
|
| 35 |
};
|
| 36 |
|
| 37 |
+
// Initial load: fetch workspaces
|
| 38 |
useEffect(() => {
|
| 39 |
async function init() {
|
| 40 |
try {
|
| 41 |
const wsList = await api.getWorkspaces();
|
| 42 |
setWorkspaces(wsList);
|
| 43 |
+
if (wsList && wsList.length > 0) {
|
| 44 |
setCurrentWorkspace(wsList[0]);
|
| 45 |
}
|
| 46 |
} catch (err) {
|
| 47 |
+
showToast(`Connecting to DiffWeave Bridge...`, 'info');
|
| 48 |
}
|
| 49 |
}
|
| 50 |
init();
|
| 51 |
}, []);
|
| 52 |
|
| 53 |
+
// Fetch workspace details based on active tab
|
| 54 |
const refreshData = useCallback(async () => {
|
| 55 |
if (!currentWorkspace?.id) return;
|
| 56 |
const wsId = currentWorkspace.id;
|
| 57 |
+
setRefreshing(true);
|
| 58 |
|
| 59 |
try {
|
| 60 |
if (activeTab === 'diff') {
|
|
|
|
| 62 |
setDiff(diffRes);
|
| 63 |
} else if (activeTab === 'prs') {
|
| 64 |
const prsRes = await api.getProposals(wsId);
|
| 65 |
+
setProposals(prsRes || []);
|
| 66 |
} else if (activeTab === 'staging') {
|
| 67 |
const docsRes = await api.getDocuments(wsId);
|
| 68 |
+
setDocuments(docsRes || []);
|
| 69 |
} else if (activeTab === 'graph') {
|
| 70 |
const graphRes = await api.getKnowledgeGraph(wsId);
|
| 71 |
setGraphData(graphRes);
|
| 72 |
} else if (activeTab === 'knowledge') {
|
| 73 |
+
const kRes = await api.getKnowledgeItems(wsId);
|
| 74 |
+
setKnowledgeItems(kRes || []);
|
| 75 |
} else if (activeTab === 'rules') {
|
| 76 |
const rulesRes = await api.getRules(wsId);
|
| 77 |
+
setRules(rulesRes || []);
|
| 78 |
} else if (activeTab === 'audit') {
|
| 79 |
+
const actRes = await api.getActivityFeed(wsId);
|
| 80 |
+
setActivity(actRes || []);
|
| 81 |
}
|
| 82 |
|
| 83 |
+
// Background status fetch for navbar counters
|
| 84 |
+
try {
|
| 85 |
+
const statusRes = await api.getWorkspaceStatus(wsId);
|
| 86 |
+
if (statusRes?.stats) {
|
| 87 |
+
setStats(statusRes.stats);
|
| 88 |
+
}
|
| 89 |
+
} catch {
|
| 90 |
+
// silent fallback
|
| 91 |
+
}
|
| 92 |
} catch (err) {
|
| 93 |
console.error('Error refreshing workspace data:', err);
|
| 94 |
+
} finally {
|
| 95 |
+
setRefreshing(false);
|
| 96 |
}
|
| 97 |
}, [currentWorkspace, activeTab]);
|
| 98 |
|
|
|
|
| 104 |
const handleCreateWorkspace = async (name, desc) => {
|
| 105 |
try {
|
| 106 |
const newWs = await api.createWorkspace(name, desc);
|
| 107 |
+
setWorkspaces((prev) => [...prev, newWs]);
|
| 108 |
setCurrentWorkspace(newWs);
|
| 109 |
+
showToast(`Workspace "${name}" initialized!`);
|
| 110 |
} catch (err) {
|
| 111 |
showToast(`Create failed: ${err.message}`, 'error');
|
| 112 |
}
|
| 113 |
};
|
| 114 |
|
| 115 |
+
const handleReview = async (proposalId, decision, comments = '') => {
|
| 116 |
try {
|
| 117 |
+
await api.reviewProposal(proposalId, decision, comments);
|
| 118 |
+
showToast(`Proposal ${decision.toLowerCase()}!`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
refreshData();
|
| 120 |
} catch (err) {
|
| 121 |
showToast(`Review failed: ${err.message}`, 'error');
|
|
|
|
| 124 |
|
| 125 |
const handleBatchReview = async (decision) => {
|
| 126 |
try {
|
| 127 |
+
const res = await api.batchReview(currentWorkspace.id, decision);
|
| 128 |
showToast(`Batch ${decision.toLowerCase()} processed (${res.processed_count} items)!`);
|
| 129 |
refreshData();
|
| 130 |
} catch (err) {
|
|
|
|
| 135 |
const handleUpload = async (file) => {
|
| 136 |
try {
|
| 137 |
const res = await api.uploadDocument(currentWorkspace.id, file);
|
| 138 |
+
showToast(`Document ${file.name} staged!`);
|
| 139 |
refreshData();
|
| 140 |
return res;
|
| 141 |
} catch (err) {
|
|
|
|
| 144 |
}
|
| 145 |
};
|
| 146 |
|
| 147 |
+
const handleCreateRule = async (name, operator, condition, isBlocking) => {
|
| 148 |
try {
|
| 149 |
+
await api.createRule(currentWorkspace.id, name, operator, condition, isBlocking);
|
| 150 |
+
showToast(`Policy rule "${name}" created.`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
refreshData();
|
| 152 |
} catch (err) {
|
| 153 |
+
showToast(`Rule creation failed: ${err.message}`, 'error');
|
| 154 |
}
|
| 155 |
};
|
| 156 |
|
| 157 |
+
const handleDeleteRule = async (ruleId) => {
|
| 158 |
try {
|
| 159 |
+
await api.deleteRule(ruleId);
|
| 160 |
+
showToast('Rule deleted.');
|
| 161 |
refreshData();
|
| 162 |
} catch (err) {
|
| 163 |
+
showToast(`Rule deletion failed: ${err.message}`, 'error');
|
| 164 |
}
|
| 165 |
};
|
| 166 |
|
| 167 |
+
const handleValidate = async () => {
|
| 168 |
try {
|
| 169 |
+
const valRes = await api.validateProposals(currentWorkspace.id);
|
| 170 |
+
showToast(valRes.has_violations ? 'Policy violations detected!' : 'All CI policy checks passed!');
|
| 171 |
+
return valRes;
|
| 172 |
} catch (err) {
|
| 173 |
showToast(`Validation check failed: ${err.message}`, 'error');
|
| 174 |
+
throw err;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
}
|
| 176 |
};
|
| 177 |
|
| 178 |
return (
|
| 179 |
+
<div className="min-h-screen bg-[#070A10] text-slate-100 flex flex-col font-sans selection:bg-emerald-500/20 selection:text-emerald-300">
|
| 180 |
+
{/* Top Navbar */}
|
| 181 |
<Navbar
|
| 182 |
workspaces={workspaces}
|
| 183 |
currentWorkspace={currentWorkspace}
|
|
|
|
| 188 |
stats={stats}
|
| 189 |
/>
|
| 190 |
|
| 191 |
+
{/* Main Content Area */}
|
| 192 |
+
<main className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-6 space-y-6">
|
| 193 |
+
{/* Workspace Quick Action Bar */}
|
| 194 |
+
<div className="flex items-center justify-between text-xs text-slate-400">
|
| 195 |
+
<div className="flex items-center space-x-2">
|
| 196 |
+
<span className="font-semibold text-white">Active Branch:</span>
|
| 197 |
+
<span className="px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-400 font-mono border border-emerald-500/20 font-bold">
|
| 198 |
+
main
|
| 199 |
+
</span>
|
| 200 |
+
<span className="text-slate-600">•</span>
|
| 201 |
+
<span className="text-slate-400 font-mono text-[11px]">
|
| 202 |
+
Workspace: {currentWorkspace?.id || '4314fb04-95be-41a2-bef4-50bf27c9c363'}
|
| 203 |
+
</span>
|
| 204 |
+
</div>
|
| 205 |
+
|
| 206 |
+
<button
|
| 207 |
+
onClick={() => refreshData()}
|
| 208 |
+
className="flex items-center space-x-1 px-2.5 py-1 rounded-lg bg-[#0E1422] hover:bg-[#141B2D] border border-slate-800 text-slate-300 hover:text-white transition"
|
| 209 |
+
>
|
| 210 |
+
<RefreshCw className={`w-3.5 h-3.5 ${refreshing ? 'animate-spin text-emerald-400' : ''}`} />
|
| 211 |
+
<span>Sync</span>
|
| 212 |
+
</button>
|
| 213 |
+
</div>
|
| 214 |
+
|
| 215 |
+
{/* Tab View Switcher */}
|
| 216 |
{activeTab === 'diff' && (
|
| 217 |
+
<SemanticDiffViewer
|
| 218 |
+
diffData={diff}
|
| 219 |
+
onReviewProposal={handleReview}
|
| 220 |
+
onBatchReview={handleBatchReview}
|
| 221 |
+
loading={loading}
|
| 222 |
+
/>
|
| 223 |
)}
|
| 224 |
+
|
| 225 |
{activeTab === 'prs' && (
|
| 226 |
<PRReviewDeck
|
| 227 |
proposals={proposals}
|
| 228 |
+
onReviewProposal={handleReview}
|
| 229 |
onBatchReview={handleBatchReview}
|
| 230 |
loading={loading}
|
| 231 |
/>
|
| 232 |
)}
|
| 233 |
+
|
| 234 |
{activeTab === 'staging' && (
|
| 235 |
+
<IngestionStaging
|
| 236 |
+
documents={documents}
|
| 237 |
+
onUploadDocument={handleUpload}
|
| 238 |
+
currentWorkspace={currentWorkspace}
|
| 239 |
+
loading={loading}
|
| 240 |
+
/>
|
| 241 |
)}
|
| 242 |
+
|
| 243 |
{activeTab === 'graph' && (
|
| 244 |
<KnowledgeGraph graphData={graphData} loading={loading} />
|
| 245 |
)}
|
| 246 |
+
|
| 247 |
{activeTab === 'knowledge' && (
|
| 248 |
+
<KnowledgeBase
|
| 249 |
+
knowledgeItems={knowledgeItems}
|
| 250 |
+
currentWorkspace={currentWorkspace}
|
| 251 |
+
loading={loading}
|
| 252 |
+
/>
|
| 253 |
)}
|
| 254 |
+
|
| 255 |
{activeTab === 'rules' && (
|
| 256 |
<PolicyRulesLab
|
| 257 |
rules={rules}
|
| 258 |
+
currentWorkspace={currentWorkspace}
|
| 259 |
onCreateRule={handleCreateRule}
|
| 260 |
+
onDeleteRule={handleDeleteRule}
|
| 261 |
+
onValidate={handleValidate}
|
| 262 |
+
loading={loading}
|
| 263 |
/>
|
| 264 |
)}
|
| 265 |
+
|
| 266 |
{activeTab === 'audit' && (
|
| 267 |
<AuditLog activityFeed={activity} loading={loading} />
|
| 268 |
)}
|
| 269 |
</main>
|
| 270 |
|
| 271 |
+
{/* Floating Toast Notification */}
|
| 272 |
{toast && (
|
| 273 |
+
<div className="fixed bottom-6 right-6 z-50 animate-bounce">
|
| 274 |
+
<div
|
| 275 |
+
className={`px-4 py-2.5 rounded-xl border shadow-2xl flex items-center space-x-2 text-xs font-medium backdrop-blur-md ${
|
| 276 |
+
toast.type === 'error'
|
| 277 |
+
? 'bg-rose-950/90 border-rose-500/50 text-rose-200'
|
| 278 |
+
: 'bg-emerald-950/90 border-emerald-500/50 text-emerald-200'
|
| 279 |
+
}`}
|
| 280 |
+
>
|
| 281 |
+
{toast.type === 'error' ? (
|
| 282 |
+
<AlertCircle className="w-4 h-4 text-rose-400" />
|
| 283 |
+
) : (
|
| 284 |
+
<CheckCircle2 className="w-4 h-4 text-emerald-400" />
|
| 285 |
+
)}
|
| 286 |
+
<span>{toast.message}</span>
|
| 287 |
+
</div>
|
| 288 |
</div>
|
| 289 |
)}
|
| 290 |
</div>
|
studio/src/components/AuditLog.jsx
CHANGED
|
@@ -1,74 +1,148 @@
|
|
| 1 |
-
import React from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
export default function AuditLog({ activityFeed, loading }) {
|
|
|
|
|
|
|
| 4 |
if (loading) {
|
| 5 |
return (
|
| 6 |
-
<div className="flex items-center justify-center
|
| 7 |
-
<
|
|
|
|
| 8 |
</div>
|
| 9 |
);
|
| 10 |
}
|
| 11 |
|
| 12 |
-
const events = activityFeed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
return (
|
| 15 |
<div className="space-y-6">
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
</
|
| 23 |
-
</
|
| 24 |
-
<p className="text-xs text-slate-400 mt-1">
|
| 25 |
-
Immutable provenance record tracking every document upload, rule trigger, and human review decision.
|
| 26 |
-
</p>
|
| 27 |
</div>
|
| 28 |
-
</div>
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
</div>
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
| 51 |
|
| 52 |
return (
|
| 53 |
-
<div key={
|
| 54 |
-
|
| 55 |
-
<div className="
|
| 56 |
-
<span
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
<
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
</div>
|
| 65 |
-
<p className="text-sm text-slate-200 font-medium mt-1">{ev.message}</p>
|
| 66 |
</div>
|
| 67 |
);
|
| 68 |
})}
|
| 69 |
</div>
|
| 70 |
-
|
| 71 |
-
|
| 72 |
</div>
|
| 73 |
);
|
| 74 |
}
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import {
|
| 3 |
+
History,
|
| 4 |
+
GitCommit,
|
| 5 |
+
CheckCircle2,
|
| 6 |
+
AlertCircle,
|
| 7 |
+
FileText,
|
| 8 |
+
Clock,
|
| 9 |
+
ShieldCheck,
|
| 10 |
+
ChevronDown
|
| 11 |
+
} from 'lucide-react';
|
| 12 |
|
| 13 |
export default function AuditLog({ activityFeed, loading }) {
|
| 14 |
+
const [filterType, setFilterType] = useState('ALL');
|
| 15 |
+
|
| 16 |
if (loading) {
|
| 17 |
return (
|
| 18 |
+
<div className="flex flex-col items-center justify-center py-24 space-y-4">
|
| 19 |
+
<div className="w-8 h-8 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin"></div>
|
| 20 |
+
<p className="text-xs text-slate-400 font-mono">Loading Git Audit Trail...</p>
|
| 21 |
</div>
|
| 22 |
);
|
| 23 |
}
|
| 24 |
|
| 25 |
+
const events = activityFeed || [];
|
| 26 |
+
|
| 27 |
+
const filtered = events.filter((e) => {
|
| 28 |
+
if (filterType === 'ALL') return true;
|
| 29 |
+
return e.type === filterType;
|
| 30 |
+
});
|
| 31 |
+
|
| 32 |
+
const getEventBadge = (type) => {
|
| 33 |
+
switch (type) {
|
| 34 |
+
case 'proposal_approved':
|
| 35 |
+
return {
|
| 36 |
+
color: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',
|
| 37 |
+
label: 'COMMITTED',
|
| 38 |
+
};
|
| 39 |
+
case 'proposal_created':
|
| 40 |
+
return {
|
| 41 |
+
color: 'bg-amber-500/10 text-amber-300 border-amber-500/20',
|
| 42 |
+
label: 'PROPOSED',
|
| 43 |
+
};
|
| 44 |
+
case 'workflow_completed':
|
| 45 |
+
return {
|
| 46 |
+
color: 'bg-blue-500/10 text-blue-300 border-blue-500/20',
|
| 47 |
+
label: 'WORKFLOW',
|
| 48 |
+
};
|
| 49 |
+
default:
|
| 50 |
+
return {
|
| 51 |
+
color: 'bg-slate-800 text-slate-300 border-slate-700',
|
| 52 |
+
label: 'ACTIVITY',
|
| 53 |
+
};
|
| 54 |
+
}
|
| 55 |
+
};
|
| 56 |
|
| 57 |
return (
|
| 58 |
<div className="space-y-6">
|
| 59 |
+
{/* Audit Log Header */}
|
| 60 |
+
<div className="bg-[#0C101A] border border-slate-800/90 rounded-xl p-4 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
| 61 |
+
<div className="flex items-center space-x-3">
|
| 62 |
+
<History className="w-5 h-5 text-emerald-400" />
|
| 63 |
+
<div>
|
| 64 |
+
<h2 className="text-sm font-semibold text-white">Immutable Git Audit Log</h2>
|
| 65 |
+
<p className="text-[11px] text-slate-400">Cryptographically verifiable lineage of knowledge commits, reviews, and workflows.</p>
|
| 66 |
+
</div>
|
|
|
|
|
|
|
|
|
|
| 67 |
</div>
|
|
|
|
| 68 |
|
| 69 |
+
<div className="flex items-center space-x-2 text-xs">
|
| 70 |
+
{['ALL', 'proposal_approved', 'proposal_created', 'workflow_completed'].map((f) => (
|
| 71 |
+
<button
|
| 72 |
+
key={f}
|
| 73 |
+
onClick={() => setFilterType(f)}
|
| 74 |
+
className={`px-3 py-1.5 rounded-lg font-medium transition ${
|
| 75 |
+
filterType === f
|
| 76 |
+
? 'bg-slate-700 text-white font-semibold'
|
| 77 |
+
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/60'
|
| 78 |
+
}`}
|
| 79 |
+
>
|
| 80 |
+
{f === 'ALL'
|
| 81 |
+
? 'All Events'
|
| 82 |
+
: f === 'proposal_approved'
|
| 83 |
+
? 'Approved'
|
| 84 |
+
: f === 'proposal_created'
|
| 85 |
+
? 'Proposed'
|
| 86 |
+
: 'Workflows'}
|
| 87 |
+
</button>
|
| 88 |
+
))}
|
| 89 |
</div>
|
| 90 |
+
</div>
|
| 91 |
+
|
| 92 |
+
{/* Visual Commit History Timeline */}
|
| 93 |
+
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-6">
|
| 94 |
+
{filtered.length === 0 ? (
|
| 95 |
+
<div className="py-16 text-center text-xs text-slate-500">
|
| 96 |
+
No audit records found matching criteria.
|
| 97 |
+
</div>
|
| 98 |
+
) : (
|
| 99 |
+
<div className="relative pl-6 space-y-6 before:absolute before:left-3 before:top-2 before:bottom-2 before:w-0.5 before:bg-slate-800">
|
| 100 |
+
{filtered.map((evt, idx) => {
|
| 101 |
+
const badge = getEventBadge(evt.type);
|
| 102 |
+
const dateStr = evt.timestamp ? new Date(evt.timestamp).toLocaleString() : 'Recent';
|
| 103 |
+
const sha = (evt.id || 'commit').slice(-7);
|
| 104 |
|
| 105 |
return (
|
| 106 |
+
<div key={evt.id || idx} className="relative flex items-start space-x-4 group">
|
| 107 |
+
{/* Timeline Dot */}
|
| 108 |
+
<div className="absolute -left-[29px] top-1 w-4 h-4 rounded-full bg-[#0D121F] border-2 border-emerald-500 flex items-center justify-center group-hover:scale-125 transition">
|
| 109 |
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
|
| 110 |
+
</div>
|
| 111 |
+
|
| 112 |
+
<div className="flex-1 bg-[#121828] border border-slate-800/80 rounded-xl p-3.5 hover:border-slate-700 transition space-y-1.5 text-xs">
|
| 113 |
+
<div className="flex items-center justify-between">
|
| 114 |
+
<div className="flex items-center space-x-2">
|
| 115 |
+
<span className="font-mono text-emerald-400 bg-emerald-500/10 px-1.5 py-0.2 rounded border border-emerald-500/20 text-[11px] font-bold">
|
| 116 |
+
{sha}
|
| 117 |
+
</span>
|
| 118 |
+
<span className={`text-[10px] font-mono px-2 py-0.2 rounded font-bold border ${badge.color}`}>
|
| 119 |
+
{badge.label}
|
| 120 |
+
</span>
|
| 121 |
+
<span className="text-slate-400 text-[11px] flex items-center space-x-1 font-mono">
|
| 122 |
+
<ShieldCheck className="w-3 h-3 text-emerald-400" />
|
| 123 |
+
<span>Verified</span>
|
| 124 |
+
</span>
|
| 125 |
+
</div>
|
| 126 |
+
|
| 127 |
+
<span className="text-[11px] text-slate-500 font-mono">{dateStr}</span>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
<p className="text-slate-200 font-medium text-xs leading-relaxed font-sans">
|
| 131 |
+
{evt.message}
|
| 132 |
+
</p>
|
| 133 |
+
|
| 134 |
+
{evt.metadata && (
|
| 135 |
+
<div className="text-[10px] text-slate-400 font-mono pt-1">
|
| 136 |
+
Metadata: {JSON.stringify(evt.metadata)}
|
| 137 |
+
</div>
|
| 138 |
+
)}
|
| 139 |
</div>
|
|
|
|
| 140 |
</div>
|
| 141 |
);
|
| 142 |
})}
|
| 143 |
</div>
|
| 144 |
+
)}
|
| 145 |
+
</div>
|
| 146 |
</div>
|
| 147 |
);
|
| 148 |
}
|
studio/src/components/IngestionStaging.jsx
CHANGED
|
@@ -1,156 +1,158 @@
|
|
| 1 |
-
import React, { useState } from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
export default function IngestionStaging({ documents,
|
|
|
|
| 4 |
const [uploading, setUploading] = useState(false);
|
| 5 |
-
const [
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
const
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
setUploading(true);
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
try {
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
} catch (err) {
|
| 17 |
-
|
| 18 |
} finally {
|
| 19 |
setUploading(false);
|
| 20 |
}
|
| 21 |
};
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
return (
|
| 24 |
<div className="space-y-6">
|
| 25 |
{/* Drag & Drop Zone */}
|
| 26 |
<div
|
| 27 |
onDragOver={(e) => {
|
| 28 |
e.preventDefault();
|
| 29 |
-
|
| 30 |
}}
|
| 31 |
-
onDragLeave={() =>
|
| 32 |
-
onDrop={
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
dragOver
|
| 39 |
-
? 'border-emerald-500 bg-emerald-500/10'
|
| 40 |
-
: 'border-slate-700/80 bg-[#0E1524]/60 hover:border-slate-600'
|
| 41 |
}`}
|
| 42 |
>
|
| 43 |
-
<
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
type="file"
|
| 54 |
-
className="hidden"
|
| 55 |
-
onChange={(e) => handleFiles(e.target.files)}
|
| 56 |
-
disabled={uploading}
|
| 57 |
-
/>
|
| 58 |
-
</label>
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
| 63 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
</div>
|
| 65 |
|
| 66 |
-
{/*
|
| 67 |
-
<div className="
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
<span className="text-[11px] font-bold text-emerald-400 block">{s.step}</span>
|
| 81 |
-
<span className="text-[10px] text-slate-400 block mt-0.5">{s.desc}</span>
|
| 82 |
</div>
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
| 92 |
</div>
|
| 93 |
|
| 94 |
-
{
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
</div>
|
| 98 |
-
) : (
|
| 99 |
-
<div className="overflow-x-auto">
|
| 100 |
-
<table className="w-full text-left text-xs text-slate-300">
|
| 101 |
-
<thead className="bg-[#121A2C] text-slate-400 font-mono text-[11px] border-b border-slate-800">
|
| 102 |
-
<tr>
|
| 103 |
-
<th className="px-5 py-3 font-semibold">Filename</th>
|
| 104 |
-
<th className="px-5 py-3 font-semibold">Pipeline State</th>
|
| 105 |
-
<th className="px-5 py-3 font-semibold">Document UUID</th>
|
| 106 |
-
<th className="px-5 py-3 font-semibold">Actions</th>
|
| 107 |
-
</tr>
|
| 108 |
-
</thead>
|
| 109 |
-
<tbody className="divide-y divide-slate-800/60 font-medium">
|
| 110 |
-
{documents.map((d) => {
|
| 111 |
-
const status = d.workflow_status || d.status || 'COMPLETED';
|
| 112 |
-
let statusBadge = (
|
| 113 |
-
<span className="px-2 py-0.5 rounded bg-slate-800 text-slate-400">{status}</span>
|
| 114 |
-
);
|
| 115 |
-
if (status === 'COMPLETED') {
|
| 116 |
-
statusBadge = (
|
| 117 |
-
<span className="px-2 py-0.5 rounded bg-emerald-500/15 text-emerald-400 border border-emerald-500/30">
|
| 118 |
-
✔ COMMITTED
|
| 119 |
-
</span>
|
| 120 |
-
);
|
| 121 |
-
} else if (status === 'WAITING_FOR_REVIEW') {
|
| 122 |
-
statusBadge = (
|
| 123 |
-
<span className="px-2 py-0.5 rounded bg-amber-500/15 text-amber-300 border border-amber-500/30 font-bold">
|
| 124 |
-
● REVIEW REQUIRED
|
| 125 |
-
</span>
|
| 126 |
-
);
|
| 127 |
-
} else if (status === 'RUNNING') {
|
| 128 |
-
statusBadge = (
|
| 129 |
-
<span className="px-2 py-0.5 rounded bg-cyan-500/15 text-cyan-300 animate-pulse">
|
| 130 |
-
⚙ PROCESSING
|
| 131 |
-
</span>
|
| 132 |
-
);
|
| 133 |
-
}
|
| 134 |
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
</td>
|
| 142 |
-
<td className="px-5 py-3">
|
| 143 |
-
<span className="text-[11px] text-slate-400 font-mono">
|
| 144 |
-
WF: {d.workflow_id?.slice(0, 8) || 'N/A'}
|
| 145 |
-
</span>
|
| 146 |
-
</td>
|
| 147 |
-
</tr>
|
| 148 |
-
);
|
| 149 |
-
})}
|
| 150 |
-
</tbody>
|
| 151 |
-
</table>
|
| 152 |
</div>
|
| 153 |
-
|
| 154 |
</div>
|
| 155 |
</div>
|
| 156 |
);
|
|
|
|
| 1 |
+
import React, { useState, useRef } from 'react';
|
| 2 |
+
import {
|
| 3 |
+
Upload,
|
| 4 |
+
FileText,
|
| 5 |
+
Clock,
|
| 6 |
+
CheckCircle2,
|
| 7 |
+
AlertCircle,
|
| 8 |
+
Terminal,
|
| 9 |
+
FileCode,
|
| 10 |
+
ArrowRight,
|
| 11 |
+
Sparkles,
|
| 12 |
+
RefreshCw
|
| 13 |
+
} from 'lucide-react';
|
| 14 |
|
| 15 |
+
export default function IngestionStaging({ documents, onUploadDocument, currentWorkspace, loading }) {
|
| 16 |
+
const [dragActive, setDragActive] = useState(false);
|
| 17 |
const [uploading, setUploading] = useState(false);
|
| 18 |
+
const [terminalLogs, setTerminalLogs] = useState([
|
| 19 |
+
'[0.00s] DiffWeave Staging ready. Bound to DocWeave FastMCP engine.',
|
| 20 |
+
'[0.02s] Workspace ID: ' + (currentWorkspace?.id || '4314fb04-95be-41a2-bef4-50bf27c9c363'),
|
| 21 |
+
'[0.05s] Monitoring document staging buffer...',
|
| 22 |
+
]);
|
| 23 |
+
const fileInputRef = useRef(null);
|
| 24 |
|
| 25 |
+
const addLog = (msg) => {
|
| 26 |
+
setTerminalLogs((prev) => [...prev, `[${new Date().toLocaleTimeString()}] ${msg}`]);
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
const handleFile = async (file) => {
|
| 30 |
+
if (!file) return;
|
| 31 |
setUploading(true);
|
| 32 |
+
addLog(`Ingesting file: ${file.name} (${Math.round(file.size / 1024)} KB)...`);
|
| 33 |
+
addLog(`Running LangGraph neural extraction pipeline...`);
|
| 34 |
+
|
| 35 |
try {
|
| 36 |
+
await onUploadDocument(file);
|
| 37 |
+
addLog(`DocWeave workflow completed for ${file.name}`);
|
| 38 |
+
addLog(`3-Way semantic fact proposals generated successfully.`);
|
| 39 |
} catch (err) {
|
| 40 |
+
addLog(`Upload error: ${err.message}`);
|
| 41 |
} finally {
|
| 42 |
setUploading(false);
|
| 43 |
}
|
| 44 |
};
|
| 45 |
|
| 46 |
+
const handleDrop = (e) => {
|
| 47 |
+
e.preventDefault();
|
| 48 |
+
setDragActive(false);
|
| 49 |
+
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
|
| 50 |
+
handleFile(e.dataTransfer.files[0]);
|
| 51 |
+
}
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
return (
|
| 55 |
<div className="space-y-6">
|
| 56 |
{/* Drag & Drop Zone */}
|
| 57 |
<div
|
| 58 |
onDragOver={(e) => {
|
| 59 |
e.preventDefault();
|
| 60 |
+
setDragActive(true);
|
| 61 |
}}
|
| 62 |
+
onDragLeave={() => setDragActive(false)}
|
| 63 |
+
onDrop={handleDrop}
|
| 64 |
+
onClick={() => fileInputRef.current?.click()}
|
| 65 |
+
className={`border-2 border-dashed rounded-2xl p-10 text-center cursor-pointer transition flex flex-col items-center justify-center space-y-3 ${
|
| 66 |
+
dragActive
|
| 67 |
+
? 'border-emerald-400 bg-emerald-500/10'
|
| 68 |
+
: 'border-slate-800 bg-[#0C101A] hover:border-slate-700 hover:bg-[#0E1422]'
|
|
|
|
|
|
|
|
|
|
| 69 |
}`}
|
| 70 |
>
|
| 71 |
+
<input
|
| 72 |
+
ref={fileInputRef}
|
| 73 |
+
type="file"
|
| 74 |
+
className="hidden"
|
| 75 |
+
onChange={(e) => {
|
| 76 |
+
if (e.target.files && e.target.files[0]) {
|
| 77 |
+
handleFile(e.target.files[0]);
|
| 78 |
+
}
|
| 79 |
+
}}
|
| 80 |
+
/>
|
| 81 |
|
| 82 |
+
<div className="w-12 h-12 rounded-2xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 flex items-center justify-center shadow-lg shadow-emerald-500/10">
|
| 83 |
+
<Upload className="w-6 h-6" />
|
| 84 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
+
<div>
|
| 87 |
+
<h3 className="text-sm font-semibold text-white">Stage Document for Knowledge Extraction</h3>
|
| 88 |
+
<p className="text-xs text-slate-400 mt-1">
|
| 89 |
+
Drag & drop clinical protocols, amendments, or regulatory specs (TXT, PDF, DOCX)
|
| 90 |
+
</p>
|
| 91 |
</div>
|
| 92 |
+
|
| 93 |
+
<button
|
| 94 |
+
type="button"
|
| 95 |
+
disabled={uploading}
|
| 96 |
+
className="px-4 py-2 rounded-xl bg-[#141B2D] hover:bg-[#1E2740] border border-slate-700 text-xs font-semibold text-slate-200 transition shadow-sm"
|
| 97 |
+
>
|
| 98 |
+
{uploading ? 'Processing Extraction...' : 'Select File from Disk'}
|
| 99 |
+
</button>
|
| 100 |
</div>
|
| 101 |
|
| 102 |
+
{/* Grid: Staged Documents Table & Live Terminal */}
|
| 103 |
+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
| 104 |
+
{/* Staged Documents List (7 cols) */}
|
| 105 |
+
<div className="lg:col-span-7 bg-[#0D121F] border border-slate-800 rounded-2xl p-5 space-y-4">
|
| 106 |
+
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
| 107 |
+
<span className="text-xs font-bold text-white flex items-center space-x-2">
|
| 108 |
+
<FileCode className="w-4 h-4 text-emerald-400" />
|
| 109 |
+
<span>Tracked Staged Documents ({documents.length})</span>
|
| 110 |
+
</span>
|
| 111 |
+
</div>
|
| 112 |
+
|
| 113 |
+
{documents.length === 0 ? (
|
| 114 |
+
<div className="py-12 text-center text-xs text-slate-500">
|
| 115 |
+
No documents staged yet in this workspace. Upload a file above to begin.
|
|
|
|
|
|
|
| 116 |
</div>
|
| 117 |
+
) : (
|
| 118 |
+
<div className="divide-y divide-slate-800/80 text-xs font-mono">
|
| 119 |
+
{documents.map((doc) => (
|
| 120 |
+
<div key={doc.id} className="py-3 flex items-center justify-between">
|
| 121 |
+
<div className="flex items-center space-x-3">
|
| 122 |
+
<FileText className="w-4 h-4 text-slate-400 shrink-0" />
|
| 123 |
+
<div>
|
| 124 |
+
<span className="text-white font-medium block">{doc.filename}</span>
|
| 125 |
+
<span className="text-slate-500 text-[10px]">ID: {doc.id.slice(0, 8)}</span>
|
| 126 |
+
</div>
|
| 127 |
+
</div>
|
| 128 |
|
| 129 |
+
<span className="px-2 py-0.5 rounded text-[10px] font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
|
| 130 |
+
{doc.status || 'COMMITTED'}
|
| 131 |
+
</span>
|
| 132 |
+
</div>
|
| 133 |
+
))}
|
| 134 |
+
</div>
|
| 135 |
+
)}
|
| 136 |
</div>
|
| 137 |
|
| 138 |
+
{/* Live Pipeline Terminal (5 cols) */}
|
| 139 |
+
<div className="lg:col-span-5 bg-[#080B11] border border-slate-800 rounded-2xl p-4 font-mono text-[11px] flex flex-col h-[380px] shadow-inner">
|
| 140 |
+
<div className="flex items-center justify-between pb-2 border-b border-slate-900 text-slate-400">
|
| 141 |
+
<div className="flex items-center space-x-2">
|
| 142 |
+
<Terminal className="w-3.5 h-3.5 text-emerald-400" />
|
| 143 |
+
<span className="text-slate-200 font-semibold">Live Ingestion Stream</span>
|
| 144 |
+
</div>
|
| 145 |
+
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
|
| 146 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
+
<div className="flex-1 overflow-y-auto space-y-1.5 pt-3 text-slate-300 pr-1">
|
| 149 |
+
{terminalLogs.map((log, i) => (
|
| 150 |
+
<div key={i} className="leading-relaxed">
|
| 151 |
+
<span className="text-emerald-400">></span> {log}
|
| 152 |
+
</div>
|
| 153 |
+
))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
</div>
|
| 155 |
+
</div>
|
| 156 |
</div>
|
| 157 |
</div>
|
| 158 |
);
|
studio/src/components/KnowledgeBase.jsx
CHANGED
|
@@ -1,117 +1,184 @@
|
|
| 1 |
import React, { useState } from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
export default function KnowledgeBase({
|
| 4 |
-
const [
|
| 5 |
-
const [
|
|
|
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
};
|
| 13 |
|
| 14 |
-
const
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
return (
|
| 19 |
<div className="space-y-6">
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
<
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
</p>
|
| 31 |
</div>
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
<
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
</div>
|
| 50 |
|
| 51 |
-
{/*
|
| 52 |
-
<div className="
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
<button
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
className={`px-3 py-1 rounded-md text-xs font-mono font-medium transition ${
|
| 58 |
-
selectedType === t
|
| 59 |
-
? 'bg-slate-700 text-white font-bold'
|
| 60 |
-
: 'text-slate-400 hover:text-white'
|
| 61 |
-
}`}
|
| 62 |
>
|
| 63 |
-
|
|
|
|
| 64 |
</button>
|
| 65 |
-
))}
|
| 66 |
-
</div>
|
| 67 |
-
|
| 68 |
-
{loading ? (
|
| 69 |
-
<div className="text-center py-20 text-slate-400 text-xs">
|
| 70 |
-
<span className="pulse-glow mr-2">⚙</span> Fetching knowledge items...
|
| 71 |
-
</div>
|
| 72 |
-
) : filtered.length === 0 ? (
|
| 73 |
-
<div className="text-center py-16 border border-dashed border-slate-800 rounded-xl bg-[#090E17]">
|
| 74 |
-
<span className="text-2xl text-slate-500 block mb-2 font-mono">∅</span>
|
| 75 |
-
<h3 className="text-sm font-semibold text-white">No Committed Items Found</h3>
|
| 76 |
-
<p className="text-xs text-slate-400 mt-1">
|
| 77 |
-
Approve proposals in the Pull Requests tab or run 'dw commit' to register knowledge.
|
| 78 |
-
</p>
|
| 79 |
</div>
|
| 80 |
-
|
| 81 |
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 82 |
-
{filtered.map((item) => (
|
| 83 |
-
<div
|
| 84 |
-
key={item.id}
|
| 85 |
-
className="p-5 rounded-xl border border-slate-800 bg-[#0F1423] hover:border-slate-700 transition"
|
| 86 |
-
>
|
| 87 |
-
<div className="flex items-center justify-between gap-2 mb-2">
|
| 88 |
-
<span className="text-[10px] font-mono font-bold text-cyan-400 px-2 py-0.5 rounded bg-cyan-950/40 border border-cyan-500/20">
|
| 89 |
-
{item.type}
|
| 90 |
-
</span>
|
| 91 |
-
<span className="text-[11px] font-mono text-emerald-400">
|
| 92 |
-
Confidence: {(item.confidence * 100).toFixed(0)}%
|
| 93 |
-
</span>
|
| 94 |
-
</div>
|
| 95 |
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
-
|
| 99 |
-
<span className="text-[10px] font-mono text-slate-500 block mb-1">Committed Value:</span>
|
| 100 |
-
<p className="text-xs text-slate-200 font-mono">"{item.value}"</p>
|
| 101 |
-
</div>
|
| 102 |
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
</div>
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
</div>
|
| 116 |
);
|
| 117 |
}
|
|
|
|
| 1 |
import React, { useState } from 'react';
|
| 2 |
+
import {
|
| 3 |
+
Database,
|
| 4 |
+
Search,
|
| 5 |
+
Download,
|
| 6 |
+
Filter,
|
| 7 |
+
CheckCircle2,
|
| 8 |
+
Sparkles,
|
| 9 |
+
FileText,
|
| 10 |
+
Copy,
|
| 11 |
+
Layers
|
| 12 |
+
} from 'lucide-react';
|
| 13 |
|
| 14 |
+
export default function KnowledgeBase({ knowledgeItems, currentWorkspace, loading }) {
|
| 15 |
+
const [search, setSearch] = useState('');
|
| 16 |
+
const [categoryFilter, setCategoryFilter] = useState('ALL');
|
| 17 |
+
const [copiedId, setCopiedId] = useState(null);
|
| 18 |
|
| 19 |
+
if (loading) {
|
| 20 |
+
return (
|
| 21 |
+
<div className="flex flex-col items-center justify-center py-24 space-y-4">
|
| 22 |
+
<div className="w-8 h-8 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin"></div>
|
| 23 |
+
<p className="text-xs text-slate-400 font-mono">Loading Master Knowledge Register...</p>
|
| 24 |
+
</div>
|
| 25 |
+
);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
const filtered = knowledgeItems.filter((item) => {
|
| 29 |
+
const matchesCat = categoryFilter === 'ALL' || item.type === categoryFilter;
|
| 30 |
+
const matchesSearch =
|
| 31 |
+
!search ||
|
| 32 |
+
item.title.toLowerCase().includes(search.toLowerCase()) ||
|
| 33 |
+
String(item.value || '').toLowerCase().includes(search.toLowerCase());
|
| 34 |
+
return matchesCat && matchesSearch;
|
| 35 |
+
});
|
| 36 |
+
|
| 37 |
+
const handleCopy = (val, id) => {
|
| 38 |
+
navigator.clipboard.writeText(val);
|
| 39 |
+
setCopiedId(id);
|
| 40 |
+
setTimeout(() => setCopiedId(null), 1800);
|
| 41 |
};
|
| 42 |
|
| 43 |
+
const exportJSON = () => {
|
| 44 |
+
const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(knowledgeItems, null, 2));
|
| 45 |
+
const a = document.createElement('a');
|
| 46 |
+
a.href = dataStr;
|
| 47 |
+
a.download = `diffweave-knowledge-${currentWorkspace?.id || 'register'}.json`;
|
| 48 |
+
a.click();
|
| 49 |
+
};
|
| 50 |
|
| 51 |
return (
|
| 52 |
<div className="space-y-6">
|
| 53 |
+
{/* Top Metrics Cards */}
|
| 54 |
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
| 55 |
+
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-4 flex items-center space-x-3.5">
|
| 56 |
+
<div className="w-10 h-10 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 flex items-center justify-center">
|
| 57 |
+
<CheckCircle2 className="w-5 h-5" />
|
| 58 |
+
</div>
|
| 59 |
+
<div>
|
| 60 |
+
<div className="text-xl font-bold text-white font-mono">{knowledgeItems.length}</div>
|
| 61 |
+
<div className="text-[11px] text-slate-400 font-medium">Committed Facts in Register</div>
|
| 62 |
+
</div>
|
|
|
|
| 63 |
</div>
|
| 64 |
|
| 65 |
+
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-4 flex items-center space-x-3.5">
|
| 66 |
+
<div className="w-10 h-10 rounded-xl bg-blue-500/10 border border-blue-500/20 text-blue-400 flex items-center justify-center">
|
| 67 |
+
<Sparkles className="w-5 h-5" />
|
| 68 |
+
</div>
|
| 69 |
+
<div>
|
| 70 |
+
<div className="text-xl font-bold text-white font-mono">
|
| 71 |
+
{knowledgeItems.filter((i) => (i.confidence || 0.95) >= 0.9).length}
|
| 72 |
+
</div>
|
| 73 |
+
<div className="text-[11px] text-slate-400 font-medium">High Confidence (≥90%)</div>
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-4 flex items-center space-x-3.5">
|
| 78 |
+
<div className="w-10 h-10 rounded-xl bg-violet-500/10 border border-violet-500/20 text-violet-400 flex items-center justify-center">
|
| 79 |
+
<Layers className="w-5 h-5" />
|
| 80 |
+
</div>
|
| 81 |
+
<div>
|
| 82 |
+
<div className="text-xl font-bold text-white font-mono">
|
| 83 |
+
{new Set(knowledgeItems.map((i) => i.type)).size}
|
| 84 |
+
</div>
|
| 85 |
+
<div className="text-[11px] text-slate-400 font-medium">Unique Semantic Categories</div>
|
| 86 |
+
</div>
|
| 87 |
+
</div>
|
| 88 |
</div>
|
| 89 |
|
| 90 |
+
{/* Control Bar */}
|
| 91 |
+
<div className="bg-[#0C101A] border border-slate-800/90 rounded-xl p-4 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
| 92 |
+
<div className="flex items-center space-x-3">
|
| 93 |
+
<Database className="w-5 h-5 text-emerald-400" />
|
| 94 |
+
<h2 className="text-sm font-semibold text-white">Master Knowledge Register</h2>
|
| 95 |
+
<span className="text-xs px-2 py-0.5 rounded bg-slate-800 text-slate-300 font-mono">
|
| 96 |
+
{filtered.length} entries
|
| 97 |
+
</span>
|
| 98 |
+
</div>
|
| 99 |
+
|
| 100 |
+
<div className="flex items-center space-x-3 text-xs">
|
| 101 |
+
{/* Search Input */}
|
| 102 |
+
<div className="relative">
|
| 103 |
+
<Search className="w-3.5 h-3.5 text-slate-500 absolute left-2.5 top-2.5" />
|
| 104 |
+
<input
|
| 105 |
+
type="text"
|
| 106 |
+
placeholder="Search facts..."
|
| 107 |
+
value={search}
|
| 108 |
+
onChange={(e) => setSearch(e.target.value)}
|
| 109 |
+
className="bg-[#141B2D] border border-slate-800 rounded-lg pl-8 pr-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500 w-44"
|
| 110 |
+
/>
|
| 111 |
+
</div>
|
| 112 |
+
|
| 113 |
+
{/* Category Filter */}
|
| 114 |
+
<select
|
| 115 |
+
value={categoryFilter}
|
| 116 |
+
onChange={(e) => setCategoryFilter(e.target.value)}
|
| 117 |
+
className="bg-[#141B2D] border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-slate-300 focus:outline-none"
|
| 118 |
+
>
|
| 119 |
+
<option value="ALL">All Categories</option>
|
| 120 |
+
<option value="CLAIM">Claims</option>
|
| 121 |
+
<option value="METRIC">Metrics</option>
|
| 122 |
+
<option value="ENTITY">Entities</option>
|
| 123 |
+
<option value="DATE">Dates</option>
|
| 124 |
+
</select>
|
| 125 |
+
|
| 126 |
+
{/* Export Button */}
|
| 127 |
<button
|
| 128 |
+
onClick={exportJSON}
|
| 129 |
+
className="px-3 py-1.5 rounded-lg bg-[#141B2D] hover:bg-[#1E2740] border border-slate-700 text-slate-200 font-semibold transition flex items-center space-x-1.5"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
>
|
| 131 |
+
<Download className="w-3.5 h-3.5" />
|
| 132 |
+
<span>Export JSON</span>
|
| 133 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
</div>
|
| 135 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
+
{/* Facts Table */}
|
| 138 |
+
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl overflow-hidden shadow-sm">
|
| 139 |
+
{filtered.length === 0 ? (
|
| 140 |
+
<div className="py-16 text-center text-xs text-slate-500">
|
| 141 |
+
No knowledge entries found matching filter criteria.
|
| 142 |
+
</div>
|
| 143 |
+
) : (
|
| 144 |
+
<div className="divide-y divide-slate-800/80">
|
| 145 |
+
{filtered.map((item) => (
|
| 146 |
+
<div key={item.id} className="p-4 hover:bg-[#121828] transition flex flex-col md:flex-row md:items-start justify-between gap-4">
|
| 147 |
+
<div className="space-y-1.5 max-w-2xl">
|
| 148 |
+
<div className="flex items-center space-x-2.5">
|
| 149 |
+
<span className="text-xs font-semibold text-white">{item.title}</span>
|
| 150 |
+
<span className="text-[10px] uppercase font-mono px-2 py-0.5 rounded font-bold bg-slate-800 text-slate-300 border border-slate-700">
|
| 151 |
+
{item.type || 'CLAIM'}
|
| 152 |
+
</span>
|
| 153 |
+
<span className="text-emerald-400 font-mono text-[11px] font-semibold">
|
| 154 |
+
{Math.round((item.confidence || 0.95) * 100)}% Conf
|
| 155 |
+
</span>
|
| 156 |
+
</div>
|
| 157 |
|
| 158 |
+
<p className="text-xs text-slate-300 leading-relaxed font-sans">{item.value}</p>
|
|
|
|
|
|
|
|
|
|
| 159 |
|
| 160 |
+
<div className="text-[11px] text-slate-500 font-mono">
|
| 161 |
+
ID: {item.id.slice(0, 8)}
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
|
| 165 |
+
<div className="flex items-center space-x-2 shrink-0">
|
| 166 |
+
<button
|
| 167 |
+
onClick={() => handleCopy(item.value, item.id)}
|
| 168 |
+
className="p-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white transition"
|
| 169 |
+
title="Copy Value"
|
| 170 |
+
>
|
| 171 |
+
<Copy className="w-3.5 h-3.5" />
|
| 172 |
+
</button>
|
| 173 |
+
{copiedId === item.id && (
|
| 174 |
+
<span className="text-[10px] text-emerald-400 font-mono">Copied!</span>
|
| 175 |
+
)}
|
| 176 |
+
</div>
|
| 177 |
</div>
|
| 178 |
+
))}
|
| 179 |
+
</div>
|
| 180 |
+
)}
|
| 181 |
+
</div>
|
| 182 |
</div>
|
| 183 |
);
|
| 184 |
}
|
studio/src/components/KnowledgeGraph.jsx
CHANGED
|
@@ -1,175 +1,331 @@
|
|
| 1 |
-
import React, { useState } from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
export default function KnowledgeGraph({ graphData, loading }) {
|
|
|
|
|
|
|
| 4 |
const [selectedNode, setSelectedNode] = useState(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
if (loading) {
|
| 7 |
return (
|
| 8 |
-
<div className="flex items-center justify-center
|
| 9 |
-
<
|
|
|
|
| 10 |
</div>
|
| 11 |
);
|
| 12 |
}
|
| 13 |
|
| 14 |
-
const nodes = graphData?.nodes || [];
|
| 15 |
-
const edges = graphData?.edges || [];
|
| 16 |
-
|
| 17 |
return (
|
| 18 |
<div className="space-y-6">
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
</
|
| 27 |
-
<p className="text-xs text-slate-400 mt-1">
|
| 28 |
-
Visual map of semantic relationships and incoming PR conflict connections across documents.
|
| 29 |
-
</p>
|
| 30 |
</div>
|
| 31 |
-
</div>
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
<
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
</div>
|
| 40 |
|
| 41 |
-
{
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
<
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
(e) => (e.source === n.id || e.target === n.id) && e.relationship === 'CONTRADICTS'
|
| 54 |
-
);
|
| 55 |
-
|
| 56 |
-
let badgeColor = 'border-emerald-500/40 bg-emerald-950/20 text-emerald-300';
|
| 57 |
-
if (isProposal) badgeColor = 'border-amber-500/50 bg-amber-950/30 text-amber-200';
|
| 58 |
-
if (isContradiction) badgeColor = 'border-rose-500/60 bg-rose-950/30 text-rose-200 glow-crimson';
|
| 59 |
-
|
| 60 |
-
return (
|
| 61 |
-
<button
|
| 62 |
-
key={n.id || idx}
|
| 63 |
-
onClick={() => setSelectedNode(n)}
|
| 64 |
-
className={`p-3 rounded-xl border text-left transition transform hover:scale-105 shadow-md max-w-xs ${badgeColor} ${
|
| 65 |
-
isSelected ? 'ring-2 ring-cyan-400 scale-105' : ''
|
| 66 |
-
}`}
|
| 67 |
-
>
|
| 68 |
-
<div className="flex items-center justify-between gap-2 mb-1">
|
| 69 |
-
<span className="text-[10px] font-mono font-bold uppercase tracking-wider px-1.5 py-0.5 rounded bg-black/40">
|
| 70 |
-
{n.type}
|
| 71 |
-
</span>
|
| 72 |
-
<span className="text-[10px] font-mono opacity-70">
|
| 73 |
-
{(n.confidence * 100).toFixed(0)}%
|
| 74 |
-
</span>
|
| 75 |
-
</div>
|
| 76 |
-
<h4 className="text-xs font-semibold text-white line-clamp-1">{n.title}</h4>
|
| 77 |
-
{n.value && (
|
| 78 |
-
<p className="text-[11px] text-slate-300 mt-1 line-clamp-2 opacity-80 font-mono">
|
| 79 |
-
"{n.value}"
|
| 80 |
-
</p>
|
| 81 |
-
)}
|
| 82 |
-
</button>
|
| 83 |
-
);
|
| 84 |
-
})}
|
| 85 |
-
</div>
|
| 86 |
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
</div>
|
| 113 |
</div>
|
| 114 |
|
| 115 |
-
{/* Selected Node
|
| 116 |
-
<div className="
|
| 117 |
-
<div>
|
| 118 |
-
<
|
| 119 |
-
|
| 120 |
-
|
| 121 |
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
<h4 className="text-sm font-bold text-white">{selectedNode.title}</h4>
|
| 129 |
-
</div>
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
<
|
| 151 |
-
<span className="text-slate-500 block">Confidence:</span>
|
| 152 |
-
<span className="text-amber-300 font-semibold">
|
| 153 |
-
{(selectedNode.confidence * 100).toFixed(0)}%
|
| 154 |
-
</span>
|
| 155 |
-
</div>
|
| 156 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
</span>
|
| 164 |
</div>
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
</
|
| 171 |
-
|
| 172 |
-
|
|
|
|
| 173 |
</div>
|
| 174 |
</div>
|
| 175 |
</div>
|
|
|
|
| 1 |
+
import React, { useState, useMemo } from 'react';
|
| 2 |
+
import {
|
| 3 |
+
Network,
|
| 4 |
+
ZoomIn,
|
| 5 |
+
ZoomOut,
|
| 6 |
+
RotateCcw,
|
| 7 |
+
Search,
|
| 8 |
+
SlidersHorizontal,
|
| 9 |
+
ChevronRight,
|
| 10 |
+
Sparkles,
|
| 11 |
+
Link2,
|
| 12 |
+
FileText,
|
| 13 |
+
Info
|
| 14 |
+
} from 'lucide-react';
|
| 15 |
|
| 16 |
export default function KnowledgeGraph({ graphData, loading }) {
|
| 17 |
+
const [zoom, setZoom] = useState(1);
|
| 18 |
+
const [searchQuery, setSearchQuery] = useState('');
|
| 19 |
const [selectedNode, setSelectedNode] = useState(null);
|
| 20 |
+
const [filterType, setFilterType] = useState('ALL');
|
| 21 |
+
|
| 22 |
+
const rawNodes = graphData?.nodes || [];
|
| 23 |
+
const rawEdges = graphData?.edges || [];
|
| 24 |
+
|
| 25 |
+
// Filter nodes based on type and search query
|
| 26 |
+
const filteredNodes = useMemo(() => {
|
| 27 |
+
return rawNodes.filter((n) => {
|
| 28 |
+
const matchesType = filterType === 'ALL' || n.type === filterType;
|
| 29 |
+
const matchesSearch =
|
| 30 |
+
!searchQuery ||
|
| 31 |
+
n.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
| 32 |
+
String(n.value || '').toLowerCase().includes(searchQuery.toLowerCase());
|
| 33 |
+
return matchesType && matchesSearch;
|
| 34 |
+
});
|
| 35 |
+
}, [rawNodes, filterType, searchQuery]);
|
| 36 |
+
|
| 37 |
+
// Generate deterministic circular / force layout coordinates
|
| 38 |
+
const positionedNodes = useMemo(() => {
|
| 39 |
+
const total = filteredNodes.length;
|
| 40 |
+
if (total === 0) return [];
|
| 41 |
+
|
| 42 |
+
const centerX = 400;
|
| 43 |
+
const centerY = 300;
|
| 44 |
+
const radius = Math.min(centerX, centerY) - 80;
|
| 45 |
+
|
| 46 |
+
return filteredNodes.map((node, i) => {
|
| 47 |
+
// Golden angle distribution for natural cluster spread
|
| 48 |
+
const angle = (i * 2.399963) % (2 * Math.PI);
|
| 49 |
+
const r = 40 + (radius - 40) * Math.sqrt((i + 1) / total);
|
| 50 |
+
const x = centerX + r * Math.cos(angle);
|
| 51 |
+
const y = centerY + r * Math.sin(angle);
|
| 52 |
+
return { ...node, x, y };
|
| 53 |
+
});
|
| 54 |
+
}, [filteredNodes]);
|
| 55 |
+
|
| 56 |
+
const nodeMap = useMemo(() => {
|
| 57 |
+
const map = {};
|
| 58 |
+
positionedNodes.forEach((n) => {
|
| 59 |
+
map[n.id] = n;
|
| 60 |
+
});
|
| 61 |
+
return map;
|
| 62 |
+
}, [positionedNodes]);
|
| 63 |
+
|
| 64 |
+
const getNodeColor = (type) => {
|
| 65 |
+
switch (type) {
|
| 66 |
+
case 'ENTITY':
|
| 67 |
+
return '#06B6D4'; // Cyan
|
| 68 |
+
case 'CLAIM':
|
| 69 |
+
return '#8B5CF6'; // Violet
|
| 70 |
+
case 'METRIC':
|
| 71 |
+
return '#10B981'; // Emerald
|
| 72 |
+
case 'DATE':
|
| 73 |
+
return '#F59E0B'; // Amber
|
| 74 |
+
case 'PROPOSAL':
|
| 75 |
+
return '#EC4899'; // Pink
|
| 76 |
+
default:
|
| 77 |
+
return '#3B82F6'; // Blue
|
| 78 |
+
}
|
| 79 |
+
};
|
| 80 |
|
| 81 |
if (loading) {
|
| 82 |
return (
|
| 83 |
+
<div className="flex flex-col items-center justify-center py-24 space-y-4">
|
| 84 |
+
<div className="w-8 h-8 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin"></div>
|
| 85 |
+
<p className="text-xs text-slate-400 font-mono">Building Knowledge Topology Graph...</p>
|
| 86 |
</div>
|
| 87 |
);
|
| 88 |
}
|
| 89 |
|
|
|
|
|
|
|
|
|
|
| 90 |
return (
|
| 91 |
<div className="space-y-6">
|
| 92 |
+
{/* Graph Control Bar */}
|
| 93 |
+
<div className="bg-[#0C101A] border border-slate-800/90 rounded-xl p-4 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
| 94 |
+
<div className="flex items-center space-x-3">
|
| 95 |
+
<Network className="w-5 h-5 text-emerald-400" />
|
| 96 |
+
<h2 className="text-sm font-semibold text-white">Knowledge Graph Topology</h2>
|
| 97 |
+
<span className="text-xs px-2 py-0.5 rounded bg-slate-800 text-slate-300 font-mono">
|
| 98 |
+
{positionedNodes.length} nodes • {rawEdges.length} edges
|
| 99 |
+
</span>
|
|
|
|
|
|
|
|
|
|
| 100 |
</div>
|
|
|
|
| 101 |
|
| 102 |
+
<div className="flex items-center space-x-3 text-xs">
|
| 103 |
+
{/* Node Search Bar */}
|
| 104 |
+
<div className="relative">
|
| 105 |
+
<Search className="w-3.5 h-3.5 text-slate-500 absolute left-2.5 top-2.5" />
|
| 106 |
+
<input
|
| 107 |
+
type="text"
|
| 108 |
+
placeholder="Search graph..."
|
| 109 |
+
value={searchQuery}
|
| 110 |
+
onChange={(e) => setSearchQuery(e.target.value)}
|
| 111 |
+
className="bg-[#141B2D] border border-slate-800 rounded-lg pl-8 pr-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500 w-44"
|
| 112 |
+
/>
|
| 113 |
</div>
|
| 114 |
|
| 115 |
+
{/* Type Filter */}
|
| 116 |
+
<select
|
| 117 |
+
value={filterType}
|
| 118 |
+
onChange={(e) => setFilterType(e.target.value)}
|
| 119 |
+
className="bg-[#141B2D] border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-slate-300 focus:outline-none"
|
| 120 |
+
>
|
| 121 |
+
<option value="ALL">All Types</option>
|
| 122 |
+
<option value="CLAIM">Claims</option>
|
| 123 |
+
<option value="METRIC">Metrics</option>
|
| 124 |
+
<option value="ENTITY">Entities</option>
|
| 125 |
+
<option value="PROPOSAL">Pending PRs</option>
|
| 126 |
+
</select>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
+
{/* Zoom Controls */}
|
| 129 |
+
<div className="flex items-center bg-[#141B2D] border border-slate-800 rounded-lg p-0.5">
|
| 130 |
+
<button
|
| 131 |
+
onClick={() => setZoom((z) => Math.min(z + 0.15, 2.0))}
|
| 132 |
+
className="p-1.5 hover:text-white text-slate-400 transition"
|
| 133 |
+
title="Zoom In"
|
| 134 |
+
>
|
| 135 |
+
<ZoomIn className="w-3.5 h-3.5" />
|
| 136 |
+
</button>
|
| 137 |
+
<button
|
| 138 |
+
onClick={() => setZoom((z) => Math.max(z - 0.15, 0.6))}
|
| 139 |
+
className="p-1.5 hover:text-white text-slate-400 transition"
|
| 140 |
+
title="Zoom Out"
|
| 141 |
+
>
|
| 142 |
+
<ZoomOut className="w-3.5 h-3.5" />
|
| 143 |
+
</button>
|
| 144 |
+
<button
|
| 145 |
+
onClick={() => setZoom(1)}
|
| 146 |
+
className="p-1.5 hover:text-white text-slate-400 transition"
|
| 147 |
+
title="Reset Zoom"
|
| 148 |
+
>
|
| 149 |
+
<RotateCcw className="w-3.5 h-3.5" />
|
| 150 |
+
</button>
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
{/* Main Canvas & Detail Drawer Grid */}
|
| 156 |
+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
| 157 |
+
{/* SVG Canvas (8 Cols) */}
|
| 158 |
+
<div className="lg:col-span-8 bg-[#090D16] border border-slate-800/80 rounded-2xl p-4 overflow-hidden relative min-h-[560px] flex items-center justify-center">
|
| 159 |
+
{/* Interactive SVG */}
|
| 160 |
+
<svg
|
| 161 |
+
viewBox="0 0 800 600"
|
| 162 |
+
className="w-full h-full transition-transform duration-200 cursor-grab active:cursor-grabbing"
|
| 163 |
+
style={{ transform: `scale(${zoom})` }}
|
| 164 |
+
>
|
| 165 |
+
{/* Background Grid Pattern */}
|
| 166 |
+
<defs>
|
| 167 |
+
<pattern id="graphGrid" width="40" height="40" patternUnits="userSpaceOnUse">
|
| 168 |
+
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#161F32" strokeWidth="0.8" />
|
| 169 |
+
</pattern>
|
| 170 |
+
</defs>
|
| 171 |
+
<rect width="100%" height="100%" fill="url(#graphGrid)" />
|
| 172 |
+
|
| 173 |
+
{/* Edges */}
|
| 174 |
+
{rawEdges.map((edge, idx) => {
|
| 175 |
+
const src = nodeMap[edge.source];
|
| 176 |
+
const tgt = nodeMap[edge.target];
|
| 177 |
+
if (!src || !tgt) return null;
|
| 178 |
+
|
| 179 |
+
const isHighlighted = selectedNode && (selectedNode.id === src.id || selectedNode.id === tgt.id);
|
| 180 |
+
|
| 181 |
+
return (
|
| 182 |
+
<g key={edge.id || idx}>
|
| 183 |
+
<line
|
| 184 |
+
x1={src.x}
|
| 185 |
+
y1={src.y}
|
| 186 |
+
x2={tgt.x}
|
| 187 |
+
y2={tgt.y}
|
| 188 |
+
stroke={isHighlighted ? '#10B981' : '#334155'}
|
| 189 |
+
strokeWidth={isHighlighted ? 2.5 : 1.2}
|
| 190 |
+
strokeDasharray={edge.relationship === 'PROPOSED_UPDATE' ? '4 3' : 'none'}
|
| 191 |
+
opacity={isHighlighted ? 0.9 : 0.4}
|
| 192 |
+
/>
|
| 193 |
+
</g>
|
| 194 |
+
);
|
| 195 |
+
})}
|
| 196 |
+
|
| 197 |
+
{/* Nodes */}
|
| 198 |
+
{positionedNodes.map((node) => {
|
| 199 |
+
const isSelected = selectedNode?.id === node.id;
|
| 200 |
+
const color = getNodeColor(node.type);
|
| 201 |
+
|
| 202 |
+
return (
|
| 203 |
+
<g
|
| 204 |
+
key={node.id}
|
| 205 |
+
transform={`translate(${node.x}, ${node.y})`}
|
| 206 |
+
onClick={() => setSelectedNode(node)}
|
| 207 |
+
className="cursor-pointer group"
|
| 208 |
+
>
|
| 209 |
+
{/* Glow circle on select */}
|
| 210 |
+
{isSelected && (
|
| 211 |
+
<circle r="22" fill="none" stroke={color} strokeWidth="2.5" opacity="0.8" className="animate-pulse" />
|
| 212 |
+
)}
|
| 213 |
+
|
| 214 |
+
{/* Node Circle */}
|
| 215 |
+
<circle
|
| 216 |
+
r={node.type === 'PROPOSAL' ? 14 : 12}
|
| 217 |
+
fill="#0D121F"
|
| 218 |
+
stroke={color}
|
| 219 |
+
strokeWidth={isSelected ? 3 : 2}
|
| 220 |
+
strokeDasharray={node.type === 'PROPOSAL' ? '3 2' : 'none'}
|
| 221 |
+
className="transition hover:scale-125"
|
| 222 |
+
/>
|
| 223 |
+
|
| 224 |
+
{/* Inner Type Indicator Dot */}
|
| 225 |
+
<circle r="4" fill={color} />
|
| 226 |
+
|
| 227 |
+
{/* Node Label Text */}
|
| 228 |
+
<text
|
| 229 |
+
y="24"
|
| 230 |
+
textAnchor="middle"
|
| 231 |
+
fill={isSelected ? '#F8FAFC' : '#94A3B8'}
|
| 232 |
+
fontSize="9.5"
|
| 233 |
+
fontFamily="Inter, sans-serif"
|
| 234 |
+
fontWeight={isSelected ? '600' : '400'}
|
| 235 |
+
className="select-none pointer-events-none"
|
| 236 |
+
>
|
| 237 |
+
{node.title.length > 22 ? node.title.slice(0, 20) + '...' : node.title}
|
| 238 |
+
</text>
|
| 239 |
+
</g>
|
| 240 |
+
);
|
| 241 |
+
})}
|
| 242 |
+
</svg>
|
| 243 |
|
| 244 |
+
{/* Color Legend Overlay */}
|
| 245 |
+
<div className="absolute bottom-4 left-4 bg-[#0E1422]/90 backdrop-blur-sm border border-slate-800 rounded-xl p-3 text-[11px] font-mono flex items-center space-x-3">
|
| 246 |
+
<span className="flex items-center space-x-1">
|
| 247 |
+
<span className="w-2.5 h-2.5 rounded-full bg-cyan-400"></span>
|
| 248 |
+
<span className="text-slate-300">Entity</span>
|
| 249 |
+
</span>
|
| 250 |
+
<span className="flex items-center space-x-1">
|
| 251 |
+
<span className="w-2.5 h-2.5 rounded-full bg-violet-400"></span>
|
| 252 |
+
<span className="text-slate-300">Claim</span>
|
| 253 |
+
</span>
|
| 254 |
+
<span className="flex items-center space-x-1">
|
| 255 |
+
<span className="w-2.5 h-2.5 rounded-full bg-emerald-400"></span>
|
| 256 |
+
<span className="text-slate-300">Metric</span>
|
| 257 |
+
</span>
|
| 258 |
+
<span className="flex items-center space-x-1">
|
| 259 |
+
<span className="w-2.5 h-2.5 rounded-full bg-pink-400 border border-dashed border-pink-300"></span>
|
| 260 |
+
<span className="text-slate-300">PR Delta</span>
|
| 261 |
+
</span>
|
| 262 |
</div>
|
| 263 |
</div>
|
| 264 |
|
| 265 |
+
{/* Selected Node Details Drawer (4 Cols) */}
|
| 266 |
+
<div className="lg:col-span-4 bg-[#0D121F] border border-slate-800 rounded-2xl p-6 space-y-4">
|
| 267 |
+
<div className="flex items-center space-x-2 pb-3 border-b border-slate-800 text-xs text-slate-400">
|
| 268 |
+
<Info className="w-4 h-4 text-emerald-400" />
|
| 269 |
+
<span className="font-semibold text-white">Node Provenance Inspector</span>
|
| 270 |
+
</div>
|
| 271 |
|
| 272 |
+
{selectedNode ? (
|
| 273 |
+
<div className="space-y-4 text-xs font-mono">
|
| 274 |
+
<div>
|
| 275 |
+
<span className="text-[10px] text-slate-500 uppercase tracking-wider block">Identifier</span>
|
| 276 |
+
<span className="text-slate-300 font-semibold">{selectedNode.id}</span>
|
| 277 |
+
</div>
|
|
|
|
|
|
|
| 278 |
|
| 279 |
+
<div>
|
| 280 |
+
<span className="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">Classification Type</span>
|
| 281 |
+
<span
|
| 282 |
+
className="px-2 py-0.5 rounded text-[11px] font-bold"
|
| 283 |
+
style={{
|
| 284 |
+
backgroundColor: `${getNodeColor(selectedNode.type)}20`,
|
| 285 |
+
color: getNodeColor(selectedNode.type),
|
| 286 |
+
border: `1px solid ${getNodeColor(selectedNode.type)}40`,
|
| 287 |
+
}}
|
| 288 |
+
>
|
| 289 |
+
{selectedNode.type}
|
| 290 |
+
</span>
|
| 291 |
+
</div>
|
| 292 |
|
| 293 |
+
<div>
|
| 294 |
+
<span className="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">Fact Statement</span>
|
| 295 |
+
<div className="bg-[#141B2D] p-3 rounded-lg border border-slate-800 text-slate-200 leading-relaxed font-sans text-xs">
|
| 296 |
+
{selectedNode.value || selectedNode.title}
|
| 297 |
+
</div>
|
| 298 |
+
</div>
|
| 299 |
|
| 300 |
+
<div className="grid grid-cols-2 gap-2 text-[11px] pt-1">
|
| 301 |
+
<div className="bg-[#141B2D] p-2 rounded border border-slate-800">
|
| 302 |
+
<span className="text-slate-500 block text-[10px]">Confidence</span>
|
| 303 |
+
<span className="text-emerald-400 font-bold">
|
| 304 |
+
{Math.round((selectedNode.confidence || 0.95) * 100)}%
|
| 305 |
+
</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
</div>
|
| 307 |
+
<div className="bg-[#141B2D] p-2 rounded border border-slate-800">
|
| 308 |
+
<span className="text-slate-500 block text-[10px]">State</span>
|
| 309 |
+
<span className="text-slate-300 font-semibold">{selectedNode.status || 'ACTIVE'}</span>
|
| 310 |
+
</div>
|
| 311 |
+
</div>
|
| 312 |
|
| 313 |
+
{selectedNode.filename && (
|
| 314 |
+
<div>
|
| 315 |
+
<span className="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">Source Document</span>
|
| 316 |
+
<div className="flex items-center space-x-1.5 text-slate-300 bg-[#141B2D] p-2 rounded border border-slate-800 text-[11px]">
|
| 317 |
+
<FileText className="w-3.5 h-3.5 text-slate-400" />
|
| 318 |
+
<span className="truncate">{selectedNode.filename}</span>
|
| 319 |
</div>
|
| 320 |
+
</div>
|
| 321 |
+
)}
|
| 322 |
+
</div>
|
| 323 |
+
) : (
|
| 324 |
+
<div className="py-16 text-center text-xs text-slate-500">
|
| 325 |
+
<Network className="w-8 h-8 text-slate-700 mx-auto mb-2" />
|
| 326 |
+
<p>Click on any node in the topology canvas to inspect its semantic provenance and connected relationships.</p>
|
| 327 |
+
</div>
|
| 328 |
+
)}
|
| 329 |
</div>
|
| 330 |
</div>
|
| 331 |
</div>
|
studio/src/components/Navbar.jsx
CHANGED
|
@@ -1,4 +1,23 @@
|
|
| 1 |
import React, { useState, useEffect } from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import { api } from '../api/client';
|
| 3 |
|
| 4 |
export default function Navbar({
|
|
@@ -17,7 +36,7 @@ export default function Navbar({
|
|
| 17 |
// Auth Modal State
|
| 18 |
const [showAuthModal, setShowAuthModal] = useState(false);
|
| 19 |
const [currentUser, setCurrentUser] = useState(null);
|
| 20 |
-
const [authMode, setAuthMode] = useState('demo');
|
| 21 |
const [loginEmail, setLoginEmail] = useState('');
|
| 22 |
const [loginPassword, setLoginPassword] = useState('');
|
| 23 |
const [customApiKey, setCustomApiKey] = useState('');
|
|
@@ -45,7 +64,6 @@ export default function Navbar({
|
|
| 45 |
setCurrentUser(user);
|
| 46 |
setShowAuthModal(false);
|
| 47 |
} catch (err) {
|
| 48 |
-
// Fallback local demo user
|
| 49 |
api.setApiKey('demo-evaluator-token');
|
| 50 |
setCurrentUser({ username: 'Demo Evaluator', email: 'demo@docweave.io' });
|
| 51 |
setShowAuthModal(false);
|
|
@@ -84,28 +102,70 @@ export default function Navbar({
|
|
| 84 |
|
| 85 |
const pendingCount = stats?.pending_proposals || 0;
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
return (
|
| 88 |
-
<header className="border-b border-slate-800 bg-[#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 90 |
-
<div className="flex items-center justify-between h-
|
| 91 |
-
{/* Logo & Workspace
|
| 92 |
-
<div className="flex items-center space-x-
|
| 93 |
<div className="flex items-center space-x-2">
|
| 94 |
-
<
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
</div>
|
| 99 |
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
| 102 |
<select
|
| 103 |
-
className="bg-
|
| 104 |
value={currentWorkspace?.id || ''}
|
| 105 |
onChange={(e) => onSelectWorkspace(e.target.value)}
|
| 106 |
>
|
| 107 |
{workspaces.map((w) => (
|
| 108 |
-
<option key={w.id} value={w.id}>
|
| 109 |
{w.name}
|
| 110 |
</option>
|
| 111 |
))}
|
|
@@ -113,40 +173,35 @@ export default function Navbar({
|
|
| 113 |
|
| 114 |
<button
|
| 115 |
onClick={() => setShowNewModal(true)}
|
| 116 |
-
className="text-
|
| 117 |
title="Create Workspace"
|
| 118 |
>
|
| 119 |
-
|
|
|
|
| 120 |
</button>
|
| 121 |
</div>
|
| 122 |
</div>
|
| 123 |
|
| 124 |
{/* Navigation Tabs */}
|
| 125 |
<nav className="flex space-x-1">
|
| 126 |
-
{
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
{ id: 'staging', label: 'Staging & Files' },
|
| 130 |
-
{ id: 'graph', label: 'Knowledge Graph' },
|
| 131 |
-
{ id: 'knowledge', label: 'Knowledge Register' },
|
| 132 |
-
{ id: 'rules', label: 'Policy Rules' },
|
| 133 |
-
{ id: 'audit', label: 'Audit Trail' },
|
| 134 |
-
].map((tab) => {
|
| 135 |
-
const isActive = activeTab === tab.id;
|
| 136 |
return (
|
| 137 |
<button
|
| 138 |
-
key={
|
| 139 |
-
onClick={() => onSelectTab(
|
| 140 |
-
className={`px-3 py-1.5 text-xs font-medium rounded-
|
| 141 |
isActive
|
| 142 |
-
? 'bg-emerald-500/15 text-emerald-
|
| 143 |
-
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/
|
| 144 |
}`}
|
| 145 |
>
|
| 146 |
-
<
|
| 147 |
-
{
|
| 148 |
-
|
| 149 |
-
|
|
|
|
| 150 |
</span>
|
| 151 |
)}
|
| 152 |
</button>
|
|
@@ -154,35 +209,29 @@ export default function Navbar({
|
|
| 154 |
})}
|
| 155 |
</nav>
|
| 156 |
|
| 157 |
-
{/*
|
| 158 |
<div className="flex items-center space-x-3">
|
| 159 |
-
{/* MCP Status Indicator */}
|
| 160 |
-
<div className="flex items-center space-x-2 px-2.5 py-1 rounded-full bg-emerald-950/40 border border-emerald-500/30 text-emerald-400 text-xs font-mono">
|
| 161 |
-
<span className="w-2 h-2 rounded-full bg-emerald-400 pulse-glow"></span>
|
| 162 |
-
<span>MCP: 29 Tools</span>
|
| 163 |
-
</div>
|
| 164 |
-
|
| 165 |
-
{/* Auth / User Control */}
|
| 166 |
{currentUser ? (
|
| 167 |
-
<div className="flex items-center space-x-2 bg-
|
| 168 |
<div className="w-5 h-5 rounded-full bg-emerald-600 text-white flex items-center justify-center font-bold text-[10px]">
|
| 169 |
{currentUser.username ? currentUser.username[0].toUpperCase() : 'U'}
|
| 170 |
</div>
|
| 171 |
-
<span className="text-slate-200 font-medium max-w-[
|
| 172 |
<button
|
| 173 |
onClick={handleLogout}
|
| 174 |
-
className="text-slate-500 hover:text-red-400
|
| 175 |
-
title="
|
| 176 |
>
|
| 177 |
-
|
| 178 |
</button>
|
| 179 |
</div>
|
| 180 |
) : (
|
| 181 |
<button
|
| 182 |
onClick={() => setShowAuthModal(true)}
|
| 183 |
-
className="px-3 py-1 text-xs font-
|
| 184 |
>
|
| 185 |
-
|
|
|
|
| 186 |
</button>
|
| 187 |
)}
|
| 188 |
</div>
|
|
@@ -191,37 +240,51 @@ export default function Navbar({
|
|
| 191 |
|
| 192 |
{/* Auth / API Key Modal */}
|
| 193 |
{showAuthModal && (
|
| 194 |
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/
|
| 195 |
-
<div className="bg-[#
|
| 196 |
<div className="flex justify-between items-center mb-4">
|
| 197 |
-
<
|
| 198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
</div>
|
| 200 |
|
| 201 |
-
{/* Tabs */}
|
| 202 |
<div className="flex border-b border-slate-800 mb-4 text-xs font-medium">
|
| 203 |
<button
|
| 204 |
onClick={() => setAuthMode('demo')}
|
| 205 |
-
className={`pb-2 px-3
|
|
|
|
|
|
|
| 206 |
>
|
| 207 |
-
|
|
|
|
| 208 |
</button>
|
| 209 |
<button
|
| 210 |
onClick={() => setAuthMode('login')}
|
| 211 |
-
className={`pb-2 px-3
|
|
|
|
|
|
|
| 212 |
>
|
| 213 |
-
|
|
|
|
| 214 |
</button>
|
| 215 |
<button
|
| 216 |
onClick={() => setAuthMode('apikey')}
|
| 217 |
-
className={`pb-2 px-3
|
|
|
|
|
|
|
| 218 |
>
|
| 219 |
-
|
|
|
|
| 220 |
</button>
|
| 221 |
</div>
|
| 222 |
|
| 223 |
{authError && (
|
| 224 |
-
<div className="p-2 mb-3 rounded bg-red-950/
|
| 225 |
{authError}
|
| 226 |
</div>
|
| 227 |
)}
|
|
@@ -229,14 +292,15 @@ export default function Navbar({
|
|
| 229 |
{authMode === 'demo' && (
|
| 230 |
<div>
|
| 231 |
<p className="text-xs text-slate-300 mb-4 leading-relaxed">
|
| 232 |
-
|
| 233 |
</p>
|
| 234 |
<button
|
| 235 |
onClick={handleDemoLogin}
|
| 236 |
disabled={authLoading}
|
| 237 |
-
className="w-full py-2 px-4 rounded-lg bg-emerald-600 hover:
|
| 238 |
>
|
| 239 |
-
|
|
|
|
| 240 |
</button>
|
| 241 |
</div>
|
| 242 |
)}
|
|
@@ -244,14 +308,14 @@ export default function Navbar({
|
|
| 244 |
{authMode === 'login' && (
|
| 245 |
<form onSubmit={handleEmailLogin} className="space-y-3">
|
| 246 |
<div>
|
| 247 |
-
<label className="block text-xs font-medium text-slate-400 mb-1">Email</label>
|
| 248 |
<input
|
| 249 |
type="email"
|
| 250 |
required
|
| 251 |
-
placeholder="
|
| 252 |
value={loginEmail}
|
| 253 |
onChange={(e) => setLoginEmail(e.target.value)}
|
| 254 |
-
className="w-full bg-[#
|
| 255 |
/>
|
| 256 |
</div>
|
| 257 |
<div>
|
|
@@ -262,15 +326,15 @@ export default function Navbar({
|
|
| 262 |
placeholder="••••••••"
|
| 263 |
value={loginPassword}
|
| 264 |
onChange={(e) => setLoginPassword(e.target.value)}
|
| 265 |
-
className="w-full bg-[#
|
| 266 |
/>
|
| 267 |
</div>
|
| 268 |
<button
|
| 269 |
type="submit"
|
| 270 |
disabled={authLoading}
|
| 271 |
-
className="w-full py-2 px-4 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-medium text-
|
| 272 |
>
|
| 273 |
-
{authLoading ? 'Signing In...' : 'Sign In'}
|
| 274 |
</button>
|
| 275 |
</form>
|
| 276 |
)}
|
|
@@ -282,20 +346,20 @@ export default function Navbar({
|
|
| 282 |
<input
|
| 283 |
type="text"
|
| 284 |
required
|
| 285 |
-
placeholder="
|
| 286 |
value={customApiKey}
|
| 287 |
onChange={(e) => setCustomApiKey(e.target.value)}
|
| 288 |
-
className="w-full bg-[#
|
| 289 |
/>
|
| 290 |
</div>
|
| 291 |
<p className="text-[11px] text-slate-400 leading-normal">
|
| 292 |
-
|
| 293 |
</p>
|
| 294 |
<button
|
| 295 |
type="submit"
|
| 296 |
-
className="w-full py-2 px-4 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-medium text-
|
| 297 |
>
|
| 298 |
-
Save &
|
| 299 |
</button>
|
| 300 |
</form>
|
| 301 |
)}
|
|
@@ -305,19 +369,19 @@ export default function Navbar({
|
|
| 305 |
|
| 306 |
{/* New Workspace Modal */}
|
| 307 |
{showNewModal && (
|
| 308 |
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/
|
| 309 |
-
<div className="bg-[#
|
| 310 |
-
<h3 className="text-
|
| 311 |
<form onSubmit={handleCreate}>
|
| 312 |
<div className="mb-4">
|
| 313 |
-
<label className="block text-xs font-medium text-slate-400 mb-1">Workspace Name</label>
|
| 314 |
<input
|
| 315 |
type="text"
|
| 316 |
required
|
| 317 |
-
placeholder="e.g. Clinical
|
| 318 |
value={newWsName}
|
| 319 |
onChange={(e) => setNewWsName(e.target.value)}
|
| 320 |
-
className="w-full bg-[#
|
| 321 |
/>
|
| 322 |
</div>
|
| 323 |
<div className="mb-6">
|
|
@@ -327,22 +391,22 @@ export default function Navbar({
|
|
| 327 |
value={newWsDesc}
|
| 328 |
onChange={(e) => setNewWsDesc(e.target.value)}
|
| 329 |
rows="3"
|
| 330 |
-
className="w-full bg-[#
|
| 331 |
></textarea>
|
| 332 |
</div>
|
| 333 |
<div className="flex justify-end space-x-3">
|
| 334 |
<button
|
| 335 |
type="button"
|
| 336 |
onClick={() => setShowNewModal(false)}
|
| 337 |
-
className="px-4 py-2 text-
|
| 338 |
>
|
| 339 |
Cancel
|
| 340 |
</button>
|
| 341 |
<button
|
| 342 |
type="submit"
|
| 343 |
-
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-
|
| 344 |
>
|
| 345 |
-
|
| 346 |
</button>
|
| 347 |
</div>
|
| 348 |
</form>
|
|
|
|
| 1 |
import React, { useState, useEffect } from 'react';
|
| 2 |
+
import {
|
| 3 |
+
GitPullRequest,
|
| 4 |
+
FileDiff,
|
| 5 |
+
Network,
|
| 6 |
+
Database,
|
| 7 |
+
ShieldCheck,
|
| 8 |
+
History,
|
| 9 |
+
FolderGit2,
|
| 10 |
+
Plus,
|
| 11 |
+
Search,
|
| 12 |
+
CheckCircle2,
|
| 13 |
+
Key,
|
| 14 |
+
LogOut,
|
| 15 |
+
User,
|
| 16 |
+
Zap,
|
| 17 |
+
ExternalLink,
|
| 18 |
+
ChevronDown,
|
| 19 |
+
Layers
|
| 20 |
+
} from 'lucide-react';
|
| 21 |
import { api } from '../api/client';
|
| 22 |
|
| 23 |
export default function Navbar({
|
|
|
|
| 36 |
// Auth Modal State
|
| 37 |
const [showAuthModal, setShowAuthModal] = useState(false);
|
| 38 |
const [currentUser, setCurrentUser] = useState(null);
|
| 39 |
+
const [authMode, setAuthMode] = useState('demo');
|
| 40 |
const [loginEmail, setLoginEmail] = useState('');
|
| 41 |
const [loginPassword, setLoginPassword] = useState('');
|
| 42 |
const [customApiKey, setCustomApiKey] = useState('');
|
|
|
|
| 64 |
setCurrentUser(user);
|
| 65 |
setShowAuthModal(false);
|
| 66 |
} catch (err) {
|
|
|
|
| 67 |
api.setApiKey('demo-evaluator-token');
|
| 68 |
setCurrentUser({ username: 'Demo Evaluator', email: 'demo@docweave.io' });
|
| 69 |
setShowAuthModal(false);
|
|
|
|
| 102 |
|
| 103 |
const pendingCount = stats?.pending_proposals || 0;
|
| 104 |
|
| 105 |
+
const navItems = [
|
| 106 |
+
{ id: 'diff', label: 'Semantic Diff', icon: FileDiff },
|
| 107 |
+
{ id: 'prs', label: 'Knowledge PRs', icon: GitPullRequest, badge: pendingCount > 0 ? pendingCount : null },
|
| 108 |
+
{ id: 'staging', label: 'Staging & Files', icon: Layers },
|
| 109 |
+
{ id: 'graph', label: 'Topology Graph', icon: Network },
|
| 110 |
+
{ id: 'knowledge', label: 'Knowledge Register', icon: Database },
|
| 111 |
+
{ id: 'rules', label: 'CI Policy Rules', icon: ShieldCheck },
|
| 112 |
+
{ id: 'audit', label: 'Git Audit Log', icon: History },
|
| 113 |
+
];
|
| 114 |
+
|
| 115 |
return (
|
| 116 |
+
<header className="border-b border-slate-800/80 bg-[#070A10]/95 backdrop-blur-md sticky top-0 z-50">
|
| 117 |
+
{/* Top Meta Bar */}
|
| 118 |
+
<div className="border-b border-slate-900 px-4 sm:px-6 lg:px-8 py-1.5 flex items-center justify-between text-[11px] text-slate-400 bg-[#05070C]">
|
| 119 |
+
<div className="flex items-center space-x-3">
|
| 120 |
+
<span className="flex items-center space-x-1.5 font-medium text-slate-300">
|
| 121 |
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
|
| 122 |
+
<span>DiffWeave Knowledge GitOps Platform</span>
|
| 123 |
+
</span>
|
| 124 |
+
<span className="text-slate-600">|</span>
|
| 125 |
+
<span className="text-slate-400 font-mono">v0.1.0-prod</span>
|
| 126 |
+
<span className="text-slate-600">|</span>
|
| 127 |
+
<span className="text-slate-400 flex items-center space-x-1">
|
| 128 |
+
<span>Powered by DocWeave MCP</span>
|
| 129 |
+
</span>
|
| 130 |
+
</div>
|
| 131 |
+
<div className="flex items-center space-x-4">
|
| 132 |
+
<div className="flex items-center space-x-1.5 text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20 font-mono">
|
| 133 |
+
<CheckCircle2 className="w-3 h-3 text-emerald-400" />
|
| 134 |
+
<span>FastMCP: 29 Tools Active</span>
|
| 135 |
+
</div>
|
| 136 |
+
<span className="text-slate-500">Latency: 14ms</span>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
|
| 140 |
+
{/* Main Navigation Bar */}
|
| 141 |
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 142 |
+
<div className="flex items-center justify-between h-14">
|
| 143 |
+
{/* Logo & Workspace Breadcrumbs */}
|
| 144 |
+
<div className="flex items-center space-x-4">
|
| 145 |
<div className="flex items-center space-x-2">
|
| 146 |
+
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-emerald-500 to-teal-700 flex items-center justify-center shadow-lg shadow-emerald-500/20">
|
| 147 |
+
<span className="text-white font-bold text-base">🧶</span>
|
| 148 |
+
</div>
|
| 149 |
+
<div>
|
| 150 |
+
<span className="text-base font-bold text-white tracking-tight">DiffWeave</span>
|
| 151 |
+
<span className="text-[10px] ml-1.5 px-1.5 py-0.5 rounded bg-slate-800 text-slate-300 border border-slate-700 font-mono">
|
| 152 |
+
Studio
|
| 153 |
+
</span>
|
| 154 |
+
</div>
|
| 155 |
</div>
|
| 156 |
|
| 157 |
+
<span className="text-slate-700">/</span>
|
| 158 |
+
|
| 159 |
+
{/* Workspace Selector */}
|
| 160 |
+
<div className="flex items-center space-x-2 bg-[#0E1422] border border-slate-800 rounded-lg p-1">
|
| 161 |
+
<FolderGit2 className="w-3.5 h-3.5 text-slate-400 ml-2" />
|
| 162 |
<select
|
| 163 |
+
className="bg-transparent text-slate-200 text-xs rounded px-2 py-1 focus:outline-none font-medium cursor-pointer"
|
| 164 |
value={currentWorkspace?.id || ''}
|
| 165 |
onChange={(e) => onSelectWorkspace(e.target.value)}
|
| 166 |
>
|
| 167 |
{workspaces.map((w) => (
|
| 168 |
+
<option key={w.id} value={w.id} className="bg-[#0E1422] text-slate-200">
|
| 169 |
{w.name}
|
| 170 |
</option>
|
| 171 |
))}
|
|
|
|
| 173 |
|
| 174 |
<button
|
| 175 |
onClick={() => setShowNewModal(true)}
|
| 176 |
+
className="text-[11px] text-slate-300 hover:text-white px-2 py-1 rounded bg-slate-800 hover:bg-slate-700 transition flex items-center space-x-1"
|
| 177 |
title="Create Workspace"
|
| 178 |
>
|
| 179 |
+
<Plus className="w-3 h-3" />
|
| 180 |
+
<span>New</span>
|
| 181 |
</button>
|
| 182 |
</div>
|
| 183 |
</div>
|
| 184 |
|
| 185 |
{/* Navigation Tabs */}
|
| 186 |
<nav className="flex space-x-1">
|
| 187 |
+
{navItems.map((item) => {
|
| 188 |
+
const Icon = item.icon;
|
| 189 |
+
const isActive = activeTab === item.id;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
return (
|
| 191 |
<button
|
| 192 |
+
key={item.id}
|
| 193 |
+
onClick={() => onSelectTab(item.id)}
|
| 194 |
+
className={`px-3 py-1.5 text-xs font-medium rounded-lg transition flex items-center space-x-2 ${
|
| 195 |
isActive
|
| 196 |
+
? 'bg-emerald-500/15 text-emerald-300 border border-emerald-500/30 shadow-sm shadow-emerald-500/10'
|
| 197 |
+
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/60'
|
| 198 |
}`}
|
| 199 |
>
|
| 200 |
+
<Icon className={`w-3.5 h-3.5 ${isActive ? 'text-emerald-400' : 'text-slate-400'}`} />
|
| 201 |
+
<span>{item.label}</span>
|
| 202 |
+
{item.badge && (
|
| 203 |
+
<span className="px-1.5 py-0.2 rounded-full text-[10px] bg-amber-500/20 text-amber-300 font-mono font-semibold border border-amber-500/30">
|
| 204 |
+
{item.badge}
|
| 205 |
</span>
|
| 206 |
)}
|
| 207 |
</button>
|
|
|
|
| 209 |
})}
|
| 210 |
</nav>
|
| 211 |
|
| 212 |
+
{/* User Session & Auth Action */}
|
| 213 |
<div className="flex items-center space-x-3">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
{currentUser ? (
|
| 215 |
+
<div className="flex items-center space-x-2 bg-[#0E1422] border border-slate-800 px-2.5 py-1 rounded-lg text-xs">
|
| 216 |
<div className="w-5 h-5 rounded-full bg-emerald-600 text-white flex items-center justify-center font-bold text-[10px]">
|
| 217 |
{currentUser.username ? currentUser.username[0].toUpperCase() : 'U'}
|
| 218 |
</div>
|
| 219 |
+
<span className="text-slate-200 font-medium max-w-[110px] truncate">{currentUser.username}</span>
|
| 220 |
<button
|
| 221 |
onClick={handleLogout}
|
| 222 |
+
className="text-slate-500 hover:text-red-400 p-0.5 rounded transition"
|
| 223 |
+
title="Sign Out"
|
| 224 |
>
|
| 225 |
+
<LogOut className="w-3.5 h-3.5" />
|
| 226 |
</button>
|
| 227 |
</div>
|
| 228 |
) : (
|
| 229 |
<button
|
| 230 |
onClick={() => setShowAuthModal(true)}
|
| 231 |
+
className="px-3 py-1.5 text-xs font-semibold rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white transition shadow-sm shadow-emerald-600/30 flex items-center space-x-1.5"
|
| 232 |
>
|
| 233 |
+
<Key className="w-3.5 h-3.5" />
|
| 234 |
+
<span>Connect / API Key</span>
|
| 235 |
</button>
|
| 236 |
)}
|
| 237 |
</div>
|
|
|
|
| 240 |
|
| 241 |
{/* Auth / API Key Modal */}
|
| 242 |
{showAuthModal && (
|
| 243 |
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-md">
|
| 244 |
+
<div className="bg-[#0D121F] border border-slate-700/80 p-6 rounded-2xl w-full max-w-md shadow-2xl">
|
| 245 |
<div className="flex justify-between items-center mb-4">
|
| 246 |
+
<div className="flex items-center space-x-2">
|
| 247 |
+
<div className="w-6 h-6 rounded bg-emerald-500/20 text-emerald-400 flex items-center justify-center">
|
| 248 |
+
<Key className="w-3.5 h-3.5" />
|
| 249 |
+
</div>
|
| 250 |
+
<h3 className="text-base font-semibold text-white">Connect DocWeave Engine</h3>
|
| 251 |
+
</div>
|
| 252 |
+
<button onClick={() => setShowAuthModal(false)} className="text-slate-400 hover:text-white text-sm">✕</button>
|
| 253 |
</div>
|
| 254 |
|
| 255 |
+
{/* Mode Tabs */}
|
| 256 |
<div className="flex border-b border-slate-800 mb-4 text-xs font-medium">
|
| 257 |
<button
|
| 258 |
onClick={() => setAuthMode('demo')}
|
| 259 |
+
className={`pb-2.5 px-3 flex items-center space-x-1.5 transition ${
|
| 260 |
+
authMode === 'demo' ? 'border-b-2 border-emerald-400 text-emerald-400' : 'text-slate-400 hover:text-slate-300'
|
| 261 |
+
}`}
|
| 262 |
>
|
| 263 |
+
<Zap className="w-3.5 h-3.5" />
|
| 264 |
+
<span>1-Click Demo</span>
|
| 265 |
</button>
|
| 266 |
<button
|
| 267 |
onClick={() => setAuthMode('login')}
|
| 268 |
+
className={`pb-2.5 px-3 flex items-center space-x-1.5 transition ${
|
| 269 |
+
authMode === 'login' ? 'border-b-2 border-emerald-400 text-emerald-400' : 'text-slate-400 hover:text-slate-300'
|
| 270 |
+
}`}
|
| 271 |
>
|
| 272 |
+
<User className="w-3.5 h-3.5" />
|
| 273 |
+
<span>Email Sign-In</span>
|
| 274 |
</button>
|
| 275 |
<button
|
| 276 |
onClick={() => setAuthMode('apikey')}
|
| 277 |
+
className={`pb-2.5 px-3 flex items-center space-x-1.5 transition ${
|
| 278 |
+
authMode === 'apikey' ? 'border-b-2 border-emerald-400 text-emerald-400' : 'text-slate-400 hover:text-slate-300'
|
| 279 |
+
}`}
|
| 280 |
>
|
| 281 |
+
<Key className="w-3.5 h-3.5" />
|
| 282 |
+
<span>API Key / PAT</span>
|
| 283 |
</button>
|
| 284 |
</div>
|
| 285 |
|
| 286 |
{authError && (
|
| 287 |
+
<div className="p-2.5 mb-3 rounded-lg bg-red-950/40 border border-red-500/30 text-red-300 text-xs">
|
| 288 |
{authError}
|
| 289 |
</div>
|
| 290 |
)}
|
|
|
|
| 292 |
{authMode === 'demo' && (
|
| 293 |
<div>
|
| 294 |
<p className="text-xs text-slate-300 mb-4 leading-relaxed">
|
| 295 |
+
Enter immediately with the <strong className="text-white">Demo Evaluator</strong> account. Full access to diffing, PR reviews, and AI knowledge committing without registration.
|
| 296 |
</p>
|
| 297 |
<button
|
| 298 |
onClick={handleDemoLogin}
|
| 299 |
disabled={authLoading}
|
| 300 |
+
className="w-full py-2.5 px-4 rounded-lg bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 text-white font-medium text-xs transition flex items-center justify-center space-x-2 shadow-lg shadow-emerald-600/20"
|
| 301 |
>
|
| 302 |
+
<Zap className="w-4 h-4" />
|
| 303 |
+
<span>{authLoading ? 'Connecting...' : 'Launch Demo Evaluator Session'}</span>
|
| 304 |
</button>
|
| 305 |
</div>
|
| 306 |
)}
|
|
|
|
| 308 |
{authMode === 'login' && (
|
| 309 |
<form onSubmit={handleEmailLogin} className="space-y-3">
|
| 310 |
<div>
|
| 311 |
+
<label className="block text-xs font-medium text-slate-400 mb-1">DocWeave Account Email</label>
|
| 312 |
<input
|
| 313 |
type="email"
|
| 314 |
required
|
| 315 |
+
placeholder="evaluator@docweave.io"
|
| 316 |
value={loginEmail}
|
| 317 |
onChange={(e) => setLoginEmail(e.target.value)}
|
| 318 |
+
className="w-full bg-[#141B2D] border border-slate-700/80 rounded-lg px-3 py-2 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 319 |
/>
|
| 320 |
</div>
|
| 321 |
<div>
|
|
|
|
| 326 |
placeholder="••••••••"
|
| 327 |
value={loginPassword}
|
| 328 |
onChange={(e) => setLoginPassword(e.target.value)}
|
| 329 |
+
className="w-full bg-[#141B2D] border border-slate-700/80 rounded-lg px-3 py-2 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 330 |
/>
|
| 331 |
</div>
|
| 332 |
<button
|
| 333 |
type="submit"
|
| 334 |
disabled={authLoading}
|
| 335 |
+
className="w-full py-2.5 px-4 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-medium text-xs transition mt-2"
|
| 336 |
>
|
| 337 |
+
{authLoading ? 'Signing In...' : 'Sign In with DocWeave'}
|
| 338 |
</button>
|
| 339 |
</form>
|
| 340 |
)}
|
|
|
|
| 346 |
<input
|
| 347 |
type="text"
|
| 348 |
required
|
| 349 |
+
placeholder="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
| 350 |
value={customApiKey}
|
| 351 |
onChange={(e) => setCustomApiKey(e.target.value)}
|
| 352 |
+
className="w-full bg-[#141B2D] border border-slate-700/80 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 353 |
/>
|
| 354 |
</div>
|
| 355 |
<p className="text-[11px] text-slate-400 leading-normal">
|
| 356 |
+
Connects DiffWeave Studio directly to your remote DocWeave MCP server endpoint with authenticated Bearer permissions.
|
| 357 |
</p>
|
| 358 |
<button
|
| 359 |
type="submit"
|
| 360 |
+
className="w-full py-2.5 px-4 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-medium text-xs transition mt-2"
|
| 361 |
>
|
| 362 |
+
Save & Bind API Token
|
| 363 |
</button>
|
| 364 |
</form>
|
| 365 |
)}
|
|
|
|
| 369 |
|
| 370 |
{/* New Workspace Modal */}
|
| 371 |
{showNewModal && (
|
| 372 |
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-md">
|
| 373 |
+
<div className="bg-[#0D121F] border border-slate-700/80 p-6 rounded-2xl w-full max-w-md shadow-2xl">
|
| 374 |
+
<h3 className="text-base font-semibold text-white mb-4">Initialize Knowledge Workspace</h3>
|
| 375 |
<form onSubmit={handleCreate}>
|
| 376 |
<div className="mb-4">
|
| 377 |
+
<label className="block text-xs font-medium text-slate-400 mb-1">Workspace Identifier Name</label>
|
| 378 |
<input
|
| 379 |
type="text"
|
| 380 |
required
|
| 381 |
+
placeholder="e.g. Oncology Clinical Protocol 2026"
|
| 382 |
value={newWsName}
|
| 383 |
onChange={(e) => setNewWsName(e.target.value)}
|
| 384 |
+
className="w-full bg-[#141B2D] border border-slate-700/80 rounded-lg px-3 py-2 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 385 |
/>
|
| 386 |
</div>
|
| 387 |
<div className="mb-6">
|
|
|
|
| 391 |
value={newWsDesc}
|
| 392 |
onChange={(e) => setNewWsDesc(e.target.value)}
|
| 393 |
rows="3"
|
| 394 |
+
className="w-full bg-[#141B2D] border border-slate-700/80 rounded-lg px-3 py-2 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 395 |
></textarea>
|
| 396 |
</div>
|
| 397 |
<div className="flex justify-end space-x-3">
|
| 398 |
<button
|
| 399 |
type="button"
|
| 400 |
onClick={() => setShowNewModal(false)}
|
| 401 |
+
className="px-4 py-2 text-xs text-slate-400 hover:text-white transition"
|
| 402 |
>
|
| 403 |
Cancel
|
| 404 |
</button>
|
| 405 |
<button
|
| 406 |
type="submit"
|
| 407 |
+
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-semibold rounded-lg transition"
|
| 408 |
>
|
| 409 |
+
Initialize Workspace
|
| 410 |
</button>
|
| 411 |
</div>
|
| 412 |
</form>
|
studio/src/components/PRReviewDeck.jsx
CHANGED
|
@@ -1,175 +1,276 @@
|
|
| 1 |
import React, { useState } from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
export default function PRReviewDeck({ proposals,
|
| 4 |
-
const [
|
| 5 |
-
const [
|
| 6 |
-
const [
|
| 7 |
-
|
| 8 |
-
const handleSingle = async (proposalId, decision) => {
|
| 9 |
-
setActingId(proposalId);
|
| 10 |
-
try {
|
| 11 |
-
const comment = commentMap[proposalId] || null;
|
| 12 |
-
await onReview(proposalId, decision, comment);
|
| 13 |
-
} finally {
|
| 14 |
-
setActingId(null);
|
| 15 |
-
}
|
| 16 |
-
};
|
| 17 |
-
|
| 18 |
-
const handleBatch = async (decision) => {
|
| 19 |
-
setBatchActing(true);
|
| 20 |
-
try {
|
| 21 |
-
await onBatchReview(decision);
|
| 22 |
-
} finally {
|
| 23 |
-
setBatchActing(false);
|
| 24 |
-
}
|
| 25 |
-
};
|
| 26 |
|
| 27 |
if (loading) {
|
| 28 |
return (
|
| 29 |
-
<div className="flex items-center justify-center
|
| 30 |
-
<
|
|
|
|
| 31 |
</div>
|
| 32 |
);
|
| 33 |
}
|
| 34 |
|
| 35 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
return (
|
| 38 |
<div className="space-y-6">
|
| 39 |
-
{/* Header
|
| 40 |
-
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4
|
| 41 |
-
<div>
|
| 42 |
-
<
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
</h2>
|
| 48 |
-
<p className="text-xs text-slate-400 mt-1">
|
| 49 |
-
Human approval gate: unreviewed proposals never enter Master Truth without explicit sign-off.
|
| 50 |
-
</p>
|
| 51 |
</div>
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
<button
|
| 56 |
-
disabled={batchActing}
|
| 57 |
-
onClick={() => handleBatch('APPROVED')}
|
| 58 |
-
className="px-4 py-2 rounded-lg text-xs font-semibold bg-emerald-500 hover:bg-emerald-400 text-black transition shadow-lg disabled:opacity-50"
|
| 59 |
-
>
|
| 60 |
-
✓ Approve & Merge All ({pendingList.length})
|
| 61 |
-
</button>
|
| 62 |
<button
|
| 63 |
-
|
| 64 |
-
onClick={() =>
|
| 65 |
-
className=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
>
|
| 67 |
-
|
| 68 |
</button>
|
| 69 |
-
|
| 70 |
-
|
| 71 |
</div>
|
| 72 |
|
| 73 |
-
{
|
| 74 |
-
<div className="
|
| 75 |
-
<
|
| 76 |
-
|
| 77 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
</div>
|
| 79 |
) : (
|
| 80 |
-
<div className="grid grid-cols-1 gap-
|
| 81 |
-
{
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
<
|
| 100 |
-
{
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
</div>
|
| 104 |
|
| 105 |
-
<
|
| 106 |
-
|
| 107 |
-
<
|
|
|
|
|
|
|
|
|
|
| 108 |
</div>
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
{
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
</
|
| 123 |
-
<p className="text-sm text-slate-100 font-medium font-mono">"{val}"</p>
|
| 124 |
-
</div>
|
| 125 |
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
|
|
|
|
|
|
| 132 |
</div>
|
| 133 |
|
| 134 |
-
{/*
|
| 135 |
-
<div className="
|
| 136 |
-
<
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
/>
|
| 145 |
-
|
| 146 |
-
<div className="
|
| 147 |
-
<
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
>
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
<
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
>
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
onClick={() => handleSingle(p.proposal_id, 'APPROVED')}
|
| 164 |
-
className="px-4 py-1.5 rounded-lg text-xs font-semibold text-black bg-emerald-400 hover:bg-emerald-300 transition shadow-sm"
|
| 165 |
-
>
|
| 166 |
-
✓ Approve & Commit
|
| 167 |
-
</button>
|
| 168 |
</div>
|
| 169 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
</div>
|
| 171 |
-
)
|
| 172 |
-
|
| 173 |
</div>
|
| 174 |
)}
|
| 175 |
</div>
|
|
|
|
| 1 |
import React, { useState } from 'react';
|
| 2 |
+
import {
|
| 3 |
+
GitPullRequest,
|
| 4 |
+
CheckCircle2,
|
| 5 |
+
XCircle,
|
| 6 |
+
Archive,
|
| 7 |
+
AlertCircle,
|
| 8 |
+
Clock,
|
| 9 |
+
User,
|
| 10 |
+
ShieldCheck,
|
| 11 |
+
ChevronRight,
|
| 12 |
+
Sparkles,
|
| 13 |
+
Quote,
|
| 14 |
+
MessageSquare,
|
| 15 |
+
FileText,
|
| 16 |
+
Filter
|
| 17 |
+
} from 'lucide-react';
|
| 18 |
+
import confetti from 'canvas-confetti';
|
| 19 |
|
| 20 |
+
export default function PRReviewDeck({ proposals, onReviewProposal, onBatchReview, loading }) {
|
| 21 |
+
const [selectedProposal, setSelectedProposal] = useState(null);
|
| 22 |
+
const [filterStatus, setFilterStatus] = useState('PENDING');
|
| 23 |
+
const [reviewComment, setReviewComment] = useState('');
|
| 24 |
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
if (loading) {
|
| 27 |
return (
|
| 28 |
+
<div className="flex flex-col items-center justify-center py-24 space-y-4">
|
| 29 |
+
<div className="w-8 h-8 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin"></div>
|
| 30 |
+
<p className="text-xs text-slate-400 font-mono">Loading Knowledge Pull Requests...</p>
|
| 31 |
</div>
|
| 32 |
);
|
| 33 |
}
|
| 34 |
|
| 35 |
+
const filteredProposals = proposals.filter((p) => {
|
| 36 |
+
if (filterStatus === 'ALL') return true;
|
| 37 |
+
return p.status === filterStatus;
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
const activePR = selectedProposal || filteredProposals[0] || null;
|
| 41 |
+
|
| 42 |
+
const handleReview = async (decision) => {
|
| 43 |
+
if (!activePR) return;
|
| 44 |
+
setIsSubmitting(true);
|
| 45 |
+
try {
|
| 46 |
+
await onReviewProposal(activePR.id, decision, reviewComment);
|
| 47 |
+
setReviewComment('');
|
| 48 |
+
if (decision === 'APPROVED') {
|
| 49 |
+
confetti({
|
| 50 |
+
particleCount: 70,
|
| 51 |
+
spread: 70,
|
| 52 |
+
origin: { y: 0.6 },
|
| 53 |
+
});
|
| 54 |
+
}
|
| 55 |
+
// Select next PR
|
| 56 |
+
const next = filteredProposals.find((p) => p.id !== activePR.id);
|
| 57 |
+
setSelectedProposal(next || null);
|
| 58 |
+
} finally {
|
| 59 |
+
setIsSubmitting(false);
|
| 60 |
+
}
|
| 61 |
+
};
|
| 62 |
|
| 63 |
return (
|
| 64 |
<div className="space-y-6">
|
| 65 |
+
{/* Header and Filter Controls */}
|
| 66 |
+
<div className="bg-[#0C101A] border border-slate-800/90 rounded-xl p-4 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
| 67 |
+
<div className="flex items-center space-x-3">
|
| 68 |
+
<GitPullRequest className="w-5 h-5 text-emerald-400" />
|
| 69 |
+
<h2 className="text-sm font-semibold text-white">Knowledge Pull Requests (PRs)</h2>
|
| 70 |
+
<span className="text-xs px-2 py-0.5 rounded bg-slate-800 text-slate-300 font-mono">
|
| 71 |
+
{filteredProposals.length} open
|
| 72 |
+
</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
</div>
|
| 74 |
|
| 75 |
+
<div className="flex items-center space-x-2 text-xs">
|
| 76 |
+
{['PENDING', 'APPROVED', 'REJECTED', 'ALL'].map((status) => (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
<button
|
| 78 |
+
key={status}
|
| 79 |
+
onClick={() => setFilterStatus(status)}
|
| 80 |
+
className={`px-3 py-1.5 rounded-lg font-medium transition ${
|
| 81 |
+
filterStatus === status
|
| 82 |
+
? 'bg-slate-700 text-white font-semibold'
|
| 83 |
+
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/60'
|
| 84 |
+
}`}
|
| 85 |
>
|
| 86 |
+
{status}
|
| 87 |
</button>
|
| 88 |
+
))}
|
| 89 |
+
</div>
|
| 90 |
</div>
|
| 91 |
|
| 92 |
+
{filteredProposals.length === 0 ? (
|
| 93 |
+
<div className="bg-[#0C101A] border border-slate-800/80 rounded-2xl p-16 text-center shadow-inner">
|
| 94 |
+
<div className="w-12 h-12 rounded-full bg-slate-800 text-slate-400 flex items-center justify-center mx-auto mb-4">
|
| 95 |
+
<CheckCircle2 className="w-6 h-6" />
|
| 96 |
+
</div>
|
| 97 |
+
<h3 className="text-base font-semibold text-white mb-1">All PRs Reviewed</h3>
|
| 98 |
+
<p className="text-xs text-slate-400 max-w-sm mx-auto">
|
| 99 |
+
There are currently no proposals matching filter <strong className="text-slate-300">{filterStatus}</strong>.
|
| 100 |
+
</p>
|
| 101 |
</div>
|
| 102 |
) : (
|
| 103 |
+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
| 104 |
+
{/* PR List Column (Left 5 Cols) */}
|
| 105 |
+
<div className="lg:col-span-5 space-y-2">
|
| 106 |
+
{filteredProposals.map((pr) => {
|
| 107 |
+
const isSelected = activePR?.id === pr.id;
|
| 108 |
+
const changes = pr.proposed_changes || {};
|
| 109 |
+
const conf = Math.round((changes.confidence || (changes.proposed?.confidence) || 0.95) * 100);
|
| 110 |
+
|
| 111 |
+
return (
|
| 112 |
+
<div
|
| 113 |
+
key={pr.id}
|
| 114 |
+
onClick={() => setSelectedProposal(pr)}
|
| 115 |
+
className={`p-3.5 rounded-xl border transition cursor-pointer text-xs ${
|
| 116 |
+
isSelected
|
| 117 |
+
? 'bg-[#121828] border-emerald-500/50 shadow-md shadow-emerald-500/5'
|
| 118 |
+
: 'bg-[#0D121F] border-slate-800 hover:border-slate-700'
|
| 119 |
+
}`}
|
| 120 |
+
>
|
| 121 |
+
<div className="flex items-center justify-between mb-1.5">
|
| 122 |
+
<div className="flex items-center space-x-2">
|
| 123 |
+
<GitPullRequest className={`w-3.5 h-3.5 ${isSelected ? 'text-emerald-400' : 'text-slate-400'}`} />
|
| 124 |
+
<span className="font-mono text-[11px] text-slate-400">PR-{pr.id.slice(0, 6)}</span>
|
| 125 |
+
<span
|
| 126 |
+
className={`text-[10px] font-mono px-1.5 py-0.2 rounded font-semibold ${
|
| 127 |
+
pr.status === 'PENDING'
|
| 128 |
+
? 'bg-amber-500/10 text-amber-300 border border-amber-500/20'
|
| 129 |
+
: pr.status === 'APPROVED'
|
| 130 |
+
? 'bg-emerald-500/10 text-emerald-300 border border-emerald-500/20'
|
| 131 |
+
: 'bg-rose-500/10 text-rose-300 border border-rose-500/20'
|
| 132 |
+
}`}
|
| 133 |
+
>
|
| 134 |
+
{pr.status}
|
| 135 |
+
</span>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
<span className="text-[11px] font-mono text-emerald-400 font-semibold">{conf}% Conf</span>
|
| 139 |
</div>
|
| 140 |
|
| 141 |
+
<p className="text-white font-medium line-clamp-1 mb-1">{pr.summary || 'Knowledge Proposal'}</p>
|
| 142 |
+
|
| 143 |
+
<div className="flex items-center justify-between text-[11px] text-slate-500 font-mono">
|
| 144 |
+
<span>{pr.proposal_type} DELTA</span>
|
| 145 |
+
<span>{pr.created_at ? new Date(pr.created_at).toLocaleTimeString() : 'Just now'}</span>
|
| 146 |
+
</div>
|
| 147 |
</div>
|
| 148 |
+
);
|
| 149 |
+
})}
|
| 150 |
+
</div>
|
| 151 |
|
| 152 |
+
{/* PR Details Deck (Right 7 Cols) */}
|
| 153 |
+
<div className="lg:col-span-7">
|
| 154 |
+
{activePR && (
|
| 155 |
+
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-6 shadow-xl space-y-6">
|
| 156 |
+
{/* PR Header */}
|
| 157 |
+
<div className="border-b border-slate-800 pb-4">
|
| 158 |
+
<div className="flex items-center space-x-2 text-xs font-mono text-slate-400 mb-2">
|
| 159 |
+
<span className="px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 font-semibold">
|
| 160 |
+
PR #{activePR.id.slice(0, 8)}
|
| 161 |
+
</span>
|
| 162 |
+
<span>•</span>
|
| 163 |
+
<span className="text-slate-300 uppercase font-semibold">{activePR.proposal_type} OPERATION</span>
|
| 164 |
+
</div>
|
| 165 |
+
<h3 className="text-lg font-bold text-white leading-tight">{activePR.summary || 'Knowledge Delta'}</h3>
|
| 166 |
+
</div>
|
| 167 |
|
| 168 |
+
{/* Proposed Changes Content Box */}
|
| 169 |
+
<div className="bg-[#090D16] border border-slate-800/90 rounded-xl p-4 font-mono text-xs space-y-3">
|
| 170 |
+
<div className="flex items-center justify-between text-slate-400 text-[11px] pb-2 border-b border-slate-800">
|
| 171 |
+
<span className="text-emerald-400 font-semibold flex items-center space-x-1">
|
| 172 |
+
<Sparkles className="w-3.5 h-3.5" />
|
| 173 |
+
<span>Proposed Extraction Value</span>
|
| 174 |
+
</span>
|
| 175 |
+
<span className="text-slate-400">
|
| 176 |
+
Conf: {Math.round(((activePR.proposed_changes?.confidence) || 0.95) * 100)}%
|
| 177 |
+
</span>
|
| 178 |
+
</div>
|
| 179 |
|
| 180 |
+
<div className="text-slate-100 leading-relaxed bg-[#101726] p-3 rounded-lg border border-slate-800">
|
| 181 |
+
{activePR.proposed_changes?.value ||
|
| 182 |
+
activePR.proposed_changes?.proposed?.value ||
|
| 183 |
+
JSON.stringify(activePR.proposed_changes, null, 2)}
|
| 184 |
+
</div>
|
|
|
|
|
|
|
| 185 |
|
| 186 |
+
{activePR.proposed_changes?.evidence && activePR.proposed_changes.evidence.length > 0 && (
|
| 187 |
+
<div className="pt-2 flex items-start space-x-2 text-slate-300">
|
| 188 |
+
<Quote className="w-3.5 h-3.5 text-slate-500 shrink-0 mt-0.5" />
|
| 189 |
+
<span className="text-[11px] italic font-sans text-slate-400">
|
| 190 |
+
"{activePR.proposed_changes.evidence[0]}"
|
| 191 |
+
</span>
|
| 192 |
+
</div>
|
| 193 |
+
)}
|
| 194 |
</div>
|
| 195 |
|
| 196 |
+
{/* Automated CI Policy Lint Box */}
|
| 197 |
+
<div className="bg-[#121828] border border-slate-800 rounded-xl p-4 text-xs space-y-2.5">
|
| 198 |
+
<div className="flex items-center justify-between">
|
| 199 |
+
<span className="font-semibold text-white flex items-center space-x-1.5">
|
| 200 |
+
<ShieldCheck className="w-4 h-4 text-emerald-400" />
|
| 201 |
+
<span>Policy Rules CI Status</span>
|
| 202 |
+
</span>
|
| 203 |
+
<span className="text-emerald-400 font-mono text-[11px] bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20 font-semibold">
|
| 204 |
+
Checks Passed
|
| 205 |
+
</span>
|
| 206 |
+
</div>
|
| 207 |
+
|
| 208 |
+
<div className="grid grid-cols-2 gap-2 text-[11px] font-mono text-slate-300 pt-1">
|
| 209 |
+
<div className="flex items-center space-x-1.5">
|
| 210 |
+
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" />
|
| 211 |
+
<span>Confidence > 80%</span>
|
| 212 |
+
</div>
|
| 213 |
+
<div className="flex items-center space-x-1.5">
|
| 214 |
+
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" />
|
| 215 |
+
<span>Source Provenance Valid</span>
|
| 216 |
+
</div>
|
| 217 |
+
<div className="flex items-center space-x-1.5">
|
| 218 |
+
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" />
|
| 219 |
+
<span>Schema RegEx Matched</span>
|
| 220 |
+
</div>
|
| 221 |
+
<div className="flex items-center space-x-1.5">
|
| 222 |
+
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" />
|
| 223 |
+
<span>Reconciliation Verified</span>
|
| 224 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
</div>
|
| 226 |
</div>
|
| 227 |
+
|
| 228 |
+
{/* Reviewer Comment Box */}
|
| 229 |
+
{activePR.status === 'PENDING' && (
|
| 230 |
+
<div className="space-y-3 pt-2 border-t border-slate-800">
|
| 231 |
+
<label className="block text-xs font-semibold text-slate-300">Review Decision & Notes</label>
|
| 232 |
+
<textarea
|
| 233 |
+
placeholder="Add an approval note or rejection justification (optional)..."
|
| 234 |
+
value={reviewComment}
|
| 235 |
+
onChange={(e) => setReviewComment(e.target.value)}
|
| 236 |
+
rows={2}
|
| 237 |
+
className="w-full bg-[#121828] border border-slate-700/80 rounded-xl px-3 py-2 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 238 |
+
/>
|
| 239 |
+
|
| 240 |
+
{/* Review Actions */}
|
| 241 |
+
<div className="flex items-center justify-end space-x-2 pt-1 text-xs">
|
| 242 |
+
<button
|
| 243 |
+
onClick={() => handleReview('ARCHIVED')}
|
| 244 |
+
disabled={isSubmitting}
|
| 245 |
+
className="px-3.5 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white transition font-medium flex items-center space-x-1.5"
|
| 246 |
+
>
|
| 247 |
+
<Archive className="w-3.5 h-3.5" />
|
| 248 |
+
<span>Archive</span>
|
| 249 |
+
</button>
|
| 250 |
+
|
| 251 |
+
<button
|
| 252 |
+
onClick={() => handleReview('REJECTED')}
|
| 253 |
+
disabled={isSubmitting}
|
| 254 |
+
className="px-3.5 py-2 rounded-xl bg-rose-950/40 hover:bg-rose-900/60 text-rose-300 border border-rose-500/30 transition font-medium flex items-center space-x-1.5"
|
| 255 |
+
>
|
| 256 |
+
<XCircle className="w-3.5 h-3.5 text-rose-400" />
|
| 257 |
+
<span>Reject PR</span>
|
| 258 |
+
</button>
|
| 259 |
+
|
| 260 |
+
<button
|
| 261 |
+
onClick={() => handleReview('APPROVED')}
|
| 262 |
+
disabled={isSubmitting}
|
| 263 |
+
className="px-4 py-2 rounded-xl bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 text-white font-semibold transition flex items-center space-x-1.5 shadow-lg shadow-emerald-600/20"
|
| 264 |
+
>
|
| 265 |
+
<CheckCircle2 className="w-3.5 h-3.5" />
|
| 266 |
+
<span>Approve & Commit to Master</span>
|
| 267 |
+
</button>
|
| 268 |
+
</div>
|
| 269 |
+
</div>
|
| 270 |
+
)}
|
| 271 |
</div>
|
| 272 |
+
)}
|
| 273 |
+
</div>
|
| 274 |
</div>
|
| 275 |
)}
|
| 276 |
</div>
|
studio/src/components/PolicyRulesLab.jsx
CHANGED
|
@@ -1,235 +1,256 @@
|
|
| 1 |
import React, { useState } from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
export default function PolicyRulesLab({ rules,
|
| 4 |
const [showAddModal, setShowAddModal] = useState(false);
|
| 5 |
const [ruleName, setRuleName] = useState('');
|
| 6 |
-
const [
|
| 7 |
-
const [
|
|
|
|
|
|
|
|
|
|
| 8 |
const [validating, setValidating] = useState(false);
|
| 9 |
|
| 10 |
const handleCreate = async (e) => {
|
| 11 |
e.preventDefault();
|
| 12 |
if (!ruleName.trim()) return;
|
| 13 |
|
| 14 |
-
let
|
| 15 |
-
if (
|
| 16 |
-
|
| 17 |
-
} else if (
|
| 18 |
-
|
|
|
|
|
|
|
| 19 |
}
|
| 20 |
|
| 21 |
-
await onCreateRule(ruleName.trim(),
|
| 22 |
setRuleName('');
|
| 23 |
setShowAddModal(false);
|
| 24 |
};
|
| 25 |
|
| 26 |
-
const
|
|
|
|
| 27 |
setValidating(true);
|
| 28 |
try {
|
| 29 |
-
await
|
|
|
|
| 30 |
} finally {
|
| 31 |
setValidating(false);
|
| 32 |
}
|
| 33 |
};
|
| 34 |
|
| 35 |
-
const ruleList = rules || [];
|
| 36 |
-
|
| 37 |
return (
|
| 38 |
<div className="space-y-6">
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
</
|
| 46 |
-
</
|
| 47 |
-
<p className="text-xs text-slate-400 mt-1">
|
| 48 |
-
Dynamic gates that validate extracted claims during ingestion. Failing rules automatically block auto-merge and open a Knowledge PR.
|
| 49 |
-
</p>
|
| 50 |
</div>
|
| 51 |
|
| 52 |
-
<div className="flex items-center space-x-
|
| 53 |
<button
|
| 54 |
-
onClick={
|
| 55 |
-
|
|
|
|
| 56 |
>
|
| 57 |
-
|
|
|
|
| 58 |
</button>
|
|
|
|
| 59 |
<button
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
className="px-4 py-2 text-xs font-semibold text-black bg-emerald-400 hover:bg-emerald-300 rounded-lg transition shadow disabled:opacity-50"
|
| 63 |
>
|
| 64 |
-
|
|
|
|
| 65 |
</button>
|
| 66 |
</div>
|
| 67 |
</div>
|
| 68 |
|
| 69 |
-
{/*
|
| 70 |
-
{
|
| 71 |
-
<div
|
| 72 |
-
className=
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
>
|
| 78 |
-
<div className="flex items-center justify-between mb-2">
|
| 79 |
-
<h3 className="text-sm font-bold text-white flex items-center gap-2">
|
| 80 |
-
<span>Policy Evaluation Report</span>
|
| 81 |
-
<span
|
| 82 |
-
className={`text-xs px-2 py-0.5 rounded font-mono ${
|
| 83 |
-
validationResult.compliant
|
| 84 |
-
? 'bg-emerald-500/20 text-emerald-300'
|
| 85 |
-
: 'bg-rose-500/20 text-rose-300'
|
| 86 |
-
}`}
|
| 87 |
-
>
|
| 88 |
-
{validationResult.compliant ? '[PASS] Compliant' : '[FAIL] Violations Detected'}
|
| 89 |
</span>
|
| 90 |
-
</
|
| 91 |
-
<span
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
</span>
|
| 94 |
</div>
|
| 95 |
|
| 96 |
-
<div className="
|
| 97 |
-
{(
|
| 98 |
-
<div
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
: 'bg-amber-500/20 text-amber-300'
|
| 110 |
-
}`}
|
| 111 |
-
>
|
| 112 |
-
{r.status}
|
| 113 |
-
</span>
|
| 114 |
-
<span className="text-white font-medium">{r.rule_name}</span>
|
| 115 |
</div>
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
</div>
|
| 118 |
))}
|
| 119 |
</div>
|
| 120 |
</div>
|
| 121 |
)}
|
| 122 |
|
| 123 |
-
{/* Rules
|
| 124 |
-
<div className="
|
| 125 |
-
|
| 126 |
-
<
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
<
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
))}
|
| 168 |
-
</tbody>
|
| 169 |
-
</table>
|
| 170 |
</div>
|
| 171 |
|
| 172 |
{/* Add Rule Modal */}
|
| 173 |
{showAddModal && (
|
| 174 |
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/
|
| 175 |
-
<div className="bg-[#
|
| 176 |
-
<h3 className="text-
|
| 177 |
-
<form onSubmit={handleCreate}>
|
| 178 |
-
<div
|
| 179 |
<label className="block text-xs font-medium text-slate-400 mb-1">Rule Name</label>
|
| 180 |
<input
|
| 181 |
type="text"
|
| 182 |
required
|
| 183 |
-
placeholder="e.g.
|
| 184 |
value={ruleName}
|
| 185 |
onChange={(e) => setRuleName(e.target.value)}
|
| 186 |
-
className="w-full bg-[#
|
| 187 |
/>
|
| 188 |
</div>
|
| 189 |
|
| 190 |
-
<div
|
| 191 |
-
<label className="block text-xs font-medium text-slate-400 mb-1">
|
| 192 |
<select
|
| 193 |
-
value={
|
| 194 |
-
onChange={(e) =>
|
| 195 |
-
className="w-full bg-[#
|
| 196 |
>
|
| 197 |
-
<option value="
|
| 198 |
-
<option value="
|
| 199 |
-
<option value="
|
| 200 |
</select>
|
| 201 |
</div>
|
| 202 |
|
| 203 |
-
|
| 204 |
-
<
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
|
| 220 |
-
<div className="flex justify-end space-x-3
|
| 221 |
<button
|
| 222 |
type="button"
|
| 223 |
onClick={() => setShowAddModal(false)}
|
| 224 |
-
className="px-4 py-2 text-xs
|
| 225 |
>
|
| 226 |
Cancel
|
| 227 |
</button>
|
| 228 |
<button
|
| 229 |
type="submit"
|
| 230 |
-
className="px-4 py-2
|
| 231 |
>
|
| 232 |
-
|
| 233 |
</button>
|
| 234 |
</div>
|
| 235 |
</form>
|
|
|
|
| 1 |
import React, { useState } from 'react';
|
| 2 |
+
import {
|
| 3 |
+
ShieldCheck,
|
| 4 |
+
Plus,
|
| 5 |
+
Play,
|
| 6 |
+
Trash2,
|
| 7 |
+
AlertTriangle,
|
| 8 |
+
CheckCircle2,
|
| 9 |
+
XCircle,
|
| 10 |
+
Sliders,
|
| 11 |
+
Filter,
|
| 12 |
+
Code2,
|
| 13 |
+
FileCheck2
|
| 14 |
+
} from 'lucide-react';
|
| 15 |
+
import { api } from '../api/client';
|
| 16 |
|
| 17 |
+
export default function PolicyRulesLab({ rules, currentWorkspace, onCreateRule, onDeleteRule, onValidate, loading }) {
|
| 18 |
const [showAddModal, setShowAddModal] = useState(false);
|
| 19 |
const [ruleName, setRuleName] = useState('');
|
| 20 |
+
const [ruleType, setRuleType] = useState('CONFIDENCE_THRESHOLD');
|
| 21 |
+
const [conditionVal, setConditionVal] = useState('0.85');
|
| 22 |
+
const [isBlocking, setIsBlocking] = useState(true);
|
| 23 |
+
|
| 24 |
+
const [lintResults, setLintResults] = useState(null);
|
| 25 |
const [validating, setValidating] = useState(false);
|
| 26 |
|
| 27 |
const handleCreate = async (e) => {
|
| 28 |
e.preventDefault();
|
| 29 |
if (!ruleName.trim()) return;
|
| 30 |
|
| 31 |
+
let condition = {};
|
| 32 |
+
if (ruleType === 'CONFIDENCE_THRESHOLD') {
|
| 33 |
+
condition = { min_confidence: parseFloat(conditionVal) || 0.85 };
|
| 34 |
+
} else if (ruleType === 'REGEX_FORMAT') {
|
| 35 |
+
condition = { pattern: conditionVal };
|
| 36 |
+
} else {
|
| 37 |
+
condition = { field: conditionVal };
|
| 38 |
}
|
| 39 |
|
| 40 |
+
await onCreateRule(ruleName.trim(), ruleType, condition, isBlocking);
|
| 41 |
setRuleName('');
|
| 42 |
setShowAddModal(false);
|
| 43 |
};
|
| 44 |
|
| 45 |
+
const runLinter = async () => {
|
| 46 |
+
if (!currentWorkspace?.id) return;
|
| 47 |
setValidating(true);
|
| 48 |
try {
|
| 49 |
+
const res = await onValidate();
|
| 50 |
+
setLintResults(res);
|
| 51 |
} finally {
|
| 52 |
setValidating(false);
|
| 53 |
}
|
| 54 |
};
|
| 55 |
|
|
|
|
|
|
|
| 56 |
return (
|
| 57 |
<div className="space-y-6">
|
| 58 |
+
{/* Policy Rules Lab Header */}
|
| 59 |
+
<div className="bg-[#0C101A] border border-slate-800/90 rounded-xl p-4 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
| 60 |
+
<div className="flex items-center space-x-3">
|
| 61 |
+
<ShieldCheck className="w-5 h-5 text-emerald-400" />
|
| 62 |
+
<div>
|
| 63 |
+
<h2 className="text-sm font-semibold text-white">Policy as Code (CI Linting Engine)</h2>
|
| 64 |
+
<p className="text-[11px] text-slate-400">Automated quality gates and regulatory guardrails executed before knowledge merges.</p>
|
| 65 |
+
</div>
|
|
|
|
|
|
|
|
|
|
| 66 |
</div>
|
| 67 |
|
| 68 |
+
<div className="flex items-center space-x-2.5">
|
| 69 |
<button
|
| 70 |
+
onClick={runLinter}
|
| 71 |
+
disabled={validating}
|
| 72 |
+
className="px-3.5 py-1.5 rounded-lg bg-[#141B2D] hover:bg-[#1E2740] border border-slate-700 text-slate-200 text-xs font-semibold transition flex items-center space-x-1.5 shadow-sm"
|
| 73 |
>
|
| 74 |
+
<Play className={`w-3.5 h-3.5 text-emerald-400 ${validating ? 'animate-spin' : ''}`} />
|
| 75 |
+
<span>{validating ? 'Linting PRs...' : 'Dry-Run CI Check'}</span>
|
| 76 |
</button>
|
| 77 |
+
|
| 78 |
<button
|
| 79 |
+
onClick={() => setShowAddModal(true)}
|
| 80 |
+
className="px-3.5 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-semibold transition flex items-center space-x-1.5 shadow-sm shadow-emerald-600/30"
|
|
|
|
| 81 |
>
|
| 82 |
+
<Plus className="w-3.5 h-3.5" />
|
| 83 |
+
<span>Add Policy Rule</span>
|
| 84 |
</button>
|
| 85 |
</div>
|
| 86 |
</div>
|
| 87 |
|
| 88 |
+
{/* Lint Results Suite (GitHub Actions Style) */}
|
| 89 |
+
{lintResults && (
|
| 90 |
+
<div className="bg-[#0D121F] border border-slate-800 rounded-2xl p-5 space-y-4">
|
| 91 |
+
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
| 92 |
+
<div className="flex items-center space-x-2">
|
| 93 |
+
<FileCheck2 className="w-4 h-4 text-emerald-400" />
|
| 94 |
+
<span className="text-xs font-bold text-white uppercase tracking-wider font-mono">
|
| 95 |
+
CI Check Suite Summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
</span>
|
| 97 |
+
</div>
|
| 98 |
+
<span
|
| 99 |
+
className={`text-xs px-2.5 py-0.5 rounded-full font-mono font-bold ${
|
| 100 |
+
lintResults.has_violations
|
| 101 |
+
? 'bg-rose-500/10 text-rose-300 border border-rose-500/30'
|
| 102 |
+
: 'bg-emerald-500/10 text-emerald-300 border border-emerald-500/30'
|
| 103 |
+
}`}
|
| 104 |
+
>
|
| 105 |
+
{lintResults.has_violations
|
| 106 |
+
? `FAILED: ${lintResults.violations_count} Violations`
|
| 107 |
+
: 'ALL CHECKS PASSED'}
|
| 108 |
</span>
|
| 109 |
</div>
|
| 110 |
|
| 111 |
+
<div className="divide-y divide-slate-800/80 text-xs font-mono">
|
| 112 |
+
{(lintResults.validations || []).map((val, idx) => (
|
| 113 |
+
<div key={idx} className="py-2.5 flex items-center justify-between">
|
| 114 |
+
<div className="flex items-center space-x-2.5">
|
| 115 |
+
{val.severity === 'PASS' ? (
|
| 116 |
+
<CheckCircle2 className="w-4 h-4 text-emerald-400 shrink-0" />
|
| 117 |
+
) : (
|
| 118 |
+
<XCircle className="w-4 h-4 text-rose-400 shrink-0" />
|
| 119 |
+
)}
|
| 120 |
+
<div>
|
| 121 |
+
<span className="text-white font-medium">{val.rule_name}</span>
|
| 122 |
+
<span className="text-slate-400 ml-2 font-sans text-[11px]">{val.message}</span>
|
| 123 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
</div>
|
| 125 |
+
|
| 126 |
+
<span
|
| 127 |
+
className={`text-[10px] px-1.5 py-0.2 rounded font-bold ${
|
| 128 |
+
val.severity === 'PASS' ? 'text-emerald-400 bg-emerald-950/40' : 'text-rose-400 bg-rose-950/40'
|
| 129 |
+
}`}
|
| 130 |
+
>
|
| 131 |
+
{val.severity}
|
| 132 |
+
</span>
|
| 133 |
</div>
|
| 134 |
))}
|
| 135 |
</div>
|
| 136 |
</div>
|
| 137 |
)}
|
| 138 |
|
| 139 |
+
{/* Active Rules Grid */}
|
| 140 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
| 141 |
+
{rules.map((rule) => (
|
| 142 |
+
<div
|
| 143 |
+
key={rule.id}
|
| 144 |
+
className="bg-[#0D121F] border border-slate-800 rounded-xl p-4 flex flex-col justify-between space-y-4 hover:border-slate-700 transition"
|
| 145 |
+
>
|
| 146 |
+
<div>
|
| 147 |
+
<div className="flex items-center justify-between mb-2">
|
| 148 |
+
<span className="text-xs font-semibold text-white tracking-wide">{rule.name}</span>
|
| 149 |
+
<span
|
| 150 |
+
className={`text-[10px] font-mono px-2 py-0.5 rounded font-bold ${
|
| 151 |
+
rule.is_blocking
|
| 152 |
+
? 'bg-rose-500/10 text-rose-300 border border-rose-500/20'
|
| 153 |
+
: 'bg-amber-500/10 text-amber-300 border border-amber-500/20'
|
| 154 |
+
}`}
|
| 155 |
+
>
|
| 156 |
+
{rule.is_blocking ? 'BLOCKING GATE' : 'ADVISORY'}
|
| 157 |
+
</span>
|
| 158 |
+
</div>
|
| 159 |
+
|
| 160 |
+
<div className="space-y-1.5 text-xs font-mono text-slate-400">
|
| 161 |
+
<div className="flex justify-between text-[11px]">
|
| 162 |
+
<span>Type:</span>
|
| 163 |
+
<span className="text-slate-200">{rule.rule_type}</span>
|
| 164 |
+
</div>
|
| 165 |
+
<div className="bg-[#141B2D] p-2 rounded border border-slate-800/80 text-[11px] text-slate-300">
|
| 166 |
+
{JSON.stringify(rule.condition || {})}
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
</div>
|
| 170 |
+
|
| 171 |
+
<div className="flex items-center justify-between pt-2 border-t border-slate-800 text-xs">
|
| 172 |
+
<span className="text-[10px] text-slate-500 font-mono">ID: {rule.id.slice(0, 8)}</span>
|
| 173 |
+
<button
|
| 174 |
+
onClick={() => onDeleteRule(rule.id)}
|
| 175 |
+
className="text-slate-500 hover:text-rose-400 transition p-1"
|
| 176 |
+
title="Delete Rule"
|
| 177 |
+
>
|
| 178 |
+
<Trash2 className="w-3.5 h-3.5" />
|
| 179 |
+
</button>
|
| 180 |
+
</div>
|
| 181 |
+
</div>
|
| 182 |
+
))}
|
|
|
|
|
|
|
|
|
|
| 183 |
</div>
|
| 184 |
|
| 185 |
{/* Add Rule Modal */}
|
| 186 |
{showAddModal && (
|
| 187 |
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-md">
|
| 188 |
+
<div className="bg-[#0D121F] border border-slate-700/80 p-6 rounded-2xl w-full max-w-md shadow-2xl">
|
| 189 |
+
<h3 className="text-base font-semibold text-white mb-4">Add Validation Policy Rule</h3>
|
| 190 |
+
<form onSubmit={handleCreate} className="space-y-4">
|
| 191 |
+
<div>
|
| 192 |
<label className="block text-xs font-medium text-slate-400 mb-1">Rule Name</label>
|
| 193 |
<input
|
| 194 |
type="text"
|
| 195 |
required
|
| 196 |
+
placeholder="e.g. Clinical Endpoint Floor Check"
|
| 197 |
value={ruleName}
|
| 198 |
onChange={(e) => setRuleName(e.target.value)}
|
| 199 |
+
className="w-full bg-[#141B2D] border border-slate-700/80 rounded-lg px-3 py-2 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 200 |
/>
|
| 201 |
</div>
|
| 202 |
|
| 203 |
+
<div>
|
| 204 |
+
<label className="block text-xs font-medium text-slate-400 mb-1">Rule Condition Type</label>
|
| 205 |
<select
|
| 206 |
+
value={ruleType}
|
| 207 |
+
onChange={(e) => setRuleType(e.target.value)}
|
| 208 |
+
className="w-full bg-[#141B2D] border border-slate-700/80 rounded-lg px-3 py-2 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 209 |
>
|
| 210 |
+
<option value="CONFIDENCE_THRESHOLD">Confidence Threshold Floor</option>
|
| 211 |
+
<option value="REGEX_FORMAT">RegEx Format Validator</option>
|
| 212 |
+
<option value="REQUIRED_FIELD">Required Field Presence</option>
|
| 213 |
</select>
|
| 214 |
</div>
|
| 215 |
|
| 216 |
+
<div>
|
| 217 |
+
<label className="block text-xs font-medium text-slate-400 mb-1">Threshold / Pattern Value</label>
|
| 218 |
+
<input
|
| 219 |
+
type="text"
|
| 220 |
+
required
|
| 221 |
+
placeholder="e.g. 0.85 or ^PROTOCOL-[A-Z0-9-]+$"
|
| 222 |
+
value={conditionVal}
|
| 223 |
+
onChange={(e) => setConditionVal(e.target.value)}
|
| 224 |
+
className="w-full bg-[#141B2D] border border-slate-700/80 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 225 |
+
/>
|
| 226 |
+
</div>
|
| 227 |
+
|
| 228 |
+
<div className="flex items-center space-x-2 pt-1">
|
| 229 |
+
<input
|
| 230 |
+
type="checkbox"
|
| 231 |
+
id="blockingCheck"
|
| 232 |
+
checked={isBlocking}
|
| 233 |
+
onChange={(e) => setIsBlocking(e.target.checked)}
|
| 234 |
+
className="rounded border-slate-700 text-emerald-600 focus:ring-emerald-500 bg-[#141B2D]"
|
| 235 |
+
/>
|
| 236 |
+
<label htmlFor="blockingCheck" className="text-xs text-slate-300 font-medium">
|
| 237 |
+
Enforce as Blocking Gate (Prevents PR Merge if failed)
|
| 238 |
+
</label>
|
| 239 |
+
</div>
|
| 240 |
|
| 241 |
+
<div className="flex justify-end space-x-3 pt-3 border-t border-slate-800">
|
| 242 |
<button
|
| 243 |
type="button"
|
| 244 |
onClick={() => setShowAddModal(false)}
|
| 245 |
+
className="px-4 py-2 text-xs text-slate-400 hover:text-white transition"
|
| 246 |
>
|
| 247 |
Cancel
|
| 248 |
</button>
|
| 249 |
<button
|
| 250 |
type="submit"
|
| 251 |
+
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-semibold rounded-lg transition"
|
| 252 |
>
|
| 253 |
+
Save Policy Rule
|
| 254 |
</button>
|
| 255 |
</div>
|
| 256 |
</form>
|
studio/src/components/SemanticDiffViewer.jsx
CHANGED
|
@@ -1,264 +1,408 @@
|
|
| 1 |
import React, { useState } from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
export default function SemanticDiffViewer({
|
| 4 |
-
const [
|
| 5 |
-
const [
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
if (loading) {
|
| 8 |
return (
|
| 9 |
-
<div className="flex items-center justify-center
|
| 10 |
-
<
|
|
|
|
| 11 |
</div>
|
| 12 |
);
|
| 13 |
}
|
| 14 |
|
| 15 |
-
const
|
| 16 |
-
const
|
| 17 |
-
const
|
| 18 |
-
const
|
| 19 |
-
|
|
|
|
| 20 |
|
| 21 |
const handleAction = async (proposalId, decision) => {
|
| 22 |
-
|
| 23 |
try {
|
| 24 |
-
await
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
} finally {
|
| 26 |
-
|
| 27 |
}
|
| 28 |
};
|
| 29 |
|
| 30 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
return (
|
| 33 |
<div className="space-y-6">
|
| 34 |
-
{/*
|
| 35 |
-
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4
|
| 36 |
-
<div>
|
| 37 |
-
<
|
| 38 |
-
<
|
| 39 |
-
<span className="
|
| 40 |
-
|
|
|
|
| 41 |
</span>
|
| 42 |
-
</
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
</div>
|
| 47 |
|
| 48 |
-
{/*
|
| 49 |
-
<div className="flex items-center space-x-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
<
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
</div>
|
| 86 |
</div>
|
| 87 |
|
| 88 |
-
{
|
| 89 |
-
<div className="
|
| 90 |
-
<
|
| 91 |
-
|
| 92 |
-
<
|
| 93 |
-
|
|
|
|
|
|
|
| 94 |
</p>
|
| 95 |
</div>
|
| 96 |
) : (
|
| 97 |
<div className="space-y-4">
|
| 98 |
-
{/* Conflicts
|
| 99 |
-
{(
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
|
| 106 |
-
<div
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
| 111 |
</span>
|
| 112 |
-
<span className="text-
|
| 113 |
</div>
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
<button
|
| 117 |
-
disabled={actingId === c.proposal_id}
|
| 118 |
-
onClick={() => handleAction(c.proposal_id, 'APPROVED')}
|
| 119 |
-
className="px-3 py-1.5 rounded-lg text-xs font-semibold bg-emerald-500/20 text-emerald-300 hover:bg-emerald-500/30 border border-emerald-500/40 transition disabled:opacity-50"
|
| 120 |
-
>
|
| 121 |
-
✓ Accept & Merge
|
| 122 |
-
</button>
|
| 123 |
-
<button
|
| 124 |
-
disabled={actingId === c.proposal_id}
|
| 125 |
-
onClick={() => handleAction(c.proposal_id, 'REJECTED')}
|
| 126 |
-
className="px-3 py-1.5 rounded-lg text-xs font-semibold bg-rose-500/20 text-rose-300 hover:bg-rose-500/30 border border-rose-500/40 transition disabled:opacity-50"
|
| 127 |
-
>
|
| 128 |
-
✕ Reject
|
| 129 |
-
</button>
|
| 130 |
</div>
|
| 131 |
</div>
|
| 132 |
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
<div className="rounded-lg bg-rose-950/20 border border-rose-500/20 p-4">
|
| 139 |
-
<span className="text-[11px] font-mono text-rose-400 uppercase tracking-wider block mb-1">
|
| 140 |
-
◀ Baseline Truth (Committed)
|
| 141 |
-
</span>
|
| 142 |
-
<p className="text-sm text-slate-200 font-medium">"{c.existing?.value || 'N/A'}"</p>
|
| 143 |
-
<span className="text-[11px] text-slate-400 mt-2 block font-mono">
|
| 144 |
-
Confidence: {c.existing?.confidence || 1.0}
|
| 145 |
</span>
|
|
|
|
| 146 |
</div>
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
<div className="rounded-lg bg-emerald-950/20 border border-emerald-500/20 p-4">
|
| 150 |
-
<span className="text-[11px] font-mono text-emerald-400 uppercase tracking-wider block mb-1">
|
| 151 |
-
▶ Proposed Change (From Incoming Document)
|
| 152 |
-
</span>
|
| 153 |
-
<p className="text-sm text-slate-100 font-medium">"{c.proposed?.value}"</p>
|
| 154 |
-
<div className="mt-3 pt-2 border-t border-emerald-500/10">
|
| 155 |
-
<span className="text-[11px] text-slate-400 block font-mono mb-1">
|
| 156 |
-
Verbatim Evidence (Page {page}):
|
| 157 |
-
</span>
|
| 158 |
-
<blockquote className="text-xs text-cyan-300 italic bg-cyan-950/20 p-2 rounded border-l-2 border-cyan-500">
|
| 159 |
-
"{quote}"
|
| 160 |
-
</blockquote>
|
| 161 |
-
</div>
|
| 162 |
</div>
|
| 163 |
</div>
|
| 164 |
</div>
|
| 165 |
-
);
|
| 166 |
-
})}
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
return (
|
| 176 |
-
<div key={a.proposal_id || idx} className="rounded-xl border border-emerald-500/30 bg-[#0F1422] p-5 shadow-lg">
|
| 177 |
-
<div className="flex items-center justify-between border-b border-slate-800 pb-3 mb-4">
|
| 178 |
-
<div className="flex items-center space-x-2">
|
| 179 |
-
<span className="px-2 py-0.5 rounded text-[11px] font-bold bg-emerald-500/20 text-emerald-400 border border-emerald-500/30">
|
| 180 |
-
+ NEW {a.type || 'CLAIM'}
|
| 181 |
-
</span>
|
| 182 |
-
<span className="text-xs text-slate-400 font-mono">PR: {a.proposal_id?.slice(0, 8)}</span>
|
| 183 |
</div>
|
|
|
|
|
|
|
| 184 |
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
>
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
</div>
|
|
|
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
</span>
|
| 213 |
-
<blockquote className="text-xs text-cyan-300 italic">"{quote}"</blockquote>
|
| 214 |
</div>
|
| 215 |
</div>
|
| 216 |
-
);
|
| 217 |
-
})}
|
| 218 |
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
<div
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
|
|
|
|
|
|
| 229 |
</div>
|
|
|
|
| 230 |
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
className="
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
</button>
|
| 239 |
-
<button
|
| 240 |
-
disabled={actingId === ch.proposal_id}
|
| 241 |
-
onClick={() => handleAction(ch.proposal_id, 'REJECTED')}
|
| 242 |
-
className="px-3 py-1.5 rounded-lg text-xs font-semibold bg-rose-500/20 text-rose-300 hover:bg-rose-500/30 border border-rose-500/40 transition disabled:opacity-50"
|
| 243 |
-
>
|
| 244 |
-
✕ Reject
|
| 245 |
-
</button>
|
| 246 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
</div>
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
</div>
|
| 255 |
-
<div className="
|
| 256 |
-
<
|
| 257 |
-
<
|
| 258 |
</div>
|
| 259 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
</div>
|
| 261 |
-
|
|
|
|
| 262 |
</div>
|
| 263 |
)}
|
| 264 |
</div>
|
|
|
|
| 1 |
import React, { useState } from 'react';
|
| 2 |
+
import {
|
| 3 |
+
FileDiff,
|
| 4 |
+
AlertTriangle,
|
| 5 |
+
CheckCircle2,
|
| 6 |
+
XCircle,
|
| 7 |
+
Archive,
|
| 8 |
+
Layers,
|
| 9 |
+
Sparkles,
|
| 10 |
+
Quote,
|
| 11 |
+
Eye,
|
| 12 |
+
Columns,
|
| 13 |
+
AlignLeft,
|
| 14 |
+
ChevronRight,
|
| 15 |
+
MessageSquare,
|
| 16 |
+
Send,
|
| 17 |
+
SlidersHorizontal,
|
| 18 |
+
ShieldCheck
|
| 19 |
+
} from 'lucide-react';
|
| 20 |
+
import confetti from 'canvas-confetti';
|
| 21 |
|
| 22 |
+
export default function SemanticDiffViewer({ diffData, onReviewProposal, onBatchReview, loading }) {
|
| 23 |
+
const [viewMode, setViewMode] = useState('split'); // 'split' | 'unified'
|
| 24 |
+
const [filterType, setFilterType] = useState('all'); // 'all' | 'conflicts' | 'additions' | 'changes'
|
| 25 |
+
const [comments, setComments] = useState({});
|
| 26 |
+
const [expandedComments, setExpandedComments] = useState({});
|
| 27 |
+
const [actionLoading, setActionLoading] = useState({});
|
| 28 |
|
| 29 |
if (loading) {
|
| 30 |
return (
|
| 31 |
+
<div className="flex flex-col items-center justify-center py-24 space-y-4">
|
| 32 |
+
<div className="w-8 h-8 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin"></div>
|
| 33 |
+
<p className="text-xs text-slate-400 font-mono">Running semantic fact diff against Master Knowledge Register...</p>
|
| 34 |
</div>
|
| 35 |
);
|
| 36 |
}
|
| 37 |
|
| 38 |
+
const additions = diffData?.additions || [];
|
| 39 |
+
const changes = diffData?.changes || [];
|
| 40 |
+
const conflicts = diffData?.conflicts || [];
|
| 41 |
+
const removals = diffData?.removals || [];
|
| 42 |
+
|
| 43 |
+
const totalDeltas = additions.length + changes.length + conflicts.length + removals.length;
|
| 44 |
|
| 45 |
const handleAction = async (proposalId, decision) => {
|
| 46 |
+
setActionLoading((prev) => ({ ...prev, [proposalId]: true }));
|
| 47 |
try {
|
| 48 |
+
await onReviewProposal(proposalId, decision, comments[proposalId] || '');
|
| 49 |
+
if (decision === 'APPROVED') {
|
| 50 |
+
confetti({
|
| 51 |
+
particleCount: 50,
|
| 52 |
+
spread: 60,
|
| 53 |
+
origin: { y: 0.8 },
|
| 54 |
+
colors: ['#10B981', '#14B8A6', '#3B82F6'],
|
| 55 |
+
});
|
| 56 |
+
}
|
| 57 |
} finally {
|
| 58 |
+
setActionLoading((prev) => ({ ...prev, [proposalId]: false }));
|
| 59 |
}
|
| 60 |
};
|
| 61 |
|
| 62 |
+
const handleBatch = async (decision) => {
|
| 63 |
+
await onBatchReview(decision);
|
| 64 |
+
if (decision === 'APPROVED') {
|
| 65 |
+
confetti({
|
| 66 |
+
particleCount: 90,
|
| 67 |
+
spread: 80,
|
| 68 |
+
origin: { y: 0.7 },
|
| 69 |
+
});
|
| 70 |
+
}
|
| 71 |
+
};
|
| 72 |
|
| 73 |
return (
|
| 74 |
<div className="space-y-6">
|
| 75 |
+
{/* Diff Toolbar & Delta Summary */}
|
| 76 |
+
<div className="bg-[#0C101A] border border-slate-800/90 rounded-xl p-4 shadow-sm flex flex-col md:flex-row md:items-center justify-between gap-4">
|
| 77 |
+
<div className="flex items-center space-x-4">
|
| 78 |
+
<div className="flex items-center space-x-2">
|
| 79 |
+
<FileDiff className="w-5 h-5 text-emerald-400" />
|
| 80 |
+
<span className="font-semibold text-white text-sm">3-Way Semantic Fact Diff</span>
|
| 81 |
+
<span className="text-xs px-2 py-0.5 rounded bg-slate-800 text-slate-300 font-mono font-medium">
|
| 82 |
+
{totalDeltas} pending {totalDeltas === 1 ? 'delta' : 'deltas'}
|
| 83 |
</span>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
<div className="hidden sm:flex items-center space-x-2 text-xs font-mono">
|
| 87 |
+
<span className="text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20">
|
| 88 |
+
+{additions.length} added
|
| 89 |
+
</span>
|
| 90 |
+
<span className="text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded border border-blue-500/20">
|
| 91 |
+
~{changes.length} updated
|
| 92 |
+
</span>
|
| 93 |
+
{conflicts.length > 0 && (
|
| 94 |
+
<span className="text-rose-400 bg-rose-500/10 px-2 py-0.5 rounded border border-rose-500/20 flex items-center space-x-1">
|
| 95 |
+
<AlertTriangle className="w-3 h-3 text-rose-400" />
|
| 96 |
+
<span>!{conflicts.length} conflicts</span>
|
| 97 |
+
</span>
|
| 98 |
+
)}
|
| 99 |
+
</div>
|
| 100 |
</div>
|
| 101 |
|
| 102 |
+
{/* View Controls & Filters */}
|
| 103 |
+
<div className="flex items-center space-x-3 text-xs">
|
| 104 |
+
{/* Filter Pills */}
|
| 105 |
+
<div className="flex items-center bg-[#141B2D] p-1 rounded-lg border border-slate-800">
|
| 106 |
+
<button
|
| 107 |
+
onClick={() => setFilterType('all')}
|
| 108 |
+
className={`px-2.5 py-1 rounded font-medium transition ${
|
| 109 |
+
filterType === 'all' ? 'bg-slate-700 text-white' : 'text-slate-400 hover:text-slate-200'
|
| 110 |
+
}`}
|
| 111 |
+
>
|
| 112 |
+
All ({totalDeltas})
|
| 113 |
+
</button>
|
| 114 |
+
<button
|
| 115 |
+
onClick={() => setFilterType('conflicts')}
|
| 116 |
+
className={`px-2.5 py-1 rounded font-medium transition flex items-center space-x-1 ${
|
| 117 |
+
filterType === 'conflicts' ? 'bg-rose-900/60 text-rose-300' : 'text-slate-400 hover:text-rose-300'
|
| 118 |
+
}`}
|
| 119 |
+
>
|
| 120 |
+
<span>Conflicts</span>
|
| 121 |
+
{conflicts.length > 0 && (
|
| 122 |
+
<span className="w-1.5 h-1.5 rounded-full bg-rose-400"></span>
|
| 123 |
+
)}
|
| 124 |
+
</button>
|
| 125 |
+
<button
|
| 126 |
+
onClick={() => setFilterType('additions')}
|
| 127 |
+
className={`px-2.5 py-1 rounded font-medium transition ${
|
| 128 |
+
filterType === 'additions' ? 'bg-emerald-950/60 text-emerald-300' : 'text-slate-400 hover:text-slate-200'
|
| 129 |
+
}`}
|
| 130 |
+
>
|
| 131 |
+
Additions
|
| 132 |
+
</button>
|
| 133 |
+
</div>
|
| 134 |
+
|
| 135 |
+
{/* Split / Unified Toggle */}
|
| 136 |
+
<div className="flex items-center bg-[#141B2D] p-1 rounded-lg border border-slate-800">
|
| 137 |
+
<button
|
| 138 |
+
onClick={() => setViewMode('split')}
|
| 139 |
+
className={`p-1.5 rounded transition ${viewMode === 'split' ? 'bg-slate-700 text-white' : 'text-slate-400 hover:text-white'}`}
|
| 140 |
+
title="Split View (Side by Side)"
|
| 141 |
+
>
|
| 142 |
+
<Columns className="w-3.5 h-3.5" />
|
| 143 |
+
</button>
|
| 144 |
+
<button
|
| 145 |
+
onClick={() => setViewMode('unified')}
|
| 146 |
+
className={`p-1.5 rounded transition ${viewMode === 'unified' ? 'bg-slate-700 text-white' : 'text-slate-400 hover:text-white'}`}
|
| 147 |
+
title="Unified View"
|
| 148 |
+
>
|
| 149 |
+
<AlignLeft className="w-3.5 h-3.5" />
|
| 150 |
+
</button>
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
{/* Bulk Approve Button */}
|
| 154 |
+
{totalDeltas > 0 && (
|
| 155 |
+
<button
|
| 156 |
+
onClick={() => handleBatch('APPROVED')}
|
| 157 |
+
className="px-3 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs transition shadow-sm shadow-emerald-600/20 flex items-center space-x-1.5"
|
| 158 |
+
>
|
| 159 |
+
<CheckCircle2 className="w-3.5 h-3.5" />
|
| 160 |
+
<span>Approve All</span>
|
| 161 |
+
</button>
|
| 162 |
+
)}
|
| 163 |
</div>
|
| 164 |
</div>
|
| 165 |
|
| 166 |
+
{totalDeltas === 0 ? (
|
| 167 |
+
<div className="bg-[#0C101A] border border-slate-800/80 rounded-2xl p-16 text-center shadow-inner">
|
| 168 |
+
<div className="w-12 h-12 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 flex items-center justify-center mx-auto mb-4">
|
| 169 |
+
<CheckCircle2 className="w-6 h-6" />
|
| 170 |
+
</div>
|
| 171 |
+
<h3 className="text-base font-semibold text-white mb-1">Knowledge Register is Up to Date</h3>
|
| 172 |
+
<p className="text-xs text-slate-400 max-w-md mx-auto mb-6">
|
| 173 |
+
No pending knowledge proposals or conflicts found. Stage a new document or protocol version to generate 3-way fact diffs.
|
| 174 |
</p>
|
| 175 |
</div>
|
| 176 |
) : (
|
| 177 |
<div className="space-y-4">
|
| 178 |
+
{/* Conflicts Section */}
|
| 179 |
+
{(filterType === 'all' || filterType === 'conflicts') && conflicts.map((c) => (
|
| 180 |
+
<div
|
| 181 |
+
key={c.proposal_id}
|
| 182 |
+
className="bg-[#0D121F] border border-rose-500/40 rounded-xl overflow-hidden shadow-lg shadow-rose-950/20 transition hover:border-rose-500/70"
|
| 183 |
+
>
|
| 184 |
+
{/* Card Header */}
|
| 185 |
+
<div className="bg-rose-950/25 px-4 py-2.5 border-b border-rose-500/30 flex items-center justify-between">
|
| 186 |
+
<div className="flex items-center space-x-2.5">
|
| 187 |
+
<span className="p-1 rounded bg-rose-500/20 text-rose-300">
|
| 188 |
+
<AlertTriangle className="w-4 h-4 text-rose-400" />
|
| 189 |
+
</span>
|
| 190 |
+
<div>
|
| 191 |
+
<span className="text-xs font-semibold text-white tracking-wide">{c.title || 'Fact Conflict'}</span>
|
| 192 |
+
<span className="ml-2 text-[10px] uppercase font-mono px-1.5 py-0.5 rounded bg-rose-500/20 text-rose-300 border border-rose-500/30">
|
| 193 |
+
Conflict
|
| 194 |
+
</span>
|
| 195 |
+
</div>
|
| 196 |
+
</div>
|
| 197 |
+
|
| 198 |
+
<div className="flex items-center space-x-2 text-xs">
|
| 199 |
+
<span className="text-slate-400 font-mono text-[11px]">ID: {c.proposal_id.slice(0, 8)}</span>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
|
| 203 |
+
{/* Conflict Body */}
|
| 204 |
+
<div className="p-4 space-y-4">
|
| 205 |
+
{c.rationale && (
|
| 206 |
+
<p className="text-xs text-slate-300 bg-slate-900/60 p-2.5 rounded-lg border border-slate-800 leading-relaxed font-sans">
|
| 207 |
+
<strong className="text-rose-400">Reconciliation Note: </strong>
|
| 208 |
+
{c.rationale}
|
| 209 |
+
</p>
|
| 210 |
+
)}
|
| 211 |
|
| 212 |
+
{/* Side by Side Diff Comparison */}
|
| 213 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs font-mono">
|
| 214 |
+
{/* Baseline / Existing */}
|
| 215 |
+
<div className="bg-[#13111C] border border-slate-800 rounded-lg p-3">
|
| 216 |
+
<div className="flex items-center justify-between text-slate-400 text-[11px] mb-2 pb-1 border-b border-slate-800/80">
|
| 217 |
+
<span className="text-slate-400 flex items-center space-x-1">
|
| 218 |
+
<span>- Master Truth (Existing Fact)</span>
|
| 219 |
</span>
|
| 220 |
+
<span className="text-[10px] text-slate-500">Conf: {Math.round((c.existing?.confidence || 0.9) * 100)}%</span>
|
| 221 |
</div>
|
| 222 |
+
<div className="text-rose-200 bg-rose-950/20 p-2 rounded border border-rose-900/30 leading-relaxed break-words">
|
| 223 |
+
{c.existing?.value || 'N/A'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
</div>
|
| 225 |
</div>
|
| 226 |
|
| 227 |
+
{/* Proposed / Amendment */}
|
| 228 |
+
<div className="bg-[#0C1518] border border-emerald-900/40 rounded-lg p-3">
|
| 229 |
+
<div className="flex items-center justify-between text-slate-400 text-[11px] mb-2 pb-1 border-b border-slate-800/80">
|
| 230 |
+
<span className="text-emerald-400 flex items-center space-x-1 font-semibold">
|
| 231 |
+
<span>+ Proposed Fact (Extracted Amendment)</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
</span>
|
| 233 |
+
<span className="text-[10px] text-emerald-400 font-bold">Conf: {Math.round((c.proposed?.confidence || 0.95) * 100)}%</span>
|
| 234 |
</div>
|
| 235 |
+
<div className="text-emerald-200 bg-emerald-950/25 p-2 rounded border border-emerald-800/30 leading-relaxed break-words font-medium">
|
| 236 |
+
{c.proposed?.value || 'N/A'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
</div>
|
| 238 |
</div>
|
| 239 |
</div>
|
|
|
|
|
|
|
| 240 |
|
| 241 |
+
{/* Evidence Provenance */}
|
| 242 |
+
{c.proposed?.evidence && c.proposed.evidence.length > 0 && (
|
| 243 |
+
<div className="text-xs bg-[#101524] p-2.5 rounded-lg border border-slate-800 text-slate-300 flex items-start space-x-2">
|
| 244 |
+
<Quote className="w-4 h-4 text-slate-500 shrink-0 mt-0.5" />
|
| 245 |
+
<div>
|
| 246 |
+
<span className="text-[10px] text-slate-500 uppercase tracking-wider block font-mono">Source Provenance Citation</span>
|
| 247 |
+
<span className="text-slate-300 italic">{c.proposed.evidence[0]}</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
</div>
|
| 249 |
+
</div>
|
| 250 |
+
)}
|
| 251 |
|
| 252 |
+
{/* Action Bar */}
|
| 253 |
+
<div className="flex items-center justify-between pt-2 border-t border-slate-800/80 text-xs">
|
| 254 |
+
<button
|
| 255 |
+
onClick={() => setExpandedComments((p) => ({ ...p, [c.proposal_id]: !p[c.proposal_id] }))}
|
| 256 |
+
className="text-slate-400 hover:text-slate-200 flex items-center space-x-1"
|
| 257 |
+
>
|
| 258 |
+
<MessageSquare className="w-3.5 h-3.5" />
|
| 259 |
+
<span>{comments[c.proposal_id] ? 'Edit Reviewer Note' : 'Add Note'}</span>
|
| 260 |
+
</button>
|
| 261 |
+
|
| 262 |
+
<div className="flex items-center space-x-2">
|
| 263 |
+
<button
|
| 264 |
+
onClick={() => handleAction(c.proposal_id, 'REJECTED')}
|
| 265 |
+
disabled={actionLoading[c.proposal_id]}
|
| 266 |
+
className="px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white transition flex items-center space-x-1 font-medium"
|
| 267 |
+
>
|
| 268 |
+
<XCircle className="w-3.5 h-3.5 text-rose-400" />
|
| 269 |
+
<span>Reject Amendment</span>
|
| 270 |
+
</button>
|
| 271 |
+
<button
|
| 272 |
+
onClick={() => handleAction(c.proposal_id, 'APPROVED')}
|
| 273 |
+
disabled={actionLoading[c.proposal_id]}
|
| 274 |
+
className="px-3 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white transition flex items-center space-x-1 font-semibold shadow-sm shadow-emerald-600/30"
|
| 275 |
+
>
|
| 276 |
+
<CheckCircle2 className="w-3.5 h-3.5" />
|
| 277 |
+
<span>Accept & Resolve Conflict</span>
|
| 278 |
+
</button>
|
| 279 |
</div>
|
| 280 |
+
</div>
|
| 281 |
|
| 282 |
+
{/* Reviewer Note Drawer */}
|
| 283 |
+
{expandedComments[c.proposal_id] && (
|
| 284 |
+
<div className="pt-2">
|
| 285 |
+
<input
|
| 286 |
+
type="text"
|
| 287 |
+
placeholder="Attach reviewer comment or clinical justification..."
|
| 288 |
+
value={comments[c.proposal_id] || ''}
|
| 289 |
+
onChange={(e) => setComments({ ...comments, [c.proposal_id]: e.target.value })}
|
| 290 |
+
className="w-full bg-[#161F32] border border-slate-700 text-xs text-slate-200 px-3 py-2 rounded-lg focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
| 291 |
+
/>
|
| 292 |
</div>
|
| 293 |
+
)}
|
| 294 |
+
</div>
|
| 295 |
+
</div>
|
| 296 |
+
))}
|
| 297 |
|
| 298 |
+
{/* Additions Section */}
|
| 299 |
+
{(filterType === 'all' || filterType === 'additions') && additions.map((a) => (
|
| 300 |
+
<div
|
| 301 |
+
key={a.proposal_id}
|
| 302 |
+
className="bg-[#0D121F] border border-slate-800 rounded-xl overflow-hidden shadow-sm transition hover:border-emerald-500/40"
|
| 303 |
+
>
|
| 304 |
+
{/* Card Header */}
|
| 305 |
+
<div className="bg-[#121828] px-4 py-2.5 border-b border-slate-800 flex items-center justify-between">
|
| 306 |
+
<div className="flex items-center space-x-2.5">
|
| 307 |
+
<span className="p-1 rounded bg-emerald-500/20 text-emerald-400">
|
| 308 |
+
<Sparkles className="w-4 h-4 text-emerald-400" />
|
| 309 |
+
</span>
|
| 310 |
+
<div>
|
| 311 |
+
<span className="text-xs font-semibold text-white tracking-wide">{a.title || 'New Knowledge Delta'}</span>
|
| 312 |
+
<span className="ml-2 text-[10px] uppercase font-mono px-1.5 py-0.5 rounded bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 font-semibold">
|
| 313 |
+
+{a.type || 'CLAIM'}
|
| 314 |
</span>
|
|
|
|
| 315 |
</div>
|
| 316 |
</div>
|
|
|
|
|
|
|
| 317 |
|
| 318 |
+
<div className="flex items-center space-x-3 text-xs">
|
| 319 |
+
<span className="text-emerald-400 font-mono text-[11px] font-semibold">
|
| 320 |
+
{Math.round((a.confidence || 0.95) * 100)}% Confidence
|
| 321 |
+
</span>
|
| 322 |
+
</div>
|
| 323 |
+
</div>
|
| 324 |
+
|
| 325 |
+
{/* Card Body */}
|
| 326 |
+
<div className="p-4 space-y-3">
|
| 327 |
+
<div className="bg-[#0C1518] border border-emerald-900/30 rounded-lg p-3">
|
| 328 |
+
<div className="text-xs text-emerald-200 leading-relaxed font-mono">
|
| 329 |
+
{a.proposed_value || a.summary || 'Extracted content'}
|
| 330 |
</div>
|
| 331 |
+
</div>
|
| 332 |
|
| 333 |
+
{a.evidence && a.evidence.length > 0 && (
|
| 334 |
+
<div className="text-xs bg-[#101524] p-2.5 rounded-lg border border-slate-800 text-slate-300 flex items-start space-x-2">
|
| 335 |
+
<Quote className="w-4 h-4 text-slate-500 shrink-0 mt-0.5" />
|
| 336 |
+
<div>
|
| 337 |
+
<span className="text-[10px] text-slate-500 uppercase tracking-wider block font-mono">Document Citation</span>
|
| 338 |
+
<span className="text-slate-300 italic">{a.evidence[0]}</span>
|
| 339 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
</div>
|
| 341 |
+
)}
|
| 342 |
+
|
| 343 |
+
{/* Action Bar */}
|
| 344 |
+
<div className="flex items-center justify-end space-x-2 pt-2 border-t border-slate-800/80 text-xs">
|
| 345 |
+
<button
|
| 346 |
+
onClick={() => handleAction(a.proposal_id, 'REJECTED')}
|
| 347 |
+
disabled={actionLoading[a.proposal_id]}
|
| 348 |
+
className="px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white transition font-medium"
|
| 349 |
+
>
|
| 350 |
+
Reject
|
| 351 |
+
</button>
|
| 352 |
+
<button
|
| 353 |
+
onClick={() => handleAction(a.proposal_id, 'APPROVED')}
|
| 354 |
+
disabled={actionLoading[a.proposal_id]}
|
| 355 |
+
className="px-3 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white transition font-semibold flex items-center space-x-1"
|
| 356 |
+
>
|
| 357 |
+
<CheckCircle2 className="w-3.5 h-3.5" />
|
| 358 |
+
<span>Approve Fact</span>
|
| 359 |
+
</button>
|
| 360 |
</div>
|
| 361 |
+
</div>
|
| 362 |
+
</div>
|
| 363 |
+
))}
|
| 364 |
|
| 365 |
+
{/* Changes / Updates Section */}
|
| 366 |
+
{(filterType === 'all' || filterType === 'changes') && changes.map((ch) => (
|
| 367 |
+
<div
|
| 368 |
+
key={ch.proposal_id}
|
| 369 |
+
className="bg-[#0D121F] border border-slate-800 rounded-xl overflow-hidden shadow-sm transition hover:border-blue-500/40"
|
| 370 |
+
>
|
| 371 |
+
<div className="bg-[#121828] px-4 py-2.5 border-b border-slate-800 flex items-center justify-between">
|
| 372 |
+
<div className="flex items-center space-x-2.5">
|
| 373 |
+
<span className="p-1 rounded bg-blue-500/20 text-blue-400">
|
| 374 |
+
<FileDiff className="w-4 h-4 text-blue-400" />
|
| 375 |
+
</span>
|
| 376 |
+
<span className="text-xs font-semibold text-white">{ch.title || 'Knowledge Update'}</span>
|
| 377 |
+
<span className="text-[10px] uppercase font-mono px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-300 border border-blue-500/20">
|
| 378 |
+
UPDATE
|
| 379 |
+
</span>
|
| 380 |
+
</div>
|
| 381 |
+
</div>
|
| 382 |
+
|
| 383 |
+
<div className="p-4 space-y-3">
|
| 384 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs font-mono">
|
| 385 |
+
<div className="bg-[#141824] border border-slate-800 p-2.5 rounded text-slate-400">
|
| 386 |
+
<div className="text-[10px] text-slate-500 mb-1">- Previous Value</div>
|
| 387 |
+
<div>{ch.existing?.value || 'N/A'}</div>
|
| 388 |
</div>
|
| 389 |
+
<div className="bg-[#0C1518] border border-emerald-900/30 p-2.5 rounded text-emerald-300">
|
| 390 |
+
<div className="text-[10px] text-emerald-400 mb-1">+ Updated Value</div>
|
| 391 |
+
<div>{ch.proposed?.value || 'N/A'}</div>
|
| 392 |
</div>
|
| 393 |
</div>
|
| 394 |
+
|
| 395 |
+
<div className="flex items-center justify-end space-x-2 pt-2 border-t border-slate-800/80 text-xs">
|
| 396 |
+
<button
|
| 397 |
+
onClick={() => handleAction(ch.proposal_id, 'APPROVED')}
|
| 398 |
+
className="px-3 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-semibold transition"
|
| 399 |
+
>
|
| 400 |
+
Approve Update
|
| 401 |
+
</button>
|
| 402 |
+
</div>
|
| 403 |
</div>
|
| 404 |
+
</div>
|
| 405 |
+
))}
|
| 406 |
</div>
|
| 407 |
)}
|
| 408 |
</div>
|
studio/tailwind.config.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('tailwindcss').Config} */
|
| 2 |
+
export default {
|
| 3 |
+
content: [
|
| 4 |
+
"./index.html",
|
| 5 |
+
"./src/**/*.{js,ts,jsx,tsx}",
|
| 6 |
+
],
|
| 7 |
+
darkMode: 'class',
|
| 8 |
+
theme: {
|
| 9 |
+
extend: {
|
| 10 |
+
colors: {
|
| 11 |
+
canvas: {
|
| 12 |
+
default: '#080B11',
|
| 13 |
+
subtle: '#0D131F',
|
| 14 |
+
inset: '#06080D',
|
| 15 |
+
},
|
| 16 |
+
border: {
|
| 17 |
+
subtle: 'rgba(255, 255, 255, 0.08)',
|
| 18 |
+
strong: 'rgba(255, 255, 255, 0.16)',
|
| 19 |
+
},
|
| 20 |
+
},
|
| 21 |
+
fontFamily: {
|
| 22 |
+
sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
|
| 23 |
+
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
| 24 |
+
},
|
| 25 |
+
},
|
| 26 |
+
},
|
| 27 |
+
plugins: [],
|
| 28 |
+
}
|