diff --git a/extensions/dag-builder/.gitignore b/extensions/dag-builder/.gitignore new file mode 100644 index 00000000..799753f9 --- /dev/null +++ b/extensions/dag-builder/.gitignore @@ -0,0 +1,80 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +PYTHON-EGG-CACHE + +# Virtual Environment +venv/ +env/ +ENV/ +.venv + +# Node +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# TypeScript +*.tsbuildinfo +dist/ +*.d.ts.map + +# Build outputs +out/ +build/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Testing +coverage/ +.nyc_output/ +*.lcov +.coverage +.pytest_cache/ +htmlcov/ + +# Environment variables +.env +.env.local +.env.*.local + +# Logs +logs/ +*.log + +# Temporary files +*.tmp +.cache/ +.temp/ + +rsconnect-python +.posit diff --git a/extensions/dag-builder/README.md b/extensions/dag-builder/README.md new file mode 100644 index 00000000..6c42c914 --- /dev/null +++ b/extensions/dag-builder/README.md @@ -0,0 +1,162 @@ +# DAG Builder + +A visual DAG (Directed Acyclic Graph) builder and builder for Posit Connect content, built with FastAPI and React. This application allows you to create, visualize, and execute workflows that combine Posit Connect content with custom action nodes. + +## Features + +- **Visual DAG Builder**: Drag-and-drop interface for creating content workflows +- **Posit Connect Integration**: Search and add content from your Posit Connect server +- **Custom Action Nodes**: Add webhooks, delays, and conditional logic to your workflows +- **Background Publishing**: Publish DAGs to Posit Connect as Quarto documents with parallel execution +- **Historical Tracking**: Download and re-publish previous DAG versions +- **Real-time Validation**: Automatic cycle detection and connectivity validation + +## Directory Structure + +- **`py/`** - Python FastAPI backend application + - `app.py` - Main FastAPI server with WebSocket endpoint and REST API routes + - `dag_storage.py` - DAG persistence and artifact management + - `dag_validation.py` - DAG validation and topological sorting + - `connect_client.py` - Posit Connect API client + - `quarto_generator.py` - Quarto document generation + - `api_handlers.py` - REST API endpoint handlers +- **`srcts/`** - TypeScript/React frontend source code + - `main.tsx` - Entry point that renders the React app + - `DAGBuilder.tsx` - Main DAG builder component with ReactFlow + - `hooks/useWebSocket.tsx` - WebSocket hook for backend communication + - `styles.css` - Application styling and custom node styles +- **`artifacts/`** - Published DAG artifacts and metadata (generated) +- **`py/www/`** - Built JavaScript/CSS output (generated) +- **`node_modules/`** - npm dependencies (generated) + +## Prerequisites + +### Posit Connect Integration + +This application integrates with Posit Connect to search for content and deploy DAGs. You'll need: + +1. **Posit Connect Server**: Access to a Posit Connect server +2. **API Access**: Valid API credentials for your Posit Connect server + +### Setting Up Posit Connect Access + +#### 1. Create an API Key + +1. Log into your Posit Connect server +2. Navigate to your user profile (click your name in top-right corner) +3. Go to **"API Keys"** section +4. Click **"Create API Key"** +5. Give it a descriptive name (e.g., "DAG Builder") +6. Copy the generated API key (you won't see it again!) + +#### 2. Configure Environment Variables + +Create a `.env` file in the project root with your Posit Connect credentials: + +```bash +# .env file +CONNECT_SERVER_URL=https://your-connect-server.com +CONNECT_API_KEY=your-api-key-here +ENVIRONMENT=development +``` + +**Important Security Notes:** +- Never commit your `.env` file to version control (it's already in `.gitignore`) +- Use environment-specific API keys (separate keys for dev/staging/production) +- Rotate API keys regularly according to your organization's security policy + +#### 3. Install rsconnect-python (Required for Publishing) + +The DAG Builder uses `rsconnect-python` to deploy generated Quarto documents to Posit Connect: + +```bash +# Install rsconnect-python +pip install rsconnect-python + +# Configure rsconnect with your server (one-time setup) +rsconnect add \ + --account your-account-name \ + --name your-server-name \ + --server https://your-connect-server.com \ + --key your-api-key-here +``` + +## Getting Started + +```bash +# Install npm dependencies +npm install + +# Install Python dependencies +pip install -r py/requirements.txt + +# Start development with hot-reload (recommended) +npm run dev +``` + +The `npm run dev` command will automatically: +- Build the TypeScript/React frontend with CSS processing +- Start the FastAPI server with hot-reload enabled +- Open your browser to http://localhost:8000 +- Watch for changes and rebuild/restart as needed + +## Available npm Scripts + +This application includes several npm scripts for different development and build workflows: + +### Development Scripts (Recommended) + +- **`npm run dev`** - ๐Ÿš€ **Primary development command** - Builds frontend and starts FastAPI server automatically with hot-reload +- **`npm run watch`** - ๐Ÿ‘€ **Frontend-only watching** - Watch TypeScript/React files for changes and rebuild automatically +- **`npm run fastapi`** - ๐Ÿ–ฅ๏ธ **Backend-only server** - Start only the FastAPI server + +### Build Scripts + +- **`npm run build`** - ๐Ÿ”จ **Development build** - Build frontend once with TypeScript checking and CSS processing +- **`npm run clean`** - ๐Ÿงน **Clean build** - Remove all generated build files + +### Port Configuration + +You can customize the port (default is 8000): + +```bash +# Use custom port +PORT=3000 npm run dev +PORT=3000 npm run fastapi +``` + +## Manual Development Setup + +If you prefer more control, you can run the frontend and backend separately: + +### 1. Start Build Watcher (in one terminal) + +```bash +npm run watch +``` + +This watches TypeScript/React files for changes and rebuilds automatically. + +### 2. Run FastAPI Server (in another terminal) + +```bash +cd py +uvicorn app:app --reload --port 8000 +``` + +### 3. View Your App + +Open your browser and navigate to `http://localhost:8000`. + +## Production Deployment + +For production builds: + +```bash +# Create optimized production build +npm run build + +# Run FastAPI with production settings +cd py +uvicorn app:app --host 0.0.0.0 --port 8000 +``` diff --git a/extensions/dag-builder/package-lock.json b/extensions/dag-builder/package-lock.json new file mode 100644 index 00000000..c5eb7250 --- /dev/null +++ b/extensions/dag-builder/package-lock.json @@ -0,0 +1,1120 @@ +{ + "name": "dag-builder", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dag-builder", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@xyflow/react": "^12.8.5", + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@types/react": "^19.1.12", + "@types/react-dom": "^19.1.9", + "concurrently": "^9.0.1", + "esbuild": "^0.25.9", + "typescript": "^5.9.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/react": { + "version": "19.1.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", + "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@xyflow/react": { + "version": "12.8.5", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.5.tgz", + "integrity": "sha512-NRwcE8QE7dh6BbaIT7GmNccP7/RMDZJOKtzK4HQw599TAfzC8e5E/zw/7MwtpnSbbkqBYc+jZyOisd57sp/hPQ==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.69", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.69", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.69.tgz", + "integrity": "sha512-+KYwHDnsapZQ1xSgsYwOKYN93fUR770LwfCT5qrvcmzoMaabO1rHa6twiEk7E5VUIceWciF8ukgfq9JC83B5jQ==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/extensions/dag-builder/package.json b/extensions/dag-builder/package.json new file mode 100644 index 00000000..51cdfcaa --- /dev/null +++ b/extensions/dag-builder/package.json @@ -0,0 +1,26 @@ +{ + "private": true, + "name": "dag-builder", + "version": "0.1.0", + "type": "module", + "description": "DAG Builder - FastAPI + React web app", + "scripts": { + "dev": "concurrently -c auto \"npm run watch\" \"npm run fastapi\"", + "build": "concurrently -c auto \"tsc --noEmit\" \"esbuild srcts/main.tsx --bundle --outfile=py/www/main.js --format=esm --sourcemap --minify --alias:react=react\"", + "watch": "concurrently -c auto \"tsc --noEmit --watch --preserveWatchOutput\" \"esbuild srcts/main.tsx --bundle --outfile=py/www/main.js --format=esm --sourcemap --minify --alias:react=react --watch\"", + "fastapi": "cd py && uvicorn app:app --reload --port ${PORT:-8000}", + "clean": "rm -rf py/www" + }, + "devDependencies": { + "@types/react": "^19.1.12", + "@types/react-dom": "^19.1.9", + "concurrently": "^9.0.1", + "esbuild": "^0.25.9", + "typescript": "^5.9.2" + }, + "dependencies": { + "@xyflow/react": "^12.8.5", + "react": "^19.1.1", + "react-dom": "^19.1.1" + } +} diff --git a/extensions/dag-builder/py/api_handlers.py b/extensions/dag-builder/py/api_handlers.py new file mode 100644 index 00000000..a21d2f2f --- /dev/null +++ b/extensions/dag-builder/py/api_handlers.py @@ -0,0 +1,671 @@ +""" +REST API Handlers + +This module contains all REST API endpoint handlers for DAG CRUD operations. +Backend-agnostic request handling and response formatting. +""" + +from typing import Optional +import json +import logging +import tempfile +from starlette.requests import Request +from starlette.responses import JSONResponse, FileResponse + +from dag_storage import ( + save_artifact, load_user_artifacts, load_artifact_metadata, + delete_artifact, create_artifact_zip, artifact_exists +) +from dag_validation import ( + validate_dag, topological_sort_in_batches, convert_api_nodes_to_reactflow, + validate_api_dag_input +) +from connect_client import get_user_guid_from_api_key, deploy_to_connect +from quarto_generator import generate_quarto_document, create_deployment_files + +logger = logging.getLogger(__name__) + + +def extract_api_key(request: Request) -> Optional[str]: + """Extract API key from Authorization header.""" + auth_header = request.headers.get("Authorization", "") + if auth_header.startswith("Key "): + return auth_header[4:] # Remove "Key " prefix + return None + + +async def create_dag_handler(request: Request): + """Create a DAG programmatically via API.""" + try: + # Extract and validate API key + api_key = extract_api_key(request) + if not api_key: + return JSONResponse({ + "error": "Missing or invalid Authorization header. Use 'Authorization: Key '" + }, status_code=401) + + user_guid = get_user_guid_from_api_key(api_key) + if not user_guid: + return JSONResponse({"error": "Invalid API key"}, status_code=401) + + # Parse request body + body = await request.json() + + # Validate input + is_valid, errors = validate_api_dag_input(body) + if not is_valid: + return JSONResponse({ + "error": "Invalid request body", + "validation_errors": errors + }, status_code=400) + + title = body.get("title", "") + nodes_data = body.get("nodes", []) + edges_data = body.get("edges", []) + + # Convert nodes to ReactFlow format with auto-layout + positioned_nodes, reactflow_edges = convert_api_nodes_to_reactflow(nodes_data, edges_data) + + # Validate DAG structure + validation = validate_dag(positioned_nodes, reactflow_edges) + if not validation["isValid"]: + return JSONResponse({ + "error": "Invalid DAG structure", + "validation_errors": validation["errors"] + }, status_code=400) + + # Generate batches and Quarto content + dag_batches = topological_sort_in_batches(positioned_nodes, reactflow_edges) + quarto_content = generate_quarto_document(positioned_nodes, reactflow_edges, dag_batches) + + # Save artifact + artifact_metadata = save_artifact( + quarto_content, positioned_nodes, reactflow_edges, dag_batches, title, user_guid + ) + + return JSONResponse({ + "message": "DAG created successfully", + "dag_id": artifact_metadata["id"], + "metadata": { + "id": artifact_metadata["id"], + "title": artifact_metadata["title"], + "timestamp": artifact_metadata["timestamp"], + "nodes_count": artifact_metadata["nodes_count"], + "edges_count": artifact_metadata["edges_count"], + "batches_count": artifact_metadata["batches_count"] + } + }, status_code=201) + + except json.JSONDecodeError: + return JSONResponse({"error": "Invalid JSON in request body"}, status_code=400) + except ValueError as e: + return JSONResponse({"error": f"Validation error: {str(e)}"}, status_code=400) + except Exception as e: + logger.error(f"Error creating DAG: {e}") + return JSONResponse({"error": f"Failed to create DAG: {str(e)}"}, status_code=500) + + +async def list_dags_handler(request: Request): + """List all DAGs for the authenticated user.""" + try: + # Extract and validate API key + api_key = extract_api_key(request) + if not api_key: + return JSONResponse({ + "error": "Missing or invalid Authorization header. Use 'Authorization: Key '" + }, status_code=401) + + user_guid = get_user_guid_from_api_key(api_key) + if not user_guid: + return JSONResponse({"error": "Invalid API key"}, status_code=401) + + # Load user artifacts + artifacts = load_user_artifacts(user_guid) + + # Return simplified metadata + dags = [ + { + "id": artifact["id"], + "title": artifact["title"], + "timestamp": artifact["timestamp"], + "nodes_count": artifact["nodes_count"], + "edges_count": artifact["edges_count"], + "batches_count": artifact["batches_count"] + } + for artifact in artifacts + ] + + return JSONResponse({"dags": dags}) + + except Exception as e: + logger.error(f"Error listing DAGs: {e}") + return JSONResponse({"error": f"Failed to list DAGs: {str(e)}"}, status_code=500) + + +async def get_dag_handler(request: Request): + """Get metadata for a specific DAG.""" + try: + # Extract and validate API key + api_key = extract_api_key(request) + if not api_key: + return JSONResponse({ + "error": "Missing or invalid Authorization header. Use 'Authorization: Key '" + }, status_code=401) + + user_guid = get_user_guid_from_api_key(api_key) + if not user_guid: + return JSONResponse({"error": "Invalid API key"}, status_code=401) + + dag_id = request.path_params['dag_id'] + + # Load artifact metadata + metadata = load_artifact_metadata(user_guid, dag_id) + if not metadata: + return JSONResponse({"error": "DAG not found"}, status_code=404) + + return JSONResponse({"dag": metadata}) + + except Exception as e: + logger.error(f"Error getting DAG: {e}") + return JSONResponse({"error": f"Failed to get DAG: {str(e)}"}, status_code=500) + + +async def delete_dag_handler(request: Request): + """Delete a specific DAG.""" + try: + # Extract and validate API key + api_key = extract_api_key(request) + if not api_key: + return JSONResponse({ + "error": "Missing or invalid Authorization header. Use 'Authorization: Key '" + }, status_code=401) + + user_guid = get_user_guid_from_api_key(api_key) + if not user_guid: + return JSONResponse({"error": "Invalid API key"}, status_code=401) + + dag_id = request.path_params['dag_id'] + + # Check if DAG exists and delete it + if not artifact_exists(user_guid, dag_id): + return JSONResponse({"error": "DAG not found"}, status_code=404) + + success = delete_artifact(user_guid, dag_id) + if not success: + return JSONResponse({"error": "Failed to delete DAG"}, status_code=500) + + return JSONResponse({"message": "DAG deleted successfully"}) + + except Exception as e: + logger.error(f"Error deleting DAG: {e}") + return JSONResponse({"error": f"Failed to delete DAG: {str(e)}"}, status_code=500) + + +async def publish_dag_handler(request: Request): + """Publish a specific DAG to Posit Connect.""" + try: + # Extract and validate API key + api_key = extract_api_key(request) + if not api_key: + return JSONResponse({ + "error": "Missing or invalid Authorization header. Use 'Authorization: Key '" + }, status_code=401) + + user_guid = get_user_guid_from_api_key(api_key) + if not user_guid: + return JSONResponse({"error": "Invalid API key"}, status_code=401) + + dag_id = request.path_params['dag_id'] + + # Load artifact metadata + metadata = load_artifact_metadata(user_guid, dag_id) + if not metadata: + return JSONResponse({"error": "DAG not found"}, status_code=404) + + # Get nodes, edges, and title from metadata + nodes = metadata.get("nodes", []) + edges = metadata.get("edges", []) + title = metadata.get("title", metadata.get("name", "")) + + if not nodes: + return JSONResponse({"error": "DAG has no nodes to publish"}, status_code=400) + + # Validate DAG before publishing + validation = validate_dag(nodes, edges) + if not validation["isValid"]: + return JSONResponse({ + "error": "Cannot publish invalid DAG", + "validation_errors": validation["errors"] + }, status_code=400) + + # Generate batches and Quarto content + dag_batches = topological_sort_in_batches(nodes, edges) + quarto_content = generate_quarto_document(nodes, edges, dag_batches) + + # Create temporary directory for deployment files + with tempfile.TemporaryDirectory() as temp_dir: + create_deployment_files(quarto_content, temp_dir) + + # Deploy to Connect + deploy_title = title if title.strip() else f"dag-execution-{dag_id}" + result = deploy_to_connect(temp_dir, deploy_title) + + if result["success"]: + logger.info(f"DAG '{deploy_title}' deployed successfully to Posit Connect via API") + return JSONResponse({ + "message": f"DAG '{deploy_title}' published successfully to Posit Connect", + "dag_id": dag_id, + "deployment_title": deploy_title + }) + else: + return JSONResponse({ + "error": result["message"] + }, status_code=500) + + except json.JSONDecodeError: + return JSONResponse({"error": "Invalid metadata format"}, status_code=400) + except Exception as e: + logger.error(f"Error publishing DAG: {e}") + return JSONResponse({"error": f"Failed to publish DAG: {str(e)}"}, status_code=500) + + +async def download_artifact_handler(request: Request): + """Serve artifact zip files for download.""" + try: + user_guid = request.path_params['user_guid'] + artifact_id = request.path_params['artifact_id'] + + if not artifact_exists(user_guid, artifact_id): + return JSONResponse({"error": "Artifact not found"}, status_code=404) + + # Create or get existing zip file + try: + zip_path = create_artifact_zip(artifact_id, user_guid) + logger.info(f"Created zip file at: {zip_path}") + except FileNotFoundError as e: + logger.error(f"Artifact not found: {e}") + return JSONResponse({"error": "Artifact not found"}, status_code=404) + + # Verify the zip file exists and has content + if zip_path.exists(): + file_size = zip_path.stat().st_size + logger.info(f"Zip file exists with size: {file_size} bytes") + + if file_size == 0: + logger.warning(f"Zip file is empty: {zip_path}") + return JSONResponse({"error": "Artifact zip file is empty"}, status_code=500) + + # Ensure path is absolute + absolute_path = zip_path.resolve() + logger.info(f"Serving file from: {absolute_path}") + + return FileResponse( + path=str(absolute_path), + filename=f"dag_execution_{artifact_id}.zip", + media_type="application/zip", + headers={ + "Content-Disposition": f"attachment; filename=dag_execution_{artifact_id}.zip", + "Cache-Control": "no-cache" + } + ) + else: + logger.error(f"Zip file does not exist: {zip_path}") + return JSONResponse({"error": "Failed to create artifact zip"}, status_code=500) + + except Exception as e: + logger.error(f"Error serving artifact download: {e}") + return JSONResponse({"error": "Failed to serve artifact"}, status_code=500) + + +async def api_docs_handler(request: Request): + """Serve OpenAPI documentation.""" + openapi_spec = { + "openapi": "3.0.0", + "info": { + "title": "DAG Builder API", + "version": "1.0.0", + "description": "REST API for managing Directed Acyclic Graphs (DAGs) in the DAG Builder application" + }, + "servers": [ + { + "url": "/api", + "description": "API Server" + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "components": { + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "Authorization", + "description": "API key authentication. Use 'Bearer YOUR_API_KEY'" + } + }, + "schemas": { + "DAGNode": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "label": {"type": "string"}, + "type": {"type": "string", "enum": ["content", "custom"]}, + "data": {"type": "object"} + }, + "required": ["id", "label", "type"] + }, + "DAGEdge": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "source": {"type": "string"}, + "target": {"type": "string"} + }, + "required": ["id", "source", "target"] + }, + "DAGInput": { + "type": "object", + "properties": { + "title": {"type": "string"}, + "nodes": { + "type": "array", + "items": {"$ref": "#/components/schemas/DAGNode"} + }, + "edges": { + "type": "array", + "items": {"$ref": "#/components/schemas/DAGEdge"} + } + }, + "required": ["title", "nodes", "edges"] + }, + "DAGMetadata": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "title": {"type": "string"}, + "timestamp": {"type": "string"}, + "nodes_count": {"type": "integer"}, + "edges_count": {"type": "integer"}, + "batches_count": {"type": "integer"} + } + }, + "Error": { + "type": "object", + "properties": { + "error": {"type": "string"} + } + } + } + }, + "paths": { + "/dags": { + "post": { + "summary": "Create a new DAG", + "description": "Create and save a new DAG with the provided nodes and edges", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/DAGInput"} + } + } + }, + "responses": { + "201": { + "description": "DAG created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": {"type": "string"}, + "dag_id": {"type": "string"} + } + } + } + } + }, + "400": { + "description": "Invalid DAG data", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + } + } + }, + "get": { + "summary": "List all DAGs", + "description": "Retrieve all DAGs for the authenticated user", + "responses": { + "200": { + "description": "List of DAGs", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": {"$ref": "#/components/schemas/DAGMetadata"} + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + } + } + } + }, + "/dags/{dag_id}": { + "get": { + "summary": "Get a specific DAG", + "description": "Retrieve details of a specific DAG by ID", + "parameters": [ + { + "name": "dag_id", + "in": "path", + "required": True, + "schema": {"type": "string"}, + "description": "The ID of the DAG to retrieve" + } + ], + "responses": { + "200": { + "description": "DAG details", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/DAGMetadata"} + } + } + }, + "404": { + "description": "DAG not found", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + } + } + }, + "delete": { + "summary": "Delete a DAG", + "description": "Delete a specific DAG by ID", + "parameters": [ + { + "name": "dag_id", + "in": "path", + "required": True, + "schema": {"type": "string"}, + "description": "The ID of the DAG to delete" + } + ], + "responses": { + "200": { + "description": "DAG deleted successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": {"type": "string"} + } + } + } + } + }, + "404": { + "description": "DAG not found", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + } + } + } + }, + "/dags/{dag_id}/publish": { + "post": { + "summary": "Publish a DAG to Posit Connect", + "description": "Deploy a DAG to Posit Connect for execution", + "parameters": [ + { + "name": "dag_id", + "in": "path", + "required": True, + "schema": {"type": "string"}, + "description": "The ID of the DAG to publish" + } + ], + "responses": { + "200": { + "description": "DAG published successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": {"type": "string"}, + "dag_id": {"type": "string"}, + "deployment_title": {"type": "string"} + } + } + } + } + }, + "400": { + "description": "Invalid DAG or validation failed", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + }, + "404": { + "description": "DAG not found", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + } + } + } + } + } + } + } + + # Return JSON OpenAPI spec + return JSONResponse(openapi_spec) + + +async def swagger_ui_handler(request: Request): + """Serve Swagger UI for interactive API documentation.""" + html_content = """ + + + + DAG Builder API Documentation + + + + +
+ + + + + + """ + from starlette.responses import HTMLResponse + return HTMLResponse(html_content) diff --git a/extensions/dag-builder/py/app.py b/extensions/dag-builder/py/app.py new file mode 100644 index 00000000..bf59abdd --- /dev/null +++ b/extensions/dag-builder/py/app.py @@ -0,0 +1,567 @@ +""" +DAG Builder Main Application - FastAPI Backend + +Refactored application using FastAPI for REST API and WebSocket communication. +This file replaces the Shiny-based architecture with a standard Python web framework. +""" + +from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Query +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse, JSONResponse, HTMLResponse +from pathlib import Path +import json +import asyncio +import logging +from typing import Optional, List, Dict, Any +import tempfile +from datetime import datetime + +# Import our modular components +from dag_storage import ( + save_artifact, load_user_artifacts, create_artifact_zip, + load_artifact_metadata, get_user_artifacts_dir, find_dag_by_title, + delete_artifact +) +from dag_validation import validate_dag, topological_sort_in_batches +from connect_client import get_current_user_guid, search_posit_connect, deploy_to_connect_async +from quarto_generator import generate_quarto_document, create_deployment_files +from api_handlers import ( + create_dag_handler, list_dags_handler, get_dag_handler, + delete_dag_handler, publish_dag_handler, + api_docs_handler, swagger_ui_handler +) + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Create FastAPI app +app = FastAPI( + title="DAG Builder API", + description="Visual DAG builder and builder for Posit Connect content", + version="1.0.0" +) + +# Global state for connected WebSocket clients +class ConnectionManager: + def __init__(self): + self.active_connections: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket): + await websocket.accept() + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_message(self, websocket: WebSocket, message_type: str, data: Dict[str, Any]): + await websocket.send_json({"type": message_type, "data": data}) + + async def broadcast(self, message_type: str, data: Dict[str, Any]): + for connection in self.active_connections: + try: + await connection.send_json({"type": message_type, "data": data}) + except: + pass + +manager = ConnectionManager() + +# In-memory storage for user sessions (replace with Redis or database in production) +user_sessions: Dict[str, Dict[str, Any]] = {} + + +async def publish_dag_background(nodes: List[Dict], edges: List[Dict], title: str, websocket: Optional[WebSocket]): + """Background task for publishing DAG using the modular architecture.""" + + async def send_status(message: str, msg_type: str): + """Send status message to either specific websocket or broadcast to all.""" + status_data = {"message": message, "type": msg_type} + if websocket: + await manager.send_message(websocket, "logEvent", status_data) + else: + await manager.broadcast("logEvent", status_data) + + try: + await send_status("Starting DAG publishing process in background...", "info") + + # Generate batches for parallel execution + dag_batches = topological_sort_in_batches(nodes, edges) + + logger.info(f"DAG has {len(nodes)} nodes and {len(edges)} edges, organized into {len(dag_batches)} execution batches") + + # Generate Quarto document + quarto_content = generate_quarto_document(nodes, edges, dag_batches) + + # Create temporary directory for deployment files + with tempfile.TemporaryDirectory() as temp_dir: + create_deployment_files(quarto_content, temp_dir) + + # Create message callback for deployment progress + async def message_callback(message: str, msg_type: str): + await send_status(message, msg_type) + + # Deploy to Connect + result = await deploy_to_connect_async(temp_dir, title, message_callback) + + if result["success"]: + # Save artifact for historical tracking + try: + user_guid = get_current_user_guid() + if user_guid: + artifact_metadata = save_artifact(quarto_content, nodes, edges, dag_batches, title, user_guid) + + # Broadcast updated artifacts list to all connected clients + user_artifacts = load_user_artifacts(user_guid) + await manager.broadcast("artifacts_list", user_artifacts) + + await send_status( + f"DAG '{title or artifact_metadata['name']}' published successfully!", + "success" + ) + + logger.info(f"Saved artifact: {artifact_metadata['id']}") + else: + await send_status(f"DAG '{title}' published successfully!", "success") + except Exception as e: + logger.error(f"Failed to save artifact: {e}") + await send_status( + f"DAG published but failed to save artifact: {str(e)}", + "warning" + ) + else: + await send_status(f"Publishing failed: {result['message']}", "error") + + except ValueError as e: + # Handle cycle detection + await send_status(f"DAG validation failed: {str(e)}", "error") + except Exception as e: + logger.error(f"Publishing error: {e}") + await send_status(f"Publishing failed: {str(e)}", "error") + + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + """ + WebSocket endpoint for real-time bidirectional communication. + + Now focused on real-time updates only: + - DAG validation (real-time as user edits) + - Search results (real-time as user types) + - Status messages (logEvent broadcasts from background tasks) + - Artifact list updates (broadcasts when DAGs are saved/deleted) + + CRUD operations have been moved to REST endpoints. + """ + await manager.connect(websocket) + + # Send initial user GUID and artifacts + user_guid = get_current_user_guid() + if user_guid: + await manager.send_message(websocket, "user_guid", {"guid": user_guid}) + user_artifacts = load_user_artifacts(user_guid) + await manager.send_message(websocket, "artifacts_list", user_artifacts) + else: + logger.warning("Could not retrieve user GUID") + + try: + while True: + data = await websocket.receive_json() + message_type = data.get("type") + payload = data.get("data", {}) + + logger.info(f"Received WebSocket message: {message_type}") + + if message_type == "dag_data": + # Real-time DAG validation as user edits + nodes = payload.get("nodes", []) + edges = payload.get("edges", []) + + if nodes: + validation = validate_dag(nodes, edges) + await manager.send_message(websocket, "dag_validation", validation) + + elif message_type == "search_query": + # Real-time search as user types + query = payload.get("query", "") + if query and len(query.strip()) >= 2: + try: + results = search_posit_connect(query.strip()) + await manager.send_message(websocket, "search_results", results) + except Exception as e: + logger.error(f"Search error: {e}") + await manager.send_message(websocket, "search_results", []) + else: + await manager.send_message(websocket, "search_results", []) + + else: + # Unknown message type + logger.warning(f"Unknown WebSocket message type: {message_type}") + await manager.send_message(websocket, "logEvent", { + "message": f"Unknown message type: {message_type}. Use REST API for CRUD operations.", + "type": "warning" + }) + + except WebSocketDisconnect: + manager.disconnect(websocket) + logger.info("WebSocket disconnected") + + +# REST API routes + +@app.get("/download-artifact/{user_guid}/{artifact_id}") +async def download_artifact(user_guid: str, artifact_id: str): + """Download artifact as zip file.""" + from pathlib import Path + import re + + def to_kebab_case(text: str) -> str: + """Convert text to kebab-case.""" + # Replace spaces and underscores with hyphens + text = re.sub(r'[\s_]+', '-', text) + # Remove special characters except hyphens + text = re.sub(r'[^a-zA-Z0-9\-]', '', text) + # Convert to lowercase + text = text.lower() + # Remove consecutive hyphens + text = re.sub(r'-+', '-', text) + # Remove leading/trailing hyphens + text = text.strip('-') + return text + + try: + # Load metadata to get the title + metadata = load_artifact_metadata(user_guid, artifact_id) + if not metadata: + raise HTTPException(status_code=404, detail="Artifact not found") + + # Get title and convert to kebab-case + title = metadata.get("title", metadata.get("name", "dag")) + kebab_title = to_kebab_case(title) + + # Create or get existing zip file + zip_path = create_artifact_zip(artifact_id, user_guid) + + if not zip_path.exists(): + raise HTTPException(status_code=404, detail="Artifact not found") + + # Create filename with kebab-case title and artifact ID + filename = f"{kebab_title}-{artifact_id}.zip" + + # Return file for download + return FileResponse( + path=str(zip_path), + filename=filename, + media_type="application/zip" + ) + except FileNotFoundError: + raise HTTPException(status_code=404, detail="Artifact not found") + except Exception as e: + logger.error(f"Error serving artifact download: {e}") + raise HTTPException(status_code=500, detail=f"Failed to prepare download: {str(e)}") + + +@app.post("/api/dags/save") +async def save_dag_endpoint(request: Dict[str, Any]): + """Save a DAG (create or update).""" + nodes = request.get("nodes", []) + edges = request.get("edges", []) + title = request.get("title", "") + loaded_dag_id = request.get("loaded_dag_id") + + if not nodes: + raise HTTPException(status_code=400, detail="Cannot save empty DAG") + + if not title.strip(): + raise HTTPException(status_code=400, detail="DAG title is required") + + # Validate DAG + validation = validate_dag(nodes, edges) + if not validation["isValid"]: + raise HTTPException( + status_code=400, + detail=f"Cannot save invalid DAG: {', '.join(validation['errors'])}" + ) + + user_guid = get_current_user_guid() + if not user_guid: + raise HTTPException(status_code=401, detail="User not authenticated") + + is_updating = loaded_dag_id is not None + + # If not updating, check for title uniqueness + if not is_updating: + existing_dag_id = find_dag_by_title(user_guid, title) + if existing_dag_id: + raise HTTPException( + status_code=409, + detail=f"A DAG with title '{title}' already exists. Load the existing DAG to update it." + ) + + try: + # Generate batches and save + dag_batches = topological_sort_in_batches(nodes, edges) + quarto_content = generate_quarto_document(nodes, edges, dag_batches) + + artifact_metadata = save_artifact( + quarto_content, nodes, edges, dag_batches, title, user_guid, + update_existing=is_updating, + artifact_id=loaded_dag_id # Pass the specific artifact ID when updating + ) + + # Broadcast updated artifacts list to all connected clients + user_artifacts = load_user_artifacts(user_guid) + await manager.broadcast("artifacts_list", user_artifacts) + await manager.broadcast("loaded_dag_id", {"id": artifact_metadata['id']}) + + return JSONResponse({ + "success": True, + "message": f"DAG '{title}' {'updated' if is_updating else 'saved'} successfully!", + "artifact_id": artifact_metadata['id'], + "artifact": artifact_metadata + }) + except Exception as e: + logger.error(f"Save error: {e}") + raise HTTPException(status_code=500, detail=f"Save failed: {str(e)}") + + +@app.get("/api/dags/{artifact_id}") +async def load_dag_endpoint(artifact_id: str): + """Load a specific DAG by ID.""" + user_guid = get_current_user_guid() + if not user_guid: + raise HTTPException(status_code=401, detail="User not authenticated") + + try: + metadata = load_artifact_metadata(user_guid, artifact_id) + if not metadata: + raise HTTPException(status_code=404, detail=f"DAG {artifact_id} not found") + + return JSONResponse({ + "success": True, + "dag": { + "nodes": metadata.get("nodes", []), + "edges": metadata.get("edges", []), + "title": metadata.get("title", metadata.get("name", "")) + }, + "artifact_id": artifact_id + }) + except HTTPException: + raise + except Exception as e: + logger.error(f"Error loading DAG: {e}") + raise HTTPException(status_code=500, detail=f"Failed to load DAG: {str(e)}") + + +@app.delete("/api/dags/{artifact_id}") +async def delete_dag_endpoint(artifact_id: str): + """Delete a specific DAG.""" + user_guid = get_current_user_guid() + if not user_guid: + raise HTTPException(status_code=401, detail="User not authenticated") + + try: + success = delete_artifact(user_guid, artifact_id) + if not success: + raise HTTPException(status_code=404, detail=f"Failed to delete artifact {artifact_id}") + + # Broadcast updated artifacts list + user_artifacts = load_user_artifacts(user_guid) + await manager.broadcast("artifacts_list", user_artifacts) + + return JSONResponse({ + "success": True, + "message": f"DAG {artifact_id} deleted successfully" + }) + except HTTPException: + raise + except Exception as e: + logger.error(f"Error deleting artifact: {e}") + raise HTTPException(status_code=500, detail=f"Failed to delete artifact: {str(e)}") + + +@app.post("/api/dags/{artifact_id}/clone") +async def clone_dag_endpoint(artifact_id: str): + """Clone an existing DAG with a new title.""" + user_guid = get_current_user_guid() + if not user_guid: + raise HTTPException(status_code=401, detail="User not authenticated") + + try: + # Load the original DAG + metadata = load_artifact_metadata(user_guid, artifact_id) + if not metadata: + raise HTTPException(status_code=404, detail=f"Artifact {artifact_id} not found") + + # Get original title + original_title = metadata.get("title", metadata.get("name", "DAG")) + + # Find the next available copy number + user_artifacts = load_user_artifacts(user_guid) + copy_number = 1 + while True: + new_title = f"{original_title} - copy {copy_number}" + # Check if this title exists + title_exists = any( + artifact.get("title", "").lower() == new_title.lower() + for artifact in user_artifacts + ) + if not title_exists: + break + copy_number += 1 + + # Get nodes and edges from metadata + nodes = metadata.get("nodes", []) + edges = metadata.get("edges", []) + + # Validate and generate new artifact + validation = validate_dag(nodes, edges) + if not validation["isValid"]: + raise HTTPException( + status_code=400, + detail=f"Source DAG is invalid: {', '.join(validation['errors'])}" + ) + + # Generate batches and save as new artifact + dag_batches = topological_sort_in_batches(nodes, edges) + quarto_content = generate_quarto_document(nodes, edges, dag_batches) + + new_artifact_metadata = save_artifact( + quarto_content, nodes, edges, dag_batches, new_title, user_guid, + update_existing=False, + artifact_id=None # Create new artifact + ) + + # Broadcast updated artifacts list + user_artifacts = load_user_artifacts(user_guid) + await manager.broadcast("artifacts_list", user_artifacts) + + return JSONResponse({ + "success": True, + "message": f"DAG cloned as '{new_title}'", + "artifact_id": new_artifact_metadata['id'], + "artifact": new_artifact_metadata + }) + except HTTPException: + raise + except Exception as e: + logger.error(f"Error cloning artifact: {e}") + raise HTTPException(status_code=500, detail=f"Failed to clone DAG: {str(e)}") + + +@app.get("/api/dags") +async def list_dags_endpoint(): + """List all DAGs for the current user.""" + user_guid = get_current_user_guid() + if not user_guid: + raise HTTPException(status_code=401, detail="User not authenticated") + + try: + artifacts = load_user_artifacts(user_guid) + return JSONResponse({ + "success": True, + "artifacts": artifacts + }) + except Exception as e: + logger.error(f"Error listing DAGs: {e}") + raise HTTPException(status_code=500, detail=f"Failed to list DAGs: {str(e)}") + + +@app.post("/api/dags/publish") +async def publish_dag_endpoint(request: Dict[str, Any]): + """Publish a DAG to Posit Connect (starts background task).""" + nodes = request.get("nodes", []) + edges = request.get("edges", []) + title = request.get("title", "") + + if not nodes: + raise HTTPException(status_code=400, detail="Cannot publish empty DAG") + + # Validate DAG + validation = validate_dag(nodes, edges) + if not validation["isValid"]: + raise HTTPException( + status_code=400, + detail=f"Cannot publish invalid DAG: {', '.join(validation['errors'])}" + ) + + # Start publishing as background task + # Status updates will be sent via WebSocket + asyncio.create_task(publish_dag_background(nodes, edges, title, None)) + + return JSONResponse({ + "success": True, + "message": "Publishing started. Check status messages for progress." + }) + + +@app.post("/api/dags/{artifact_id}/publish") +async def publish_saved_dag_endpoint(artifact_id: str): + """Publish a saved DAG to Posit Connect (starts background task).""" + user_guid = get_current_user_guid() + if not user_guid: + raise HTTPException(status_code=401, detail="User not authenticated") + + try: + metadata = load_artifact_metadata(user_guid, artifact_id) + if not metadata: + raise HTTPException(status_code=404, detail=f"Artifact {artifact_id} not found") + + nodes = metadata.get("nodes", []) + edges = metadata.get("edges", []) + title = metadata.get("title", metadata.get("name", "")) + + # Start publishing as background task + # Status updates will be sent via WebSocket + asyncio.create_task(publish_dag_background(nodes, edges, title, None)) + + return JSONResponse({ + "success": True, + "message": f"Publishing '{title}' started. Check status messages for progress." + }) + except HTTPException: + raise + except Exception as e: + logger.error(f"Error in direct publish: {e}") + raise HTTPException(status_code=500, detail=f"Failed to start publish: {str(e)}") + + +@app.get("/api/docs") +async def api_docs(): + """Get OpenAPI documentation.""" + return await swagger_ui_handler() + + +@app.get("/api/docs/openapi.json") +async def openapi_json(): + """Get OpenAPI JSON schema.""" + return await api_docs_handler() + + +# Serve static files (React build output) +www_path = Path(__file__).parent / "www" +app.mount("/assets", StaticFiles(directory=www_path), name="assets") + + +@app.get("/") +async def root(): + """Serve the main HTML page.""" + return HTMLResponse(""" + + + + + + DAG Builder + + + +
+ + + +""") + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info") diff --git a/extensions/dag-builder/py/connect_client.py b/extensions/dag-builder/py/connect_client.py new file mode 100644 index 00000000..07b397bf --- /dev/null +++ b/extensions/dag-builder/py/connect_client.py @@ -0,0 +1,227 @@ +""" +Posit Connect Client Operations + +This module handles all Posit Connect API interactions including user authentication, +content search, and deployment operations. Backend-agnostic Connect integration. +""" + +from typing import List, Dict, Any, Optional +import logging +import subprocess +import tempfile +import os + +logger = logging.getLogger(__name__) + +try: + from posit.connect import Client + POSIT_CONNECT_AVAILABLE = True +except ImportError: + POSIT_CONNECT_AVAILABLE = False + logger.warning("posit-sdk not available. Search functionality will be mocked.") + + +def get_current_user_guid() -> Optional[str]: + """Get the current user's GUID from Posit Connect.""" + if not POSIT_CONNECT_AVAILABLE: + # Mock GUID for development + return "mock-user-guid-dev" + + try: + # Initialize Posit Connect client + client = Client() + + # Get current user information + response = client.get("/v1/user") + user_data = response.json() + + user_guid = user_data.get("guid") + username = user_data.get("username", "unknown") + + logger.info(f"Retrieved user GUID: {user_guid} for username: {username}") + return user_guid + + except Exception as e: + logger.error(f"Error getting user GUID: {e}") + return None + + +def get_user_guid_from_api_key(api_key: str) -> Optional[str]: + """Get user GUID from API key using Posit Connect API.""" + if not POSIT_CONNECT_AVAILABLE: + # Mock GUID for development + return "mock-user-guid-dev" + + try: + # Initialize Posit Connect client with provided API key + original_key = os.getenv('CONNECT_API_KEY') + os.environ['CONNECT_API_KEY'] = api_key + + client = Client() + response = client.get("/v1/user") + user_data = response.json() + + # Restore original key + if original_key: + os.environ['CONNECT_API_KEY'] = original_key + + user_guid = user_data.get("guid") + logger.info(f"API key resolved to user GUID: {user_guid}") + return user_guid + + except Exception as e: + logger.error(f"Error resolving API key to user GUID: {e}") + return None + + +def search_posit_connect(query: str) -> List[Dict[str, Any]]: + """Search Posit Connect for content matching the query.""" + if not POSIT_CONNECT_AVAILABLE: + # Mock data for development + mock_results = [ + { + "guid": f"mock-guid-{i}", + "name": f"Sample Content {i}: {query}", + "content_type": "notebook" if i % 2 == 0 else "report", + "url": f"https://connect.example.com/content/mock-guid-{i}/", + "description": f"This is a sample {('notebook' if i % 2 == 0 else 'report')} that demonstrates {query} functionality.", + "created_time": "2024-01-15T10:30:00Z", + "last_deployed_time": "2024-01-20T14:45:00Z", + "author": f"user{i}" + } + for i in range(1, 6) + ] + return mock_results + + try: + # Initialize Posit Connect client + client = Client() + + # Use the search endpoint directly for fuzzy search + search_params = { + "q": query, # Search terms will match name, title, or description + "page_size": 10, # Limit to 10 results + "sort": "name", # Sort by name + "order": "desc", # Descending order + "include": "owner" # Include owner details + } + + # Make the search request using the client's get method + response = client.get("/v1/search/content", params=search_params) + search_data = response.json() + + logger.info(f"Search query: '{query}' - Found {len(search_data.get('results', []))} items") + + results = [] + for content in search_data.get("results", [])[:10]: # Limit to 10 results + # Format dates + created_time = content.get("created_time", "") + last_deployed_time = content.get("last_deployed_time", "") + + # Get owner information + owner_info = content.get("owner", {}) + author = owner_info.get("username", "Unknown") if owner_info else "Unknown" + + results.append({ + "guid": content.get("guid"), + "name": content.get("name") or content.get("title", "Unknown"), + "content_type": content.get("app_mode", "unknown"), + "url": content.get("content_url", ""), + "description": content.get("description", "")[:100] + "..." if content.get("description") and len(content.get("description", "")) > 100 else content.get("description", ""), + "created_time": created_time, + "last_deployed_time": last_deployed_time, + "author": author + }) + + return results + except Exception as e: + logger.error(f"Error searching Posit Connect: {e}") + return [] + + +def deploy_to_connect(temp_dir: str, title: str = "") -> Dict[str, Any]: + """Deploy the generated content to Posit Connect.""" + deploy_title = title if title.strip() else f"dag-execution-{os.path.basename(temp_dir)}" + + try: + logger.info(f"Starting deployment of '{deploy_title}' to Posit Connect...") + + # Try using rsconnect-python for deployment + cmd = [ + "rsconnect", "deploy", "quarto", + "--title", deploy_title, + "--no-verify", + str(temp_dir) + ] + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + + if result.returncode == 0: + logger.info(f"DAG '{deploy_title}' deployed successfully to Posit Connect") + return {"success": True, "message": "Deployment successful"} + else: + error_msg = result.stderr + result.stdout + logger.error(f"Deployment failed: {error_msg}") + return {"success": False, "message": f"Deployment failed: {error_msg}"} + + except subprocess.TimeoutExpired: + error_msg = "Deployment timed out" + logger.error(error_msg) + return {"success": False, "message": error_msg} + except FileNotFoundError: + error_msg = "rsconnect-python not found. Please install rsconnect-python package." + logger.error(error_msg) + return {"success": False, "message": error_msg} + except Exception as e: + error_msg = f"Deployment error: {str(e)}" + logger.error(error_msg) + return {"success": False, "message": error_msg} + + +async def deploy_to_connect_async(temp_dir: str, title: str = "", message_callback=None) -> Dict[str, Any]: + """ + Async version of deploy_to_connect with optional message callback. + Message callback should be a coroutine that accepts (message: str, type: str). + """ + deploy_title = title if title.strip() else f"dag-execution-{os.path.basename(temp_dir)}" + + if message_callback: + await message_callback(f"Starting deployment of '{deploy_title}' to Posit Connect...", "info") + + try: + # Try using rsconnect-python for deployment + cmd = [ + "rsconnect", "deploy", "quarto", + "--title", deploy_title, + "--no-verify", + str(temp_dir) + ] + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + + if result.returncode == 0: + if message_callback: + await message_callback(f"DAG '{deploy_title}' deployed successfully to Posit Connect", "success") + return {"success": True, "message": "Deployment successful"} + else: + error_msg = result.stderr + result.stdout + logger.error(f"Deployment failed: {error_msg}") + if message_callback: + await message_callback(f"Deployment failed: {error_msg}", "error") + return {"success": False, "message": f"Deployment failed: {error_msg}"} + + except subprocess.TimeoutExpired: + error_msg = "Deployment timed out" + if message_callback: + await message_callback(error_msg, "error") + return {"success": False, "message": error_msg} + except FileNotFoundError: + error_msg = "rsconnect-python not found. Please install rsconnect-python package." + if message_callback: + await message_callback(error_msg, "error") + return {"success": False, "message": error_msg} + except Exception as e: + error_msg = f"Deployment error: {str(e)}" + if message_callback: + await message_callback(error_msg, "error") + return {"success": False, "message": error_msg} \ No newline at end of file diff --git a/extensions/dag-builder/py/dag_storage.py b/extensions/dag-builder/py/dag_storage.py new file mode 100644 index 00000000..7903c1d3 --- /dev/null +++ b/extensions/dag-builder/py/dag_storage.py @@ -0,0 +1,249 @@ +""" +DAG Storage Management + +This module handles artifact storage, user directories, and DAG metadata management. +Backend-agnostic storage operations for DAG builder. +""" + +from pathlib import Path +from typing import List, Dict, Any, Optional +from datetime import datetime +import json +import zipfile +import logging + +logger = logging.getLogger(__name__) + +# Global configuration +APP_DATA_DIR = Path("app-data") +APP_DATA_DIR.mkdir(exist_ok=True) + + +def get_user_artifacts_dir(user_guid: str) -> Path: + """Get the artifacts directory for a specific user.""" + user_dir = APP_DATA_DIR / user_guid + user_dir.mkdir(exist_ok=True) + return user_dir + + +def find_dag_by_title(user_guid: str, title: str) -> Optional[str]: + """Find existing DAG ID by title for a user.""" + if not user_guid or not title.strip(): + return None + + try: + user_artifacts = load_user_artifacts(user_guid) + for artifact in user_artifacts: + if artifact.get("title", "").strip().lower() == title.strip().lower(): + return artifact.get("id") + return None + except Exception as e: + logger.error(f"Error finding DAG by title: {e}") + return None + + +def save_artifact( + quarto_content: str, + nodes: List[Dict], + edges: List[Dict], + dag_batches: List[List[Dict]], + title: str = "", + user_guid: str = None, + update_existing: bool = False, + artifact_id: str = None +) -> Dict[str, Any]: + """Save published DAG as an artifact for historical tracking.""" + if not user_guid: + raise ValueError("User GUID is required to save artifacts") + + if not title.strip(): + raise ValueError("DAG title is required") + + display_title = title.strip() + + # If artifact_id is provided, we're updating that specific artifact + if artifact_id: + # Verify the artifact exists for this user + existing_metadata = load_artifact_metadata(user_guid, artifact_id) + if not existing_metadata: + raise ValueError(f"Cannot update: Artifact {artifact_id} not found") + + # Use the provided artifact_id for update + timestamp = datetime.now() + artifact_name = f"dag_execution_{artifact_id}" + else: + # Check for existing DAG with same title when creating new + existing_dag_id = find_dag_by_title(user_guid, display_title) + + if existing_dag_id: + raise ValueError(f"A DAG with title '{display_title}' already exists. Load the existing DAG to update it.") + + # Create new artifact + timestamp = datetime.now() + artifact_id = timestamp.strftime('%Y%m%d_%H%M%S') + artifact_name = f"dag_execution_{artifact_id}" + + # Create user-specific artifact directory + user_artifacts_dir = get_user_artifacts_dir(user_guid) + artifact_dir = user_artifacts_dir / artifact_id + artifact_dir.mkdir(exist_ok=True) + + # Import here to avoid circular dependency + from quarto_generator import create_deployment_files + + # Save all files + create_deployment_files(quarto_content, str(artifact_dir)) + + # Save DAG metadata + metadata = { + "id": artifact_id, + "name": artifact_name, + "title": display_title, + "timestamp": timestamp.isoformat(), + "user_guid": user_guid, + "nodes_count": len(nodes), + "edges_count": len(edges), + "batches_count": len(dag_batches), + "nodes": nodes, + "edges": edges, + "batches": [[node['id'] for node in batch] for batch in dag_batches] + } + + metadata_path = artifact_dir / "metadata.json" + with open(metadata_path, "w") as f: + json.dump(metadata, f, indent=2) + + return metadata + + +def load_user_artifacts(user_guid: str) -> List[Dict[str, Any]]: + """Load all saved artifacts for a specific user.""" + if not user_guid: + return [] + + try: + user_artifacts_dir = get_user_artifacts_dir(user_guid) + artifacts = [] + + # Scan user's directory for artifact folders + for artifact_dir in user_artifacts_dir.iterdir(): + if artifact_dir.is_dir(): + metadata_path = artifact_dir / "metadata.json" + if metadata_path.exists(): + try: + with open(metadata_path, 'r') as f: + metadata = json.load(f) + artifacts.append(metadata) + except Exception as e: + logger.error(f"Error loading artifact metadata from {metadata_path}: {e}") + + # Sort by timestamp, most recent first + artifacts.sort(key=lambda x: x.get('timestamp', ''), reverse=True) + logger.info(f"Loaded {len(artifacts)} artifacts for user {user_guid}") + return artifacts + + except Exception as e: + logger.error(f"Error loading user artifacts: {e}") + return [] + + +def load_artifact_metadata(user_guid: str, artifact_id: str) -> Optional[Dict[str, Any]]: + """Load metadata for a specific artifact.""" + if not user_guid or not artifact_id: + return None + + try: + user_artifacts_dir = get_user_artifacts_dir(user_guid) + metadata_path = user_artifacts_dir / artifact_id / "metadata.json" + + if not metadata_path.exists(): + return None + + with open(metadata_path, 'r') as f: + return json.load(f) + + except Exception as e: + logger.error(f"Error loading artifact metadata: {e}") + return None + + +def delete_artifact(user_guid: str, artifact_id: str) -> bool: + """Delete an artifact and its associated files.""" + if not user_guid or not artifact_id: + return False + + try: + user_artifacts_dir = get_user_artifacts_dir(user_guid) + artifact_dir = user_artifacts_dir / artifact_id + + if not artifact_dir.exists(): + return False + + # Delete the entire artifact directory + import shutil + shutil.rmtree(artifact_dir) + + # Also delete zip file if it exists + zip_path = user_artifacts_dir / f"{artifact_id}.zip" + if zip_path.exists(): + zip_path.unlink() + + return True + + except Exception as e: + logger.error(f"Error deleting artifact {artifact_id}: {e}") + return False + + +def create_artifact_zip(artifact_id: str, user_guid: str) -> Path: + """Create a zip file of the artifact.""" + user_artifacts_dir = get_user_artifacts_dir(user_guid) + artifact_dir = user_artifacts_dir / artifact_id + zip_path = user_artifacts_dir / f"{artifact_id}.zip" + + if not artifact_dir.exists(): + logger.error(f"Artifact directory not found: {artifact_dir}") + raise FileNotFoundError(f"Artifact {artifact_id} not found for user {user_guid}") + + # Remove existing zip file if it exists + if zip_path.exists(): + zip_path.unlink() + + try: + files_added = 0 + with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for file_path in artifact_dir.rglob('*'): + if file_path.is_file(): + arcname = file_path.relative_to(artifact_dir) + zipf.write(file_path, arcname) + files_added += 1 + logger.debug(f"Added to zip: {arcname}") + + logger.info(f"Created zip file with {files_added} files: {zip_path}") + + # Verify zip file was created with content + if not zip_path.exists(): + raise RuntimeError(f"Zip file was not created: {zip_path}") + + if zip_path.stat().st_size == 0: + raise RuntimeError(f"Zip file is empty: {zip_path}") + + return zip_path + + except Exception as e: + logger.error(f"Error creating zip file: {e}") + if zip_path.exists(): + zip_path.unlink() # Clean up partial file + raise + + +def artifact_exists(user_guid: str, artifact_id: str) -> bool: + """Check if an artifact exists for a user.""" + if not user_guid or not artifact_id: + return False + + user_artifacts_dir = get_user_artifacts_dir(user_guid) + artifact_dir = user_artifacts_dir / artifact_id + metadata_path = artifact_dir / "metadata.json" + + return artifact_dir.exists() and metadata_path.exists() diff --git a/extensions/dag-builder/py/dag_validation.py b/extensions/dag-builder/py/dag_validation.py new file mode 100644 index 00000000..8ec2bde5 --- /dev/null +++ b/extensions/dag-builder/py/dag_validation.py @@ -0,0 +1,299 @@ +""" +DAG Validation and Layout + +This module handles DAG validation, topological sorting, and automatic layout algorithms. +Backend-agnostic DAG processing logic. +""" + +from typing import List, Dict, Any +import collections +import logging + +logger = logging.getLogger(__name__) + + +def validate_dag(nodes: List[Dict], edges: List[Dict]) -> Dict[str, Any]: + """Validate the DAG for cycles and connectivity.""" + if not nodes: + return {"isValid": True, "errors": []} + + errors = [] + + # Build adjacency list + adj_list = {node["id"]: [] for node in nodes} + for edge in edges: + if edge["source"] in adj_list and edge["target"] in adj_list: + adj_list[edge["source"]].append(edge["target"]) + + # Check for cycles using DFS + def has_cycle(): + visited = set() + rec_stack = set() + + def dfs(node): + visited.add(node) + rec_stack.add(node) + + for neighbor in adj_list[node]: + if neighbor not in visited: + if dfs(neighbor): + return True + elif neighbor in rec_stack: + return True + + rec_stack.remove(node) + return False + + for node_id in adj_list: + if node_id not in visited: + if dfs(node_id): + return True + return False + + if has_cycle(): + errors.append("DAG contains cycles") + + # Check for disconnected components (if more than one node) + if len(nodes) > 1: + # Find all connected components + visited = set() + components = 0 + + def dfs_undirected(node, component): + visited.add(node) + component.add(node) + + # Check both incoming and outgoing edges + for edge in edges: + if edge["source"] == node and edge["target"] not in visited: + dfs_undirected(edge["target"], component) + elif edge["target"] == node and edge["source"] not in visited: + dfs_undirected(edge["source"], component) + + for node in nodes: + node_id = node["id"] + if node_id not in visited: + component = set() + dfs_undirected(node_id, component) + components += 1 + + if components > 1: + errors.append("DAG has disconnected components") + + return { + "isValid": len(errors) == 0, + "errors": errors + } + + +def topological_sort_in_batches(nodes: List[Dict[str, Any]], edges: List[Dict[str, str]]) -> List[List[Dict[str, Any]]]: + """ + Performs a topological sort that groups nodes into parallelizable batches. + """ + node_map = {node['id']: node for node in nodes} + adjacency_list = collections.defaultdict(list) + in_degree = {node_id: 0 for node_id in node_map} + + for edge in edges: + source_id, target_id = edge['source'], edge['target'] + if source_id in node_map and target_id in node_map: + adjacency_list[source_id].append(target_id) + in_degree[target_id] += 1 + + queue = collections.deque([node_id for node_id, degree in in_degree.items() if degree == 0]) + + sorted_batches = [] + processed_node_count = 0 + while queue: + current_batch_size = len(queue) + current_batch = [] + for _ in range(current_batch_size): + current_node_id = queue.popleft() + current_batch.append(node_map[current_node_id]) + processed_node_count += 1 + for neighbor_id in adjacency_list[current_node_id]: + in_degree[neighbor_id] -= 1 + if in_degree[neighbor_id] == 0: + queue.append(neighbor_id) + sorted_batches.append(current_batch) + + if processed_node_count != len(nodes): + unsorted_nodes = [node_id for node_id, degree in in_degree.items() if degree > 0] + raise ValueError(f"Cycle detected in graph. Unprocessed nodes: {unsorted_nodes}") + + return sorted_batches + + +def auto_layout_nodes(nodes: List[Dict[str, Any]], edges: List[Dict[str, str]]) -> List[Dict[str, Any]]: + """Automatically layout nodes using a simple hierarchical layout.""" + if not nodes: + return nodes + + # Build adjacency lists + incoming = {node['id']: [] for node in nodes} + outgoing = {node['id']: [] for node in nodes} + + for edge in edges: + source, target = edge['source'], edge['target'] + if source in outgoing and target in incoming: + outgoing[source].append(target) + incoming[target].append(source) + + # Find root nodes (no incoming edges) + root_nodes = [node['id'] for node in nodes if not incoming[node['id']]] + if not root_nodes: + # If no roots (cycle), just pick the first node + root_nodes = [nodes[0]['id']] + + # Perform level-order layout + levels = [] + visited = set() + queue = [(node_id, 0) for node_id in root_nodes] + + max_level = 0 + while queue: + node_id, level = queue.pop(0) + if node_id in visited: + continue + + visited.add(node_id) + + # Ensure we have enough levels + while len(levels) <= level: + levels.append([]) + + levels[level].append(node_id) + max_level = max(max_level, level) + + # Add children to next level + for child_id in outgoing[node_id]: + if child_id not in visited: + queue.append((child_id, level + 1)) + + # Add any unvisited nodes to the end + for node in nodes: + if node['id'] not in visited: + levels[-1].append(node['id']) + + # Calculate positions + positioned_nodes = [] + node_map = {node['id']: node for node in nodes} + + level_height = 150 + node_width = 200 + + for level_idx, level_nodes in enumerate(levels): + y = level_idx * level_height + level_width = len(level_nodes) * node_width + start_x = -level_width / 2 + + for node_idx, node_id in enumerate(level_nodes): + x = start_x + (node_idx * node_width) + (node_width / 2) + + node = node_map[node_id].copy() + node['position'] = {'x': x, 'y': y} + positioned_nodes.append(node) + + return positioned_nodes + + +def convert_api_nodes_to_reactflow(nodes_data: List[Dict[str, Any]], edges_data: List[Dict[str, Any]]) -> tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + """Convert API node format to ReactFlow format with auto-layout.""" + import time + + # Convert nodes to ReactFlow format + reactflow_nodes = [] + for i, node_data in enumerate(nodes_data): + node_id = node_data.get("id", f"node-{int(time.time() * 1000)}-{i}") + + if node_data.get("type") == "content": + # Content node + node = { + "id": node_id, + "type": "contentNode", + "data": { + "label": node_data.get("label", "Content Node"), + "contentGuid": node_data.get("contentGuid", ""), + "contentType": node_data.get("contentType", "unknown"), + "contentUrl": node_data.get("contentUrl", ""), + "author": node_data.get("author", "Unknown"), + "description": node_data.get("description", "") + } + } + else: + # Custom node + node = { + "id": node_id, + "type": "customNode", + "data": { + "label": node_data.get("label", "Custom Node"), + "nodeType": node_data.get("customType", "webhook"), + "customType": node_data.get("customType", "webhook"), + "description": node_data.get("description", ""), + "icon": node_data.get("icon", "โš™๏ธ"), + "config": node_data.get("config", {}) + } + } + + reactflow_nodes.append(node) + + # Convert edges to ReactFlow format + reactflow_edges = [] + for i, edge_data in enumerate(edges_data): + edge = { + "id": edge_data.get("id", f"edge-{i}"), + "source": edge_data["source"], + "target": edge_data["target"], + "type": "deletable" + } + reactflow_edges.append(edge) + + # Auto-layout nodes + positioned_nodes = auto_layout_nodes(reactflow_nodes, reactflow_edges) + + return positioned_nodes, reactflow_edges + + +def validate_api_dag_input(body: Dict[str, Any]) -> tuple[bool, List[str]]: + """Validate API DAG input and return validation status and errors.""" + errors = [] + + title = body.get("title", "") + nodes_data = body.get("nodes", []) + edges_data = body.get("edges", []) + + if not title.strip(): + errors.append("DAG title is required") + + if not nodes_data: + errors.append("DAG must have at least one node") + + # Validate required node fields + for i, node in enumerate(nodes_data): + if not node.get("id"): + errors.append(f"Node {i} missing required 'id' field") + + if node.get("type") == "content": + if not node.get("contentGuid"): + errors.append(f"Content node {i} missing required 'contentGuid' field") + elif node.get("type") == "custom": + if not node.get("customType"): + errors.append(f"Custom node {i} missing required 'customType' field") + + # Validate edge references + node_ids = {node.get("id") for node in nodes_data if node.get("id")} + for i, edge in enumerate(edges_data): + source = edge.get("source") + target = edge.get("target") + + if not source: + errors.append(f"Edge {i} missing required 'source' field") + elif source not in node_ids: + errors.append(f"Edge {i} references unknown source node: {source}") + + if not target: + errors.append(f"Edge {i} missing required 'target' field") + elif target not in node_ids: + errors.append(f"Edge {i} references unknown target node: {target}") + + return len(errors) == 0, errors \ No newline at end of file diff --git a/extensions/dag-builder/py/quarto_generator.py b/extensions/dag-builder/py/quarto_generator.py new file mode 100644 index 00000000..a7c6e656 --- /dev/null +++ b/extensions/dag-builder/py/quarto_generator.py @@ -0,0 +1,329 @@ +""" +Quarto Document Generation for DAG Builder + +This module handles the generation of Quarto documents for DAG execution, +including Mermaid diagrams, custom node code generation, and deployment files. +""" + +from typing import List, Dict, Any +from pathlib import Path +import json +from jinja2 import Environment, FileSystemLoader, select_autoescape + + +def generate_mermaid_diagram(nodes: List[Dict[str, Any]], edges: List[Dict[str, str]], dag_batches: List[List[Dict[str, Any]]]) -> str: + """Generate Mermaid flowchart diagram from DAG data.""" + mermaid_lines = ["flowchart TD"] + + # Add nodes with styling based on type + for node in nodes: + node_id = node.get('id', 'unknown').replace('-', '_') + node_data = node.get('data', {}) + label = node_data.get('label', 'Unknown') + + # Determine node type and styling + if node_data.get('contentGuid'): + # Content node (Posit Connect content) + content_type = node_data.get('contentType', 'content') + mermaid_lines.append(f' {node_id}["{label}
{content_type}"]') + mermaid_lines.append(f' class {node_id} contentNode') + elif node_data.get('customType'): + # Custom action node + custom_type = node_data.get('customType', 'custom') + icon = node_data.get('icon', 'โš™๏ธ') + mermaid_lines.append(f' {node_id}["{icon} {label}
{custom_type}"]') + mermaid_lines.append(f' class {node_id} customNode') + else: + # Unknown node type + mermaid_lines.append(f' {node_id}["{label}"]') + mermaid_lines.append(f' class {node_id} unknownNode') + + # Add edges + for edge in edges: + source_id = edge.get('source', '').replace('-', '_') + target_id = edge.get('target', '').replace('-', '_') + if source_id and target_id: + mermaid_lines.append(f' {source_id} --> {target_id}') + + # Add batch grouping (subgraphs) + for i, batch in enumerate(dag_batches, 1): + if len(batch) > 1: # Only show subgraph if batch has multiple nodes + batch_nodes = [node.get('id', '').replace('-', '_') for node in batch if node.get('id')] + if batch_nodes: + mermaid_lines.append(f' subgraph batch{i} ["Batch {i} (Parallel)"]') + for node_id in batch_nodes: + mermaid_lines.append(f' {node_id}') + mermaid_lines.append(' end') + + # Add styling classes + mermaid_lines.extend([ + '', + ' %% Styling', + ' classDef contentNode fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#000', + ' classDef customNode fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#000', + ' classDef unknownNode fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000' + ]) + + return '\n'.join(mermaid_lines) + + +def generate_custom_node_code(node: Dict[str, Any]) -> str: + """Generate Python code for custom nodes based on their type and configuration.""" + node_data = node.get('data', {}) + custom_type = node_data.get('customType') + config = node_data.get('config', {}) + node_id = node.get('id', 'unknown') + + if custom_type == 'webhook': + url = config.get('url', '') + method = config.get('method', 'GET').upper() + headers = config.get('headers', '{}') + body = config.get('body', '') + + # Use repr() to safely embed strings with quotes + url_repr = repr(url) + method_repr = repr(method) + headers_repr = repr(headers) + body_repr = repr(body) + + return f"""def execute_webhook_{node_id.replace('-', '_')}(): + import requests + import json + + try: + url = {url_repr} + method = {method_repr} + headers_str = {headers_repr} + body_str = {body_repr} + + headers = json.loads(headers_str) + + print(f"๐Ÿ”— WEBHOOK [{node_id}]: {{method}} {{url}}") + + if method in ['POST', 'PUT', 'PATCH'] and body_str.strip(): + body_data = json.loads(body_str) + response = requests.request(method, url, headers=headers, json=body_data, timeout=30) + else: + response = requests.request(method, url, headers=headers, timeout=30) + + print(f"โœ… WEBHOOK [{node_id}]: Status {{response.status_code}}") + if response.status_code >= 400: + print(f"โš ๏ธ WEBHOOK [{node_id}]: Response: {{response.text[:200]}}") + + response.raise_for_status() + return {{"status": "success", "status_code": response.status_code, "response": response.text[:500]}} + + except requests.exceptions.RequestException as e: + error_msg = f"โŒ WEBHOOK [{node_id}]: Request failed: {{str(e)}}" + print(error_msg) + return {{"status": "error", "error": error_msg}} + except json.JSONDecodeError as e: + error_msg = f"โŒ WEBHOOK [{node_id}]: JSON parsing error: {{str(e)}}" + print(error_msg) + return {{"status": "error", "error": error_msg}} + except Exception as e: + error_msg = f"โŒ WEBHOOK [{node_id}]: Unexpected error: {{str(e)}}" + print(error_msg) + return {{"status": "error", "error": error_msg}} + +webhook_result_{node_id.replace('-', '_')} = execute_webhook_{node_id.replace('-', '_')}()""" + + elif custom_type == 'delay': + duration = config.get('duration', 5) + unit = config.get('unit', 'seconds') + + # Convert to seconds + multiplier = {'seconds': 1, 'minutes': 60, 'hours': 3600}.get(unit, 1) + total_seconds = duration * multiplier + + return f"""def execute_delay_{node_id.replace('-', '_')}(): + import time + + duration = {duration} + unit = "{unit}" + total_seconds = {total_seconds} + + print(f"โฑ๏ธ DELAY [{node_id}]: Waiting {{duration}} {{unit}}") + time.sleep(total_seconds) + print(f"โœ… DELAY [{node_id}]: Wait completed") + return {{"status": "success", "duration": total_seconds}} + +delay_result_{node_id.replace('-', '_')} = execute_delay_{node_id.replace('-', '_')}()""" + + elif custom_type == 'condition': + condition = config.get('condition', 'True') + true_action = config.get('trueAction', 'continue') + false_action = config.get('falseAction', 'skip_remaining') + notification_message = config.get('notificationMessage', '') + + # Escape quotes in the condition for safe inclusion in the generated code + safe_condition = (condition if condition.strip() else 'True').replace('"', '\\"') + + return f"""def execute_condition_{node_id.replace('-', '_')}(previous_nodes, runtime_context): + try: + # Available variables for condition evaluation + # previous_nodes: dict of all previous node results + # runtime_context: current time, environment, utilities + + print(f"๐Ÿ”€ CONDITION [{node_id}]: Evaluating condition") + print(f"๐Ÿ”€ CONDITION [{node_id}]: Available previous nodes: {{list(previous_nodes.keys())}}") + + # Evaluate condition: {condition} + condition_expression = "{safe_condition}" + condition_result = bool(eval(condition_expression)) + + print(f"๐Ÿ”€ CONDITION [{node_id}]: Expression: {{condition_expression}}") + print(f"๐Ÿ”€ CONDITION [{node_id}]: Result: {{condition_result}}") + + if condition_result: + action = "{true_action}" + print(f"โœ… CONDITION [{node_id}]: True - Action: {{action}}") + else: + action = "{false_action}" + print(f"โŒ CONDITION [{node_id}]: False - Action: {{action}}") + + # Handle notification actions + if action == "notify": + notification = "{notification_message}" or f"Condition {node_id} triggered" + print(f"๐Ÿ“ข NOTIFICATION [{node_id}]: {{notification}}") + + result = {{ + "status": "success", + "condition_result": condition_result, + "action": action, + "condition_expression": condition_expression + }} + + # Handle special actions that affect execution flow + if action in ["stop", "skip_remaining"]: + result["halt_execution"] = True + result["halt_reason"] = action + + return result + + except NameError as e: + error_msg = f"โŒ CONDITION [{node_id}]: Variable not found: {{str(e)}}" + print(error_msg) + print(f"๐Ÿ” Available variables: previous_nodes={{list(previous_nodes.keys())}}, runtime_context={{list(runtime_context.keys())}}") + return {{"status": "error", "error": error_msg}} + except Exception as e: + error_msg = f"โŒ CONDITION [{node_id}]: Evaluation failed: {{str(e)}}" + print(error_msg) + return {{"status": "error", "error": error_msg}} + +condition_result_{node_id.replace('-', '_')} = execute_condition_{node_id.replace('-', '_')}(previous_nodes, runtime_context)""" + + else: + return f"""# Unknown custom node type: {custom_type} for node {node_id} +print(f"โš ๏ธ UNKNOWN [{node_id}]: Custom node type '{custom_type}' not implemented")""" + + +def _get_jinja_env() -> Environment: + """Get Jinja2 environment configured for templates.""" + template_dir = Path(__file__).parent / "templates" + template_dir.mkdir(exist_ok=True) + + env = Environment( + loader=FileSystemLoader(str(template_dir)), + autoescape=select_autoescape(['html', 'xml']), + trim_blocks=True, + lstrip_blocks=True + ) + return env + + +def _prepare_batch_data(batch: List[Dict[str, Any]], batch_num: int) -> Dict[str, Any]: + """Prepare batch data for template rendering.""" + content_nodes = [node for node in batch if node.get('data', {}).get('contentGuid')] + custom_nodes = [node for node in batch if node.get('data', {}).get('customType')] + + # Generate custom node code and metadata + custom_nodes_data = [] + for node in custom_nodes: + node_id = node.get('id', 'unknown') + node_label = node.get('data', {}).get('label', 'Unknown') + custom_type = node.get('data', {}).get('customType', 'unknown') + + custom_nodes_data.append({ + 'id': node_id, + 'label': node_label, + 'type': custom_type, + 'code': generate_custom_node_code(node) + }) + + # Get GUIDs for content nodes + content_guids = [node.get('data', {}).get('contentGuid', 'unknown') for node in content_nodes] + + return { + 'content_nodes': content_nodes, + 'custom_nodes': custom_nodes_data, + 'content_guids': content_guids + } + + +def generate_quarto_document(nodes: List[Dict[str, Any]], edges: List[Dict[str, str]], dag_batches: List[List[Dict[str, Any]]]) -> str: + """ + Generates a full Quarto document using Jinja2 templates. + + This is much cleaner and more maintainable than string concatenation. + """ + # Get Jinja2 environment + env = _get_jinja_env() + + # Generate Mermaid diagram + mermaid_diagram = generate_mermaid_diagram(nodes, edges, dag_batches) + + # Count node types + content_node_count = len([n for n in nodes if n.get('data', {}).get('contentGuid')]) + custom_node_count = len([n for n in nodes if n.get('data', {}).get('customType')]) + + # Prepare batch data for template + batches_data = [] + for i, batch in enumerate(dag_batches, 1): + batch_data = _prepare_batch_data(batch, i) + batches_data.append((i, batch_data)) + + # Render template + template = env.get_template('quarto_document.qmd.j2') + return template.render( + nodes=nodes, + edges=edges, + dag_batches=batches_data, + mermaid_diagram=mermaid_diagram, + content_node_count=content_node_count, + custom_node_count=custom_node_count + ) + + +def create_deployment_files(quarto_content: str, temp_dir: str): + """Create the necessary files for deployment.""" + # Create Quarto document + quarto_path = Path(temp_dir) / "dag_execution.qmd" + with open(quarto_path, "w") as f: + f.write(quarto_content) + + # Create _quarto.yml project file + quarto_yml_path = Path(temp_dir) / "_quarto.yml" + quarto_yml_content = """project: + type: website + +format: + html: + theme: default + toc: true + code-fold: false + +execute: + enabled: true +""" + with open(quarto_yml_path, "w") as f: + f.write(quarto_yml_content) + + # Create requirements.txt + requirements_path = Path(temp_dir) / "requirements.txt" + with open(requirements_path, "w") as f: + f.write("""posit-sdk +requests +""") + + return quarto_path, requirements_path, quarto_yml_path diff --git a/extensions/dag-builder/py/requirements.txt b/extensions/dag-builder/py/requirements.txt new file mode 100644 index 00000000..90bf9b7e --- /dev/null +++ b/extensions/dag-builder/py/requirements.txt @@ -0,0 +1,8 @@ +fastapi>=0.115.0 +uvicorn[standard]>=0.32.0 +websockets>=14.1 +python-multipart>=0.0.20 +jinja2>=3.1.0 +posit-sdk +rsconnect-python +quarto diff --git a/extensions/dag-builder/py/templates/quarto_document.qmd.j2 b/extensions/dag-builder/py/templates/quarto_document.qmd.j2 new file mode 100644 index 00000000..e969b529 --- /dev/null +++ b/extensions/dag-builder/py/templates/quarto_document.qmd.j2 @@ -0,0 +1,248 @@ +--- +title: "DAG Execution Report" +date: "today" +format: + html: + toc: true + code-fold: false + mermaid: + theme: default +--- + +## 1. Execution Plan + +This report details the execution of a DAG with {{ nodes|length }} nodes and {{ edges|length }} edges. The visual plan below shows the nodes and their dependencies. Nodes grouped in the same batch were executed in parallel. + +```{mermaid} +{{ mermaid_diagram }} +``` + +### DAG Statistics + +- **Total Nodes**: {{ nodes|length }} +- **Total Edges**: {{ edges|length }} +- **Execution Batches**: {{ dag_batches|length }} +- **Content Nodes**: {{ content_node_count }} +- **Custom Action Nodes**: {{ custom_node_count }} + +## 2. Execution Logs + +```{python} +#| echo: false + +import os +import posit.connect +import concurrent.futures +import time + +def process_node(guid: str, client: posit.connect.Client) -> str: + try: + content_item = client.content.get(guid=guid) + item_name = content_item['name'] + print(f"Processing: '{item_name}' (GUID: {guid})") + + if content_item.is_rendered: + task = content_item.render() + task.wait_for() + return f"โœ… SUCCESS: Rendered '{item_name}'" + elif content_item.is_interactive: + content_item.restart() + return f"โœ… SUCCESS: Restarted '{item_name}'" + else: + return f"โšช SKIPPED: '{item_name}' (type: {content_item.type}) has no action." + except Exception as e: + return f"โŒ ERROR processing node {guid}: {e}" + +print('--- Initializing Execution ---') +client = posit.connect.Client(api_key=os.getenv('CONNECT_API_KEY'), url=os.getenv('CONNECT_SERVER_URL')) +MAX_WORKERS = 8 +``` + +```{python} +#| echo: false + +# Execution control variables +batch_execution_successful = True +failed_batches = [] +total_batches = {{ dag_batches|length }} +``` + +{% for batch_num, batch in dag_batches %} +```{python} +#| echo: false + +# Check if previous batches were successful before proceeding +if not batch_execution_successful: + print(f'\n๐Ÿ›‘ STOPPING EXECUTION: Previous batch failed. Skipping Batch {{ batch_num }}.') + print(f'Failed batches: {", ".join(map(str, failed_batches))}') +else: + print(f'\nExecuting Batch {{ batch_num }}...') + batch_{{ batch_num }}_success = True + batch_{{ batch_num }}_results = [] + + # Build runtime context for condition evaluation + import os + from datetime import datetime + + current_time = datetime.now() + runtime_context = { + "current_hour": current_time.hour, + "current_minute": current_time.minute, + "weekday": current_time.weekday(), # 0=Monday, 6=Sunday + "is_weekday": current_time.weekday() < 5, + "is_weekend": current_time.weekday() >= 5, + "environment": os.getenv("ENVIRONMENT", "development"), + "user": os.getenv("USER", "unknown"), + "file_exists": lambda path: os.path.exists(path), + "current_datetime": current_time.isoformat(), + "batch_number": {{ batch_num }}, + "total_batches": {{ dag_batches|length }} + } + + # Collect results from all previous nodes (across all previous batches) + if 'all_node_results' not in globals(): + all_node_results = {} + + previous_nodes = all_node_results.copy() + + print(f"๐Ÿ” Runtime context available: {list(runtime_context.keys())}") + print(f"๐Ÿ” Previous nodes available: {list(previous_nodes.keys())}") + + # Execute custom nodes first (they run synchronously) + custom_node_results = [] + +{% for custom_node in batch.custom_nodes %} +{{ custom_node.code | indent(4, first=True) }} + +{% endfor %} + # Process custom nodes in sequence + custom_nodes_to_process = [ +{% for custom_node in batch.custom_nodes %} + {"id": "{{ custom_node.id }}", "label": "{{ custom_node.label }}", "type": "{{ custom_node.type }}"}{{ "," if not loop.last }} +{% endfor %} + ] + + # Execute each custom node by calling its function + for custom_node_info in custom_nodes_to_process: + node_id = custom_node_info["id"] + node_label = custom_node_info["label"] + custom_type = custom_node_info["type"] + result_var_name = f'{custom_type}_result_{node_id.replace("-", "_")}' + + try: + # The function was already defined above, just retrieve the result variable + node_result = globals().get(result_var_name, {"status": "completed"}) + all_node_results[node_id] = node_result + + custom_node_results.append(f"โœ… CUSTOM: {node_label} completed") + + # Handle execution flow control for condition nodes + if "halt_execution" in node_result and node_result["halt_execution"]: + halt_reason = node_result.get("halt_reason", "unknown") + if halt_reason == "stop": + print(f"๐Ÿ›‘ STOPPING: Condition {node_id} triggered stop execution") + batch_{{ batch_num }}_success = False + batch_execution_successful = False + break + elif halt_reason == "skip_remaining": + print(f"โญ๏ธ SKIPPING: Condition {node_id} triggered skip remaining batches") + # This will be handled after the batch completes + pass + + except Exception as e: + error_msg = f"โŒ CUSTOM: {node_label} failed: {str(e)}" + custom_node_results.append(error_msg) + batch_{{ batch_num }}_success = False + print(error_msg) + + # Print custom node results + for result in custom_node_results: + print(result) + batch_{{ batch_num }}_results.append(result) + + # Execute content nodes (Posit Connect content) in parallel + if {{ batch.content_nodes|length }} > 0: + with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: + guids_to_process = {{ batch.content_guids }} + future_to_guid = {executor.submit(process_node, guid, client): guid for guid in guids_to_process} + + for future in concurrent.futures.as_completed(future_to_guid): + guid = future_to_guid[future] + try: + result = future.result() + batch_{{ batch_num }}_results.append(result) + print(result) + + # Store content node results for future reference + content_node_result = { + "status": "success" if result.startswith('โœ…') else "error", + "result_message": result, + "guid": guid + } + + # Store using GUID as key for content nodes + if guid: + all_node_results[guid] = content_node_result + + # Check if this specific node failed + if result.startswith('โŒ ERROR'): + batch_{{ batch_num }}_success = False + + except Exception as exc: + error_msg = f'โŒ ERROR: Node {guid} in batch {{ batch_num }} generated an exception: {exc}' + batch_{{ batch_num }}_results.append(error_msg) + print(error_msg) + batch_{{ batch_num }}_success = False + + # Store error result + if guid: + all_node_results[guid] = { + "status": "error", + "error": str(exc), + "guid": guid + } + + # Check if any condition node triggered skip_remaining + skip_remaining_triggered = False + for node_id, result in all_node_results.items(): + if isinstance(result, dict) and result.get("halt_execution") and result.get("halt_reason") == "skip_remaining": + skip_remaining_triggered = True + print(f'โญ๏ธ SKIP REMAINING: Condition {node_id} triggered skip remaining batches') + break + + # Update global execution status + if not batch_{{ batch_num }}_success: + batch_execution_successful = False + failed_batches.append({{ batch_num }}) + print(f'\n๐Ÿ’ฅ BATCH {{ batch_num }} FAILED - Stopping execution of remaining batches') + print(f'Failed nodes in batch {{ batch_num }}:') + for result in batch_{{ batch_num }}_results: + if result.startswith('โŒ'): + print(f' - {result}') + elif skip_remaining_triggered: + # Mark as successful but stop further execution + print(f'\nโญ๏ธ BATCH {{ batch_num }} COMPLETED - Skipping remaining batches due to condition') + batch_execution_successful = False # This will prevent further batches + else: + print(f'\nโœ… BATCH {{ batch_num }} COMPLETED SUCCESSFULLY') +``` + +{% endfor %} +```{python} +#| echo: false + +# Final execution summary +print('\n' + '='*50) +print('--- DAG EXECUTION SUMMARY ---') +print('='*50) + +if batch_execution_successful: + print(f'โœ… SUCCESS: All {total_batches} batches completed successfully!') + print('๐ŸŽ‰ DAG execution completed without errors.') +else: + print(f'โŒ FAILURE: DAG execution stopped due to batch failures.') + print(f'๐Ÿ“Š Failed batches: {", ".join(map(str, failed_batches))} out of {total_batches} total batches') + print(f'โš ๏ธ Remaining batches were skipped to prevent cascading failures.') + +print('='*50) +``` diff --git a/extensions/dag-builder/py/www/main.css b/extensions/dag-builder/py/www/main.css new file mode 100644 index 00000000..8d2f84a9 --- /dev/null +++ b/extensions/dag-builder/py/www/main.css @@ -0,0 +1,2 @@ +.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;margin:0;padding:0;background-color:#f5f5f5;height:100vh;overflow:hidden}#root{height:100vh;width:100vw}.dag-builder{height:100vh;width:100vw;display:flex;flex-direction:row}.dag-container{flex:1;height:100%;display:flex;flex-direction:column}.dag-toolbar{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:10px 20px;background:#fff;border-bottom:1px solid #ddd;box-shadow:0 2px 4px #0000001a}.dag-title-section{display:flex;align-items:center;gap:10px;flex:1}.title-label{font-size:14px;font-weight:500;color:#333;white-space:nowrap}.title-input{padding:8px 12px;border:1px solid #ddd;border-radius:4px;font-size:14px;min-width:200px;max-width:300px;flex:1}.title-input:focus{outline:none;border-color:#007bff;box-shadow:0 0 0 2px #007bff40}.toolbar-actions{display:flex;align-items:center;gap:10px}.toolbar-button{padding:8px 16px;border:1px solid #ddd;border-radius:4px;background:#fff;cursor:pointer;font-size:14px;transition:all .2s ease}.toolbar-button:hover{background:#f8f9fa;border-color:#007bff}.toolbar-button.publish{background:#28a745;color:#fff;border-color:#28a745}.toolbar-button.publish:hover{background:#218838}.toolbar-button.disabled{background:#f8f9fa;color:#6c757d;cursor:not-allowed;border-color:#dee2e6}.validation-errors{margin-left:20px;color:#dc3545;font-size:12px}.validation-errors ul{margin:4px 0;padding-left:16px}.dag-flow{flex:1;height:calc(100vh - 60px)}.content-sidebar{width:350px;height:100vh;background:#fff;border-right:1px solid #ddd;display:flex;flex-direction:column;box-shadow:2px 0 4px #0000001a}.artifacts-sidebar{width:350px;height:100vh;background:#fff;border-left:1px solid #ddd;display:flex;flex-direction:column;box-shadow:-2px 0 4px #0000001a}.sidebar-header{padding:20px;border-bottom:1px solid #eee;background:#f8f9fa}.sidebar-header h2{margin:0;color:#333;font-size:18px}.sidebar-search{padding:15px 20px;border-bottom:1px solid #eee}.sidebar-search .search-form{margin:0}.sidebar-search .search-input{width:100%;padding:10px;border:1px solid #ddd;border-radius:4px;font-size:14px}.sidebar-content{flex:1;overflow-y:auto;padding:10px}.search-hint,.no-results{text-align:center;color:#666;font-style:italic;padding:20px;margin:20px 0}.search-result-item.draggable{position:relative;cursor:grab;border:2px dashed transparent;transition:all .2s ease;margin-bottom:12px}.search-result-item.draggable:hover{border-color:#007bff;background:#f8f9fa;transform:translateY(-1px);box-shadow:0 4px 8px #0000001a}.search-result-item.draggable:active{cursor:grabbing}.result-meta{display:flex;flex-direction:column;gap:2px;margin:4px 0}.result-author,.result-date{font-size:11px;color:#666}.drag-hint{position:absolute;top:5px;right:5px;font-size:10px;color:#999;background:#ffffffe6;padding:2px 6px;border-radius:3px;opacity:0;transition:opacity .2s ease}.search-result-item.draggable:hover .drag-hint{opacity:1}.content-node{background:#fff;border:2px solid #ddd;border-radius:8px;padding:10px;min-width:150px;box-shadow:0 2px 8px #0000001a}.content-node.selected{border-color:#007bff}.node-header{margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid #eee}.node-body{font-size:12px;color:#666}.content-type{background:#007bff;color:#fff;padding:2px 6px;border-radius:3px;display:inline-block;margin-bottom:4px;font-size:10px;text-transform:uppercase}.content-author{font-size:11px;color:#666;margin-bottom:4px}.content-guid{font-family:monospace;font-size:10px;color:#999;word-break:break-all}.search-results{overflow-y:auto}.search-result-item{padding:12px;border:1px solid #eee;border-radius:4px;margin-bottom:8px;transition:all .2s ease}.search-result-item:not(.draggable):hover{background:#f8f9fa;border-color:#007bff}.result-name{font-weight:700;margin-bottom:4px;color:#333}.result-type{background:#6c757d;color:#fff;padding:2px 6px;border-radius:3px;display:inline-block;font-size:10px;text-transform:uppercase;margin-bottom:4px}.result-description{font-size:12px;color:#666;margin:4px 0;line-height:1.4}.result-guid{font-family:monospace;font-size:11px;color:#999;word-break:break-all}.button-edge__label{position:absolute;pointer-events:all;font-size:12px;z-index:1000}.button-edge__button{width:20px;height:20px;background:#ff4757;border:none;color:#fff;border-radius:50%;cursor:pointer;font-size:12px;line-height:1;opacity:1;transition:all .2s ease;display:flex;align-items:center;justify-content:center;font-weight:700;box-shadow:0 2px 4px #0003;z-index:1001;position:relative}.button-edge__button:hover{background:#ff3742;transform:scale(1.1);box-shadow:0 2px 8px #0000004d}.react-flow__edge-labels{z-index:1000}.react-flow__edge-labels>div{z-index:1000}.notifications{position:fixed;top:20px;right:20px;z-index:2000;max-width:400px}.toast{background:#fff;border-radius:6px;box-shadow:0 4px 12px #00000026;margin-bottom:10px;padding:12px 16px;border-left:4px solid #007bff;animation:slideIn .3s ease-out}.toast-success{border-left-color:#28a745}.toast-error{border-left-color:#dc3545}.toast-info{border-left-color:#17a2b8}.toast-warning{border-left-color:#ffc107}@keyframes slideIn{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.artifacts-list{overflow-y:auto;padding:10px}.artifact-item{padding:12px;border:1px solid #eee;border-radius:4px;margin-bottom:8px;transition:all .2s ease;background:#fff;position:relative}.artifact-item:hover{background:#f8f9fa;border-color:#007bff;transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.artifact-item-active{background:#e7f3ff!important;border:2px solid #007bff!important;box-shadow:0 2px 8px #007bff4d!important}.artifact-item-active:hover{background:#d4e9ff!important;border-color:#0056b3!important}.loaded-indicator{color:#28a745;font-size:12px;font-weight:400;animation:pulse 2s ease-in-out infinite}.publishing-spinner{display:inline-block;color:#007bff;font-size:16px;margin-right:6px;animation:spin 1s linear infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.artifact-name{font-weight:700;margin-bottom:4px;color:#333;font-size:14px}.artifact-meta{display:flex;flex-direction:column;gap:2px;margin:4px 0;font-size:11px;color:#666}.artifact-stats{display:flex;gap:8px;margin:4px 0}.artifact-stat{background:#e9ecef;padding:2px 6px;border-radius:3px;font-size:10px;color:#495057}.artifact-id{font-family:monospace;font-size:10px;color:#999;margin:4px 0}.artifact-buttons{display:flex;gap:8px;margin-top:8px}.clone-button,.download-button,.publish-direct-button{flex:1;border:none;padding:8px 12px;border-radius:4px;font-size:12px;cursor:pointer;transition:all .2s ease}.clone-button{background:#6f42c1;color:#fff}.clone-button:hover{background:#5a32a3;transform:translateY(-1px)}.download-button{background:#007bff;color:#fff}.download-button:hover{background:#0056b3;transform:translateY(-1px)}.publish-direct-button{background:#28a745;color:#fff}.publish-direct-button:hover{background:#218838;transform:translateY(-1px)}.clone-button:active,.download-button:active,.publish-direct-button:active{transform:translateY(0)}.clone-button:disabled,.download-button:disabled,.publish-direct-button:disabled{opacity:.6;cursor:not-allowed;transform:none}.clone-button:disabled:hover,.download-button:disabled:hover,.publish-direct-button:disabled:hover{transform:none;background:#6f42c1}.publish-direct-button:disabled:hover{background:#28a745}.download-button:disabled:hover{background:#007bff}.no-artifacts{text-align:center;color:#666;font-style:italic;padding:40px 20px;margin:20px 0}.artifact-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.delete-artifact-button{background:#dc3545;color:#fff;border:none;border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:16px;font-weight:700;line-height:1;transition:all .2s ease;flex-shrink:0}.delete-artifact-button:hover{background:#c82333;transform:scale(1.1)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:10000}.modal-content{background:#fff;border-radius:8px;box-shadow:0 4px 16px #0003;max-width:400px;width:90%;max-height:90vh;overflow-y:auto}.modal-header{padding:20px 20px 10px;border-bottom:1px solid #eee}.modal-header h3{margin:0;color:#333;font-size:18px}.modal-body{padding:20px}.modal-body p{margin:0 0 10px;color:#666;line-height:1.4}.modal-footer{padding:10px 20px 20px;display:flex;gap:10px;justify-content:flex-end}.modal-button{padding:8px 16px;border:none;border-radius:4px;font-size:14px;cursor:pointer;transition:all .2s ease}.modal-button.cancel{background:#6c757d;color:#fff}.modal-button.cancel:hover{background:#5a6268}.modal-button.delete{background:#dc3545;color:#fff}.modal-button.delete:hover{background:#c82333}.custom-nodes-pane{border-top:1px solid #eee;margin-top:10px;padding-top:15px}.custom-nodes-header{padding:0 10px 10px}.custom-nodes-header h3{margin:0;color:#333;font-size:16px;font-weight:600}.custom-nodes-list{display:flex;flex-direction:column;gap:8px;padding:0 10px}.custom-node-item{position:relative;display:flex;align-items:center;gap:12px;padding:12px;border:2px dashed transparent;border-radius:6px;background:linear-gradient(135deg,#f8f9fa,#e9ecef);cursor:grab;transition:all .2s ease}.custom-node-item.draggable:hover{border-color:#ff6b6b;background:linear-gradient(135deg,#fff5f5,#ffe3e3);transform:translateY(-1px);box-shadow:0 4px 8px #ff6b6b26}.custom-node-item.draggable:active{cursor:grabbing}.custom-node-icon{font-size:20px;flex-shrink:0}.custom-node-content{flex:1}.custom-node-name{font-weight:600;color:#333;margin-bottom:2px;font-size:14px}.custom-node-description{font-size:11px;color:#666;line-height:1.3}.custom-node{background:linear-gradient(135deg,#fff5f5,#ffe3e3);border:2px solid #ff6b6b;border-radius:8px;padding:12px;min-width:200px;max-width:350px;box-shadow:0 4px 12px #ff6b6b26;font-size:13px}.custom-node.selected{border-color:#ff4757;box-shadow:0 0 0 2px #ff47574d}.custom-node .node-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;padding-bottom:6px;border-bottom:1px solid rgba(255,107,107,.2)}.custom-node-title{display:flex;align-items:center;gap:8px}.custom-node-title .custom-node-icon{font-size:16px}.expand-button{background:#ff6b6b;color:#fff;border:none;border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;font-weight:700;transition:all .2s ease}.expand-button:hover{background:#ff4757;transform:scale(1.1)}.node-summary{margin-top:4px}.node-summary .custom-node-description{font-size:11px;color:#666;font-style:italic}.custom-node-form{margin-top:8px}.form-group{margin-bottom:10px}.form-group:last-child{margin-bottom:0}.form-group label{display:block;font-size:11px;font-weight:600;color:#555;margin-bottom:4px}.form-input,.form-select,.form-textarea{width:100%;padding:6px 8px;border:1px solid #ddd;border-radius:4px;font-size:12px;font-family:inherit;transition:border-color .2s ease}.form-input:focus,.form-select:focus,.form-textarea:focus{outline:none;border-color:#ff6b6b;box-shadow:0 0 0 2px #ff6b6b33}.form-textarea{resize:vertical;min-height:60px;font-family:Monaco,Menlo,Ubuntu Mono,monospace}.form-select{background:#fff;cursor:pointer}.condition-textarea{font-family:Monaco,Menlo,Ubuntu Mono,monospace;font-size:11px}.condition-help{margin-top:6px}.condition-help details{font-size:10px}.condition-help summary{cursor:pointer;color:#666;font-weight:500}.condition-help summary:hover{color:#333}.help-content{margin-top:6px;padding:8px;background:#f8f9fa;border-radius:4px;border-left:3px solid #ff6b6b;font-size:10px;line-height:1.4}.help-content code{background:#e9ecef;padding:1px 3px;border-radius:2px;font-family:Monaco,Menlo,Ubuntu Mono,monospace;font-size:9px} +/*# sourceMappingURL=main.css.map */ diff --git a/extensions/dag-builder/py/www/main.css.map b/extensions/dag-builder/py/www/main.css.map new file mode 100644 index 00000000..d377a65a --- /dev/null +++ b/extensions/dag-builder/py/www/main.css.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../node_modules/@xyflow/react/dist/style.css", "../../srcts/styles.css"], + "sourcesContent": ["/* this gets exported as style.css and can be used for the default theming */\n/* these are the necessary styles for React/Svelte Flow, they get used by base.css and style.css */\n.react-flow {\n direction: ltr;\n\n --xy-edge-stroke-default: #b1b1b7;\n --xy-edge-stroke-width-default: 1;\n --xy-edge-stroke-selected-default: #555;\n\n --xy-connectionline-stroke-default: #b1b1b7;\n --xy-connectionline-stroke-width-default: 1;\n\n --xy-attribution-background-color-default: rgba(255, 255, 255, 0.5);\n\n --xy-minimap-background-color-default: #fff;\n --xy-minimap-mask-background-color-default: rgba(240, 240, 240, 0.6);\n --xy-minimap-mask-stroke-color-default: transparent;\n --xy-minimap-mask-stroke-width-default: 1;\n --xy-minimap-node-background-color-default: #e2e2e2;\n --xy-minimap-node-stroke-color-default: transparent;\n --xy-minimap-node-stroke-width-default: 2;\n\n --xy-background-color-default: transparent;\n --xy-background-pattern-dots-color-default: #91919a;\n --xy-background-pattern-lines-color-default: #eee;\n --xy-background-pattern-cross-color-default: #e2e2e2;\n background-color: var(--xy-background-color, var(--xy-background-color-default));\n --xy-node-color-default: inherit;\n --xy-node-border-default: 1px solid #1a192b;\n --xy-node-background-color-default: #fff;\n --xy-node-group-background-color-default: rgba(240, 240, 240, 0.25);\n --xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, 0.08);\n --xy-node-boxshadow-selected-default: 0 0 0 0.5px #1a192b;\n --xy-node-border-radius-default: 3px;\n\n --xy-handle-background-color-default: #1a192b;\n --xy-handle-border-color-default: #fff;\n\n --xy-selection-background-color-default: rgba(0, 89, 220, 0.08);\n --xy-selection-border-default: 1px dotted rgba(0, 89, 220, 0.8);\n\n --xy-controls-button-background-color-default: #fefefe;\n --xy-controls-button-background-color-hover-default: #f4f4f4;\n --xy-controls-button-color-default: inherit;\n --xy-controls-button-color-hover-default: inherit;\n --xy-controls-button-border-color-default: #eee;\n --xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);\n\n --xy-edge-label-background-color-default: #ffffff;\n --xy-edge-label-color-default: inherit;\n --xy-resize-background-color-default: #3367d9;\n}\n.react-flow.dark {\n --xy-edge-stroke-default: #3e3e3e;\n --xy-edge-stroke-width-default: 1;\n --xy-edge-stroke-selected-default: #727272;\n\n --xy-connectionline-stroke-default: #b1b1b7;\n --xy-connectionline-stroke-width-default: 1;\n\n --xy-attribution-background-color-default: rgba(150, 150, 150, 0.25);\n\n --xy-minimap-background-color-default: #141414;\n --xy-minimap-mask-background-color-default: rgba(60, 60, 60, 0.6);\n --xy-minimap-mask-stroke-color-default: transparent;\n --xy-minimap-mask-stroke-width-default: 1;\n --xy-minimap-node-background-color-default: #2b2b2b;\n --xy-minimap-node-stroke-color-default: transparent;\n --xy-minimap-node-stroke-width-default: 2;\n\n --xy-background-color-default: #141414;\n --xy-background-pattern-dots-color-default: #777;\n --xy-background-pattern-lines-color-default: #777;\n --xy-background-pattern-cross-color-default: #777;\n --xy-node-color-default: #f8f8f8;\n --xy-node-border-default: 1px solid #3c3c3c;\n --xy-node-background-color-default: #1e1e1e;\n --xy-node-group-background-color-default: rgba(240, 240, 240, 0.25);\n --xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, 0.08);\n --xy-node-boxshadow-selected-default: 0 0 0 0.5px #999;\n\n --xy-handle-background-color-default: #bebebe;\n --xy-handle-border-color-default: #1e1e1e;\n\n --xy-selection-background-color-default: rgba(200, 200, 220, 0.08);\n --xy-selection-border-default: 1px dotted rgba(200, 200, 220, 0.8);\n\n --xy-controls-button-background-color-default: #2b2b2b;\n --xy-controls-button-background-color-hover-default: #3e3e3e;\n --xy-controls-button-color-default: #f8f8f8;\n --xy-controls-button-color-hover-default: #fff;\n --xy-controls-button-border-color-default: #5b5b5b;\n --xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);\n\n --xy-edge-label-background-color-default: #141414;\n --xy-edge-label-color-default: #f8f8f8;\n}\n.react-flow__background {\n background-color: var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));\n pointer-events: none;\n z-index: -1;\n}\n.react-flow__container {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n}\n.react-flow__pane {\n z-index: 1;\n}\n.react-flow__pane.draggable {\n cursor: grab;\n }\n.react-flow__pane.dragging {\n cursor: grabbing;\n }\n.react-flow__pane.selection {\n cursor: pointer;\n }\n.react-flow__viewport {\n transform-origin: 0 0;\n z-index: 2;\n pointer-events: none;\n}\n.react-flow__renderer {\n z-index: 4;\n}\n.react-flow__selection {\n z-index: 6;\n}\n.react-flow__nodesselection-rect:focus,\n.react-flow__nodesselection-rect:focus-visible {\n outline: none;\n}\n.react-flow__edge-path {\n stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));\n stroke-width: var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));\n fill: none;\n}\n.react-flow__connection-path {\n stroke: var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));\n stroke-width: var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));\n fill: none;\n}\n.react-flow .react-flow__edges {\n position: absolute;\n}\n.react-flow .react-flow__edges svg {\n overflow: visible;\n position: absolute;\n pointer-events: none;\n }\n.react-flow__edge {\n pointer-events: visibleStroke;\n}\n.react-flow__edge.selectable {\n cursor: pointer;\n }\n.react-flow__edge.animated path {\n stroke-dasharray: 5;\n animation: dashdraw 0.5s linear infinite;\n }\n.react-flow__edge.animated path.react-flow__edge-interaction {\n stroke-dasharray: none;\n animation: none;\n }\n.react-flow__edge.inactive {\n pointer-events: none;\n }\n.react-flow__edge.selected,\n .react-flow__edge:focus,\n .react-flow__edge:focus-visible {\n outline: none;\n }\n.react-flow__edge.selected .react-flow__edge-path,\n .react-flow__edge.selectable:focus .react-flow__edge-path,\n .react-flow__edge.selectable:focus-visible .react-flow__edge-path {\n stroke: var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default));\n }\n.react-flow__edge-textwrapper {\n pointer-events: all;\n }\n.react-flow__edge .react-flow__edge-text {\n pointer-events: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n }\n/* Arrowhead marker styles - use CSS custom properties as default */\n.react-flow__arrowhead polyline {\n stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));\n}\n.react-flow__arrowhead polyline.arrowclosed {\n fill: var(--xy-edge-stroke, var(--xy-edge-stroke-default));\n}\n.react-flow__connection {\n pointer-events: none;\n}\n.react-flow__connection .animated {\n stroke-dasharray: 5;\n animation: dashdraw 0.5s linear infinite;\n }\nsvg.react-flow__connectionline {\n z-index: 1001;\n overflow: visible;\n position: absolute;\n}\n.react-flow__nodes {\n pointer-events: none;\n transform-origin: 0 0;\n}\n.react-flow__node {\n position: absolute;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n pointer-events: all;\n transform-origin: 0 0;\n box-sizing: border-box;\n cursor: default;\n}\n.react-flow__node.selectable {\n cursor: pointer;\n }\n.react-flow__node.draggable {\n cursor: grab;\n pointer-events: all;\n }\n.react-flow__node.draggable.dragging {\n cursor: grabbing;\n }\n.react-flow__nodesselection {\n z-index: 3;\n transform-origin: left top;\n pointer-events: none;\n}\n.react-flow__nodesselection-rect {\n position: absolute;\n pointer-events: all;\n cursor: grab;\n }\n.react-flow__handle {\n position: absolute;\n pointer-events: none;\n min-width: 5px;\n min-height: 5px;\n width: 6px;\n height: 6px;\n background-color: var(--xy-handle-background-color, var(--xy-handle-background-color-default));\n border: 1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));\n border-radius: 100%;\n}\n.react-flow__handle.connectingfrom {\n pointer-events: all;\n }\n.react-flow__handle.connectionindicator {\n pointer-events: all;\n cursor: crosshair;\n }\n.react-flow__handle-bottom {\n top: auto;\n left: 50%;\n bottom: 0;\n transform: translate(-50%, 50%);\n }\n.react-flow__handle-top {\n top: 0;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n.react-flow__handle-left {\n top: 50%;\n left: 0;\n transform: translate(-50%, -50%);\n }\n.react-flow__handle-right {\n top: 50%;\n right: 0;\n transform: translate(50%, -50%);\n }\n.react-flow__edgeupdater {\n cursor: move;\n pointer-events: all;\n}\n.react-flow__pane.selection .react-flow__panel {\n pointer-events: none;\n}\n.react-flow__panel {\n position: absolute;\n z-index: 5;\n margin: 15px;\n}\n.react-flow__panel.top {\n top: 0;\n }\n.react-flow__panel.bottom {\n bottom: 0;\n }\n.react-flow__panel.top.center, .react-flow__panel.bottom.center {\n left: 50%;\n transform: translateX(-15px) translateX(-50%);\n }\n.react-flow__panel.left {\n left: 0;\n }\n.react-flow__panel.right {\n right: 0;\n }\n.react-flow__panel.left.center, .react-flow__panel.right.center {\n top: 50%;\n transform: translateY(-15px) translateY(-50%);\n }\n.react-flow__attribution {\n font-size: 10px;\n background: var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));\n padding: 2px 3px;\n margin: 0;\n}\n.react-flow__attribution a {\n text-decoration: none;\n color: #999;\n }\n@keyframes dashdraw {\n from {\n stroke-dashoffset: 10;\n }\n}\n.react-flow__edgelabel-renderer {\n position: absolute;\n width: 100%;\n height: 100%;\n pointer-events: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n left: 0;\n top: 0;\n}\n.react-flow__viewport-portal {\n position: absolute;\n width: 100%;\n height: 100%;\n left: 0;\n top: 0;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n}\n.react-flow__minimap {\n background: var(\n --xy-minimap-background-color-props,\n var(--xy-minimap-background-color, var(--xy-minimap-background-color-default))\n );\n}\n.react-flow__minimap-svg {\n display: block;\n }\n.react-flow__minimap-mask {\n fill: var(\n --xy-minimap-mask-background-color-props,\n var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default))\n );\n stroke: var(\n --xy-minimap-mask-stroke-color-props,\n var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default))\n );\n stroke-width: var(\n --xy-minimap-mask-stroke-width-props,\n var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default))\n );\n }\n.react-flow__minimap-node {\n fill: var(\n --xy-minimap-node-background-color-props,\n var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default))\n );\n stroke: var(\n --xy-minimap-node-stroke-color-props,\n var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default))\n );\n stroke-width: var(\n --xy-minimap-node-stroke-width-props,\n var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default))\n );\n }\n.react-flow__background-pattern.dots {\n fill: var(\n --xy-background-pattern-color-props,\n var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default))\n );\n }\n.react-flow__background-pattern.lines {\n stroke: var(\n --xy-background-pattern-color-props,\n var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default))\n );\n }\n.react-flow__background-pattern.cross {\n stroke: var(\n --xy-background-pattern-color-props,\n var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default))\n );\n }\n.react-flow__controls {\n display: flex;\n flex-direction: column;\n box-shadow: var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default));\n}\n.react-flow__controls.horizontal {\n flex-direction: row;\n }\n.react-flow__controls-button {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 26px;\n width: 26px;\n padding: 4px;\n border: none;\n background: var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));\n border-bottom: 1px solid\n var(\n --xy-controls-button-border-color-props,\n var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default))\n );\n color: var(\n --xy-controls-button-color-props,\n var(--xy-controls-button-color, var(--xy-controls-button-color-default))\n );\n cursor: pointer;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n }\n.react-flow__controls-button svg {\n width: 100%;\n max-width: 12px;\n max-height: 12px;\n fill: currentColor;\n }\n.react-flow__edge.updating .react-flow__edge-path {\n stroke: #777;\n }\n.react-flow__edge-text {\n font-size: 10px;\n }\n.react-flow__node.selectable:focus,\n .react-flow__node.selectable:focus-visible {\n outline: none;\n }\n.react-flow__node-input,\n.react-flow__node-default,\n.react-flow__node-output,\n.react-flow__node-group {\n padding: 10px;\n border-radius: var(--xy-node-border-radius, var(--xy-node-border-radius-default));\n width: 150px;\n font-size: 12px;\n color: var(--xy-node-color, var(--xy-node-color-default));\n text-align: center;\n border: var(--xy-node-border, var(--xy-node-border-default));\n background-color: var(--xy-node-background-color, var(--xy-node-background-color-default));\n}\n.react-flow__node-input.selectable:hover, .react-flow__node-default.selectable:hover, .react-flow__node-output.selectable:hover, .react-flow__node-group.selectable:hover {\n box-shadow: var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default));\n }\n.react-flow__node-input.selectable.selected,\n .react-flow__node-input.selectable:focus,\n .react-flow__node-input.selectable:focus-visible,\n .react-flow__node-default.selectable.selected,\n .react-flow__node-default.selectable:focus,\n .react-flow__node-default.selectable:focus-visible,\n .react-flow__node-output.selectable.selected,\n .react-flow__node-output.selectable:focus,\n .react-flow__node-output.selectable:focus-visible,\n .react-flow__node-group.selectable.selected,\n .react-flow__node-group.selectable:focus,\n .react-flow__node-group.selectable:focus-visible {\n box-shadow: var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default));\n }\n.react-flow__node-group {\n background-color: var(--xy-node-group-background-color, var(--xy-node-group-background-color-default));\n}\n.react-flow__nodesselection-rect,\n.react-flow__selection {\n background: var(--xy-selection-background-color, var(--xy-selection-background-color-default));\n border: var(--xy-selection-border, var(--xy-selection-border-default));\n}\n.react-flow__nodesselection-rect:focus,\n .react-flow__nodesselection-rect:focus-visible,\n .react-flow__selection:focus,\n .react-flow__selection:focus-visible {\n outline: none;\n }\n.react-flow__controls-button:hover {\n background: var(\n --xy-controls-button-background-color-hover-props,\n var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default))\n );\n color: var(\n --xy-controls-button-color-hover-props,\n var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default))\n );\n }\n.react-flow__controls-button:disabled {\n pointer-events: none;\n }\n.react-flow__controls-button:disabled svg {\n fill-opacity: 0.4;\n }\n.react-flow__controls-button:last-child {\n border-bottom: none;\n }\n.react-flow__controls.horizontal .react-flow__controls-button {\n border-bottom: none;\n border-right: 1px solid\n var(\n --xy-controls-button-border-color-props,\n var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default))\n );\n }\n.react-flow__controls.horizontal .react-flow__controls-button:last-child {\n border-right: none;\n }\n.react-flow__resize-control {\n position: absolute;\n}\n.react-flow__resize-control.left,\n.react-flow__resize-control.right {\n cursor: ew-resize;\n}\n.react-flow__resize-control.top,\n.react-flow__resize-control.bottom {\n cursor: ns-resize;\n}\n.react-flow__resize-control.top.left,\n.react-flow__resize-control.bottom.right {\n cursor: nwse-resize;\n}\n.react-flow__resize-control.bottom.left,\n.react-flow__resize-control.top.right {\n cursor: nesw-resize;\n}\n/* handle styles */\n.react-flow__resize-control.handle {\n width: 5px;\n height: 5px;\n border: 1px solid #fff;\n border-radius: 1px;\n background-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));\n translate: -50% -50%;\n}\n.react-flow__resize-control.handle.left {\n left: 0;\n top: 50%;\n}\n.react-flow__resize-control.handle.right {\n left: 100%;\n top: 50%;\n}\n.react-flow__resize-control.handle.top {\n left: 50%;\n top: 0;\n}\n.react-flow__resize-control.handle.bottom {\n left: 50%;\n top: 100%;\n}\n.react-flow__resize-control.handle.top.left {\n left: 0;\n}\n.react-flow__resize-control.handle.bottom.left {\n left: 0;\n}\n.react-flow__resize-control.handle.top.right {\n left: 100%;\n}\n.react-flow__resize-control.handle.bottom.right {\n left: 100%;\n}\n/* line styles */\n.react-flow__resize-control.line {\n border-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));\n border-width: 0;\n border-style: solid;\n}\n.react-flow__resize-control.line.left,\n.react-flow__resize-control.line.right {\n width: 1px;\n transform: translate(-50%, 0);\n top: 0;\n height: 100%;\n}\n.react-flow__resize-control.line.left {\n left: 0;\n border-left-width: 1px;\n}\n.react-flow__resize-control.line.right {\n left: 100%;\n border-right-width: 1px;\n}\n.react-flow__resize-control.line.top,\n.react-flow__resize-control.line.bottom {\n height: 1px;\n transform: translate(0, -50%);\n left: 0;\n width: 100%;\n}\n.react-flow__resize-control.line.top {\n top: 0;\n border-top-width: 1px;\n}\n.react-flow__resize-control.line.bottom {\n border-bottom-width: 1px;\n top: 100%;\n}\n.react-flow__edge-textbg {\n fill: var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default));\n}\n.react-flow__edge-text {\n fill: var(--xy-edge-label-color, var(--xy-edge-label-color-default));\n}\n", "body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen,\n Ubuntu, Cantarell, \"Open Sans\", \"Helvetica Neue\", sans-serif;\n margin: 0;\n padding: 0;\n background-color: #f5f5f5;\n height: 100vh;\n overflow: hidden;\n}\n\n#root {\n height: 100vh;\n width: 100vw;\n}\n\n.dag-builder {\n height: 100vh;\n width: 100vw;\n display: flex;\n flex-direction: row;\n}\n\n.dag-container {\n flex: 1;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.dag-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n padding: 10px 20px;\n background: white;\n border-bottom: 1px solid #ddd;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.dag-title-section {\n display: flex;\n align-items: center;\n gap: 10px;\n flex: 1;\n}\n\n.title-label {\n font-size: 14px;\n font-weight: 500;\n color: #333;\n white-space: nowrap;\n}\n\n.title-input {\n padding: 8px 12px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 14px;\n min-width: 200px;\n max-width: 300px;\n flex: 1;\n}\n\n.title-input:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);\n}\n\n.toolbar-actions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.toolbar-button {\n padding: 8px 16px;\n border: 1px solid #ddd;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 14px;\n transition: all 0.2s ease;\n}\n\n.toolbar-button:hover {\n background: #f8f9fa;\n border-color: #007bff;\n}\n\n.toolbar-button.publish {\n background: #28a745;\n color: white;\n border-color: #28a745;\n}\n\n.toolbar-button.publish:hover {\n background: #218838;\n}\n\n.toolbar-button.disabled {\n background: #f8f9fa;\n color: #6c757d;\n cursor: not-allowed;\n border-color: #dee2e6;\n}\n\n.validation-errors {\n margin-left: 20px;\n color: #dc3545;\n font-size: 12px;\n}\n\n.validation-errors ul {\n margin: 4px 0;\n padding-left: 16px;\n}\n\n.dag-flow {\n flex: 1;\n height: calc(100vh - 60px);\n}\n\n/* Left Sidebar Styles */\n.content-sidebar {\n width: 350px;\n height: 100vh;\n background: white;\n border-right: 1px solid #ddd;\n display: flex;\n flex-direction: column;\n box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);\n}\n\n/* Right Sidebar Styles */\n.artifacts-sidebar {\n width: 350px;\n height: 100vh;\n background: white;\n border-left: 1px solid #ddd;\n display: flex;\n flex-direction: column;\n box-shadow: -2px 0 4px rgba(0, 0, 0, 0.1);\n}\n\n.sidebar-header {\n padding: 20px;\n border-bottom: 1px solid #eee;\n background: #f8f9fa;\n}\n\n.sidebar-header h2 {\n margin: 0;\n color: #333;\n font-size: 18px;\n}\n\n.sidebar-search {\n padding: 15px 20px;\n border-bottom: 1px solid #eee;\n}\n\n.sidebar-search .search-form {\n margin: 0;\n}\n\n.sidebar-search .search-input {\n width: 100%;\n padding: 10px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 14px;\n}\n\n.sidebar-content {\n flex: 1;\n overflow-y: auto;\n padding: 10px;\n}\n\n.search-hint,\n.no-results {\n text-align: center;\n color: #666;\n font-style: italic;\n padding: 20px;\n margin: 20px 0;\n}\n\n.search-result-item.draggable {\n position: relative;\n cursor: grab;\n border: 2px dashed transparent;\n transition: all 0.2s ease;\n margin-bottom: 12px;\n}\n\n.search-result-item.draggable:hover {\n border-color: #007bff;\n background: #f8f9fa;\n transform: translateY(-1px);\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n}\n\n.search-result-item.draggable:active {\n cursor: grabbing;\n}\n\n.result-meta {\n display: flex;\n flex-direction: column;\n gap: 2px;\n margin: 4px 0;\n}\n\n.result-author,\n.result-date {\n font-size: 11px;\n color: #666;\n}\n\n.drag-hint {\n position: absolute;\n top: 5px;\n right: 5px;\n font-size: 10px;\n color: #999;\n background: rgba(255, 255, 255, 0.9);\n padding: 2px 6px;\n border-radius: 3px;\n opacity: 0;\n transition: opacity 0.2s ease;\n}\n\n.search-result-item.draggable:hover .drag-hint {\n opacity: 1;\n}\n\n/* Custom Node Styles */\n.content-node {\n background: white;\n border: 2px solid #ddd;\n border-radius: 8px;\n padding: 10px;\n min-width: 150px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n}\n\n.content-node.selected {\n border-color: #007bff;\n}\n\n.node-header {\n margin-bottom: 8px;\n padding-bottom: 4px;\n border-bottom: 1px solid #eee;\n}\n\n.node-body {\n font-size: 12px;\n color: #666;\n}\n\n.content-type {\n background: #007bff;\n color: white;\n padding: 2px 6px;\n border-radius: 3px;\n display: inline-block;\n margin-bottom: 4px;\n font-size: 10px;\n text-transform: uppercase;\n}\n\n.content-author {\n font-size: 11px;\n color: #666;\n margin-bottom: 4px;\n}\n\n.content-guid {\n font-family: monospace;\n font-size: 10px;\n color: #999;\n word-break: break-all;\n}\n\n/* Shared styles for search results */\n.search-results {\n overflow-y: auto;\n}\n\n.search-result-item {\n padding: 12px;\n border: 1px solid #eee;\n border-radius: 4px;\n margin-bottom: 8px;\n transition: all 0.2s ease;\n}\n\n.search-result-item:not(.draggable):hover {\n background: #f8f9fa;\n border-color: #007bff;\n}\n\n.result-name {\n font-weight: bold;\n margin-bottom: 4px;\n color: #333;\n}\n\n.result-type {\n background: #6c757d;\n color: white;\n padding: 2px 6px;\n border-radius: 3px;\n display: inline-block;\n font-size: 10px;\n text-transform: uppercase;\n margin-bottom: 4px;\n}\n\n.result-description {\n font-size: 12px;\n color: #666;\n margin: 4px 0;\n line-height: 1.4;\n}\n\n.result-guid {\n font-family: monospace;\n font-size: 11px;\n color: #999;\n word-break: break-all;\n}\n\n/* Edge Delete Button Styles */\n.button-edge__label {\n position: absolute;\n pointer-events: all;\n font-size: 12px;\n z-index: 1000;\n}\n\n.button-edge__button {\n width: 20px;\n height: 20px;\n background: #ff4757;\n border: none;\n color: white;\n border-radius: 50%;\n cursor: pointer;\n font-size: 12px;\n line-height: 1;\n opacity: 1;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: bold;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n z-index: 1001;\n position: relative;\n}\n\n.button-edge__button:hover {\n background: #ff3742;\n transform: scale(1.1);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n}\n\n/* Ensure the EdgeLabelRenderer content appears above edges */\n.react-flow__edge-labels {\n z-index: 1000;\n}\n\n.react-flow__edge-labels > div {\n z-index: 1000;\n}\n\n/* Notification Styles */\n.notifications {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 2000;\n max-width: 400px;\n}\n\n.toast {\n background: white;\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n margin-bottom: 10px;\n padding: 12px 16px;\n border-left: 4px solid #007bff;\n animation: slideIn 0.3s ease-out;\n}\n\n.toast-success {\n border-left-color: #28a745;\n}\n\n.toast-error {\n border-left-color: #dc3545;\n}\n\n.toast-info {\n border-left-color: #17a2b8;\n}\n\n.toast-warning {\n border-left-color: #ffc107;\n}\n\n@keyframes slideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n/* Artifacts List Styles */\n.artifacts-list {\n overflow-y: auto;\n padding: 10px;\n}\n\n.artifact-item {\n padding: 12px;\n border: 1px solid #eee;\n border-radius: 4px;\n margin-bottom: 8px;\n transition: all 0.2s ease;\n background: white;\n position: relative;\n}\n\n.artifact-item:hover {\n background: #f8f9fa;\n border-color: #007bff;\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.artifact-item-active {\n background: #e7f3ff !important;\n border: 2px solid #007bff !important;\n box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3) !important;\n}\n\n.artifact-item-active:hover {\n background: #d4e9ff !important;\n border-color: #0056b3 !important;\n}\n\n.loaded-indicator {\n color: #28a745;\n font-size: 12px;\n font-weight: normal;\n animation: pulse 2s ease-in-out infinite;\n}\n\n.publishing-spinner {\n display: inline-block;\n color: #007bff;\n font-size: 16px;\n margin-right: 6px;\n animation: spin 1s linear infinite;\n}\n\n@keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n}\n\n@keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}\n\n.artifact-name {\n font-weight: bold;\n margin-bottom: 4px;\n color: #333;\n font-size: 14px;\n}\n\n.artifact-meta {\n display: flex;\n flex-direction: column;\n gap: 2px;\n margin: 4px 0;\n font-size: 11px;\n color: #666;\n}\n\n.artifact-stats {\n display: flex;\n gap: 8px;\n margin: 4px 0;\n}\n\n.artifact-stat {\n background: #e9ecef;\n padding: 2px 6px;\n border-radius: 3px;\n font-size: 10px;\n color: #495057;\n}\n\n.artifact-id {\n font-family: monospace;\n font-size: 10px;\n color: #999;\n margin: 4px 0;\n}\n\n.artifact-buttons {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n\n.clone-button, .download-button, .publish-direct-button {\n flex: 1;\n border: none;\n padding: 8px 12px;\n border-radius: 4px;\n font-size: 12px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.clone-button {\n background: #6f42c1;\n color: white;\n}\n\n.clone-button:hover {\n background: #5a32a3;\n transform: translateY(-1px);\n}\n\n.download-button {\n background: #007bff;\n color: white;\n}\n\n.download-button:hover {\n background: #0056b3;\n transform: translateY(-1px);\n}\n\n.publish-direct-button {\n background: #28a745;\n color: white;\n}\n\n.publish-direct-button:hover {\n background: #218838;\n transform: translateY(-1px);\n}\n\n.clone-button:active, .download-button:active, .publish-direct-button:active {\n transform: translateY(0);\n}\n\n.clone-button:disabled, .download-button:disabled, .publish-direct-button:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n transform: none;\n}\n\n.clone-button:disabled:hover, .download-button:disabled:hover, .publish-direct-button:disabled:hover {\n transform: none;\n background: #6f42c1; /* Keep original color */\n}\n\n.publish-direct-button:disabled:hover {\n background: #28a745; /* Keep original color */\n}\n\n.download-button:disabled:hover {\n background: #007bff; /* Keep original color */\n}\n\n.no-artifacts {\n text-align: center;\n color: #666;\n font-style: italic;\n padding: 40px 20px;\n margin: 20px 0;\n}\n\n/* Artifact Header and Delete Button Styles */\n.artifact-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 4px;\n}\n\n.delete-artifact-button {\n background: #dc3545;\n color: white;\n border: none;\n border-radius: 50%;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n font-size: 16px;\n font-weight: bold;\n line-height: 1;\n transition: all 0.2s ease;\n flex-shrink: 0;\n}\n\n.delete-artifact-button:hover {\n background: #c82333;\n transform: scale(1.1);\n}\n\n/* Modal Styles */\n.modal-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n}\n\n.modal-content {\n background: white;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);\n max-width: 400px;\n width: 90%;\n max-height: 90vh;\n overflow-y: auto;\n}\n\n.modal-header {\n padding: 20px 20px 10px 20px;\n border-bottom: 1px solid #eee;\n}\n\n.modal-header h3 {\n margin: 0;\n color: #333;\n font-size: 18px;\n}\n\n.modal-body {\n padding: 20px;\n}\n\n.modal-body p {\n margin: 0 0 10px 0;\n color: #666;\n line-height: 1.4;\n}\n\n.modal-footer {\n padding: 10px 20px 20px 20px;\n display: flex;\n gap: 10px;\n justify-content: flex-end;\n}\n\n.modal-button {\n padding: 8px 16px;\n border: none;\n border-radius: 4px;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.modal-button.cancel {\n background: #6c757d;\n color: white;\n}\n\n.modal-button.cancel:hover {\n background: #5a6268;\n}\n\n.modal-button.delete {\n background: #dc3545;\n color: white;\n}\n\n.modal-button.delete:hover {\n background: #c82333;\n}\n\n/* Custom Nodes Pane Styles */\n.custom-nodes-pane {\n border-top: 1px solid #eee;\n margin-top: 10px;\n padding-top: 15px;\n}\n\n.custom-nodes-header {\n padding: 0 10px 10px 10px;\n}\n\n.custom-nodes-header h3 {\n margin: 0;\n color: #333;\n font-size: 16px;\n font-weight: 600;\n}\n\n.custom-nodes-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 10px;\n}\n\n.custom-node-item {\n position: relative;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n border: 2px dashed transparent;\n border-radius: 6px;\n background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);\n cursor: grab;\n transition: all 0.2s ease;\n}\n\n.custom-node-item.draggable:hover {\n border-color: #ff6b6b;\n background: linear-gradient(135deg, #fff5f5 0%, #ffe3e3 100%);\n transform: translateY(-1px);\n box-shadow: 0 4px 8px rgba(255, 107, 107, 0.15);\n}\n\n.custom-node-item.draggable:active {\n cursor: grabbing;\n}\n\n.custom-node-icon {\n font-size: 20px;\n flex-shrink: 0;\n}\n\n.custom-node-content {\n flex: 1;\n}\n\n.custom-node-name {\n font-weight: 600;\n color: #333;\n margin-bottom: 2px;\n font-size: 14px;\n}\n\n.custom-node-description {\n font-size: 11px;\n color: #666;\n line-height: 1.3;\n}\n\n/* Custom Node Component Styles */\n.custom-node {\n background: linear-gradient(135deg, #fff5f5 0%, #ffe3e3 100%);\n border: 2px solid #ff6b6b;\n border-radius: 8px;\n padding: 12px;\n min-width: 200px;\n max-width: 350px;\n box-shadow: 0 4px 12px rgba(255, 107, 107, 0.15);\n font-size: 13px;\n}\n\n.custom-node.selected {\n border-color: #ff4757;\n box-shadow: 0 0 0 2px rgba(255, 71, 87, 0.3);\n}\n\n.custom-node .node-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n padding-bottom: 6px;\n border-bottom: 1px solid rgba(255, 107, 107, 0.2);\n}\n\n.custom-node-title {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.custom-node-title .custom-node-icon {\n font-size: 16px;\n}\n\n.expand-button {\n background: #ff6b6b;\n color: white;\n border: none;\n border-radius: 50%;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n font-size: 14px;\n font-weight: bold;\n transition: all 0.2s ease;\n}\n\n.expand-button:hover {\n background: #ff4757;\n transform: scale(1.1);\n}\n\n.node-summary {\n margin-top: 4px;\n}\n\n.node-summary .custom-node-description {\n font-size: 11px;\n color: #666;\n font-style: italic;\n}\n\n/* Custom Node Form Styles */\n.custom-node-form {\n margin-top: 8px;\n}\n\n.form-group {\n margin-bottom: 10px;\n}\n\n.form-group:last-child {\n margin-bottom: 0;\n}\n\n.form-group label {\n display: block;\n font-size: 11px;\n font-weight: 600;\n color: #555;\n margin-bottom: 4px;\n}\n\n.form-input, .form-select, .form-textarea {\n width: 100%;\n padding: 6px 8px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 12px;\n font-family: inherit;\n transition: border-color 0.2s ease;\n}\n\n.form-input:focus, .form-select:focus, .form-textarea:focus {\n outline: none;\n border-color: #ff6b6b;\n box-shadow: 0 0 0 2px rgba(255, 107, 107, 0.2);\n}\n\n.form-textarea {\n resize: vertical;\n min-height: 60px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n}\n\n.form-select {\n background: white;\n cursor: pointer;\n}\n\n/* Enhanced Condition Node Styles */\n.condition-textarea {\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 11px;\n}\n\n.condition-help {\n margin-top: 6px;\n}\n\n.condition-help details {\n font-size: 10px;\n}\n\n.condition-help summary {\n cursor: pointer;\n color: #666;\n font-weight: 500;\n}\n\n.condition-help summary:hover {\n color: #333;\n}\n\n.help-content {\n margin-top: 6px;\n padding: 8px;\n background: #f8f9fa;\n border-radius: 4px;\n border-left: 3px solid #ff6b6b;\n font-size: 10px;\n line-height: 1.4;\n}\n\n.help-content code {\n background: #e9ecef;\n padding: 1px 3px;\n border-radius: 2px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 9px;\n}\n"], + "mappings": "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", + "names": [] +} diff --git a/extensions/dag-builder/py/www/main.js b/extensions/dag-builder/py/www/main.js new file mode 100644 index 00000000..5b28d618 --- /dev/null +++ b/extensions/dag-builder/py/www/main.js @@ -0,0 +1,95 @@ +var vS=Object.create;var Nh=Object.defineProperty;var bS=Object.getOwnPropertyDescriptor;var xS=Object.getOwnPropertyNames;var SS=Object.getPrototypeOf,wS=Object.prototype.hasOwnProperty;var ut=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var _S=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of xS(t))!wS.call(e,a)&&a!==n&&Nh(e,a,{get:()=>t[a],enumerable:!(o=bS(t,a))||o.enumerable});return e};var Te=(e,t,n)=>(n=e!=null?vS(SS(e)):{},_S(t||!e||!e.__esModule?Nh(n,"default",{value:e,enumerable:!0}):n,e));var kh=ut(Se=>{"use strict";function ks(e,t){var n=e.length;e.push(t);e:for(;0>>1,a=e[o];if(0>>1;onr(r,n))unr(s,r)?(e[o]=s,e[u]=n,o=u):(e[o]=r,e[i]=n,o=i);else if(unr(s,n))e[o]=s,e[u]=n,o=u;else break e}}return t}function nr(e,t){var n=e.sortIndex-t.sortIndex;return n!==0?n:e.id-t.id}Se.unstable_now=void 0;typeof performance=="object"&&typeof performance.now=="function"?(Mh=performance,Se.unstable_now=function(){return Mh.now()}):(Os=Date,Ah=Os.now(),Se.unstable_now=function(){return Os.now()-Ah});var Mh,Os,Ah,cn=[],Hn=[],ES=1,Mt=null,Fe=3,Bs=!1,gl=!1,ml=!1,Us=!1,zh=typeof setTimeout=="function"?setTimeout:null,Dh=typeof clearTimeout=="function"?clearTimeout:null,Th=typeof setImmediate<"u"?setImmediate:null;function or(e){for(var t=jt(Hn);t!==null;){if(t.callback===null)ar(Hn);else if(t.startTime<=e)ar(Hn),t.sortIndex=t.expirationTime,ks(cn,t);else break;t=jt(Hn)}}function Ls(e){if(ml=!1,or(e),!gl)if(jt(cn)!==null)gl=!0,Po||(Po=!0,Io());else{var t=jt(Hn);t!==null&&Ys(Ls,t.startTime-e)}}var Po=!1,yl=-1,Oh=5,Rh=-1;function Hh(){return Us?!0:!(Se.unstable_now()-Rhe&&Hh());){var o=Mt.callback;if(typeof o=="function"){Mt.callback=null,Fe=Mt.priorityLevel;var a=o(Mt.expirationTime<=e);if(e=Se.unstable_now(),typeof a=="function"){Mt.callback=a,or(e),t=!0;break t}Mt===jt(cn)&&ar(cn),or(e)}else ar(cn);Mt=jt(cn)}if(Mt!==null)t=!0;else{var l=jt(Hn);l!==null&&Ys(Ls,l.startTime-e),t=!1}}break e}finally{Mt=null,Fe=n,Bs=!1}t=void 0}}finally{t?Io():Po=!1}}}var Io;typeof Th=="function"?Io=function(){Th(Rs)}:typeof MessageChannel<"u"?(Hs=new MessageChannel,Ch=Hs.port2,Hs.port1.onmessage=Rs,Io=function(){Ch.postMessage(null)}):Io=function(){zh(Rs,0)};var Hs,Ch;function Ys(e,t){yl=zh(function(){e(Se.unstable_now())},t)}Se.unstable_IdlePriority=5;Se.unstable_ImmediatePriority=1;Se.unstable_LowPriority=4;Se.unstable_NormalPriority=3;Se.unstable_Profiling=null;Se.unstable_UserBlockingPriority=2;Se.unstable_cancelCallback=function(e){e.callback=null};Se.unstable_forceFrameRate=function(e){0>e||125o?(e.sortIndex=n,ks(Hn,e),jt(cn)===null&&e===jt(Hn)&&(ml?(Dh(yl),yl=-1):ml=!0,Ys(Ls,n-o))):(e.sortIndex=a,ks(cn,e),gl||Bs||(gl=!0,Po||(Po=!0,Io()))),e};Se.unstable_shouldYield=Hh;Se.unstable_wrapCallback=function(e){var t=Fe;return function(){var n=Fe;Fe=t;try{return e.apply(this,arguments)}finally{Fe=n}}}});var Uh=ut((WA,Bh)=>{"use strict";Bh.exports=kh()});var Jh=ut(te=>{"use strict";var qs=Symbol.for("react.transitional.element"),NS=Symbol.for("react.portal"),MS=Symbol.for("react.fragment"),AS=Symbol.for("react.strict_mode"),TS=Symbol.for("react.profiler"),CS=Symbol.for("react.consumer"),zS=Symbol.for("react.context"),DS=Symbol.for("react.forward_ref"),OS=Symbol.for("react.suspense"),RS=Symbol.for("react.memo"),Gh=Symbol.for("react.lazy"),Lh=Symbol.iterator;function HS(e){return e===null||typeof e!="object"?null:(e=Lh&&e[Lh]||e["@@iterator"],typeof e=="function"?e:null)}var Zh={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Qh=Object.assign,Kh={};function Fo(e,t,n){this.props=e,this.context=t,this.refs=Kh,this.updater=n||Zh}Fo.prototype.isReactComponent={};Fo.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Fo.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function jh(){}jh.prototype=Fo.prototype;function Xs(e,t,n){this.props=e,this.context=t,this.refs=Kh,this.updater=n||Zh}var Gs=Xs.prototype=new jh;Gs.constructor=Xs;Qh(Gs,Fo.prototype);Gs.isPureReactComponent=!0;var Yh=Array.isArray,we={H:null,A:null,T:null,S:null,V:null},$h=Object.prototype.hasOwnProperty;function Zs(e,t,n,o,a,l){return n=l.ref,{$$typeof:qs,type:e,key:t,ref:n!==void 0?n:null,props:l}}function kS(e,t){return Zs(e.type,t,void 0,void 0,void 0,e.props)}function Qs(e){return typeof e=="object"&&e!==null&&e.$$typeof===qs}function BS(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,function(n){return t[n]})}var Vh=/\/+/g;function Vs(e,t){return typeof e=="object"&&e!==null&&e.key!=null?BS(""+e.key):t.toString(36)}function qh(){}function US(e){switch(e.status){case"fulfilled":return e.value;case"rejected":throw e.reason;default:switch(typeof e.status=="string"?e.then(qh,qh):(e.status="pending",e.then(function(t){e.status==="pending"&&(e.status="fulfilled",e.value=t)},function(t){e.status==="pending"&&(e.status="rejected",e.reason=t)})),e.status){case"fulfilled":return e.value;case"rejected":throw e.reason}}throw e}function Wo(e,t,n,o,a){var l=typeof e;(l==="undefined"||l==="boolean")&&(e=null);var i=!1;if(e===null)i=!0;else switch(l){case"bigint":case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case qs:case NS:i=!0;break;case Gh:return i=e._init,Wo(i(e._payload),t,n,o,a)}}if(i)return a=a(e),i=o===""?"."+Vs(e,0):o,Yh(a)?(n="",i!=null&&(n=i.replace(Vh,"$&/")+"/"),Wo(a,t,n,"",function(s){return s})):a!=null&&(Qs(a)&&(a=kS(a,n+(a.key==null||e&&e.key===a.key?"":(""+a.key).replace(Vh,"$&/")+"/")+i)),t.push(a)),1;i=0;var r=o===""?".":o+":";if(Yh(e))for(var u=0;u{"use strict";Ih.exports=Jh()});var Wh=ut(ot=>{"use strict";var VS=Lt();function Ph(e){var t="https://react.dev/errors/"+e;if(1{"use strict";function Fh(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Fh)}catch(e){console.error(e)}}Fh(),ep.exports=Wh()});var ny=ut(Tu=>{"use strict";var Ye=Uh(),wg=Lt(),GS=Ks();function U(e){var t="https://react.dev/errors/"+e;if(1ra||(e.current=Cc[ra],Cc[ra]=null,ra--)}function Ee(e,t){ra++,Cc[ra]=e.current,e.current=t}var Pt=en(null),Ql=en(null),$n=en(null),Ur=en(null);function Lr(e,t){switch(Ee($n,t),Ee(Ql,e),Ee(Pt,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?ug(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=ug(t),e=Z0(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}Ze(Pt),Ee(Pt,e)}function Ma(){Ze(Pt),Ze(Ql),Ze($n)}function zc(e){e.memoizedState!==null&&Ee(Ur,e);var t=Pt.current,n=Z0(t,e.type);t!==n&&(Ee(Ql,e),Ee(Pt,n))}function Yr(e){Ql.current===e&&(Ze(Pt),Ze(Ql)),Ur.current===e&&(Ze(Ur),ti._currentValue=bo)}var Dc=Object.prototype.hasOwnProperty,wf=Ye.unstable_scheduleCallback,js=Ye.unstable_cancelCallback,JS=Ye.unstable_shouldYield,IS=Ye.unstable_requestPaint,Wt=Ye.unstable_now,PS=Ye.unstable_getCurrentPriorityLevel,Tg=Ye.unstable_ImmediatePriority,Cg=Ye.unstable_UserBlockingPriority,Vr=Ye.unstable_NormalPriority,WS=Ye.unstable_LowPriority,zg=Ye.unstable_IdlePriority,FS=Ye.log,ew=Ye.unstable_setDisableYieldValue,ii=null,xt=null;function Zn(e){if(typeof FS=="function"&&ew(e),xt&&typeof xt.setStrictMode=="function")try{xt.setStrictMode(ii,e)}catch{}}var St=Math.clz32?Math.clz32:ow,tw=Math.log,nw=Math.LN2;function ow(e){return e>>>=0,e===0?32:31-(tw(e)/nw|0)|0}var ur=256,sr=4194304;function mo(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function hu(e,t,n){var o=e.pendingLanes;if(o===0)return 0;var a=0,l=e.suspendedLanes,i=e.pingedLanes;e=e.warmLanes;var r=o&134217727;return r!==0?(o=r&~l,o!==0?a=mo(o):(i&=r,i!==0?a=mo(i):n||(n=r&~e,n!==0&&(a=mo(n))))):(r=o&~l,r!==0?a=mo(r):i!==0?a=mo(i):n||(n=o&~e,n!==0&&(a=mo(n)))),a===0?0:t!==0&&t!==a&&(t&l)===0&&(l=a&-a,n=t&-t,l>=n||l===32&&(n&4194048)!==0)?t:a}function ri(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function aw(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Dg(){var e=ur;return ur<<=1,(ur&4194048)===0&&(ur=256),e}function Og(){var e=sr;return sr<<=1,(sr&62914560)===0&&(sr=4194304),e}function $s(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function ui(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function lw(e,t,n,o,a,l){var i=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var r=e.entanglements,u=e.expirationTimes,s=e.hiddenUpdates;for(n=i&~n;0)":-1a||u[o]!==s[a]){var f=` +`+u[o].replace(" at new "," at ");return e.displayName&&f.includes("")&&(f=f.replace("",e.displayName)),f}while(1<=o&&0<=a);break}}}finally{Is=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:"")?oa(n):""}function fw(e){switch(e.tag){case 26:case 27:case 5:return oa(e.type);case 16:return oa("Lazy");case 13:return oa("Suspense");case 19:return oa("SuspenseList");case 0:case 15:return Ps(e.type,!1);case 11:return Ps(e.type.render,!1);case 1:return Ps(e.type,!0);case 31:return oa("Activity");default:return""}}function rp(e){try{var t="";do t+=fw(e),e=e.return;while(e);return t}catch(n){return` +Error generating stack: `+n.message+` +`+n.stack}}function Tt(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Lg(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function dw(e){var t=Lg(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),o=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var a=n.get,l=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return a.call(this)},set:function(i){o=""+i,l.call(this,i)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return o},setValue:function(i){o=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function qr(e){e._valueTracker||(e._valueTracker=dw(e))}function Yg(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),o="";return e&&(o=Lg(e)?e.checked?"true":"false":e.value),e=o,e!==n?(t.setValue(e),!0):!1}function Xr(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var hw=/[\n"\\]/g;function Dt(e){return e.replace(hw,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Rc(e,t,n,o,a,l,i,r){e.name="",i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"?e.type=i:e.removeAttribute("type"),t!=null?i==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+Tt(t)):e.value!==""+Tt(t)&&(e.value=""+Tt(t)):i!=="submit"&&i!=="reset"||e.removeAttribute("value"),t!=null?Hc(e,i,Tt(t)):n!=null?Hc(e,i,Tt(n)):o!=null&&e.removeAttribute("value"),a==null&&l!=null&&(e.defaultChecked=!!l),a!=null&&(e.checked=a&&typeof a!="function"&&typeof a!="symbol"),r!=null&&typeof r!="function"&&typeof r!="symbol"&&typeof r!="boolean"?e.name=""+Tt(r):e.removeAttribute("name")}function Vg(e,t,n,o,a,l,i,r){if(l!=null&&typeof l!="function"&&typeof l!="symbol"&&typeof l!="boolean"&&(e.type=l),t!=null||n!=null){if(!(l!=="submit"&&l!=="reset"||t!=null))return;n=n!=null?""+Tt(n):"",t=t!=null?""+Tt(t):n,r||t===e.value||(e.value=t),e.defaultValue=t}o=o??a,o=typeof o!="function"&&typeof o!="symbol"&&!!o,e.checked=r?e.checked:!!o,e.defaultChecked=!!o,i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(e.name=i)}function Hc(e,t,n){t==="number"&&Xr(e.ownerDocument)===e||e.defaultValue===""+n||(e.defaultValue=""+n)}function ba(e,t,n,o){if(e=e.options,t){t={};for(var a=0;a"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Bc=!1;if(wn)try{ea={},Object.defineProperty(ea,"passive",{get:function(){Bc=!0}}),window.addEventListener("test",ea,ea),window.removeEventListener("test",ea,ea)}catch{Bc=!1}var ea,Qn=null,Tf=null,Mr=null;function Qg(){if(Mr)return Mr;var e,t=Tf,n=t.length,o,a="value"in Qn?Qn.value:Qn.textContent,l=a.length;for(e=0;e=Dl),pp=" ",gp=!1;function jg(e,t){switch(e){case"keyup":return qw.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function $g(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ca=!1;function Gw(e,t){switch(e){case"compositionend":return $g(t);case"keypress":return t.which!==32?null:(gp=!0,pp);case"textInput":return e=t.data,e===pp&&gp?null:e;default:return null}}function Zw(e,t){if(ca)return e==="compositionend"||!zf&&jg(e,t)?(e=Qg(),Mr=Tf=Qn=null,ca=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=o}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=bp(n)}}function Wg(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Wg(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Fg(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Xr(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Xr(e.document)}return t}function Df(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Ww=wn&&"documentMode"in document&&11>=document.documentMode,fa=null,Uc=null,Rl=null,Lc=!1;function Sp(e,t,n){var o=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Lc||fa==null||fa!==Xr(o)||(o=fa,"selectionStart"in o&&Df(o)?o={start:o.selectionStart,end:o.selectionEnd}:(o=(o.ownerDocument&&o.ownerDocument.defaultView||window).getSelection(),o={anchorNode:o.anchorNode,anchorOffset:o.anchorOffset,focusNode:o.focusNode,focusOffset:o.focusOffset}),Rl&&$l(Rl,o)||(Rl=o,o=iu(Uc,"onSelect"),0>=i,a-=i,mn=1<<32-St(t)+a|n<l?l:8;var i=W.T,r={};W.T=r,Wf(e,!1,t,n);try{var u=a(),s=W.S;if(s!==null&&s(r,u),u!==null&&typeof u=="object"&&typeof u.then=="function"){var f=r_(u,o);Yl(e,t,f,wt(e))}else Yl(e,t,o,wt(e))}catch(d){Yl(e,t,{then:function(){},status:"rejected",reason:d},wt())}finally{de.p=l,W.T=i}}function d_(){}function Pc(e,t,n,o){if(e.tag!==5)throw Error(U(476));var a=Lm(e).queue;Um(e,a,t,bo,n===null?d_:function(){return Ym(e),n(o)})}function Lm(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:bo,baseState:bo,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:_n,lastRenderedState:bo},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:_n,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Ym(e){var t=Lm(e).next.queue;Yl(e,t,{},wt())}function Pf(){return tt(ti)}function Vm(){return Be().memoizedState}function qm(){return Be().memoizedState}function h_(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=wt();e=Jn(n);var o=In(t,e,n);o!==null&&(_t(o,t,n),Bl(o,t,n)),t={cache:Uf()},e.payload=t;return}t=t.return}}function p_(e,t,n){var o=wt();n={lane:o,revertLane:0,action:n,hasEagerState:!1,eagerState:null,next:null},wu(e)?Gm(t,n):(n=Rf(e,t,n,o),n!==null&&(_t(n,e,o),Zm(n,t,o)))}function Xm(e,t,n){var o=wt();Yl(e,t,n,o)}function Yl(e,t,n,o){var a={lane:o,revertLane:0,action:n,hasEagerState:!1,eagerState:null,next:null};if(wu(e))Gm(t,a);else{var l=e.alternate;if(e.lanes===0&&(l===null||l.lanes===0)&&(l=t.lastRenderedReducer,l!==null))try{var i=t.lastRenderedState,r=l(i,n);if(a.hasEagerState=!0,a.eagerState=r,Et(r,i))return vu(e,t,a,0),ve===null&&yu(),!1}catch{}finally{}if(n=Rf(e,t,a,o),n!==null)return _t(n,e,o),Zm(n,t,o),!0}return!1}function Wf(e,t,n,o){if(o={lane:2,revertLane:id(),action:o,hasEagerState:!1,eagerState:null,next:null},wu(e)){if(t)throw Error(U(479))}else t=Rf(e,n,o,2),t!==null&&_t(t,e,2)}function wu(e){var t=e.alternate;return e===oe||t!==null&&t===oe}function Gm(e,t){wa=$r=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Zm(e,t,n){if((n&4194048)!==0){var o=t.lanes;o&=e.pendingLanes,n|=o,t.lanes=n,Hg(e,n)}}var Ir={readContext:tt,use:xu,useCallback:Re,useContext:Re,useEffect:Re,useImperativeHandle:Re,useLayoutEffect:Re,useInsertionEffect:Re,useMemo:Re,useReducer:Re,useRef:Re,useState:Re,useDebugValue:Re,useDeferredValue:Re,useTransition:Re,useSyncExternalStore:Re,useId:Re,useHostTransitionStatus:Re,useFormState:Re,useActionState:Re,useOptimistic:Re,useMemoCache:Re,useCacheRefresh:Re},Qm={readContext:tt,use:xu,useCallback:function(e,t){return st().memoizedState=[e,t===void 0?null:t],e},useContext:tt,useEffect:Up,useImperativeHandle:function(e,t,n){n=n!=null?n.concat([e]):null,Dr(4194308,4,Om.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Dr(4194308,4,e,t)},useInsertionEffect:function(e,t){Dr(4,2,e,t)},useMemo:function(e,t){var n=st();t=t===void 0?null:t;var o=e();if(To){Zn(!0);try{e()}finally{Zn(!1)}}return n.memoizedState=[o,t],o},useReducer:function(e,t,n){var o=st();if(n!==void 0){var a=n(t);if(To){Zn(!0);try{n(t)}finally{Zn(!1)}}}else a=t;return o.memoizedState=o.baseState=a,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:a},o.queue=e,e=e.dispatch=p_.bind(null,oe,e),[o.memoizedState,e]},useRef:function(e){var t=st();return e={current:e},t.memoizedState=e},useState:function(e){e=Jc(e);var t=e.queue,n=Xm.bind(null,oe,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:Jf,useDeferredValue:function(e,t){var n=st();return If(n,e,t)},useTransition:function(){var e=Jc(!1);return e=Um.bind(null,oe,e.queue,!0,!1),st().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var o=oe,a=st();if(fe){if(n===void 0)throw Error(U(407));n=n()}else{if(n=t(),ve===null)throw Error(U(349));(ce&124)!==0||vm(o,t,n)}a.memoizedState=n;var l={value:n,getSnapshot:t};return a.queue=l,Up(xm.bind(null,o,l,e),[e]),o.flags|=2048,Da(9,Su(),bm.bind(null,o,l,n,t),null),n},useId:function(){var e=st(),t=ve.identifierPrefix;if(fe){var n=yn,o=mn;n=(o&~(1<<32-St(o)-1)).toString(32)+n,t="\xAB"+t+"R"+n,n=Jr++,0N?(C=_,_=null):C=_.sibling;var L=c(g,_,p[N],y);if(L===null){_===null&&(_=C);break}e&&_&&L.alternate===null&&t(g,_),m=l(L,m,N),w===null?x=L:w.sibling=L,w=L,_=C}if(N===p.length)return n(g,_),fe&&yo(g,N),x;if(_===null){for(;NN?(C=_,_=null):C=_.sibling;var Y=c(g,_,L.value,y);if(Y===null){_===null&&(_=C);break}e&&_&&Y.alternate===null&&t(g,_),m=l(Y,m,N),w===null?x=Y:w.sibling=Y,w=Y,_=C}if(L.done)return n(g,_),fe&&yo(g,N),x;if(_===null){for(;!L.done;N++,L=p.next())L=d(g,L.value,y),L!==null&&(m=l(L,m,N),w===null?x=L:w.sibling=L,w=L);return fe&&yo(g,N),x}for(_=o(_);!L.done;N++,L=p.next())L=h(_,g,N,L.value,y),L!==null&&(e&&L.alternate!==null&&_.delete(L.key===null?N:L.key),m=l(L,m,N),w===null?x=L:w.sibling=L,w=L);return e&&_.forEach(function(D){return t(g,D)}),fe&&yo(g,N),x}function S(g,m,p,y){if(typeof p=="object"&&p!==null&&p.type===ia&&p.key===null&&(p=p.props.children),typeof p=="object"&&p!==null){switch(p.$$typeof){case rr:e:{for(var x=p.key;m!==null;){if(m.key===x){if(x=p.type,x===ia){if(m.tag===7){n(g,m.sibling),y=a(m,p.props.children),y.return=g,g=y;break e}}else if(m.elementType===x||typeof x=="object"&&x!==null&&x.$$typeof===Ln&&Lp(x)===m.type){n(g,m.sibling),y=a(m,p.props),wl(y,p),y.return=g,g=y;break e}n(g,m);break}else t(g,m);m=m.sibling}p.type===ia?(y=xo(p.props.children,g.mode,y,p.key),y.return=g,g=y):(y=Tr(p.type,p.key,p.props,null,g.mode,y),wl(y,p),y.return=g,g=y)}return i(g);case Al:e:{for(x=p.key;m!==null;){if(m.key===x)if(m.tag===4&&m.stateNode.containerInfo===p.containerInfo&&m.stateNode.implementation===p.implementation){n(g,m.sibling),y=a(m,p.children||[]),y.return=g,g=y;break e}else{n(g,m);break}else t(g,m);m=m.sibling}y=lc(p,g.mode,y),y.return=g,g=y}return i(g);case Ln:return x=p._init,p=x(p._payload),S(g,m,p,y)}if(Tl(p))return b(g,m,p,y);if(bl(p)){if(x=bl(p),typeof x!="function")throw Error(U(150));return p=x.call(p),v(g,m,p,y)}if(typeof p.then=="function")return S(g,m,mr(p),y);if(p.$$typeof===gn)return S(g,m,pr(g,p),y);yr(g,p)}return typeof p=="string"&&p!==""||typeof p=="number"||typeof p=="bigint"?(p=""+p,m!==null&&m.tag===6?(n(g,m.sibling),y=a(m,p),y.return=g,g=y):(n(g,m),y=ac(p,g.mode,y),y.return=g,g=y),i(g)):n(g,m)}return function(g,m,p,y){try{Pl=0;var x=S(g,m,p,y);return Ea=null,x}catch(_){if(_===pi||_===bu)throw _;var w=bt(29,_,null,g.mode);return w.lanes=y,w.return=g,w}finally{}}}var Oa=jm(!0),$m=jm(!1),Ht=en(null),Ft=null;function qn(e){var t=e.alternate;Ee(Le,Le.current&1),Ee(Ht,e),Ft===null&&(t===null||za.current!==null||t.memoizedState!==null)&&(Ft=e)}function Jm(e){if(e.tag===22){if(Ee(Le,Le.current),Ee(Ht,e),Ft===null){var t=e.alternate;t!==null&&t.memoizedState!==null&&(Ft=e)}}else Xn(e)}function Xn(){Ee(Le,Le.current),Ee(Ht,Ht.current)}function bn(e){Ze(Ht),Ft===e&&(Ft=null),Ze(Le)}var Le=en(0);function Pr(e){for(var t=e;t!==null;){if(t.tag===13){var n=t.memoizedState;if(n!==null&&(n=n.dehydrated,n===null||n.data==="$?"||gf(n)))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function uc(e,t,n,o){t=e.memoizedState,n=n(o,t),n=n==null?t:xe({},t,n),e.memoizedState=n,e.lanes===0&&(e.updateQueue.baseState=n)}var Wc={enqueueSetState:function(e,t,n){e=e._reactInternals;var o=wt(),a=Jn(o);a.payload=t,n!=null&&(a.callback=n),t=In(e,a,o),t!==null&&(_t(t,e,o),Bl(t,e,o))},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var o=wt(),a=Jn(o);a.tag=1,a.payload=t,n!=null&&(a.callback=n),t=In(e,a,o),t!==null&&(_t(t,e,o),Bl(t,e,o))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=wt(),o=Jn(n);o.tag=2,t!=null&&(o.callback=t),t=In(e,o,n),t!==null&&(_t(t,e,n),Bl(t,e,n))}};function Yp(e,t,n,o,a,l,i){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(o,l,i):t.prototype&&t.prototype.isPureReactComponent?!$l(n,o)||!$l(a,l):!0}function Vp(e,t,n,o){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(n,o),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(n,o),t.state!==e&&Wc.enqueueReplaceState(t,t.state,null)}function Co(e,t){var n=t;if("ref"in t){n={};for(var o in t)o!=="ref"&&(n[o]=t[o])}if(e=e.defaultProps){n===t&&(n=xe({},n));for(var a in e)n[a]===void 0&&(n[a]=e[a])}return n}var Wr=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)};function Im(e){Wr(e)}function Pm(e){console.error(e)}function Wm(e){Wr(e)}function Fr(e,t){try{var n=e.onUncaughtError;n(t.value,{componentStack:t.stack})}catch(o){setTimeout(function(){throw o})}}function qp(e,t,n){try{var o=e.onCaughtError;o(n.value,{componentStack:n.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(a){setTimeout(function(){throw a})}}function Fc(e,t,n){return n=Jn(n),n.tag=3,n.payload={element:null},n.callback=function(){Fr(e,t)},n}function Fm(e){return e=Jn(e),e.tag=3,e}function e0(e,t,n,o){var a=n.type.getDerivedStateFromError;if(typeof a=="function"){var l=o.value;e.payload=function(){return a(l)},e.callback=function(){qp(t,n,o)}}var i=n.stateNode;i!==null&&typeof i.componentDidCatch=="function"&&(e.callback=function(){qp(t,n,o),typeof a!="function"&&(Pn===null?Pn=new Set([this]):Pn.add(this));var r=o.stack;this.componentDidCatch(o.value,{componentStack:r!==null?r:""})})}function m_(e,t,n,o,a){if(n.flags|=32768,o!==null&&typeof o=="object"&&typeof o.then=="function"){if(t=n.alternate,t!==null&&di(t,n,a,!0),n=Ht.current,n!==null){switch(n.tag){case 13:return Ft===null?uf():n.alternate===null&&ze===0&&(ze=3),n.flags&=-257,n.flags|=65536,n.lanes=a,o===Qc?n.flags|=16384:(t=n.updateQueue,t===null?n.updateQueue=new Set([o]):t.add(o),bc(e,o,a)),!1;case 22:return n.flags|=65536,o===Qc?n.flags|=16384:(t=n.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([o])},n.updateQueue=t):(n=t.retryQueue,n===null?t.retryQueue=new Set([o]):n.add(o)),bc(e,o,a)),!1}throw Error(U(435,n.tag))}return bc(e,o,a),uf(),!1}if(fe)return t=Ht.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=a,o!==Vc&&(e=Error(U(422),{cause:o}),Jl(Ot(e,n)))):(o!==Vc&&(t=Error(U(423),{cause:o}),Jl(Ot(t,n))),e=e.current.alternate,e.flags|=65536,a&=-a,e.lanes|=a,o=Ot(o,n),a=Fc(e.stateNode,o,a),ic(e,a),ze!==4&&(ze=2)),!1;var l=Error(U(520),{cause:o});if(l=Ot(l,n),Xl===null?Xl=[l]:Xl.push(l),ze!==4&&(ze=2),t===null)return!0;o=Ot(o,n),n=t;do{switch(n.tag){case 3:return n.flags|=65536,e=a&-a,n.lanes|=e,e=Fc(n.stateNode,o,e),ic(n,e),!1;case 1:if(t=n.type,l=n.stateNode,(n.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||l!==null&&typeof l.componentDidCatch=="function"&&(Pn===null||!Pn.has(l))))return n.flags|=65536,a&=-a,n.lanes|=a,a=Fm(a),e0(a,e,n,o),ic(n,a),!1}n=n.return}while(n!==null);return!1}var t0=Error(U(461)),Ge=!1;function je(e,t,n,o){t.child=e===null?$m(t,null,n,o):Oa(t,e.child,n,o)}function Xp(e,t,n,o,a){n=n.render;var l=t.ref;if("ref"in o){var i={};for(var r in o)r!=="ref"&&(i[r]=o[r])}else i=o;return Ao(t),o=Xf(e,t,n,i,l,a),r=Gf(),e!==null&&!Ge?(Zf(e,t,a),En(e,t,a)):(fe&&r&&kf(t),t.flags|=1,je(e,t,o,a),t.child)}function Gp(e,t,n,o,a){if(e===null){var l=n.type;return typeof l=="function"&&!Hf(l)&&l.defaultProps===void 0&&n.compare===null?(t.tag=15,t.type=l,n0(e,t,l,o,a)):(e=Tr(n.type,null,o,t,t.mode,a),e.ref=t.ref,e.return=t,t.child=e)}if(l=e.child,!Ff(e,a)){var i=l.memoizedProps;if(n=n.compare,n=n!==null?n:$l,n(i,o)&&e.ref===t.ref)return En(e,t,a)}return t.flags|=1,e=xn(l,o),e.ref=t.ref,e.return=t,t.child=e}function n0(e,t,n,o,a){if(e!==null){var l=e.memoizedProps;if($l(l,o)&&e.ref===t.ref)if(Ge=!1,t.pendingProps=o=l,Ff(e,a))(e.flags&131072)!==0&&(Ge=!0);else return t.lanes=e.lanes,En(e,t,a)}return ef(e,t,n,o,a)}function o0(e,t,n){var o=t.pendingProps,a=o.children,l=e!==null?e.memoizedState:null;if(o.mode==="hidden"){if((t.flags&128)!==0){if(o=l!==null?l.baseLanes|n:n,e!==null){for(a=t.child=e.child,l=0;a!==null;)l=l|a.lanes|a.childLanes,a=a.sibling;t.childLanes=l&~o}else t.childLanes=0,t.child=null;return Zp(e,t,o,n)}if((n&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&Cr(t,l!==null?l.cachePool:null),l!==null?Dp(t,l):$c(),Jm(t);else return t.lanes=t.childLanes=536870912,Zp(e,t,l!==null?l.baseLanes|n:n,n)}else l!==null?(Cr(t,l.cachePool),Dp(t,l),Xn(t),t.memoizedState=null):(e!==null&&Cr(t,null),$c(),Xn(t));return je(e,t,a,n),t.child}function Zp(e,t,n,o){var a=Lf();return a=a===null?null:{parent:Ue._currentValue,pool:a},t.memoizedState={baseLanes:n,cachePool:a},e!==null&&Cr(t,null),$c(),Jm(t),e!==null&&di(e,t,o,!0),null}function Or(e,t){var n=t.ref;if(n===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof n!="function"&&typeof n!="object")throw Error(U(284));(e===null||e.ref!==n)&&(t.flags|=4194816)}}function ef(e,t,n,o,a){return Ao(t),n=Xf(e,t,n,o,void 0,a),o=Gf(),e!==null&&!Ge?(Zf(e,t,a),En(e,t,a)):(fe&&o&&kf(t),t.flags|=1,je(e,t,n,a),t.child)}function Qp(e,t,n,o,a,l){return Ao(t),t.updateQueue=null,n=mm(t,o,n,a),gm(e),o=Gf(),e!==null&&!Ge?(Zf(e,t,l),En(e,t,l)):(fe&&o&&kf(t),t.flags|=1,je(e,t,n,l),t.child)}function Kp(e,t,n,o,a){if(Ao(t),t.stateNode===null){var l=pa,i=n.contextType;typeof i=="object"&&i!==null&&(l=tt(i)),l=new n(o,l),t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,l.updater=Wc,t.stateNode=l,l._reactInternals=t,l=t.stateNode,l.props=o,l.state=t.memoizedState,l.refs={},Yf(t),i=n.contextType,l.context=typeof i=="object"&&i!==null?tt(i):pa,l.state=t.memoizedState,i=n.getDerivedStateFromProps,typeof i=="function"&&(uc(t,n,i,o),l.state=t.memoizedState),typeof n.getDerivedStateFromProps=="function"||typeof l.getSnapshotBeforeUpdate=="function"||typeof l.UNSAFE_componentWillMount!="function"&&typeof l.componentWillMount!="function"||(i=l.state,typeof l.componentWillMount=="function"&&l.componentWillMount(),typeof l.UNSAFE_componentWillMount=="function"&&l.UNSAFE_componentWillMount(),i!==l.state&&Wc.enqueueReplaceState(l,l.state,null),Ll(t,o,l,a),Ul(),l.state=t.memoizedState),typeof l.componentDidMount=="function"&&(t.flags|=4194308),o=!0}else if(e===null){l=t.stateNode;var r=t.memoizedProps,u=Co(n,r);l.props=u;var s=l.context,f=n.contextType;i=pa,typeof f=="object"&&f!==null&&(i=tt(f));var d=n.getDerivedStateFromProps;f=typeof d=="function"||typeof l.getSnapshotBeforeUpdate=="function",r=t.pendingProps!==r,f||typeof l.UNSAFE_componentWillReceiveProps!="function"&&typeof l.componentWillReceiveProps!="function"||(r||s!==i)&&Vp(t,l,o,i),Yn=!1;var c=t.memoizedState;l.state=c,Ll(t,o,l,a),Ul(),s=t.memoizedState,r||c!==s||Yn?(typeof d=="function"&&(uc(t,n,d,o),s=t.memoizedState),(u=Yn||Yp(t,n,u,o,c,s,i))?(f||typeof l.UNSAFE_componentWillMount!="function"&&typeof l.componentWillMount!="function"||(typeof l.componentWillMount=="function"&&l.componentWillMount(),typeof l.UNSAFE_componentWillMount=="function"&&l.UNSAFE_componentWillMount()),typeof l.componentDidMount=="function"&&(t.flags|=4194308)):(typeof l.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=o,t.memoizedState=s),l.props=o,l.state=s,l.context=i,o=u):(typeof l.componentDidMount=="function"&&(t.flags|=4194308),o=!1)}else{l=t.stateNode,Kc(e,t),i=t.memoizedProps,f=Co(n,i),l.props=f,d=t.pendingProps,c=l.context,s=n.contextType,u=pa,typeof s=="object"&&s!==null&&(u=tt(s)),r=n.getDerivedStateFromProps,(s=typeof r=="function"||typeof l.getSnapshotBeforeUpdate=="function")||typeof l.UNSAFE_componentWillReceiveProps!="function"&&typeof l.componentWillReceiveProps!="function"||(i!==d||c!==u)&&Vp(t,l,o,u),Yn=!1,c=t.memoizedState,l.state=c,Ll(t,o,l,a),Ul();var h=t.memoizedState;i!==d||c!==h||Yn||e!==null&&e.dependencies!==null&&Kr(e.dependencies)?(typeof r=="function"&&(uc(t,n,r,o),h=t.memoizedState),(f=Yn||Yp(t,n,f,o,c,h,u)||e!==null&&e.dependencies!==null&&Kr(e.dependencies))?(s||typeof l.UNSAFE_componentWillUpdate!="function"&&typeof l.componentWillUpdate!="function"||(typeof l.componentWillUpdate=="function"&&l.componentWillUpdate(o,h,u),typeof l.UNSAFE_componentWillUpdate=="function"&&l.UNSAFE_componentWillUpdate(o,h,u)),typeof l.componentDidUpdate=="function"&&(t.flags|=4),typeof l.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof l.componentDidUpdate!="function"||i===e.memoizedProps&&c===e.memoizedState||(t.flags|=4),typeof l.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&c===e.memoizedState||(t.flags|=1024),t.memoizedProps=o,t.memoizedState=h),l.props=o,l.state=h,l.context=u,o=f):(typeof l.componentDidUpdate!="function"||i===e.memoizedProps&&c===e.memoizedState||(t.flags|=4),typeof l.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&c===e.memoizedState||(t.flags|=1024),o=!1)}return l=o,Or(e,t),o=(t.flags&128)!==0,l||o?(l=t.stateNode,n=o&&typeof n.getDerivedStateFromError!="function"?null:l.render(),t.flags|=1,e!==null&&o?(t.child=Oa(t,e.child,null,a),t.child=Oa(t,null,n,a)):je(e,t,n,a),t.memoizedState=l.state,e=t.child):e=En(e,t,a),e}function jp(e,t,n,o){return fi(),t.flags|=256,je(e,t,n,o),t.child}var sc={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function cc(e){return{baseLanes:e,cachePool:cm()}}function fc(e,t,n){return e=e!==null?e.childLanes&~n:0,t&&(e|=Rt),e}function a0(e,t,n){var o=t.pendingProps,a=!1,l=(t.flags&128)!==0,i;if((i=l)||(i=e!==null&&e.memoizedState===null?!1:(Le.current&2)!==0),i&&(a=!0,t.flags&=-129),i=(t.flags&32)!==0,t.flags&=-33,e===null){if(fe){if(a?qn(t):Xn(t),fe){var r=Ce,u;if(u=r){e:{for(u=r,r=Jt;u.nodeType!==8;){if(!r){r=null;break e}if(u=Vt(u.nextSibling),u===null){r=null;break e}}r=u}r!==null?(t.memoizedState={dehydrated:r,treeContext:So!==null?{id:mn,overflow:yn}:null,retryLane:536870912,hydrationErrors:null},u=bt(18,null,null,0),u.stateNode=r,u.return=t,t.child=u,at=t,Ce=null,u=!0):u=!1}u||Mo(t)}if(r=t.memoizedState,r!==null&&(r=r.dehydrated,r!==null))return gf(r)?t.lanes=32:t.lanes=536870912,null;bn(t)}return r=o.children,o=o.fallback,a?(Xn(t),a=t.mode,r=eu({mode:"hidden",children:r},a),o=xo(o,a,n,null),r.return=t,o.return=t,r.sibling=o,t.child=r,a=t.child,a.memoizedState=cc(n),a.childLanes=fc(e,i,n),t.memoizedState=sc,o):(qn(t),tf(t,r))}if(u=e.memoizedState,u!==null&&(r=u.dehydrated,r!==null)){if(l)t.flags&256?(qn(t),t.flags&=-257,t=dc(e,t,n)):t.memoizedState!==null?(Xn(t),t.child=e.child,t.flags|=128,t=null):(Xn(t),a=o.fallback,r=t.mode,o=eu({mode:"visible",children:o.children},r),a=xo(a,r,n,null),a.flags|=2,o.return=t,a.return=t,o.sibling=a,t.child=o,Oa(t,e.child,null,n),o=t.child,o.memoizedState=cc(n),o.childLanes=fc(e,i,n),t.memoizedState=sc,t=a);else if(qn(t),gf(r)){if(i=r.nextSibling&&r.nextSibling.dataset,i)var s=i.dgst;i=s,o=Error(U(419)),o.stack="",o.digest=i,Jl({value:o,source:null,stack:null}),t=dc(e,t,n)}else if(Ge||di(e,t,n,!1),i=(n&e.childLanes)!==0,Ge||i){if(i=ve,i!==null&&(o=n&-n,o=(o&42)!==0?1:_f(o),o=(o&(i.suspendedLanes|n))!==0?0:o,o!==0&&o!==u.retryLane))throw u.retryLane=o,Ya(e,o),_t(i,e,o),t0;r.data==="$?"||uf(),t=dc(e,t,n)}else r.data==="$?"?(t.flags|=192,t.child=e.child,t=null):(e=u.treeContext,Ce=Vt(r.nextSibling),at=t,fe=!0,wo=null,Jt=!1,e!==null&&(Ct[zt++]=mn,Ct[zt++]=yn,Ct[zt++]=So,mn=e.id,yn=e.overflow,So=t),t=tf(t,o.children),t.flags|=4096);return t}return a?(Xn(t),a=o.fallback,r=t.mode,u=e.child,s=u.sibling,o=xn(u,{mode:"hidden",children:o.children}),o.subtreeFlags=u.subtreeFlags&65011712,s!==null?a=xn(s,a):(a=xo(a,r,n,null),a.flags|=2),a.return=t,o.return=t,o.sibling=a,t.child=o,o=a,a=t.child,r=e.child.memoizedState,r===null?r=cc(n):(u=r.cachePool,u!==null?(s=Ue._currentValue,u=u.parent!==s?{parent:s,pool:s}:u):u=cm(),r={baseLanes:r.baseLanes|n,cachePool:u}),a.memoizedState=r,a.childLanes=fc(e,i,n),t.memoizedState=sc,o):(qn(t),n=e.child,e=n.sibling,n=xn(n,{mode:"visible",children:o.children}),n.return=t,n.sibling=null,e!==null&&(i=t.deletions,i===null?(t.deletions=[e],t.flags|=16):i.push(e)),t.child=n,t.memoizedState=null,n)}function tf(e,t){return t=eu({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function eu(e,t){return e=bt(22,e,null,t),e.lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function dc(e,t,n){return Oa(t,e.child,null,n),e=tf(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function $p(e,t,n){e.lanes|=t;var o=e.alternate;o!==null&&(o.lanes|=t),Xc(e.return,t,n)}function hc(e,t,n,o,a){var l=e.memoizedState;l===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:o,tail:n,tailMode:a}:(l.isBackwards=t,l.rendering=null,l.renderingStartTime=0,l.last=o,l.tail=n,l.tailMode=a)}function l0(e,t,n){var o=t.pendingProps,a=o.revealOrder,l=o.tail;if(je(e,t,o.children,n),o=Le.current,(o&2)!==0)o=o&1|2,t.flags|=128;else{if(e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&$p(e,n,t);else if(e.tag===19)$p(e,n,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}o&=1}switch(Ee(Le,o),a){case"forwards":for(n=t.child,a=null;n!==null;)e=n.alternate,e!==null&&Pr(e)===null&&(a=n),n=n.sibling;n=a,n===null?(a=t.child,t.child=null):(a=n.sibling,n.sibling=null),hc(t,!1,a,n,l);break;case"backwards":for(n=null,a=t.child,t.child=null;a!==null;){if(e=a.alternate,e!==null&&Pr(e)===null){t.child=a;break}e=a.sibling,a.sibling=n,n=a,a=e}hc(t,!0,n,null,l);break;case"together":hc(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function En(e,t,n){if(e!==null&&(t.dependencies=e.dependencies),ao|=t.lanes,(n&t.childLanes)===0)if(e!==null){if(di(e,t,n,!1),(n&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(U(153));if(t.child!==null){for(e=t.child,n=xn(e,e.pendingProps),t.child=n,n.return=t;e.sibling!==null;)e=e.sibling,n=n.sibling=xn(e,e.pendingProps),n.return=t;n.sibling=null}return t.child}function Ff(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&Kr(e)))}function y_(e,t,n){switch(t.tag){case 3:Lr(t,t.stateNode.containerInfo),Vn(t,Ue,e.memoizedState.cache),fi();break;case 27:case 5:zc(t);break;case 4:Lr(t,t.stateNode.containerInfo);break;case 10:Vn(t,t.type,t.memoizedProps.value);break;case 13:var o=t.memoizedState;if(o!==null)return o.dehydrated!==null?(qn(t),t.flags|=128,null):(n&t.child.childLanes)!==0?a0(e,t,n):(qn(t),e=En(e,t,n),e!==null?e.sibling:null);qn(t);break;case 19:var a=(e.flags&128)!==0;if(o=(n&t.childLanes)!==0,o||(di(e,t,n,!1),o=(n&t.childLanes)!==0),a){if(o)return l0(e,t,n);t.flags|=128}if(a=t.memoizedState,a!==null&&(a.rendering=null,a.tail=null,a.lastEffect=null),Ee(Le,Le.current),o)break;return null;case 22:case 23:return t.lanes=0,o0(e,t,n);case 24:Vn(t,Ue,e.memoizedState.cache)}return En(e,t,n)}function i0(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps)Ge=!0;else{if(!Ff(e,n)&&(t.flags&128)===0)return Ge=!1,y_(e,t,n);Ge=(e.flags&131072)!==0}else Ge=!1,fe&&(t.flags&1048576)!==0&&um(t,Qr,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var o=t.elementType,a=o._init;if(o=a(o._payload),t.type=o,typeof o=="function")Hf(o)?(e=Co(o,e),t.tag=1,t=Kp(null,t,o,e,n)):(t.tag=0,t=ef(null,t,o,e,n));else{if(o!=null){if(a=o.$$typeof,a===xf){t.tag=11,t=Xp(null,t,o,e,n);break e}else if(a===Sf){t.tag=14,t=Gp(null,t,o,e,n);break e}}throw t=Tc(o)||o,Error(U(306,t,""))}}return t;case 0:return ef(e,t,t.type,t.pendingProps,n);case 1:return o=t.type,a=Co(o,t.pendingProps),Kp(e,t,o,a,n);case 3:e:{if(Lr(t,t.stateNode.containerInfo),e===null)throw Error(U(387));o=t.pendingProps;var l=t.memoizedState;a=l.element,Kc(e,t),Ll(t,o,null,n);var i=t.memoizedState;if(o=i.cache,Vn(t,Ue,o),o!==l.cache&&Gc(t,[Ue],n,!0),Ul(),o=i.element,l.isDehydrated)if(l={element:o,isDehydrated:!1,cache:i.cache},t.updateQueue.baseState=l,t.memoizedState=l,t.flags&256){t=jp(e,t,o,n);break e}else if(o!==a){a=Ot(Error(U(424)),t),Jl(a),t=jp(e,t,o,n);break e}else{switch(e=t.stateNode.containerInfo,e.nodeType){case 9:e=e.body;break;default:e=e.nodeName==="HTML"?e.ownerDocument.body:e}for(Ce=Vt(e.firstChild),at=t,fe=!0,wo=null,Jt=!0,n=$m(t,null,o,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling}else{if(fi(),o===a){t=En(e,t,n);break e}je(e,t,o,n)}t=t.child}return t;case 26:return Or(e,t),e===null?(n=hg(t.type,null,t.pendingProps,null))?t.memoizedState=n:fe||(n=t.type,e=t.pendingProps,o=ru($n.current).createElement(n),o[et]=t,o[dt]=e,Je(o,n,e),Xe(o),t.stateNode=o):t.memoizedState=hg(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return zc(t),e===null&&fe&&(o=t.stateNode=K0(t.type,t.pendingProps,$n.current),at=t,Jt=!0,a=Ce,io(t.type)?(mf=a,Ce=Vt(o.firstChild)):Ce=a),je(e,t,t.pendingProps.children,n),Or(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&fe&&((a=o=Ce)&&(o=G_(o,t.type,t.pendingProps,Jt),o!==null?(t.stateNode=o,at=t,Ce=Vt(o.firstChild),Jt=!1,a=!0):a=!1),a||Mo(t)),zc(t),a=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,o=l.children,hf(a,l)?o=null:i!==null&&hf(a,i)&&(t.flags|=32),t.memoizedState!==null&&(a=Xf(e,t,s_,null,null,n),ti._currentValue=a),Or(e,t),je(e,t,o,n),t.child;case 6:return e===null&&fe&&((e=n=Ce)&&(n=Z_(n,t.pendingProps,Jt),n!==null?(t.stateNode=n,at=t,Ce=null,e=!0):e=!1),e||Mo(t)),null;case 13:return a0(e,t,n);case 4:return Lr(t,t.stateNode.containerInfo),o=t.pendingProps,e===null?t.child=Oa(t,null,o,n):je(e,t,o,n),t.child;case 11:return Xp(e,t,t.type,t.pendingProps,n);case 7:return je(e,t,t.pendingProps,n),t.child;case 8:return je(e,t,t.pendingProps.children,n),t.child;case 12:return je(e,t,t.pendingProps.children,n),t.child;case 10:return o=t.pendingProps,Vn(t,t.type,o.value),je(e,t,o.children,n),t.child;case 9:return a=t.type._context,o=t.pendingProps.children,Ao(t),a=tt(a),o=o(a),t.flags|=1,je(e,t,o,n),t.child;case 14:return Gp(e,t,t.type,t.pendingProps,n);case 15:return n0(e,t,t.type,t.pendingProps,n);case 19:return l0(e,t,n);case 31:return o=t.pendingProps,n=t.mode,o={mode:o.mode,children:o.children},e===null?(n=eu(o,n),n.ref=t.ref,t.child=n,n.return=t,t=n):(n=xn(e.child,o),n.ref=t.ref,t.child=n,n.return=t,t=n),t;case 22:return o0(e,t,n);case 24:return Ao(t),o=tt(Ue),e===null?(a=Lf(),a===null&&(a=ve,l=Uf(),a.pooledCache=l,l.refCount++,l!==null&&(a.pooledCacheLanes|=n),a=l),t.memoizedState={parent:o,cache:a},Yf(t),Vn(t,Ue,a)):((e.lanes&n)!==0&&(Kc(e,t),Ll(t,null,null,n),Ul()),a=e.memoizedState,l=t.memoizedState,a.parent!==o?(a={parent:o,cache:o},t.memoizedState=a,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=a),Vn(t,Ue,o)):(o=l.cache,Vn(t,Ue,o),o!==a.cache&&Gc(t,[Ue],n,!0))),je(e,t,t.pendingProps.children,n),t.child;case 29:throw t.pendingProps}throw Error(U(156,t.tag))}function dn(e){e.flags|=4}function Jp(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!J0(t)){if(t=Ht.current,t!==null&&((ce&4194048)===ce?Ft!==null:(ce&62914560)!==ce&&(ce&536870912)===0||t!==Ft))throw kl=Qc,fm;e.flags|=8192}}function vr(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?Og():536870912,e.lanes|=t,Ra|=t)}function _l(e,t){if(!fe)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;t!==null;)t.alternate!==null&&(n=t),t=t.sibling;n===null?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var o=null;n!==null;)n.alternate!==null&&(o=n),n=n.sibling;o===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:o.sibling=null}}function Ae(e){var t=e.alternate!==null&&e.alternate.child===e.child,n=0,o=0;if(t)for(var a=e.child;a!==null;)n|=a.lanes|a.childLanes,o|=a.subtreeFlags&65011712,o|=a.flags&65011712,a.return=e,a=a.sibling;else for(a=e.child;a!==null;)n|=a.lanes|a.childLanes,o|=a.subtreeFlags,o|=a.flags,a.return=e,a=a.sibling;return e.subtreeFlags|=o,e.childLanes=n,t}function v_(e,t,n){var o=t.pendingProps;switch(Bf(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Ae(t),null;case 1:return Ae(t),null;case 3:return n=t.stateNode,o=null,e!==null&&(o=e.memoizedState.cache),t.memoizedState.cache!==o&&(t.flags|=2048),Sn(Ue),Ma(),n.pendingContext&&(n.context=n.pendingContext,n.pendingContext=null),(e===null||e.child===null)&&(Sl(t)?dn(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,Np())),Ae(t),null;case 26:return n=t.memoizedState,e===null?(dn(t),n!==null?(Ae(t),Jp(t,n)):(Ae(t),t.flags&=-16777217)):n?n!==e.memoizedState?(dn(t),Ae(t),Jp(t,n)):(Ae(t),t.flags&=-16777217):(e.memoizedProps!==o&&dn(t),Ae(t),t.flags&=-16777217),null;case 27:Yr(t),n=$n.current;var a=t.type;if(e!==null&&t.stateNode!=null)e.memoizedProps!==o&&dn(t);else{if(!o){if(t.stateNode===null)throw Error(U(166));return Ae(t),null}e=Pt.current,Sl(t)?_p(t,e):(e=K0(a,o,n),t.stateNode=e,dn(t))}return Ae(t),null;case 5:if(Yr(t),n=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==o&&dn(t);else{if(!o){if(t.stateNode===null)throw Error(U(166));return Ae(t),null}if(e=Pt.current,Sl(t))_p(t,e);else{switch(a=ru($n.current),e){case 1:e=a.createElementNS("http://www.w3.org/2000/svg",n);break;case 2:e=a.createElementNS("http://www.w3.org/1998/Math/MathML",n);break;default:switch(n){case"svg":e=a.createElementNS("http://www.w3.org/2000/svg",n);break;case"math":e=a.createElementNS("http://www.w3.org/1998/Math/MathML",n);break;case"script":e=a.createElement("div"),e.innerHTML="