Spaces:
Sleeping
Sleeping
Commit ·
c1ec9a0
1
Parent(s): 87372fb
Deploy 2026-01-27 14:43:06
Browse files- README.md +57 -130
- src/flow/cli/app.py +56 -1
- src/flow/experiments/data/tasks/coding.jsonl +10 -0
- src/flow/experiments/data/tasks/core.jsonl +5 -0
- src/flow/experiments/data/tasks/quick.jsonl +3 -0
- src/flow/experiments/optimizer.py +3 -24
- src/flow/experiments/types.py +69 -146
- src/flow/ui/api/configs.py +167 -3
- src/flow/ui/api/jobs.py +164 -6
- src/flow/ui/api/tasks.py +30 -12
- src/flow/ui/auth/__init__.py +20 -0
- src/flow/ui/auth/config.py +155 -0
- src/flow/ui/auth/middleware.py +175 -0
- src/flow/ui/auth/router.py +323 -0
- src/flow/ui/auth/tokens.py +153 -0
- src/flow/ui/database.py +44 -0
- src/flow/ui/main.py +63 -8
- src/flow/ui/models/config.py +5 -0
- src/flow/ui/schemas/config.py +12 -0
- src/flow/ui/services/optimizer_service.py +17 -0
- src/flow/ui/tests/test_e2e_user_journey.py +126 -0
- src/flow/ui/ui/assets/index-AqV2bzyn.js +0 -0
- src/flow/ui/ui/assets/index-AwuECPjC.js +0 -0
- src/flow/ui/ui/assets/index-B08BtjW2.js +0 -0
- src/flow/ui/ui/assets/index-B0QnqJdr.js +0 -0
- src/flow/ui/ui/assets/index-BFk_2IKX.js +0 -0
- src/flow/ui/ui/assets/index-BI-UHUHQ.js +0 -0
- src/flow/ui/ui/assets/index-BMvcolSw.css +1 -0
- src/flow/ui/ui/assets/index-BZQeHPg9.css +1 -0
- src/flow/ui/ui/assets/index-BcDpPA04.js +0 -0
- src/flow/ui/ui/assets/index-BvMZNuv9.js +0 -0
- src/flow/ui/ui/assets/index-C4qbqk6k.css +1 -0
- src/flow/ui/ui/assets/index-CJQ5Um8o.css +1 -0
- src/flow/ui/ui/assets/index-CcMPwNeP.js +0 -0
- src/flow/ui/ui/assets/index-CgNfNUQi.js +0 -0
- src/flow/ui/ui/assets/index-CpjX3LAH.css +1 -0
- src/flow/ui/ui/assets/index-D0u7uw0T.js +0 -0
- src/flow/ui/ui/assets/index-DlCyCyh_.css +1 -0
- src/flow/ui/ui/assets/index-DpPRF4Ru.css +1 -0
- src/flow/ui/ui/assets/index-FbRR3o1w.css +1 -0
- src/flow/ui/ui/assets/index-I6VIoTE8.js +0 -0
- src/flow/ui/ui/assets/index-k-MesJnS.css +1 -0
- src/flow/ui/ui/index.html +2 -2
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: Flow
|
| 3 |
emoji: 🔄
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
|
@@ -10,37 +10,32 @@ pinned: false
|
|
| 10 |
|
| 11 |
# Flow
|
| 12 |
|
| 13 |
-
**
|
| 14 |
|
| 15 |
-
Flow is a
|
| 16 |
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
| 20 |
-
- **Rich CLI**: Interactive REPL with streaming output, tool call visualization, and syntax highlighting.
|
| 21 |
-
- **Pluggable Harnesses**: Swap out the underlying agent runtime (Microsoft Agent Framework, OpenAI Swarm, etc.)
|
| 22 |
-
- **Persistent Memory**: Remember patterns, decisions, and context across sessions.
|
| 23 |
-
- **Workspace Isolation**: Secure file operations within a sandboxed workspace.
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
# Basic installation
|
| 29 |
-
pip install flow-agent
|
| 30 |
-
|
| 31 |
-
# With Microsoft Agent Framework support (recommended)
|
| 32 |
-
pip install flow-agent[agent-framework]
|
| 33 |
|
| 34 |
-
#
|
| 35 |
-
pip install flow-agent[all]
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
| 39 |
```
|
| 40 |
|
| 41 |
-
##
|
| 42 |
-
|
| 43 |
-
### 1. Configure Azure OpenAI
|
| 44 |
|
| 45 |
```bash
|
| 46 |
export AZURE_OPENAI_API_KEY="your-api-key"
|
|
@@ -48,146 +43,78 @@ export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
|
|
| 48 |
export AZURE_OPENAI_DEPLOYMENT="gpt-4o"
|
| 49 |
```
|
| 50 |
|
| 51 |
-
###
|
| 52 |
|
| 53 |
```bash
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
```
|
| 56 |
|
| 57 |
-
###
|
| 58 |
|
| 59 |
```bash
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
# Interactive mode
|
| 64 |
-
flow run -i
|
| 65 |
```
|
| 66 |
|
| 67 |
## CLI Commands
|
| 68 |
|
| 69 |
```bash
|
| 70 |
-
flow
|
| 71 |
-
flow
|
| 72 |
-
flow
|
| 73 |
-
flow
|
|
|
|
| 74 |
```
|
| 75 |
|
| 76 |
-
##
|
| 77 |
-
|
| 78 |
-
```python
|
| 79 |
-
import asyncio
|
| 80 |
-
from flow import FlowAgent
|
| 81 |
-
|
| 82 |
-
async def main():
|
| 83 |
-
agent = FlowAgent()
|
| 84 |
|
| 85 |
-
|
| 86 |
-
response = await agent.run("Create a hello world script")
|
| 87 |
-
print(response)
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
asyncio.run(main())
|
| 96 |
-
```
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
Flow can be configured via environment variables or a config file.
|
| 101 |
-
|
| 102 |
-
### Environment Variables
|
| 103 |
-
|
| 104 |
-
| Variable | Description | Default |
|
| 105 |
-
|----------|-------------|---------|
|
| 106 |
-
| `FLOW_HARNESS` | Agent harness to use | `agent-framework` |
|
| 107 |
-
| `FLOW_MODEL` | Model name | `gpt-4o` |
|
| 108 |
-
| `FLOW_WORKSPACE` | Workspace directory | `~/.flow/workspace` |
|
| 109 |
-
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API key | - |
|
| 110 |
-
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint | - |
|
| 111 |
-
| `AZURE_OPENAI_DEPLOYMENT` | Azure OpenAI deployment | - |
|
| 112 |
-
|
| 113 |
-
### Directory Structure
|
| 114 |
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
│ ├── projects/ # Per-project notes
|
| 121 |
-
│ └── decisions/ # Architecture decisions
|
| 122 |
-
└── skills/ # Domain-specific expertise
|
| 123 |
```
|
| 124 |
|
| 125 |
-
##
|
| 126 |
|
| 127 |
-
|
| 128 |
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
```
|
| 132 |
-
┌─────────────────┐
|
| 133 |
-
│ FlowAgent │
|
| 134 |
-
└────────┬────────┘
|
| 135 |
-
│
|
| 136 |
-
┌────────▼────────┐
|
| 137 |
-
│ BaseHarness │ (Abstract)
|
| 138 |
-
└────────┬────────┘
|
| 139 |
-
│
|
| 140 |
-
┌────┴────┐
|
| 141 |
-
│ │
|
| 142 |
-
┌───▼───┐ ┌───▼───┐
|
| 143 |
-
│ Agent │ │ OpenAI│
|
| 144 |
-
│ Frmwk │ │ Swarm │
|
| 145 |
-
└───────┘ └───────┘
|
| 146 |
```
|
| 147 |
|
| 148 |
-
Currently supported:
|
| 149 |
-
- **MAFHarness**: Microsoft Agent Framework with Azure OpenAI
|
| 150 |
-
|
| 151 |
-
Planned:
|
| 152 |
-
- LangChain
|
| 153 |
-
- Claude SDK
|
| 154 |
-
|
| 155 |
-
### Tools
|
| 156 |
-
|
| 157 |
-
Flow includes a comprehensive set of tools:
|
| 158 |
-
|
| 159 |
-
| Tool | Description |
|
| 160 |
-
|------|-------------|
|
| 161 |
-
| `read_file` | Read file contents with line numbers |
|
| 162 |
-
| `write_file` | Write/edit files (full write, str_replace, insert) |
|
| 163 |
-
| `list_directory` | List directory contents |
|
| 164 |
-
| `grep_search` | Search for patterns in code |
|
| 165 |
-
| `bash_execute` | Run shell commands |
|
| 166 |
-
| `python_repl` | Execute Python code snippets |
|
| 167 |
-
| `memory` | Persistent memory operations |
|
| 168 |
-
| `think` | Structured reasoning |
|
| 169 |
-
| `task_done` | Report task completion |
|
| 170 |
-
|
| 171 |
## Development
|
| 172 |
|
| 173 |
```bash
|
| 174 |
-
#
|
| 175 |
-
|
| 176 |
-
cd flow
|
| 177 |
-
|
| 178 |
-
# Install development dependencies
|
| 179 |
-
pip install -e ".[dev]"
|
| 180 |
|
| 181 |
# Run tests
|
| 182 |
-
pytest tests/ -v
|
| 183 |
|
| 184 |
# Type checking
|
| 185 |
-
pyright src/
|
| 186 |
-
mypy src/
|
| 187 |
|
| 188 |
# Linting
|
| 189 |
-
ruff check src/
|
| 190 |
-
ruff format src/
|
| 191 |
```
|
| 192 |
|
| 193 |
## License
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Flow
|
| 3 |
emoji: 🔄
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
|
|
|
| 10 |
|
| 11 |
# Flow
|
| 12 |
|
| 13 |
+
**Evaluate and Optimize Coding Agent Configurations**
|
| 14 |
|
| 15 |
+
Flow is a framework for running experiments on LLM coding agents. Compare context engineering strategies (message compaction, agent memory, sub-agents), evaluate results with LLM-as-Judge, and find optimal configurations that balance quality and token cost.
|
| 16 |
|
| 17 |
+

|
| 18 |
|
| 19 |
+
## Features
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
- **Ablation Studies**: Test different agent configurations side-by-side
|
| 22 |
+
- **LLM-as-Judge Evaluation**: Automatically score agent outputs for correctness
|
| 23 |
+
- **Pareto Analysis**: Find optimal quality vs. cost tradeoffs
|
| 24 |
+
- **Web UI**: Visual interface for managing experiments and viewing results
|
| 25 |
+
- **Config Export**: Export winning configurations for production use
|
| 26 |
|
| 27 |
+
## Quick Start
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
+
### 1. Install
|
|
|
|
| 30 |
|
| 31 |
+
```bash
|
| 32 |
+
# Clone and install with uv
|
| 33 |
+
git clone https://github.com/victordibia/flow
|
| 34 |
+
cd flow
|
| 35 |
+
uv sync
|
| 36 |
```
|
| 37 |
|
| 38 |
+
### 2. Configure Azure OpenAI
|
|
|
|
|
|
|
| 39 |
|
| 40 |
```bash
|
| 41 |
export AZURE_OPENAI_API_KEY="your-api-key"
|
|
|
|
| 43 |
export AZURE_OPENAI_DEPLOYMENT="gpt-4o"
|
| 44 |
```
|
| 45 |
|
| 46 |
+
### 3. Run Optimization
|
| 47 |
|
| 48 |
```bash
|
| 49 |
+
# Run with built-in task suite
|
| 50 |
+
uv run flow optimize --suite coding
|
| 51 |
+
|
| 52 |
+
# Or with custom tasks
|
| 53 |
+
uv run flow optimize --tasks my_tasks.jsonl
|
| 54 |
```
|
| 55 |
|
| 56 |
+
### 4. Launch Web UI
|
| 57 |
|
| 58 |
```bash
|
| 59 |
+
uv run flow serve
|
| 60 |
+
# Opens at http://localhost:8091
|
|
|
|
|
|
|
|
|
|
| 61 |
```
|
| 62 |
|
| 63 |
## CLI Commands
|
| 64 |
|
| 65 |
```bash
|
| 66 |
+
flow optimize [OPTIONS] # Run optimization experiments
|
| 67 |
+
flow serve # Start the web UI
|
| 68 |
+
flow run [TASK] # Run a single agent task
|
| 69 |
+
flow config # Show current configuration
|
| 70 |
+
flow init # Initialize Flow directories
|
| 71 |
```
|
| 72 |
|
| 73 |
+
## What Gets Optimized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
+
Flow tests different **context engineering strategies**:
|
|
|
|
|
|
|
| 76 |
|
| 77 |
+
| Strategy | Description |
|
| 78 |
+
|----------|-------------|
|
| 79 |
+
| **Message Compaction** | Keep first N + last M messages, discard middle |
|
| 80 |
+
| **Agent Memory** | Persistent storage the agent controls |
|
| 81 |
+
| **Sub-Agent Isolation** | Delegate research to isolated sub-agent |
|
| 82 |
|
| 83 |
+
Example configurations:
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
+
```python
|
| 86 |
+
from flow.experiments.ablation import AblationConfig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
+
configs = [
|
| 89 |
+
AblationConfig(name="baseline", enable_message_compaction=False),
|
| 90 |
+
AblationConfig(name="compaction", enable_message_compaction=True, compaction_head_size=10),
|
| 91 |
+
AblationConfig(name="full", enable_message_compaction=True, enable_memory_tool=True),
|
| 92 |
+
]
|
|
|
|
|
|
|
|
|
|
| 93 |
```
|
| 94 |
|
| 95 |
+
## Task Format
|
| 96 |
|
| 97 |
+
Tasks are defined in JSONL format:
|
| 98 |
|
| 99 |
+
```json
|
| 100 |
+
{"name": "fizzbuzz", "prompt": "Create fizzbuzz.py and run it", "criteria": [{"name": "correct", "instruction": "Output shows FizzBuzz pattern"}]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
```
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
## Development
|
| 104 |
|
| 105 |
```bash
|
| 106 |
+
# Install dev dependencies
|
| 107 |
+
uv sync --dev
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
# Run tests
|
| 110 |
+
uv run pytest tests/ -v
|
| 111 |
|
| 112 |
# Type checking
|
| 113 |
+
uv run pyright src/
|
|
|
|
| 114 |
|
| 115 |
# Linting
|
| 116 |
+
uv run ruff check src/
|
| 117 |
+
uv run ruff format src/
|
| 118 |
```
|
| 119 |
|
| 120 |
## License
|
src/flow/cli/app.py
CHANGED
|
@@ -152,15 +152,70 @@ def serve(
|
|
| 152 |
bool,
|
| 153 |
typer.Option("--reload", help="Enable auto-reload for development"),
|
| 154 |
] = False,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
) -> None:
|
| 156 |
"""Start the Flow web UI server.
|
| 157 |
|
| 158 |
Launches a web interface for managing agent configurations,
|
| 159 |
running optimization experiments, and viewing results.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
import uvicorn
|
| 162 |
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
uvicorn.run(
|
| 166 |
"flow.ui.main:app",
|
|
|
|
| 152 |
bool,
|
| 153 |
typer.Option("--reload", help="Enable auto-reload for development"),
|
| 154 |
] = False,
|
| 155 |
+
auth: Annotated[
|
| 156 |
+
bool,
|
| 157 |
+
typer.Option("--auth/--no-auth", help="Enable authentication"),
|
| 158 |
+
] = False,
|
| 159 |
+
auth_mode: Annotated[
|
| 160 |
+
str,
|
| 161 |
+
typer.Option("--auth-mode", help="Auth mode: 'basic' or 'github'"),
|
| 162 |
+
] = "basic",
|
| 163 |
+
auth_username: Annotated[
|
| 164 |
+
str,
|
| 165 |
+
typer.Option("--auth-username", help="Username for basic auth"),
|
| 166 |
+
] = "admin",
|
| 167 |
+
auth_password: Annotated[
|
| 168 |
+
str | None,
|
| 169 |
+
typer.Option("--auth-password", help="Password for basic auth (required if --auth)"),
|
| 170 |
+
] = None,
|
| 171 |
) -> None:
|
| 172 |
"""Start the Flow web UI server.
|
| 173 |
|
| 174 |
Launches a web interface for managing agent configurations,
|
| 175 |
running optimization experiments, and viewing results.
|
| 176 |
+
|
| 177 |
+
Authentication can be enabled with --auth flag. For basic auth mode,
|
| 178 |
+
you must provide --auth-password. For GitHub OAuth, set environment
|
| 179 |
+
variables: AUTH_GITHUB_CLIENT_ID, AUTH_GITHUB_CLIENT_SECRET,
|
| 180 |
+
AUTH_GITHUB_ALLOWED_USERS.
|
| 181 |
+
|
| 182 |
+
Examples:
|
| 183 |
+
flow serve # No auth (local dev)
|
| 184 |
+
flow serve --auth --auth-password=secret # Basic auth
|
| 185 |
+
flow serve --auth --auth-mode=github # GitHub OAuth
|
| 186 |
"""
|
| 187 |
+
from flow.ui.auth import init_auth_settings
|
| 188 |
+
|
| 189 |
+
# Validate auth configuration
|
| 190 |
+
if auth:
|
| 191 |
+
if auth_mode == "basic" and not auth_password:
|
| 192 |
+
# Check environment variable as fallback
|
| 193 |
+
env_password = os.environ.get("AUTH_BASIC_PASSWORD")
|
| 194 |
+
if not env_password:
|
| 195 |
+
console.print("[red]Error:[/] --auth-password is required when --auth is enabled")
|
| 196 |
+
console.print("[dim]Or set AUTH_BASIC_PASSWORD environment variable[/]")
|
| 197 |
+
raise typer.Exit(1)
|
| 198 |
+
auth_password = env_password
|
| 199 |
+
|
| 200 |
+
# Initialize auth settings before uvicorn starts
|
| 201 |
+
# This ensures settings are available when the app loads
|
| 202 |
+
init_auth_settings(
|
| 203 |
+
enabled=auth,
|
| 204 |
+
mode=auth_mode,
|
| 205 |
+
basic_username=auth_username,
|
| 206 |
+
basic_password=auth_password,
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
import uvicorn
|
| 210 |
|
| 211 |
+
auth_status = "[green]enabled[/]" if auth else "[dim]disabled[/]"
|
| 212 |
+
console.print(f"\n[bold blue]Flow UI[/] starting on [cyan]http://{host}:{port}[/]")
|
| 213 |
+
console.print(f" Authentication: {auth_status}")
|
| 214 |
+
if auth:
|
| 215 |
+
console.print(f" Auth mode: [cyan]{auth_mode}[/]")
|
| 216 |
+
if auth_mode == "basic":
|
| 217 |
+
console.print(f" Username: [cyan]{auth_username}[/]")
|
| 218 |
+
console.print()
|
| 219 |
|
| 220 |
uvicorn.run(
|
| 221 |
"flow.ui.main:app",
|
src/flow/experiments/data/tasks/coding.jsonl
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"name": "fizzbuzz", "prompt": "Create fizzbuzz.py that prints FizzBuzz 1-100 and run it.", "criteria": [{"name": "correct", "instruction": "Correct FizzBuzz output"}], "category": "short"}
|
| 2 |
+
{"name": "rest_api", "prompt": "Create a FastAPI CRUD TODO app with GET/POST/DELETE endpoints.", "criteria": [{"name": "has_crud", "instruction": "Has working CRUD"}], "category": "medium"}
|
| 3 |
+
{"name": "cli_tool", "prompt": "Create an argparse CLI that counts lines/words/chars in a file.", "criteria": [{"name": "works", "instruction": "CLI works correctly"}], "category": "medium"}
|
| 4 |
+
{"name": "data_pipeline", "prompt": "Create a script that reads CSV data, filters rows, aggregates, and outputs JSON.", "criteria": [{"name": "works", "instruction": "Pipeline produces correct output"}], "category": "medium"}
|
| 5 |
+
{"name": "unit_tests", "prompt": "Create calc.py with math functions and test_calc.py with pytest tests.", "criteria": [{"name": "tests_pass", "instruction": "Tests pass"}], "category": "medium"}
|
| 6 |
+
{"name": "web_scraper", "prompt": "Create a script that fetches a webpage and extracts all links.", "criteria": [{"name": "extracts_links", "instruction": "Extracts links correctly"}], "category": "medium"}
|
| 7 |
+
{"name": "async_downloader", "prompt": "Create an async script that downloads multiple URLs concurrently using aiohttp.", "criteria": [{"name": "uses_async", "instruction": "Uses async/await correctly"}], "category": "complex"}
|
| 8 |
+
{"name": "database_orm", "prompt": "Create a SQLAlchemy model for Users with CRUD operations.", "criteria": [{"name": "has_orm", "instruction": "Uses SQLAlchemy ORM correctly"}], "category": "complex"}
|
| 9 |
+
{"name": "decorator_lib", "prompt": "Create a library with timing, retry, and caching decorators.", "criteria": [{"name": "decorators_work", "instruction": "Decorators function correctly"}], "category": "complex"}
|
| 10 |
+
{"name": "config_parser", "prompt": "Create a config parser that supports YAML, JSON, and env vars with validation.", "criteria": [{"name": "multi_format", "instruction": "Supports multiple formats"}], "category": "complex"}
|
src/flow/experiments/data/tasks/core.jsonl
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"name": "fizzbuzz", "prompt": "Create a Python file fizzbuzz.py that prints FizzBuzz from 1-100. Then run it.", "criteria": [{"name": "file_created", "instruction": "fizzbuzz.py file was created"}, {"name": "correct_output", "instruction": "Output shows correct FizzBuzz pattern"}], "category": "short"}
|
| 2 |
+
{"name": "rest_api", "prompt": "Create a FastAPI app with CRUD endpoints for a TODO list (in-memory storage). Include GET /todos, POST /todos, DELETE /todos/{id}.", "criteria": [{"name": "file_created", "instruction": "API file was created"}, {"name": "has_crud", "instruction": "Contains GET, POST, DELETE endpoints"}], "category": "medium"}
|
| 3 |
+
{"name": "data_analysis", "prompt": "Create a Python script that generates 100 random data points, calculates mean/median/std, and saves results to stats.json.", "criteria": [{"name": "script_created", "instruction": "Python script was created"}, {"name": "json_output", "instruction": "stats.json was created with results"}], "category": "medium"}
|
| 4 |
+
{"name": "cli_tool", "prompt": "Create a CLI tool using argparse that takes a filename and counts lines, words, and characters (like wc).", "criteria": [{"name": "file_created", "instruction": "CLI script was created"}, {"name": "uses_argparse", "instruction": "Uses argparse for argument parsing"}], "category": "medium"}
|
| 5 |
+
{"name": "unit_tests", "prompt": "Create a calculator module (calc.py) with add/subtract/multiply/divide functions, then write pytest tests for it (test_calc.py).", "criteria": [{"name": "module_created", "instruction": "calc.py was created"}, {"name": "tests_created", "instruction": "test_calc.py was created"}, {"name": "tests_pass", "instruction": "Tests pass when run"}], "category": "medium"}
|
src/flow/experiments/data/tasks/quick.jsonl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"name": "fizzbuzz", "prompt": "Create a Python file fizzbuzz.py that prints FizzBuzz from 1-100. Then run it.", "criteria": [{"name": "file_created", "instruction": "fizzbuzz.py file was created"}, {"name": "correct_output", "instruction": "Output shows correct FizzBuzz pattern"}], "category": "short", "metadata": {"expected_duration": 60}}
|
| 2 |
+
{"name": "hello_api", "prompt": "Create a FastAPI app in api.py with a /hello endpoint that returns {'message': 'hello'}.", "criteria": [{"name": "file_created", "instruction": "api.py file was created"}, {"name": "has_endpoint", "instruction": "Contains a /hello GET endpoint"}], "category": "short", "metadata": {"expected_duration": 90}}
|
| 3 |
+
{"name": "file_counter", "prompt": "Create a Python script count_files.py that counts .py files in current directory and prints the count.", "criteria": [{"name": "file_created", "instruction": "count_files.py was created"}, {"name": "runs_correctly", "instruction": "Script runs and outputs a number"}], "category": "short", "metadata": {"expected_duration": 60}}
|
src/flow/experiments/optimizer.py
CHANGED
|
@@ -513,6 +513,7 @@ def load_tasks_from_jsonl(path: Path) -> list[Task]:
|
|
| 513 |
- prompt: Task prompt
|
| 514 |
- criteria: Optional list of evaluation criteria
|
| 515 |
- category: Optional category string
|
|
|
|
| 516 |
|
| 517 |
Args:
|
| 518 |
path: Path to JSONL file
|
|
@@ -520,28 +521,6 @@ def load_tasks_from_jsonl(path: Path) -> list[Task]:
|
|
| 520 |
Returns:
|
| 521 |
List of Task objects
|
| 522 |
"""
|
| 523 |
-
|
| 524 |
-
with open(path) as f:
|
| 525 |
-
for line in f:
|
| 526 |
-
line = line.strip()
|
| 527 |
-
if not line:
|
| 528 |
-
continue
|
| 529 |
-
|
| 530 |
-
data = json.loads(line)
|
| 531 |
-
criteria = []
|
| 532 |
-
for c in data.get("criteria", []):
|
| 533 |
-
if isinstance(c, dict):
|
| 534 |
-
criteria.append(EvalCriterion(**c))
|
| 535 |
-
else:
|
| 536 |
-
criteria.append(EvalCriterion(name="default", instruction=str(c)))
|
| 537 |
-
|
| 538 |
-
tasks.append(
|
| 539 |
-
Task(
|
| 540 |
-
name=data["name"],
|
| 541 |
-
prompt=data["prompt"],
|
| 542 |
-
criteria=criteria,
|
| 543 |
-
metadata={"category": data.get("category", "default")},
|
| 544 |
-
)
|
| 545 |
-
)
|
| 546 |
|
| 547 |
-
return
|
|
|
|
| 513 |
- prompt: Task prompt
|
| 514 |
- criteria: Optional list of evaluation criteria
|
| 515 |
- category: Optional category string
|
| 516 |
+
- metadata: Optional additional metadata dict
|
| 517 |
|
| 518 |
Args:
|
| 519 |
path: Path to JSONL file
|
|
|
|
| 521 |
Returns:
|
| 522 |
List of Task objects
|
| 523 |
"""
|
| 524 |
+
from flow.experiments.types import _load_tasks_from_jsonl
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
|
| 526 |
+
return _load_tasks_from_jsonl(path)
|
src/flow/experiments/types.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
|
| 3 |
"""Type definitions for the experiments framework."""
|
| 4 |
|
|
|
|
| 5 |
from dataclasses import dataclass, field
|
| 6 |
from pathlib import Path
|
| 7 |
from typing import Any
|
|
@@ -102,157 +103,78 @@ class EvalResult:
|
|
| 102 |
|
| 103 |
|
| 104 |
# =============================================================================
|
| 105 |
-
# Built-in Task Suites
|
| 106 |
# =============================================================================
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
prompt="Create a CLI tool using argparse that takes a filename and counts lines, words, and characters (like wc).",
|
| 169 |
-
criteria=[
|
| 170 |
-
EvalCriterion(name="file_created", instruction="CLI script was created"),
|
| 171 |
-
EvalCriterion(name="uses_argparse", instruction="Uses argparse for argument parsing"),
|
| 172 |
-
],
|
| 173 |
-
metadata={"category": "medium"},
|
| 174 |
-
),
|
| 175 |
-
Task(
|
| 176 |
-
name="unit_tests",
|
| 177 |
-
prompt="Create a calculator module (calc.py) with add/subtract/multiply/divide functions, then write pytest tests for it (test_calc.py).",
|
| 178 |
-
criteria=[
|
| 179 |
-
EvalCriterion(name="module_created", instruction="calc.py was created"),
|
| 180 |
-
EvalCriterion(name="tests_created", instruction="test_calc.py was created"),
|
| 181 |
-
EvalCriterion(name="tests_pass", instruction="Tests pass when run"),
|
| 182 |
-
],
|
| 183 |
-
metadata={"category": "medium"},
|
| 184 |
-
),
|
| 185 |
-
],
|
| 186 |
-
"coding": [
|
| 187 |
-
Task(
|
| 188 |
-
name="fizzbuzz",
|
| 189 |
-
prompt="Create fizzbuzz.py that prints FizzBuzz 1-100 and run it.",
|
| 190 |
-
criteria=[EvalCriterion(name="correct", instruction="Correct FizzBuzz output")],
|
| 191 |
-
metadata={"category": "short"},
|
| 192 |
-
),
|
| 193 |
-
Task(
|
| 194 |
-
name="rest_api",
|
| 195 |
-
prompt="Create a FastAPI CRUD TODO app with GET/POST/DELETE endpoints.",
|
| 196 |
-
criteria=[EvalCriterion(name="has_crud", instruction="Has working CRUD")],
|
| 197 |
-
metadata={"category": "medium"},
|
| 198 |
-
),
|
| 199 |
-
Task(
|
| 200 |
-
name="cli_tool",
|
| 201 |
-
prompt="Create an argparse CLI that counts lines/words/chars in a file.",
|
| 202 |
-
criteria=[EvalCriterion(name="works", instruction="CLI works correctly")],
|
| 203 |
-
metadata={"category": "medium"},
|
| 204 |
-
),
|
| 205 |
-
Task(
|
| 206 |
-
name="data_pipeline",
|
| 207 |
-
prompt="Create a script that reads CSV data, filters rows, aggregates, and outputs JSON.",
|
| 208 |
-
criteria=[EvalCriterion(name="works", instruction="Pipeline produces correct output")],
|
| 209 |
-
metadata={"category": "medium"},
|
| 210 |
-
),
|
| 211 |
-
Task(
|
| 212 |
-
name="unit_tests",
|
| 213 |
-
prompt="Create calc.py with math functions and test_calc.py with pytest tests.",
|
| 214 |
-
criteria=[EvalCriterion(name="tests_pass", instruction="Tests pass")],
|
| 215 |
-
metadata={"category": "medium"},
|
| 216 |
-
),
|
| 217 |
-
Task(
|
| 218 |
-
name="web_scraper",
|
| 219 |
-
prompt="Create a script that fetches a webpage and extracts all links.",
|
| 220 |
-
criteria=[EvalCriterion(name="extracts_links", instruction="Extracts links correctly")],
|
| 221 |
-
metadata={"category": "medium"},
|
| 222 |
-
),
|
| 223 |
-
Task(
|
| 224 |
-
name="async_downloader",
|
| 225 |
-
prompt="Create an async script that downloads multiple URLs concurrently using aiohttp.",
|
| 226 |
-
criteria=[EvalCriterion(name="uses_async", instruction="Uses async/await correctly")],
|
| 227 |
-
metadata={"category": "complex"},
|
| 228 |
-
),
|
| 229 |
-
Task(
|
| 230 |
-
name="database_orm",
|
| 231 |
-
prompt="Create a SQLAlchemy model for Users with CRUD operations.",
|
| 232 |
-
criteria=[EvalCriterion(name="has_orm", instruction="Uses SQLAlchemy ORM correctly")],
|
| 233 |
-
metadata={"category": "complex"},
|
| 234 |
-
),
|
| 235 |
-
Task(
|
| 236 |
-
name="decorator_lib",
|
| 237 |
-
prompt="Create a library with timing, retry, and caching decorators.",
|
| 238 |
-
criteria=[EvalCriterion(name="decorators_work", instruction="Decorators function correctly")],
|
| 239 |
-
metadata={"category": "complex"},
|
| 240 |
-
),
|
| 241 |
-
Task(
|
| 242 |
-
name="config_parser",
|
| 243 |
-
prompt="Create a config parser that supports YAML, JSON, and env vars with validation.",
|
| 244 |
-
criteria=[EvalCriterion(name="multi_format", instruction="Supports multiple formats")],
|
| 245 |
-
metadata={"category": "complex"},
|
| 246 |
-
),
|
| 247 |
-
],
|
| 248 |
-
}
|
| 249 |
|
| 250 |
|
| 251 |
def get_task_suite(suite_name: str) -> list[Task]:
|
| 252 |
"""Get a built-in task suite by name.
|
| 253 |
|
|
|
|
|
|
|
| 254 |
Args:
|
| 255 |
-
suite_name: Name of the suite ('quick', 'core', 'coding')
|
| 256 |
|
| 257 |
Returns:
|
| 258 |
List of Task objects
|
|
@@ -260,7 +182,8 @@ def get_task_suite(suite_name: str) -> list[Task]:
|
|
| 260 |
Raises:
|
| 261 |
ValueError: If suite_name is not found
|
| 262 |
"""
|
| 263 |
-
|
| 264 |
-
|
|
|
|
| 265 |
raise ValueError(f"Unknown suite '{suite_name}'. Available: {available}")
|
| 266 |
-
return
|
|
|
|
| 2 |
|
| 3 |
"""Type definitions for the experiments framework."""
|
| 4 |
|
| 5 |
+
import json
|
| 6 |
from dataclasses import dataclass, field
|
| 7 |
from pathlib import Path
|
| 8 |
from typing import Any
|
|
|
|
| 103 |
|
| 104 |
|
| 105 |
# =============================================================================
|
| 106 |
+
# Built-in Task Suites - loaded from data/tasks/*.jsonl
|
| 107 |
# =============================================================================
|
| 108 |
|
| 109 |
+
_DATA_DIR = Path(__file__).parent / "data" / "tasks"
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def _load_tasks_from_jsonl(path: Path) -> list[Task]:
|
| 113 |
+
"""Load tasks from a JSONL file.
|
| 114 |
+
|
| 115 |
+
Each line should be a JSON object with:
|
| 116 |
+
- name: Task name
|
| 117 |
+
- prompt: Task prompt
|
| 118 |
+
- criteria: Optional list of evaluation criteria
|
| 119 |
+
- category: Optional category string
|
| 120 |
+
- metadata: Optional additional metadata dict
|
| 121 |
+
|
| 122 |
+
Args:
|
| 123 |
+
path: Path to JSONL file
|
| 124 |
+
|
| 125 |
+
Returns:
|
| 126 |
+
List of Task objects
|
| 127 |
+
"""
|
| 128 |
+
tasks = []
|
| 129 |
+
with open(path) as f:
|
| 130 |
+
for line in f:
|
| 131 |
+
line = line.strip()
|
| 132 |
+
if not line:
|
| 133 |
+
continue
|
| 134 |
+
|
| 135 |
+
data = json.loads(line)
|
| 136 |
+
criteria = []
|
| 137 |
+
for c in data.get("criteria", []):
|
| 138 |
+
if isinstance(c, dict):
|
| 139 |
+
criteria.append(EvalCriterion(**c))
|
| 140 |
+
else:
|
| 141 |
+
criteria.append(EvalCriterion(name="default", instruction=str(c)))
|
| 142 |
+
|
| 143 |
+
metadata = data.get("metadata", {})
|
| 144 |
+
if "category" in data:
|
| 145 |
+
metadata.setdefault("category", data["category"])
|
| 146 |
+
|
| 147 |
+
tasks.append(
|
| 148 |
+
Task(
|
| 149 |
+
name=data["name"],
|
| 150 |
+
prompt=data["prompt"],
|
| 151 |
+
criteria=criteria,
|
| 152 |
+
metadata=metadata,
|
| 153 |
+
)
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
return tasks
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def get_available_suites() -> list[str]:
|
| 160 |
+
"""List available built-in task suite names.
|
| 161 |
+
|
| 162 |
+
Returns:
|
| 163 |
+
List of suite names (derived from .jsonl filenames in data/tasks/).
|
| 164 |
+
Returns empty list if the data directory doesn't exist.
|
| 165 |
+
"""
|
| 166 |
+
if not _DATA_DIR.exists():
|
| 167 |
+
return []
|
| 168 |
+
return sorted(p.stem for p in _DATA_DIR.glob("*.jsonl"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
|
| 170 |
|
| 171 |
def get_task_suite(suite_name: str) -> list[Task]:
|
| 172 |
"""Get a built-in task suite by name.
|
| 173 |
|
| 174 |
+
Loads tasks from data/tasks/{suite_name}.jsonl.
|
| 175 |
+
|
| 176 |
Args:
|
| 177 |
+
suite_name: Name of the suite (e.g. 'quick', 'core', 'coding')
|
| 178 |
|
| 179 |
Returns:
|
| 180 |
List of Task objects
|
|
|
|
| 182 |
Raises:
|
| 183 |
ValueError: If suite_name is not found
|
| 184 |
"""
|
| 185 |
+
path = _DATA_DIR / f"{suite_name}.jsonl"
|
| 186 |
+
if not path.exists():
|
| 187 |
+
available = ", ".join(get_available_suites())
|
| 188 |
raise ValueError(f"Unknown suite '{suite_name}'. Available: {available}")
|
| 189 |
+
return _load_tasks_from_jsonl(path)
|
src/flow/ui/api/configs.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
| 1 |
# Copyright (c) Microsoft. All rights reserved.
|
| 2 |
"""Config API routes."""
|
| 3 |
|
|
|
|
| 4 |
from uuid import UUID
|
| 5 |
|
| 6 |
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
| 7 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 8 |
from sqlmodel import select, desc
|
| 9 |
|
|
@@ -14,6 +16,28 @@ from ..schemas import ConfigCreate, ConfigUpdate, ConfigResponse
|
|
| 14 |
router = APIRouter(prefix="/configs", tags=["configs"])
|
| 15 |
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
def parse_uuid(id_str: str) -> UUID:
|
| 18 |
"""Parse a string to UUID, raising 400 if invalid."""
|
| 19 |
try:
|
|
@@ -23,9 +47,21 @@ def parse_uuid(id_str: str) -> UUID:
|
|
| 23 |
|
| 24 |
|
| 25 |
@router.get("", response_model=list[ConfigResponse])
|
| 26 |
-
async def list_configs(
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
return list(result.scalars().all())
|
| 30 |
|
| 31 |
|
|
@@ -119,3 +155,131 @@ async def delete_config(
|
|
| 119 |
|
| 120 |
await session.delete(config)
|
| 121 |
await session.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Copyright (c) Microsoft. All rights reserved.
|
| 2 |
"""Config API routes."""
|
| 3 |
|
| 4 |
+
from itertools import product
|
| 5 |
from uuid import UUID
|
| 6 |
|
| 7 |
from fastapi import APIRouter, Depends, HTTPException
|
| 8 |
+
from pydantic import BaseModel
|
| 9 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 10 |
from sqlmodel import select, desc
|
| 11 |
|
|
|
|
| 16 |
router = APIRouter(prefix="/configs", tags=["configs"])
|
| 17 |
|
| 18 |
|
| 19 |
+
class VariationRequest(BaseModel):
|
| 20 |
+
"""Request schema for generating config variations."""
|
| 21 |
+
|
| 22 |
+
base_name: str = "experiment"
|
| 23 |
+
|
| 24 |
+
# Which features to vary (on/off)
|
| 25 |
+
vary_compaction: bool = False
|
| 26 |
+
vary_memory: bool = False
|
| 27 |
+
vary_sub_agent: bool = False
|
| 28 |
+
|
| 29 |
+
# Which numeric parameters to vary
|
| 30 |
+
vary_compaction_head: bool = False
|
| 31 |
+
vary_compaction_tail: bool = False
|
| 32 |
+
|
| 33 |
+
# Values to use for numeric variations
|
| 34 |
+
compaction_head_values: list[int] = [5, 10, 20]
|
| 35 |
+
compaction_tail_values: list[int] = [20, 40, 60]
|
| 36 |
+
|
| 37 |
+
# Optional job ID to associate configs with
|
| 38 |
+
job_id: str | None = None
|
| 39 |
+
|
| 40 |
+
|
| 41 |
def parse_uuid(id_str: str) -> UUID:
|
| 42 |
"""Parse a string to UUID, raising 400 if invalid."""
|
| 43 |
try:
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
@router.get("", response_model=list[ConfigResponse])
|
| 50 |
+
async def list_configs(
|
| 51 |
+
include_auto_generated: bool = False,
|
| 52 |
+
session: AsyncSession = Depends(get_session),
|
| 53 |
+
) -> list[AgentConfig]:
|
| 54 |
+
"""List agent configurations.
|
| 55 |
+
|
| 56 |
+
Args:
|
| 57 |
+
include_auto_generated: If False (default), only show user-created configs.
|
| 58 |
+
If True, include auto-generated configs from jobs.
|
| 59 |
+
"""
|
| 60 |
+
query = select(AgentConfig)
|
| 61 |
+
if not include_auto_generated:
|
| 62 |
+
query = query.where(AgentConfig.is_auto_generated == False) # noqa: E712
|
| 63 |
+
query = query.order_by(desc(AgentConfig.created_at))
|
| 64 |
+
result = await session.execute(query)
|
| 65 |
return list(result.scalars().all())
|
| 66 |
|
| 67 |
|
|
|
|
| 155 |
|
| 156 |
await session.delete(config)
|
| 157 |
await session.commit()
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
@router.post("/generate-variations", response_model=list[ConfigResponse], status_code=201)
|
| 161 |
+
async def generate_variations(
|
| 162 |
+
data: VariationRequest,
|
| 163 |
+
session: AsyncSession = Depends(get_session),
|
| 164 |
+
) -> list[AgentConfig]:
|
| 165 |
+
"""Generate config variations for ablation testing.
|
| 166 |
+
|
| 167 |
+
This creates multiple configs by combining variation options.
|
| 168 |
+
Each variation is named based on the features enabled.
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
# Build variation dimensions
|
| 172 |
+
dimensions: list[list[tuple[str, str, bool | int]]] = []
|
| 173 |
+
dimension_names: list[str] = []
|
| 174 |
+
|
| 175 |
+
if data.vary_compaction:
|
| 176 |
+
dimensions.append([
|
| 177 |
+
("compaction", "enable_message_compaction", True),
|
| 178 |
+
("no_compact", "enable_message_compaction", False),
|
| 179 |
+
])
|
| 180 |
+
dimension_names.append("compaction")
|
| 181 |
+
|
| 182 |
+
if data.vary_memory:
|
| 183 |
+
dimensions.append([
|
| 184 |
+
("memory", "enable_memory_tool", True),
|
| 185 |
+
("no_mem", "enable_memory_tool", False),
|
| 186 |
+
])
|
| 187 |
+
dimension_names.append("memory")
|
| 188 |
+
|
| 189 |
+
if data.vary_sub_agent:
|
| 190 |
+
dimensions.append([
|
| 191 |
+
("subagent", "enable_sub_agent", True),
|
| 192 |
+
("no_sub", "enable_sub_agent", False),
|
| 193 |
+
])
|
| 194 |
+
dimension_names.append("sub_agent")
|
| 195 |
+
|
| 196 |
+
if data.vary_compaction_head:
|
| 197 |
+
dimensions.append([
|
| 198 |
+
(f"head{size}", "compaction_head_size", size)
|
| 199 |
+
for size in data.compaction_head_values
|
| 200 |
+
])
|
| 201 |
+
dimension_names.append("head_size")
|
| 202 |
+
|
| 203 |
+
if data.vary_compaction_tail:
|
| 204 |
+
dimensions.append([
|
| 205 |
+
(f"tail{size}", "compaction_tail_size", size)
|
| 206 |
+
for size in data.compaction_tail_values
|
| 207 |
+
])
|
| 208 |
+
dimension_names.append("tail_size")
|
| 209 |
+
|
| 210 |
+
# Parse job_id if provided
|
| 211 |
+
job_uuid = None
|
| 212 |
+
if data.job_id:
|
| 213 |
+
try:
|
| 214 |
+
job_uuid = UUID(data.job_id)
|
| 215 |
+
except ValueError:
|
| 216 |
+
pass
|
| 217 |
+
|
| 218 |
+
# If no variations selected, create a single baseline config
|
| 219 |
+
if not dimensions:
|
| 220 |
+
config = AgentConfig(
|
| 221 |
+
name=f"{data.base_name}_baseline",
|
| 222 |
+
description=f"Baseline config from {data.base_name}",
|
| 223 |
+
config_json={
|
| 224 |
+
"name": f"{data.base_name}_baseline",
|
| 225 |
+
"enable_message_compaction": True,
|
| 226 |
+
"enable_memory_tool": True,
|
| 227 |
+
"enable_sub_agent": False,
|
| 228 |
+
"compaction_head_size": 10,
|
| 229 |
+
"compaction_tail_size": 40,
|
| 230 |
+
"bash_timeout": 120,
|
| 231 |
+
},
|
| 232 |
+
is_auto_generated=True,
|
| 233 |
+
job_id=job_uuid,
|
| 234 |
+
)
|
| 235 |
+
session.add(config)
|
| 236 |
+
await session.commit()
|
| 237 |
+
await session.refresh(config)
|
| 238 |
+
return [config]
|
| 239 |
+
|
| 240 |
+
# Generate all combinations
|
| 241 |
+
configs = []
|
| 242 |
+
for combo in product(*dimensions):
|
| 243 |
+
# Build name from variation labels
|
| 244 |
+
name_parts = [label for label, _, _ in combo]
|
| 245 |
+
config_name = f"{data.base_name}_{'_'.join(name_parts)}"
|
| 246 |
+
|
| 247 |
+
# Build config JSON from defaults + variations
|
| 248 |
+
config_json = {
|
| 249 |
+
"name": config_name,
|
| 250 |
+
"enable_message_compaction": True,
|
| 251 |
+
"enable_memory_tool": True,
|
| 252 |
+
"enable_sub_agent": False,
|
| 253 |
+
"compaction_head_size": 10,
|
| 254 |
+
"compaction_tail_size": 40,
|
| 255 |
+
"bash_timeout": 120,
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
# Apply variations
|
| 259 |
+
for _, key, value in combo:
|
| 260 |
+
config_json[key] = value
|
| 261 |
+
|
| 262 |
+
# Check if config with this name already exists
|
| 263 |
+
existing = await session.execute(
|
| 264 |
+
select(AgentConfig).where(AgentConfig.name == config_name).limit(1)
|
| 265 |
+
)
|
| 266 |
+
existing_config = existing.scalar_one_or_none()
|
| 267 |
+
|
| 268 |
+
if existing_config:
|
| 269 |
+
configs.append(existing_config)
|
| 270 |
+
else:
|
| 271 |
+
config = AgentConfig(
|
| 272 |
+
name=config_name,
|
| 273 |
+
description=f"Auto-generated variation: {', '.join(name_parts)}",
|
| 274 |
+
config_json=config_json,
|
| 275 |
+
is_auto_generated=True,
|
| 276 |
+
job_id=job_uuid,
|
| 277 |
+
)
|
| 278 |
+
session.add(config)
|
| 279 |
+
configs.append(config)
|
| 280 |
+
|
| 281 |
+
await session.commit()
|
| 282 |
+
for config in configs:
|
| 283 |
+
await session.refresh(config)
|
| 284 |
+
|
| 285 |
+
return configs
|
src/flow/ui/api/jobs.py
CHANGED
|
@@ -2,15 +2,16 @@
|
|
| 2 |
"""Job API routes."""
|
| 3 |
|
| 4 |
import asyncio
|
|
|
|
| 5 |
from typing import Any, AsyncGenerator
|
| 6 |
from uuid import UUID
|
| 7 |
|
| 8 |
-
from fastapi import APIRouter, Depends, HTTPException
|
| 9 |
from fastapi.responses import StreamingResponse
|
| 10 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 11 |
from sqlmodel import select, desc
|
| 12 |
|
| 13 |
-
from ..database import get_session
|
| 14 |
from ..models.job import OptimizationJob, JobStatus
|
| 15 |
from ..models.config import AgentConfig
|
| 16 |
from ..models.task import TaskModel
|
|
@@ -18,6 +19,7 @@ from ..schemas import JobCreate, JobResponse
|
|
| 18 |
from ..services.optimizer_service import OptimizerService
|
| 19 |
|
| 20 |
router = APIRouter(prefix="/jobs", tags=["jobs"])
|
|
|
|
| 21 |
|
| 22 |
# Store running jobs for cancellation
|
| 23 |
_running_jobs: dict[str, asyncio.Task[Any]] = {}
|
|
@@ -93,12 +95,49 @@ async def get_job(
|
|
| 93 |
return job
|
| 94 |
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
@router.post("/{job_id}/start")
|
| 97 |
async def start_job(
|
| 98 |
job_id: str,
|
|
|
|
| 99 |
session: AsyncSession = Depends(get_session),
|
| 100 |
) -> StreamingResponse:
|
| 101 |
-
"""Start an optimization job and stream progress via SSE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
uuid_id = parse_uuid(job_id)
|
| 103 |
result = await session.execute(select(OptimizationJob).where(OptimizationJob.id == uuid_id))
|
| 104 |
job = result.scalar_one_or_none()
|
|
@@ -108,10 +147,52 @@ async def start_job(
|
|
| 108 |
if job.status != JobStatus.PENDING:
|
| 109 |
raise HTTPException(status_code=400, detail=f"Job is already {job.status}")
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
async def event_stream() -> AsyncGenerator[str, None]:
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
return StreamingResponse(
|
| 117 |
event_stream(),
|
|
@@ -167,3 +248,80 @@ async def delete_job(
|
|
| 167 |
# Runs will be cascade deleted due to foreign key
|
| 168 |
await session.delete(job)
|
| 169 |
await session.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
"""Job API routes."""
|
| 3 |
|
| 4 |
import asyncio
|
| 5 |
+
import logging
|
| 6 |
from typing import Any, AsyncGenerator
|
| 7 |
from uuid import UUID
|
| 8 |
|
| 9 |
+
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
|
| 10 |
from fastapi.responses import StreamingResponse
|
| 11 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 12 |
from sqlmodel import select, desc
|
| 13 |
|
| 14 |
+
from ..database import get_session, async_session
|
| 15 |
from ..models.job import OptimizationJob, JobStatus
|
| 16 |
from ..models.config import AgentConfig
|
| 17 |
from ..models.task import TaskModel
|
|
|
|
| 19 |
from ..services.optimizer_service import OptimizerService
|
| 20 |
|
| 21 |
router = APIRouter(prefix="/jobs", tags=["jobs"])
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
|
| 24 |
# Store running jobs for cancellation
|
| 25 |
_running_jobs: dict[str, asyncio.Task[Any]] = {}
|
|
|
|
| 95 |
return job
|
| 96 |
|
| 97 |
|
| 98 |
+
async def _run_job_background(job_id: str) -> None:
|
| 99 |
+
"""Run optimization job in background, updating DB with progress.
|
| 100 |
+
|
| 101 |
+
This runs independently of any HTTP connection, so job completion
|
| 102 |
+
is guaranteed even if the client disconnects.
|
| 103 |
+
"""
|
| 104 |
+
service = OptimizerService()
|
| 105 |
+
try:
|
| 106 |
+
# Consume the generator to completion
|
| 107 |
+
async for progress in service.run_job(job_id):
|
| 108 |
+
logger.debug(f"Job {job_id[:8]} progress: {progress.event} - {progress.message}")
|
| 109 |
+
except Exception as e:
|
| 110 |
+
logger.error(f"Background job {job_id[:8]} failed: {e}")
|
| 111 |
+
# Ensure job is marked as failed
|
| 112 |
+
async with async_session() as session:
|
| 113 |
+
from datetime import datetime, timezone
|
| 114 |
+
result = await session.execute(
|
| 115 |
+
select(OptimizationJob).where(OptimizationJob.id == UUID(job_id))
|
| 116 |
+
)
|
| 117 |
+
job = result.scalar_one_or_none()
|
| 118 |
+
if job and job.status == JobStatus.RUNNING:
|
| 119 |
+
job.status = JobStatus.FAILED
|
| 120 |
+
job.error = f"Background execution failed: {e}"
|
| 121 |
+
job.completed_at = datetime.now(timezone.utc)
|
| 122 |
+
await session.commit()
|
| 123 |
+
finally:
|
| 124 |
+
# Remove from running jobs tracker
|
| 125 |
+
if job_id in _running_jobs:
|
| 126 |
+
del _running_jobs[job_id]
|
| 127 |
+
|
| 128 |
+
|
| 129 |
@router.post("/{job_id}/start")
|
| 130 |
async def start_job(
|
| 131 |
job_id: str,
|
| 132 |
+
background_tasks: BackgroundTasks,
|
| 133 |
session: AsyncSession = Depends(get_session),
|
| 134 |
) -> StreamingResponse:
|
| 135 |
+
"""Start an optimization job and stream progress via SSE.
|
| 136 |
+
|
| 137 |
+
The job runs in a background task, so it will complete even if
|
| 138 |
+
the SSE connection is closed. The SSE stream polls the database
|
| 139 |
+
for progress updates.
|
| 140 |
+
"""
|
| 141 |
uuid_id = parse_uuid(job_id)
|
| 142 |
result = await session.execute(select(OptimizationJob).where(OptimizationJob.id == uuid_id))
|
| 143 |
job = result.scalar_one_or_none()
|
|
|
|
| 147 |
if job.status != JobStatus.PENDING:
|
| 148 |
raise HTTPException(status_code=400, detail=f"Job is already {job.status}")
|
| 149 |
|
| 150 |
+
# Start job in background task (runs independently of SSE connection)
|
| 151 |
+
task = asyncio.create_task(_run_job_background(job_id))
|
| 152 |
+
_running_jobs[job_id] = task
|
| 153 |
+
|
| 154 |
async def event_stream() -> AsyncGenerator[str, None]:
|
| 155 |
+
"""Stream progress by polling the database.
|
| 156 |
+
|
| 157 |
+
This is decoupled from the actual job execution, so the job
|
| 158 |
+
continues even if this stream is disconnected.
|
| 159 |
+
"""
|
| 160 |
+
from ..schemas import JobProgress
|
| 161 |
+
|
| 162 |
+
last_completed = -1
|
| 163 |
+
last_status = None
|
| 164 |
+
|
| 165 |
+
while True:
|
| 166 |
+
# Poll job status from DB
|
| 167 |
+
async with async_session() as poll_session:
|
| 168 |
+
result = await poll_session.execute(
|
| 169 |
+
select(OptimizationJob).where(OptimizationJob.id == uuid_id)
|
| 170 |
+
)
|
| 171 |
+
current_job = result.scalar_one_or_none()
|
| 172 |
+
|
| 173 |
+
if not current_job:
|
| 174 |
+
yield f"data: {JobProgress(event='error', job_id=job_id, message='Job not found').model_dump_json()}\n\n"
|
| 175 |
+
break
|
| 176 |
+
|
| 177 |
+
# Emit progress if changed
|
| 178 |
+
if current_job.completed_experiments != last_completed or current_job.status != last_status:
|
| 179 |
+
last_completed = current_job.completed_experiments
|
| 180 |
+
last_status = current_job.status
|
| 181 |
+
|
| 182 |
+
if current_job.status == JobStatus.COMPLETED:
|
| 183 |
+
yield f"data: {JobProgress(event='complete', job_id=job_id, completed=current_job.completed_experiments, total=current_job.total_experiments, message='Optimization complete').model_dump_json()}\n\n"
|
| 184 |
+
break
|
| 185 |
+
elif current_job.status == JobStatus.FAILED:
|
| 186 |
+
yield f"data: {JobProgress(event='error', job_id=job_id, message=current_job.error or 'Job failed').model_dump_json()}\n\n"
|
| 187 |
+
break
|
| 188 |
+
elif current_job.status == JobStatus.CANCELLED:
|
| 189 |
+
yield f"data: {JobProgress(event='error', job_id=job_id, message='Job cancelled').model_dump_json()}\n\n"
|
| 190 |
+
break
|
| 191 |
+
else:
|
| 192 |
+
yield f"data: {JobProgress(event='progress', job_id=job_id, completed=current_job.completed_experiments, total=current_job.total_experiments, message=f'Running... {current_job.completed_experiments}/{current_job.total_experiments}').model_dump_json()}\n\n"
|
| 193 |
+
|
| 194 |
+
# Wait before polling again
|
| 195 |
+
await asyncio.sleep(1.0)
|
| 196 |
|
| 197 |
return StreamingResponse(
|
| 198 |
event_stream(),
|
|
|
|
| 248 |
# Runs will be cascade deleted due to foreign key
|
| 249 |
await session.delete(job)
|
| 250 |
await session.commit()
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
@router.post("/{job_id}/reset", response_model=JobResponse)
|
| 254 |
+
async def reset_job(
|
| 255 |
+
job_id: str,
|
| 256 |
+
session: AsyncSession = Depends(get_session),
|
| 257 |
+
) -> OptimizationJob:
|
| 258 |
+
"""Reset a stuck job back to pending state.
|
| 259 |
+
|
| 260 |
+
Use this to recover jobs that got stuck in 'running' state
|
| 261 |
+
due to connection drops or server restarts.
|
| 262 |
+
"""
|
| 263 |
+
from datetime import datetime, timezone
|
| 264 |
+
|
| 265 |
+
uuid_id = parse_uuid(job_id)
|
| 266 |
+
result = await session.execute(select(OptimizationJob).where(OptimizationJob.id == uuid_id))
|
| 267 |
+
job = result.scalar_one_or_none()
|
| 268 |
+
if not job:
|
| 269 |
+
raise HTTPException(status_code=404, detail="Job not found")
|
| 270 |
+
|
| 271 |
+
# Only allow resetting running or failed jobs
|
| 272 |
+
if job.status not in [JobStatus.RUNNING, JobStatus.FAILED]:
|
| 273 |
+
raise HTTPException(
|
| 274 |
+
status_code=400,
|
| 275 |
+
detail=f"Can only reset running or failed jobs (current: {job.status})"
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
# Reset to pending
|
| 279 |
+
job.status = JobStatus.PENDING
|
| 280 |
+
job.started_at = None
|
| 281 |
+
job.completed_at = None
|
| 282 |
+
job.completed_experiments = 0
|
| 283 |
+
job.error = None
|
| 284 |
+
job.pareto_frontier = []
|
| 285 |
+
|
| 286 |
+
await session.commit()
|
| 287 |
+
await session.refresh(job)
|
| 288 |
+
return job
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
@router.post("/cleanup-stuck", response_model=list[JobResponse])
|
| 292 |
+
async def cleanup_stuck_jobs(
|
| 293 |
+
max_age_hours: int = 2,
|
| 294 |
+
session: AsyncSession = Depends(get_session),
|
| 295 |
+
) -> list[OptimizationJob]:
|
| 296 |
+
"""Mark stuck 'running' jobs as failed.
|
| 297 |
+
|
| 298 |
+
Jobs that have been 'running' for longer than max_age_hours without
|
| 299 |
+
any progress update are assumed to be stuck (e.g., due to server
|
| 300 |
+
restart or connection drop).
|
| 301 |
+
"""
|
| 302 |
+
from datetime import datetime, timezone, timedelta
|
| 303 |
+
|
| 304 |
+
cutoff = datetime.now(timezone.utc) - timedelta(hours=max_age_hours)
|
| 305 |
+
|
| 306 |
+
# Find jobs that are running and started before cutoff
|
| 307 |
+
result = await session.execute(
|
| 308 |
+
select(OptimizationJob).where(
|
| 309 |
+
OptimizationJob.status == JobStatus.RUNNING,
|
| 310 |
+
OptimizationJob.started_at < cutoff,
|
| 311 |
+
)
|
| 312 |
+
)
|
| 313 |
+
stuck_jobs = list(result.scalars().all())
|
| 314 |
+
|
| 315 |
+
# Mark them as failed
|
| 316 |
+
for job in stuck_jobs:
|
| 317 |
+
job.status = JobStatus.FAILED
|
| 318 |
+
job.error = f"Job timed out after {max_age_hours} hours. Reset and try again."
|
| 319 |
+
job.completed_at = datetime.now(timezone.utc)
|
| 320 |
+
|
| 321 |
+
await session.commit()
|
| 322 |
+
|
| 323 |
+
# Refresh all
|
| 324 |
+
for job in stuck_jobs:
|
| 325 |
+
await session.refresh(job)
|
| 326 |
+
|
| 327 |
+
return stuck_jobs
|
src/flow/ui/api/tasks.py
CHANGED
|
@@ -92,7 +92,10 @@ async def import_suite(
|
|
| 92 |
suite_name: str,
|
| 93 |
session: AsyncSession = Depends(get_session),
|
| 94 |
) -> list[TaskModel]:
|
| 95 |
-
"""Import tasks from a built-in suite.
|
|
|
|
|
|
|
|
|
|
| 96 |
from flow.experiments.types import get_task_suite
|
| 97 |
|
| 98 |
try:
|
|
@@ -100,20 +103,35 @@ async def import_suite(
|
|
| 100 |
except ValueError as e:
|
| 101 |
raise HTTPException(status_code=400, detail=str(e))
|
| 102 |
|
| 103 |
-
|
| 104 |
for t in suite_tasks:
|
| 105 |
-
task
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
)
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
await session.commit()
|
| 116 |
-
|
|
|
|
| 117 |
await session.refresh(task)
|
| 118 |
|
| 119 |
-
return
|
|
|
|
| 92 |
suite_name: str,
|
| 93 |
session: AsyncSession = Depends(get_session),
|
| 94 |
) -> list[TaskModel]:
|
| 95 |
+
"""Import tasks from a built-in suite.
|
| 96 |
+
|
| 97 |
+
This is idempotent - tasks that already exist (by name+suite) are returned as-is.
|
| 98 |
+
"""
|
| 99 |
from flow.experiments.types import get_task_suite
|
| 100 |
|
| 101 |
try:
|
|
|
|
| 103 |
except ValueError as e:
|
| 104 |
raise HTTPException(status_code=400, detail=str(e))
|
| 105 |
|
| 106 |
+
result_tasks = []
|
| 107 |
for t in suite_tasks:
|
| 108 |
+
# Check if task already exists with this name and suite
|
| 109 |
+
existing = await session.execute(
|
| 110 |
+
select(TaskModel).where(
|
| 111 |
+
TaskModel.name == t.name,
|
| 112 |
+
TaskModel.suite == suite_name,
|
| 113 |
+
).limit(1)
|
| 114 |
)
|
| 115 |
+
existing_task = existing.scalar_one_or_none()
|
| 116 |
+
|
| 117 |
+
if existing_task:
|
| 118 |
+
# Task already exists, use existing
|
| 119 |
+
result_tasks.append(existing_task)
|
| 120 |
+
else:
|
| 121 |
+
# Create new task
|
| 122 |
+
task = TaskModel(
|
| 123 |
+
name=t.name,
|
| 124 |
+
prompt=t.prompt,
|
| 125 |
+
criteria_json=[{"name": c.name, "instruction": c.instruction, "weight": c.weight} for c in t.criteria],
|
| 126 |
+
category=t.metadata.get("category", "default"),
|
| 127 |
+
suite=suite_name,
|
| 128 |
+
)
|
| 129 |
+
session.add(task)
|
| 130 |
+
result_tasks.append(task)
|
| 131 |
|
| 132 |
await session.commit()
|
| 133 |
+
# Refresh only newly created tasks
|
| 134 |
+
for task in result_tasks:
|
| 135 |
await session.refresh(task)
|
| 136 |
|
| 137 |
+
return result_tasks
|
src/flow/ui/auth/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Microsoft. All rights reserved.
|
| 2 |
+
"""Authentication module for Flow UI."""
|
| 3 |
+
|
| 4 |
+
from .config import AuthSettings, AuthMode, get_auth_settings, init_auth_settings
|
| 5 |
+
from .tokens import create_access_token, verify_access_token, TokenData
|
| 6 |
+
from .middleware import get_current_user, require_auth
|
| 7 |
+
from .router import router as auth_router
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"AuthSettings",
|
| 11 |
+
"AuthMode",
|
| 12 |
+
"get_auth_settings",
|
| 13 |
+
"init_auth_settings",
|
| 14 |
+
"create_access_token",
|
| 15 |
+
"verify_access_token",
|
| 16 |
+
"TokenData",
|
| 17 |
+
"get_current_user",
|
| 18 |
+
"require_auth",
|
| 19 |
+
"auth_router",
|
| 20 |
+
]
|
src/flow/ui/auth/config.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Microsoft. All rights reserved.
|
| 2 |
+
"""Authentication configuration settings."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import secrets
|
| 7 |
+
from enum import Enum
|
| 8 |
+
from typing import TYPE_CHECKING
|
| 9 |
+
|
| 10 |
+
from pydantic import Field, field_validator
|
| 11 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 12 |
+
|
| 13 |
+
if TYPE_CHECKING:
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class AuthMode(str, Enum):
|
| 18 |
+
"""Authentication mode."""
|
| 19 |
+
|
| 20 |
+
BASIC = "basic"
|
| 21 |
+
GITHUB = "github"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class AuthSettings(BaseSettings):
|
| 25 |
+
"""Authentication settings loaded from environment variables.
|
| 26 |
+
|
| 27 |
+
Environment Variables:
|
| 28 |
+
AUTH_ENABLED: Enable authentication (default: False)
|
| 29 |
+
AUTH_MODE: Authentication mode - "basic" or "github" (default: basic)
|
| 30 |
+
AUTH_SECRET: Secret key for JWT signing (required when auth enabled)
|
| 31 |
+
AUTH_SESSION_HOURS: Token validity in hours (default: 24)
|
| 32 |
+
|
| 33 |
+
For basic mode:
|
| 34 |
+
AUTH_BASIC_USERNAME: Username (default: admin)
|
| 35 |
+
AUTH_BASIC_PASSWORD: Password (required for basic mode)
|
| 36 |
+
|
| 37 |
+
For GitHub mode:
|
| 38 |
+
AUTH_GITHUB_CLIENT_ID: GitHub OAuth App client ID
|
| 39 |
+
AUTH_GITHUB_CLIENT_SECRET: GitHub OAuth App client secret
|
| 40 |
+
AUTH_GITHUB_ALLOWED_USERS: Comma-separated list of allowed GitHub usernames
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
model_config = SettingsConfigDict(
|
| 44 |
+
env_prefix="AUTH_",
|
| 45 |
+
env_file=".env",
|
| 46 |
+
env_file_encoding="utf-8",
|
| 47 |
+
extra="ignore",
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
# Core settings
|
| 51 |
+
enabled: bool = Field(default=False, description="Enable authentication")
|
| 52 |
+
mode: AuthMode = Field(default=AuthMode.BASIC, description="Authentication mode")
|
| 53 |
+
secret: str = Field(
|
| 54 |
+
default_factory=lambda: secrets.token_urlsafe(32),
|
| 55 |
+
description="Secret key for JWT signing",
|
| 56 |
+
)
|
| 57 |
+
session_hours: int = Field(default=24, description="Token validity in hours")
|
| 58 |
+
|
| 59 |
+
# Basic auth settings
|
| 60 |
+
basic_username: str = Field(default="admin", description="Basic auth username")
|
| 61 |
+
basic_password: str | None = Field(default=None, description="Basic auth password")
|
| 62 |
+
|
| 63 |
+
# GitHub OAuth settings
|
| 64 |
+
github_client_id: str | None = Field(default=None, description="GitHub OAuth client ID")
|
| 65 |
+
github_client_secret: str | None = Field(default=None, description="GitHub OAuth client secret")
|
| 66 |
+
github_allowed_users: str | None = Field(
|
| 67 |
+
default=None,
|
| 68 |
+
description="Comma-separated list of allowed GitHub usernames",
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
@field_validator("github_allowed_users", mode="before")
|
| 72 |
+
@classmethod
|
| 73 |
+
def parse_allowed_users(cls, v: str | list[str] | None) -> str | None:
|
| 74 |
+
"""Accept both comma-separated string and list."""
|
| 75 |
+
if isinstance(v, list):
|
| 76 |
+
return ",".join(v)
|
| 77 |
+
return v
|
| 78 |
+
|
| 79 |
+
def get_allowed_github_users(self) -> set[str]:
|
| 80 |
+
"""Get set of allowed GitHub usernames."""
|
| 81 |
+
if not self.github_allowed_users:
|
| 82 |
+
return set()
|
| 83 |
+
return {u.strip().lower() for u in self.github_allowed_users.split(",") if u.strip()}
|
| 84 |
+
|
| 85 |
+
def validate_config(self) -> list[str]:
|
| 86 |
+
"""Validate configuration and return list of errors."""
|
| 87 |
+
errors: list[str] = []
|
| 88 |
+
|
| 89 |
+
if not self.enabled:
|
| 90 |
+
return errors
|
| 91 |
+
|
| 92 |
+
if self.mode == AuthMode.BASIC:
|
| 93 |
+
if not self.basic_password:
|
| 94 |
+
errors.append("AUTH_BASIC_PASSWORD is required when auth is enabled with basic mode")
|
| 95 |
+
elif self.mode == AuthMode.GITHUB:
|
| 96 |
+
if not self.github_client_id:
|
| 97 |
+
errors.append("AUTH_GITHUB_CLIENT_ID is required for GitHub auth mode")
|
| 98 |
+
if not self.github_client_secret:
|
| 99 |
+
errors.append("AUTH_GITHUB_CLIENT_SECRET is required for GitHub auth mode")
|
| 100 |
+
if not self.github_allowed_users:
|
| 101 |
+
errors.append("AUTH_GITHUB_ALLOWED_USERS is required for GitHub auth mode")
|
| 102 |
+
|
| 103 |
+
return errors
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
# Global settings instance - will be initialized by the app
|
| 107 |
+
_auth_settings: AuthSettings | None = None
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def get_auth_settings() -> AuthSettings:
|
| 111 |
+
"""Get the global auth settings instance."""
|
| 112 |
+
global _auth_settings
|
| 113 |
+
if _auth_settings is None:
|
| 114 |
+
_auth_settings = AuthSettings()
|
| 115 |
+
return _auth_settings
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def init_auth_settings(
|
| 119 |
+
enabled: bool | None = None,
|
| 120 |
+
mode: AuthMode | str | None = None,
|
| 121 |
+
secret: str | None = None,
|
| 122 |
+
basic_username: str | None = None,
|
| 123 |
+
basic_password: str | None = None,
|
| 124 |
+
github_client_id: str | None = None,
|
| 125 |
+
github_client_secret: str | None = None,
|
| 126 |
+
github_allowed_users: str | None = None,
|
| 127 |
+
session_hours: int | None = None,
|
| 128 |
+
) -> AuthSettings:
|
| 129 |
+
"""Initialize auth settings with explicit values (CLI overrides env vars)."""
|
| 130 |
+
global _auth_settings
|
| 131 |
+
|
| 132 |
+
# Start with environment-based settings
|
| 133 |
+
_auth_settings = AuthSettings()
|
| 134 |
+
|
| 135 |
+
# Override with explicit values if provided
|
| 136 |
+
if enabled is not None:
|
| 137 |
+
_auth_settings.enabled = enabled
|
| 138 |
+
if mode is not None:
|
| 139 |
+
_auth_settings.mode = AuthMode(mode) if isinstance(mode, str) else mode
|
| 140 |
+
if secret is not None:
|
| 141 |
+
_auth_settings.secret = secret
|
| 142 |
+
if basic_username is not None:
|
| 143 |
+
_auth_settings.basic_username = basic_username
|
| 144 |
+
if basic_password is not None:
|
| 145 |
+
_auth_settings.basic_password = basic_password
|
| 146 |
+
if github_client_id is not None:
|
| 147 |
+
_auth_settings.github_client_id = github_client_id
|
| 148 |
+
if github_client_secret is not None:
|
| 149 |
+
_auth_settings.github_client_secret = github_client_secret
|
| 150 |
+
if github_allowed_users is not None:
|
| 151 |
+
_auth_settings.github_allowed_users = github_allowed_users
|
| 152 |
+
if session_hours is not None:
|
| 153 |
+
_auth_settings.session_hours = session_hours
|
| 154 |
+
|
| 155 |
+
return _auth_settings
|
src/flow/ui/auth/middleware.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Microsoft. All rights reserved.
|
| 2 |
+
"""Authentication middleware for FastAPI routes."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
from typing import Annotated
|
| 7 |
+
|
| 8 |
+
from fastapi import Depends, HTTPException, Request, status
|
| 9 |
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
| 10 |
+
|
| 11 |
+
from .config import get_auth_settings, AuthSettings
|
| 12 |
+
from .tokens import verify_access_token, TokenData, TokenError
|
| 13 |
+
|
| 14 |
+
# Bearer token security scheme
|
| 15 |
+
bearer_scheme = HTTPBearer(auto_error=False)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
async def get_current_user(
|
| 19 |
+
request: Request,
|
| 20 |
+
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer_scheme)],
|
| 21 |
+
) -> TokenData | None:
|
| 22 |
+
"""Get the current authenticated user from the request.
|
| 23 |
+
|
| 24 |
+
This dependency extracts and validates the JWT token from the Authorization header.
|
| 25 |
+
If auth is disabled, returns None (all requests allowed).
|
| 26 |
+
If auth is enabled and no valid token is present, raises 401.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
request: The FastAPI request object
|
| 30 |
+
credentials: The bearer token credentials
|
| 31 |
+
|
| 32 |
+
Returns:
|
| 33 |
+
TokenData if authenticated, None if auth is disabled
|
| 34 |
+
|
| 35 |
+
Raises:
|
| 36 |
+
HTTPException: 401 if auth is enabled and token is missing/invalid
|
| 37 |
+
"""
|
| 38 |
+
settings = get_auth_settings()
|
| 39 |
+
|
| 40 |
+
# If auth is disabled, allow all requests
|
| 41 |
+
if not settings.enabled:
|
| 42 |
+
return None
|
| 43 |
+
|
| 44 |
+
# Auth is enabled - token is required
|
| 45 |
+
if credentials is None:
|
| 46 |
+
raise HTTPException(
|
| 47 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 48 |
+
detail="Not authenticated",
|
| 49 |
+
headers={"WWW-Authenticate": "Bearer"},
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
token_data = verify_access_token(credentials.credentials, settings.secret)
|
| 54 |
+
return token_data
|
| 55 |
+
except TokenError as e:
|
| 56 |
+
raise HTTPException(
|
| 57 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 58 |
+
detail=str(e),
|
| 59 |
+
headers={"WWW-Authenticate": "Bearer"},
|
| 60 |
+
) from e
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
async def require_auth(
|
| 64 |
+
user: Annotated[TokenData | None, Depends(get_current_user)],
|
| 65 |
+
) -> TokenData | None:
|
| 66 |
+
"""Require authentication if enabled.
|
| 67 |
+
|
| 68 |
+
Use this as a dependency on routes that should be protected when auth is enabled.
|
| 69 |
+
This is essentially an alias for get_current_user that makes intent clearer.
|
| 70 |
+
|
| 71 |
+
Args:
|
| 72 |
+
user: The current user from get_current_user
|
| 73 |
+
|
| 74 |
+
Returns:
|
| 75 |
+
TokenData if authenticated, None if auth is disabled
|
| 76 |
+
"""
|
| 77 |
+
return user
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def get_optional_user(
|
| 81 |
+
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer_scheme)],
|
| 82 |
+
) -> TokenData | None:
|
| 83 |
+
"""Get the current user if a valid token is provided, otherwise None.
|
| 84 |
+
|
| 85 |
+
Unlike get_current_user, this never raises an error - it's for routes
|
| 86 |
+
that work differently based on whether the user is authenticated.
|
| 87 |
+
|
| 88 |
+
Args:
|
| 89 |
+
credentials: The bearer token credentials
|
| 90 |
+
|
| 91 |
+
Returns:
|
| 92 |
+
TokenData if valid token provided, None otherwise
|
| 93 |
+
"""
|
| 94 |
+
settings = get_auth_settings()
|
| 95 |
+
|
| 96 |
+
if credentials is None:
|
| 97 |
+
return None
|
| 98 |
+
|
| 99 |
+
try:
|
| 100 |
+
return verify_access_token(credentials.credentials, settings.secret)
|
| 101 |
+
except TokenError:
|
| 102 |
+
return None
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
class AuthMiddleware:
|
| 106 |
+
"""Middleware to check authentication on all /api/* routes except /api/auth/*.
|
| 107 |
+
|
| 108 |
+
This provides defense-in-depth - even if a route forgets to use the
|
| 109 |
+
require_auth dependency, requests will still be blocked if auth is enabled.
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
# Routes that don't require authentication even when auth is enabled
|
| 113 |
+
PUBLIC_PATHS = {
|
| 114 |
+
"/api/auth/config",
|
| 115 |
+
"/api/auth/login",
|
| 116 |
+
"/api/auth/github",
|
| 117 |
+
"/api/auth/github/callback",
|
| 118 |
+
"/api/health",
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
def __init__(self, settings: AuthSettings | None = None) -> None:
|
| 122 |
+
"""Initialize middleware with optional settings override."""
|
| 123 |
+
self._settings = settings
|
| 124 |
+
|
| 125 |
+
@property
|
| 126 |
+
def settings(self) -> AuthSettings:
|
| 127 |
+
"""Get auth settings."""
|
| 128 |
+
return self._settings or get_auth_settings()
|
| 129 |
+
|
| 130 |
+
def is_public_path(self, path: str) -> bool:
|
| 131 |
+
"""Check if a path is public (doesn't require auth)."""
|
| 132 |
+
# Exact matches
|
| 133 |
+
if path in self.PUBLIC_PATHS:
|
| 134 |
+
return True
|
| 135 |
+
|
| 136 |
+
# Non-API routes (static files, SPA) are handled by frontend auth
|
| 137 |
+
if not path.startswith("/api/"):
|
| 138 |
+
return True
|
| 139 |
+
|
| 140 |
+
return False
|
| 141 |
+
|
| 142 |
+
async def __call__(self, request: Request, call_next): # type: ignore[no-untyped-def]
|
| 143 |
+
"""Process the request through the middleware."""
|
| 144 |
+
# If auth is disabled, pass through
|
| 145 |
+
if not self.settings.enabled:
|
| 146 |
+
return await call_next(request)
|
| 147 |
+
|
| 148 |
+
# Check if path is public
|
| 149 |
+
if self.is_public_path(request.url.path):
|
| 150 |
+
return await call_next(request)
|
| 151 |
+
|
| 152 |
+
# For protected API routes, verify token
|
| 153 |
+
auth_header = request.headers.get("Authorization")
|
| 154 |
+
if not auth_header or not auth_header.startswith("Bearer "):
|
| 155 |
+
from fastapi.responses import JSONResponse
|
| 156 |
+
|
| 157 |
+
return JSONResponse(
|
| 158 |
+
status_code=401,
|
| 159 |
+
content={"detail": "Not authenticated"},
|
| 160 |
+
headers={"WWW-Authenticate": "Bearer"},
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
token = auth_header[7:] # Remove "Bearer " prefix
|
| 164 |
+
try:
|
| 165 |
+
verify_access_token(token, self.settings.secret)
|
| 166 |
+
except TokenError as e:
|
| 167 |
+
from fastapi.responses import JSONResponse
|
| 168 |
+
|
| 169 |
+
return JSONResponse(
|
| 170 |
+
status_code=401,
|
| 171 |
+
content={"detail": str(e)},
|
| 172 |
+
headers={"WWW-Authenticate": "Bearer"},
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
return await call_next(request)
|
src/flow/ui/auth/router.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Microsoft. All rights reserved.
|
| 2 |
+
"""Authentication API endpoints."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import secrets
|
| 7 |
+
from typing import Annotated, Any
|
| 8 |
+
from urllib.parse import urlencode
|
| 9 |
+
|
| 10 |
+
import httpx
|
| 11 |
+
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
| 12 |
+
from fastapi.responses import RedirectResponse
|
| 13 |
+
from pydantic import BaseModel
|
| 14 |
+
|
| 15 |
+
from .config import AuthMode, AuthSettings, get_auth_settings
|
| 16 |
+
from .middleware import get_current_user
|
| 17 |
+
from .tokens import TokenData, create_access_token
|
| 18 |
+
|
| 19 |
+
router = APIRouter(prefix="/auth", tags=["auth"])
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# ============================================================================
|
| 23 |
+
# Response Models
|
| 24 |
+
# ============================================================================
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class AuthConfigResponse(BaseModel):
|
| 28 |
+
"""Response for /auth/config endpoint."""
|
| 29 |
+
|
| 30 |
+
enabled: bool
|
| 31 |
+
mode: str # "basic" or "github"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class LoginRequest(BaseModel):
|
| 35 |
+
"""Request body for basic auth login."""
|
| 36 |
+
|
| 37 |
+
username: str
|
| 38 |
+
password: str
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class LoginResponse(BaseModel):
|
| 42 |
+
"""Response for successful login."""
|
| 43 |
+
|
| 44 |
+
access_token: str
|
| 45 |
+
token_type: str = "bearer"
|
| 46 |
+
expires_at: str # ISO format datetime
|
| 47 |
+
username: str
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class UserResponse(BaseModel):
|
| 51 |
+
"""Response for /auth/me endpoint."""
|
| 52 |
+
|
| 53 |
+
username: str
|
| 54 |
+
auth_mode: str
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# ============================================================================
|
| 58 |
+
# Public Endpoints (no auth required)
|
| 59 |
+
# ============================================================================
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@router.get("/config", response_model=AuthConfigResponse)
|
| 63 |
+
async def get_auth_config() -> AuthConfigResponse:
|
| 64 |
+
"""Get authentication configuration.
|
| 65 |
+
|
| 66 |
+
This endpoint is always public - the frontend needs to know
|
| 67 |
+
what auth mode to use before it can authenticate.
|
| 68 |
+
"""
|
| 69 |
+
settings = get_auth_settings()
|
| 70 |
+
return AuthConfigResponse(
|
| 71 |
+
enabled=settings.enabled,
|
| 72 |
+
mode=settings.mode.value,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
@router.post("/login", response_model=LoginResponse)
|
| 77 |
+
async def login(request: LoginRequest) -> LoginResponse:
|
| 78 |
+
"""Login with username and password (basic auth mode).
|
| 79 |
+
|
| 80 |
+
This endpoint is only available when auth mode is "basic".
|
| 81 |
+
"""
|
| 82 |
+
settings = get_auth_settings()
|
| 83 |
+
|
| 84 |
+
if not settings.enabled:
|
| 85 |
+
raise HTTPException(
|
| 86 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 87 |
+
detail="Authentication is not enabled",
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
if settings.mode != AuthMode.BASIC:
|
| 91 |
+
raise HTTPException(
|
| 92 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 93 |
+
detail="Basic auth is not enabled. Use GitHub OAuth instead.",
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
# Verify credentials using constant-time comparison
|
| 97 |
+
username_valid = secrets.compare_digest(request.username, settings.basic_username)
|
| 98 |
+
password_valid = (
|
| 99 |
+
settings.basic_password is not None
|
| 100 |
+
and secrets.compare_digest(request.password, settings.basic_password)
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
if not (username_valid and password_valid):
|
| 104 |
+
raise HTTPException(
|
| 105 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 106 |
+
detail="Invalid username or password",
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
# Create token
|
| 110 |
+
token, expires_at = create_access_token(
|
| 111 |
+
subject=request.username,
|
| 112 |
+
username=request.username,
|
| 113 |
+
auth_mode="basic",
|
| 114 |
+
secret=settings.secret,
|
| 115 |
+
expires_hours=settings.session_hours,
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
return LoginResponse(
|
| 119 |
+
access_token=token,
|
| 120 |
+
expires_at=expires_at.isoformat(),
|
| 121 |
+
username=request.username,
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
# ============================================================================
|
| 126 |
+
# GitHub OAuth Endpoints
|
| 127 |
+
# ============================================================================
|
| 128 |
+
|
| 129 |
+
# Store OAuth state tokens temporarily (in production, use Redis or similar)
|
| 130 |
+
_oauth_states: dict[str, str] = {}
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
@router.get("/github")
|
| 134 |
+
async def github_oauth_start(
|
| 135 |
+
request: Request,
|
| 136 |
+
redirect_uri: Annotated[str | None, Query()] = None,
|
| 137 |
+
) -> RedirectResponse:
|
| 138 |
+
"""Start GitHub OAuth flow.
|
| 139 |
+
|
| 140 |
+
Redirects to GitHub's authorization page.
|
| 141 |
+
"""
|
| 142 |
+
settings = get_auth_settings()
|
| 143 |
+
|
| 144 |
+
if not settings.enabled:
|
| 145 |
+
raise HTTPException(
|
| 146 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 147 |
+
detail="Authentication is not enabled",
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
if settings.mode != AuthMode.GITHUB:
|
| 151 |
+
raise HTTPException(
|
| 152 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 153 |
+
detail="GitHub auth is not enabled",
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
if not settings.github_client_id:
|
| 157 |
+
raise HTTPException(
|
| 158 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 159 |
+
detail="GitHub OAuth not configured",
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
# Generate state token for CSRF protection
|
| 163 |
+
state = secrets.token_urlsafe(32)
|
| 164 |
+
|
| 165 |
+
# Store the redirect URI with the state (where to send user after auth)
|
| 166 |
+
callback_uri = redirect_uri or str(request.base_url).rstrip("/")
|
| 167 |
+
_oauth_states[state] = callback_uri
|
| 168 |
+
|
| 169 |
+
# Build GitHub authorization URL
|
| 170 |
+
params = {
|
| 171 |
+
"client_id": settings.github_client_id,
|
| 172 |
+
"redirect_uri": f"{str(request.base_url).rstrip('/')}/api/auth/github/callback",
|
| 173 |
+
"scope": "read:user",
|
| 174 |
+
"state": state,
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
github_auth_url = f"https://github.com/login/oauth/authorize?{urlencode(params)}"
|
| 178 |
+
return RedirectResponse(url=github_auth_url)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
@router.get("/github/callback")
|
| 182 |
+
async def github_oauth_callback(
|
| 183 |
+
request: Request,
|
| 184 |
+
code: Annotated[str, Query()],
|
| 185 |
+
state: Annotated[str, Query()],
|
| 186 |
+
) -> RedirectResponse:
|
| 187 |
+
"""Handle GitHub OAuth callback.
|
| 188 |
+
|
| 189 |
+
Exchanges the code for an access token, verifies the user is allowed,
|
| 190 |
+
and redirects to the frontend with a session token.
|
| 191 |
+
"""
|
| 192 |
+
settings = get_auth_settings()
|
| 193 |
+
|
| 194 |
+
# Verify state token
|
| 195 |
+
if state not in _oauth_states:
|
| 196 |
+
raise HTTPException(
|
| 197 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 198 |
+
detail="Invalid OAuth state",
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
base_redirect = _oauth_states.pop(state)
|
| 202 |
+
|
| 203 |
+
if not settings.github_client_id or not settings.github_client_secret:
|
| 204 |
+
raise HTTPException(
|
| 205 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 206 |
+
detail="GitHub OAuth not configured",
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
# Exchange code for access token
|
| 210 |
+
async with httpx.AsyncClient() as client:
|
| 211 |
+
token_response = await client.post(
|
| 212 |
+
"https://github.com/login/oauth/access_token",
|
| 213 |
+
data={
|
| 214 |
+
"client_id": settings.github_client_id,
|
| 215 |
+
"client_secret": settings.github_client_secret,
|
| 216 |
+
"code": code,
|
| 217 |
+
},
|
| 218 |
+
headers={"Accept": "application/json"},
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
if token_response.status_code != 200:
|
| 222 |
+
return _redirect_with_error(base_redirect, "Failed to exchange code for token")
|
| 223 |
+
|
| 224 |
+
token_data = token_response.json()
|
| 225 |
+
github_access_token = token_data.get("access_token")
|
| 226 |
+
|
| 227 |
+
if not github_access_token:
|
| 228 |
+
error = token_data.get("error_description", "Unknown error")
|
| 229 |
+
return _redirect_with_error(base_redirect, f"GitHub OAuth error: {error}")
|
| 230 |
+
|
| 231 |
+
# Get user info
|
| 232 |
+
user_response = await client.get(
|
| 233 |
+
"https://api.github.com/user",
|
| 234 |
+
headers={
|
| 235 |
+
"Authorization": f"Bearer {github_access_token}",
|
| 236 |
+
"Accept": "application/vnd.github.v3+json",
|
| 237 |
+
},
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
if user_response.status_code != 200:
|
| 241 |
+
return _redirect_with_error(base_redirect, "Failed to get GitHub user info")
|
| 242 |
+
|
| 243 |
+
github_user = user_response.json()
|
| 244 |
+
github_username = github_user.get("login", "").lower()
|
| 245 |
+
github_id = str(github_user.get("id", ""))
|
| 246 |
+
|
| 247 |
+
# Check if user is allowed
|
| 248 |
+
allowed_users = settings.get_allowed_github_users()
|
| 249 |
+
if github_username not in allowed_users:
|
| 250 |
+
return _redirect_with_error(
|
| 251 |
+
base_redirect,
|
| 252 |
+
f"User '{github_username}' is not authorized to access this application",
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
# Create session token
|
| 256 |
+
token, expires_at = create_access_token(
|
| 257 |
+
subject=github_id,
|
| 258 |
+
username=github_username,
|
| 259 |
+
auth_mode="github",
|
| 260 |
+
secret=settings.secret,
|
| 261 |
+
expires_hours=settings.session_hours,
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
# Redirect to frontend with token
|
| 265 |
+
redirect_params = {
|
| 266 |
+
"token": token,
|
| 267 |
+
"expires_at": expires_at.isoformat(),
|
| 268 |
+
"username": github_username,
|
| 269 |
+
}
|
| 270 |
+
redirect_url = f"{base_redirect}?auth_callback=true&{urlencode(redirect_params)}"
|
| 271 |
+
return RedirectResponse(url=redirect_url)
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def _redirect_with_error(base_url: str, error: str) -> RedirectResponse:
|
| 275 |
+
"""Redirect to frontend with an error message."""
|
| 276 |
+
params = {"auth_error": error}
|
| 277 |
+
return RedirectResponse(url=f"{base_url}?{urlencode(params)}")
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
# ============================================================================
|
| 281 |
+
# Protected Endpoints (require auth when enabled)
|
| 282 |
+
# ============================================================================
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
@router.get("/me", response_model=UserResponse)
|
| 286 |
+
async def get_current_user_info(
|
| 287 |
+
user: Annotated[TokenData | None, Depends(get_current_user)],
|
| 288 |
+
) -> UserResponse:
|
| 289 |
+
"""Get information about the currently authenticated user.
|
| 290 |
+
|
| 291 |
+
Returns user info if authenticated, or a placeholder if auth is disabled.
|
| 292 |
+
"""
|
| 293 |
+
settings = get_auth_settings()
|
| 294 |
+
|
| 295 |
+
if not settings.enabled:
|
| 296 |
+
return UserResponse(username="anonymous", auth_mode="none")
|
| 297 |
+
|
| 298 |
+
if user is None:
|
| 299 |
+
raise HTTPException(
|
| 300 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 301 |
+
detail="Not authenticated",
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
return UserResponse(
|
| 305 |
+
username=user.username,
|
| 306 |
+
auth_mode=user.auth_mode,
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
@router.post("/logout")
|
| 311 |
+
async def logout(
|
| 312 |
+
user: Annotated[TokenData | None, Depends(get_current_user)],
|
| 313 |
+
) -> dict[str, Any]:
|
| 314 |
+
"""Logout the current user.
|
| 315 |
+
|
| 316 |
+
Since we use stateless JWTs, this doesn't invalidate the token server-side.
|
| 317 |
+
The frontend should delete the stored token.
|
| 318 |
+
|
| 319 |
+
In a production system, you might want to:
|
| 320 |
+
- Maintain a token blacklist
|
| 321 |
+
- Use short-lived tokens with refresh tokens
|
| 322 |
+
"""
|
| 323 |
+
return {"status": "ok", "message": "Logged out successfully"}
|
src/flow/ui/auth/tokens.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Microsoft. All rights reserved.
|
| 2 |
+
"""JWT token utilities for authentication."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import hashlib
|
| 7 |
+
import hmac
|
| 8 |
+
import json
|
| 9 |
+
import base64
|
| 10 |
+
from datetime import datetime, timedelta, timezone
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
+
from pydantic import BaseModel
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TokenData(BaseModel):
|
| 17 |
+
"""Data encoded in the JWT token."""
|
| 18 |
+
|
| 19 |
+
sub: str # Subject (username or GitHub ID)
|
| 20 |
+
username: str # Display name
|
| 21 |
+
auth_mode: str # "basic" or "github"
|
| 22 |
+
exp: datetime # Expiration time
|
| 23 |
+
iat: datetime # Issued at time
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class TokenError(Exception):
|
| 27 |
+
"""Token validation error."""
|
| 28 |
+
|
| 29 |
+
pass
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _base64url_encode(data: bytes) -> str:
|
| 33 |
+
"""Base64url encode without padding."""
|
| 34 |
+
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _base64url_decode(data: str) -> bytes:
|
| 38 |
+
"""Base64url decode with padding restoration."""
|
| 39 |
+
padding = 4 - len(data) % 4
|
| 40 |
+
if padding != 4:
|
| 41 |
+
data += "=" * padding
|
| 42 |
+
return base64.urlsafe_b64decode(data)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def create_access_token(
|
| 46 |
+
subject: str,
|
| 47 |
+
username: str,
|
| 48 |
+
auth_mode: str,
|
| 49 |
+
secret: str,
|
| 50 |
+
expires_hours: int = 24,
|
| 51 |
+
) -> tuple[str, datetime]:
|
| 52 |
+
"""Create a JWT access token.
|
| 53 |
+
|
| 54 |
+
Uses HMAC-SHA256 for signing (HS256 algorithm).
|
| 55 |
+
This is a simple JWT implementation without external dependencies.
|
| 56 |
+
|
| 57 |
+
Args:
|
| 58 |
+
subject: The subject identifier (username for basic, github_id for github)
|
| 59 |
+
username: Display username
|
| 60 |
+
auth_mode: Authentication mode used ("basic" or "github")
|
| 61 |
+
secret: Secret key for signing
|
| 62 |
+
expires_hours: Token validity in hours
|
| 63 |
+
|
| 64 |
+
Returns:
|
| 65 |
+
Tuple of (token string, expiration datetime)
|
| 66 |
+
"""
|
| 67 |
+
now = datetime.now(timezone.utc)
|
| 68 |
+
expires_at = now + timedelta(hours=expires_hours)
|
| 69 |
+
|
| 70 |
+
# JWT Header
|
| 71 |
+
header = {"alg": "HS256", "typ": "JWT"}
|
| 72 |
+
|
| 73 |
+
# JWT Payload
|
| 74 |
+
payload: dict[str, Any] = {
|
| 75 |
+
"sub": subject,
|
| 76 |
+
"username": username,
|
| 77 |
+
"auth_mode": auth_mode,
|
| 78 |
+
"exp": int(expires_at.timestamp()),
|
| 79 |
+
"iat": int(now.timestamp()),
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
# Encode header and payload
|
| 83 |
+
header_encoded = _base64url_encode(json.dumps(header, separators=(",", ":")).encode())
|
| 84 |
+
payload_encoded = _base64url_encode(json.dumps(payload, separators=(",", ":")).encode())
|
| 85 |
+
|
| 86 |
+
# Create signature
|
| 87 |
+
message = f"{header_encoded}.{payload_encoded}"
|
| 88 |
+
signature = hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
|
| 89 |
+
signature_encoded = _base64url_encode(signature)
|
| 90 |
+
|
| 91 |
+
# Combine all parts
|
| 92 |
+
token = f"{header_encoded}.{payload_encoded}.{signature_encoded}"
|
| 93 |
+
|
| 94 |
+
return token, expires_at
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def verify_access_token(token: str, secret: str) -> TokenData:
|
| 98 |
+
"""Verify and decode a JWT access token.
|
| 99 |
+
|
| 100 |
+
Args:
|
| 101 |
+
token: The JWT token string
|
| 102 |
+
secret: Secret key used for signing
|
| 103 |
+
|
| 104 |
+
Returns:
|
| 105 |
+
TokenData with decoded information
|
| 106 |
+
|
| 107 |
+
Raises:
|
| 108 |
+
TokenError: If token is invalid, expired, or signature doesn't match
|
| 109 |
+
"""
|
| 110 |
+
try:
|
| 111 |
+
parts = token.split(".")
|
| 112 |
+
if len(parts) != 3:
|
| 113 |
+
raise TokenError("Invalid token format")
|
| 114 |
+
|
| 115 |
+
header_encoded, payload_encoded, signature_encoded = parts
|
| 116 |
+
|
| 117 |
+
# Verify signature
|
| 118 |
+
message = f"{header_encoded}.{payload_encoded}"
|
| 119 |
+
expected_signature = hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
|
| 120 |
+
actual_signature = _base64url_decode(signature_encoded)
|
| 121 |
+
|
| 122 |
+
if not hmac.compare_digest(expected_signature, actual_signature):
|
| 123 |
+
raise TokenError("Invalid token signature")
|
| 124 |
+
|
| 125 |
+
# Decode payload
|
| 126 |
+
payload_json = _base64url_decode(payload_encoded).decode("utf-8")
|
| 127 |
+
payload = json.loads(payload_json)
|
| 128 |
+
|
| 129 |
+
# Check expiration
|
| 130 |
+
exp_timestamp = payload.get("exp")
|
| 131 |
+
if exp_timestamp is None:
|
| 132 |
+
raise TokenError("Token missing expiration")
|
| 133 |
+
|
| 134 |
+
exp_datetime = datetime.fromtimestamp(exp_timestamp, tz=timezone.utc)
|
| 135 |
+
if datetime.now(timezone.utc) > exp_datetime:
|
| 136 |
+
raise TokenError("Token has expired")
|
| 137 |
+
|
| 138 |
+
# Parse issued at time
|
| 139 |
+
iat_timestamp = payload.get("iat", exp_timestamp - 86400)
|
| 140 |
+
iat_datetime = datetime.fromtimestamp(iat_timestamp, tz=timezone.utc)
|
| 141 |
+
|
| 142 |
+
return TokenData(
|
| 143 |
+
sub=payload["sub"],
|
| 144 |
+
username=payload["username"],
|
| 145 |
+
auth_mode=payload["auth_mode"],
|
| 146 |
+
exp=exp_datetime,
|
| 147 |
+
iat=iat_datetime,
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
except TokenError:
|
| 151 |
+
raise
|
| 152 |
+
except Exception as e:
|
| 153 |
+
raise TokenError(f"Token validation failed: {e}") from e
|
src/flow/ui/database.py
CHANGED
|
@@ -21,6 +21,48 @@ engine = create_async_engine(DATABASE_URL, echo=False, future=True)
|
|
| 21 |
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
| 22 |
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
async def init_db() -> None:
|
| 25 |
"""Initialize database tables.
|
| 26 |
|
|
@@ -39,6 +81,8 @@ async def init_db() -> None:
|
|
| 39 |
try:
|
| 40 |
async with engine.begin() as conn:
|
| 41 |
await conn.run_sync(SQLModel.metadata.create_all)
|
|
|
|
|
|
|
| 42 |
except Exception as e:
|
| 43 |
# Handle race condition: "table already exists" is fine
|
| 44 |
if "already exists" in str(e).lower():
|
|
|
|
| 21 |
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
| 22 |
|
| 23 |
|
| 24 |
+
async def _migrate_schema(conn) -> None:
|
| 25 |
+
"""Apply schema migrations for new columns.
|
| 26 |
+
|
| 27 |
+
SQLModel's create_all only creates missing tables, not columns.
|
| 28 |
+
This adds any missing columns to existing tables.
|
| 29 |
+
"""
|
| 30 |
+
from sqlalchemy import text, inspect
|
| 31 |
+
|
| 32 |
+
def _sync_migrate(sync_conn):
|
| 33 |
+
inspector = inspect(sync_conn)
|
| 34 |
+
|
| 35 |
+
# Check agent_configs table
|
| 36 |
+
if inspector.has_table("agent_configs"):
|
| 37 |
+
columns = {c["name"] for c in inspector.get_columns("agent_configs")}
|
| 38 |
+
|
| 39 |
+
# Add is_auto_generated column if missing
|
| 40 |
+
if "is_auto_generated" not in columns:
|
| 41 |
+
logger.info("Adding is_auto_generated column to agent_configs")
|
| 42 |
+
sync_conn.execute(
|
| 43 |
+
text("ALTER TABLE agent_configs ADD COLUMN is_auto_generated BOOLEAN DEFAULT 0")
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
# Add job_id column if missing
|
| 47 |
+
if "job_id" not in columns:
|
| 48 |
+
logger.info("Adding job_id column to agent_configs")
|
| 49 |
+
sync_conn.execute(
|
| 50 |
+
text("ALTER TABLE agent_configs ADD COLUMN job_id VARCHAR(36)")
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
# Retroactively mark configs with "Auto-generated variation:" in description
|
| 54 |
+
logger.info("Marking auto-generated configs based on description pattern")
|
| 55 |
+
sync_conn.execute(
|
| 56 |
+
text(
|
| 57 |
+
"UPDATE agent_configs SET is_auto_generated = 1 "
|
| 58 |
+
"WHERE description LIKE 'Auto-generated variation:%' "
|
| 59 |
+
"AND (is_auto_generated IS NULL OR is_auto_generated = 0)"
|
| 60 |
+
)
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
await conn.run_sync(_sync_migrate)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
async def init_db() -> None:
|
| 67 |
"""Initialize database tables.
|
| 68 |
|
|
|
|
| 81 |
try:
|
| 82 |
async with engine.begin() as conn:
|
| 83 |
await conn.run_sync(SQLModel.metadata.create_all)
|
| 84 |
+
# Apply migrations for new columns
|
| 85 |
+
await _migrate_schema(conn)
|
| 86 |
except Exception as e:
|
| 87 |
# Handle race condition: "table already exists" is fine
|
| 88 |
if "already exists" in str(e).lower():
|
src/flow/ui/main.py
CHANGED
|
@@ -9,14 +9,32 @@ from fastapi import FastAPI
|
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
from fastapi.responses import FileResponse
|
|
|
|
| 12 |
|
| 13 |
from .database import init_db
|
| 14 |
from .api import configs_router, tasks_router, jobs_router, runs_router
|
|
|
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
@asynccontextmanager
|
| 18 |
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
| 19 |
-
"""Initialize database on startup."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
await init_db()
|
| 21 |
yield
|
| 22 |
|
|
@@ -28,7 +46,7 @@ app = FastAPI(
|
|
| 28 |
lifespan=lifespan,
|
| 29 |
)
|
| 30 |
|
| 31 |
-
# CORS for development
|
| 32 |
app.add_middleware(
|
| 33 |
CORSMiddleware,
|
| 34 |
allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
|
|
@@ -37,18 +55,29 @@ app.add_middleware(
|
|
| 37 |
allow_headers=["*"],
|
| 38 |
)
|
| 39 |
|
| 40 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
app.include_router(configs_router, prefix="/api")
|
| 42 |
app.include_router(tasks_router, prefix="/api")
|
| 43 |
app.include_router(jobs_router, prefix="/api")
|
| 44 |
app.include_router(runs_router, prefix="/api")
|
| 45 |
|
| 46 |
|
| 47 |
-
# Health check
|
| 48 |
@app.get("/api/health")
|
| 49 |
async def health_check() -> dict[str, Any]:
|
| 50 |
"""Health check endpoint."""
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
|
| 54 |
# Static files and SPA fallback
|
|
@@ -78,15 +107,41 @@ if UI_DIR.exists():
|
|
| 78 |
setup_static_files()
|
| 79 |
|
| 80 |
|
| 81 |
-
def run_server(
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
import uvicorn
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
uvicorn.run(
|
| 86 |
"flow.ui.main:app",
|
| 87 |
host=host,
|
| 88 |
port=port,
|
| 89 |
-
reload=
|
| 90 |
)
|
| 91 |
|
| 92 |
|
|
|
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
from fastapi.responses import FileResponse
|
| 12 |
+
from starlette.middleware.base import BaseHTTPMiddleware
|
| 13 |
|
| 14 |
from .database import init_db
|
| 15 |
from .api import configs_router, tasks_router, jobs_router, runs_router
|
| 16 |
+
from .auth import auth_router, AuthSettings, get_auth_settings, init_auth_settings
|
| 17 |
+
from .auth.middleware import AuthMiddleware
|
| 18 |
|
| 19 |
|
| 20 |
@asynccontextmanager
|
| 21 |
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
| 22 |
+
"""Initialize database and validate auth config on startup."""
|
| 23 |
+
# Validate auth configuration
|
| 24 |
+
settings = get_auth_settings()
|
| 25 |
+
errors = settings.validate_config()
|
| 26 |
+
if errors:
|
| 27 |
+
import sys
|
| 28 |
+
print("Authentication configuration errors:", file=sys.stderr)
|
| 29 |
+
for error in errors:
|
| 30 |
+
print(f" - {error}", file=sys.stderr)
|
| 31 |
+
if settings.enabled:
|
| 32 |
+
print("\nAuth is enabled but misconfigured. Exiting.", file=sys.stderr)
|
| 33 |
+
sys.exit(1)
|
| 34 |
+
|
| 35 |
+
if settings.enabled:
|
| 36 |
+
print(f"Authentication enabled (mode: {settings.mode.value})")
|
| 37 |
+
|
| 38 |
await init_db()
|
| 39 |
yield
|
| 40 |
|
|
|
|
| 46 |
lifespan=lifespan,
|
| 47 |
)
|
| 48 |
|
| 49 |
+
# CORS for development - include credentials for auth
|
| 50 |
app.add_middleware(
|
| 51 |
CORSMiddleware,
|
| 52 |
allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
|
|
|
|
| 55 |
allow_headers=["*"],
|
| 56 |
)
|
| 57 |
|
| 58 |
+
# Auth middleware - validates tokens on protected routes
|
| 59 |
+
# This is added AFTER CORS so CORS headers are set even on 401 responses
|
| 60 |
+
app.add_middleware(BaseHTTPMiddleware, dispatch=AuthMiddleware())
|
| 61 |
+
|
| 62 |
+
# API routes - Auth routes first (public endpoints)
|
| 63 |
+
app.include_router(auth_router, prefix="/api")
|
| 64 |
+
# Protected routes
|
| 65 |
app.include_router(configs_router, prefix="/api")
|
| 66 |
app.include_router(tasks_router, prefix="/api")
|
| 67 |
app.include_router(jobs_router, prefix="/api")
|
| 68 |
app.include_router(runs_router, prefix="/api")
|
| 69 |
|
| 70 |
|
| 71 |
+
# Health check (public, not protected by auth)
|
| 72 |
@app.get("/api/health")
|
| 73 |
async def health_check() -> dict[str, Any]:
|
| 74 |
"""Health check endpoint."""
|
| 75 |
+
settings = get_auth_settings()
|
| 76 |
+
return {
|
| 77 |
+
"status": "ok",
|
| 78 |
+
"service": "flow-ui",
|
| 79 |
+
"auth_enabled": settings.enabled,
|
| 80 |
+
}
|
| 81 |
|
| 82 |
|
| 83 |
# Static files and SPA fallback
|
|
|
|
| 107 |
setup_static_files()
|
| 108 |
|
| 109 |
|
| 110 |
+
def run_server(
|
| 111 |
+
host: str = "0.0.0.0", # noqa: S104
|
| 112 |
+
port: int = 8091,
|
| 113 |
+
reload: bool = False,
|
| 114 |
+
auth: bool | None = None,
|
| 115 |
+
auth_mode: str | None = None,
|
| 116 |
+
auth_password: str | None = None,
|
| 117 |
+
auth_username: str | None = None,
|
| 118 |
+
) -> None:
|
| 119 |
+
"""Run the FastAPI server.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
host: Host to bind to
|
| 123 |
+
port: Port to bind to
|
| 124 |
+
reload: Enable auto-reload for development
|
| 125 |
+
auth: Enable authentication (overrides AUTH_ENABLED env var)
|
| 126 |
+
auth_mode: Auth mode - "basic" or "github" (overrides AUTH_MODE env var)
|
| 127 |
+
auth_password: Password for basic auth (overrides AUTH_BASIC_PASSWORD env var)
|
| 128 |
+
auth_username: Username for basic auth (overrides AUTH_BASIC_USERNAME env var)
|
| 129 |
+
"""
|
| 130 |
import uvicorn
|
| 131 |
|
| 132 |
+
# Initialize auth settings with CLI overrides
|
| 133 |
+
init_auth_settings(
|
| 134 |
+
enabled=auth,
|
| 135 |
+
mode=auth_mode,
|
| 136 |
+
basic_username=auth_username,
|
| 137 |
+
basic_password=auth_password,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
uvicorn.run(
|
| 141 |
"flow.ui.main:app",
|
| 142 |
host=host,
|
| 143 |
port=port,
|
| 144 |
+
reload=reload,
|
| 145 |
)
|
| 146 |
|
| 147 |
|
src/flow/ui/models/config.py
CHANGED
|
@@ -20,6 +20,11 @@ class AgentConfig(SQLModel, table=True):
|
|
| 20 |
# Store AblationConfig as JSON
|
| 21 |
config_json: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
| 24 |
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
| 25 |
|
|
|
|
| 20 |
# Store AblationConfig as JSON
|
| 21 |
config_json: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
| 22 |
|
| 23 |
+
# Track auto-generated configs (created by variation endpoint)
|
| 24 |
+
is_auto_generated: bool = Field(default=False, index=True)
|
| 25 |
+
# Link to the job that created this config (if auto-generated)
|
| 26 |
+
job_id: UUID | None = Field(default=None, index=True)
|
| 27 |
+
|
| 28 |
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
| 29 |
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
| 30 |
|
src/flow/ui/schemas/config.py
CHANGED
|
@@ -55,6 +55,8 @@ class ConfigResponse(BaseModel):
|
|
| 55 |
name: str
|
| 56 |
description: str
|
| 57 |
config: dict[str, Any]
|
|
|
|
|
|
|
| 58 |
created_at: datetime
|
| 59 |
updated_at: datetime
|
| 60 |
|
|
@@ -65,3 +67,13 @@ class ConfigResponse(BaseModel):
|
|
| 65 |
if isinstance(v, UUID):
|
| 66 |
return str(v)
|
| 67 |
return v
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
name: str
|
| 56 |
description: str
|
| 57 |
config: dict[str, Any]
|
| 58 |
+
is_auto_generated: bool = False
|
| 59 |
+
job_id: str | None = None
|
| 60 |
created_at: datetime
|
| 61 |
updated_at: datetime
|
| 62 |
|
|
|
|
| 67 |
if isinstance(v, UUID):
|
| 68 |
return str(v)
|
| 69 |
return v
|
| 70 |
+
|
| 71 |
+
@field_validator("job_id", mode="before")
|
| 72 |
+
@classmethod
|
| 73 |
+
def convert_job_uuid(cls, v: UUID | str | None) -> str | None:
|
| 74 |
+
"""Convert job UUID to string."""
|
| 75 |
+
if v is None:
|
| 76 |
+
return None
|
| 77 |
+
if isinstance(v, UUID):
|
| 78 |
+
return str(v)
|
| 79 |
+
return v
|
src/flow/ui/services/optimizer_service.py
CHANGED
|
@@ -129,6 +129,21 @@ class OptimizerService:
|
|
| 129 |
# Get final result - this will re-raise any exception from the task
|
| 130 |
opt_result = await opt_task
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
# Save runs to database
|
| 133 |
for summary in opt_result.summaries:
|
| 134 |
for task_result in summary.task_results:
|
|
@@ -149,6 +164,7 @@ class OptimizerService:
|
|
| 149 |
trace_json={
|
| 150 |
"success": task_result.run_result.success,
|
| 151 |
"error": task_result.run_result.error,
|
|
|
|
| 152 |
},
|
| 153 |
is_pareto=summary.is_pareto_optimal,
|
| 154 |
pareto_rank=summary.pareto_rank or 0,
|
|
@@ -174,6 +190,7 @@ class OptimizerService:
|
|
| 174 |
except Exception as e:
|
| 175 |
job.status = JobStatus.FAILED
|
| 176 |
job.error = str(e)
|
|
|
|
| 177 |
await session.commit()
|
| 178 |
|
| 179 |
yield JobProgress(
|
|
|
|
| 129 |
# Get final result - this will re-raise any exception from the task
|
| 130 |
opt_result = await opt_task
|
| 131 |
|
| 132 |
+
# Check if all experiments failed
|
| 133 |
+
if opt_result.total_experiments == 0 or len(opt_result.summaries) == 0:
|
| 134 |
+
# No successful experiments - this is a failure
|
| 135 |
+
job.status = JobStatus.FAILED
|
| 136 |
+
job.error = "All experiments failed. Check server logs for details. Common causes: missing API keys (AZURE_OPENAI_ENDPOINT, OPENAI_API_KEY), invalid configuration."
|
| 137 |
+
job.completed_at = datetime.now(timezone.utc)
|
| 138 |
+
await session.commit()
|
| 139 |
+
|
| 140 |
+
yield JobProgress(
|
| 141 |
+
event="error",
|
| 142 |
+
job_id=str(job_id),
|
| 143 |
+
message="All experiments failed. Check server logs for details.",
|
| 144 |
+
)
|
| 145 |
+
return
|
| 146 |
+
|
| 147 |
# Save runs to database
|
| 148 |
for summary in opt_result.summaries:
|
| 149 |
for task_result in summary.task_results:
|
|
|
|
| 164 |
trace_json={
|
| 165 |
"success": task_result.run_result.success,
|
| 166 |
"error": task_result.run_result.error,
|
| 167 |
+
"spans": task_result.run_result.trace or [],
|
| 168 |
},
|
| 169 |
is_pareto=summary.is_pareto_optimal,
|
| 170 |
pareto_rank=summary.pareto_rank or 0,
|
|
|
|
| 190 |
except Exception as e:
|
| 191 |
job.status = JobStatus.FAILED
|
| 192 |
job.error = str(e)
|
| 193 |
+
job.completed_at = datetime.now(timezone.utc)
|
| 194 |
await session.commit()
|
| 195 |
|
| 196 |
yield JobProgress(
|
src/flow/ui/tests/test_e2e_user_journey.py
CHANGED
|
@@ -525,6 +525,132 @@ class TestAPIEndpoints:
|
|
| 525 |
|
| 526 |
print("\n Job start endpoint test passed!")
|
| 527 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
|
| 529 |
if __name__ == "__main__":
|
| 530 |
# Run tests directly
|
|
|
|
| 525 |
|
| 526 |
print("\n Job start endpoint test passed!")
|
| 527 |
|
| 528 |
+
@pytest.mark.asyncio
|
| 529 |
+
async def test_import_suite_idempotent(self, client: httpx.AsyncClient):
|
| 530 |
+
"""Test that importing the same suite twice doesn't create duplicates."""
|
| 531 |
+
print("\n[Import Suite Idempotency Test]")
|
| 532 |
+
|
| 533 |
+
# First import
|
| 534 |
+
resp1 = await client.post("/api/tasks/import-suite", params={"suite_name": "quick"})
|
| 535 |
+
assert resp1.status_code == 201
|
| 536 |
+
tasks1 = resp1.json()
|
| 537 |
+
task_ids1 = {t["id"] for t in tasks1}
|
| 538 |
+
print(f" ✓ First import: {len(tasks1)} tasks")
|
| 539 |
+
|
| 540 |
+
# Second import (should be idempotent)
|
| 541 |
+
resp2 = await client.post("/api/tasks/import-suite", params={"suite_name": "quick"})
|
| 542 |
+
assert resp2.status_code == 201
|
| 543 |
+
tasks2 = resp2.json()
|
| 544 |
+
task_ids2 = {t["id"] for t in tasks2}
|
| 545 |
+
print(f" ✓ Second import: {len(tasks2)} tasks")
|
| 546 |
+
|
| 547 |
+
# Should return same tasks (same IDs)
|
| 548 |
+
assert task_ids1 == task_ids2, "Import should be idempotent - same task IDs expected"
|
| 549 |
+
print(f" ✓ Same task IDs returned (idempotent)")
|
| 550 |
+
|
| 551 |
+
# Count tasks in DB to verify no duplicates
|
| 552 |
+
list_resp = await client.get("/api/tasks", params={"suite": "quick"})
|
| 553 |
+
assert list_resp.status_code == 200
|
| 554 |
+
all_quick_tasks = list_resp.json()
|
| 555 |
+
|
| 556 |
+
# Should have exactly 3 tasks (the suite size), not 6 (duplicates)
|
| 557 |
+
# Note: there may be duplicates from before this fix, so just check IDs match
|
| 558 |
+
quick_task_ids = {t["id"] for t in all_quick_tasks if t["suite"] == "quick"}
|
| 559 |
+
assert task_ids1.issubset(quick_task_ids), "Imported task IDs should exist in DB"
|
| 560 |
+
print(f" ✓ No new duplicates created")
|
| 561 |
+
|
| 562 |
+
# Cleanup - delete the tasks we imported
|
| 563 |
+
for task_id in task_ids1:
|
| 564 |
+
await client.delete(f"/api/tasks/{task_id}")
|
| 565 |
+
print(f" ✓ Cleaned up {len(task_ids1)} tasks")
|
| 566 |
+
|
| 567 |
+
print("\n Import suite idempotency test passed!")
|
| 568 |
+
|
| 569 |
+
@pytest.mark.asyncio
|
| 570 |
+
async def test_job_reset_endpoint(self, client: httpx.AsyncClient):
|
| 571 |
+
"""Test that we can reset a stuck/failed job back to pending."""
|
| 572 |
+
print("\n[Job Reset Endpoint Test]")
|
| 573 |
+
|
| 574 |
+
# Create config
|
| 575 |
+
config_resp = await client.post(
|
| 576 |
+
"/api/configs",
|
| 577 |
+
json={"name": "reset-test-config"},
|
| 578 |
+
)
|
| 579 |
+
assert config_resp.status_code == 201
|
| 580 |
+
config = config_resp.json()
|
| 581 |
+
|
| 582 |
+
# Create task
|
| 583 |
+
task_resp = await client.post(
|
| 584 |
+
"/api/tasks",
|
| 585 |
+
json={"name": "reset-test-task", "prompt": "Print hello"},
|
| 586 |
+
)
|
| 587 |
+
assert task_resp.status_code == 201
|
| 588 |
+
task = task_resp.json()
|
| 589 |
+
|
| 590 |
+
try:
|
| 591 |
+
# Create job
|
| 592 |
+
job_resp = await client.post(
|
| 593 |
+
"/api/jobs",
|
| 594 |
+
json={
|
| 595 |
+
"name": "reset-test-job",
|
| 596 |
+
"config_ids": [config["id"]],
|
| 597 |
+
"task_ids": [task["id"]],
|
| 598 |
+
},
|
| 599 |
+
)
|
| 600 |
+
assert job_resp.status_code == 201
|
| 601 |
+
job = job_resp.json()
|
| 602 |
+
job_id = job["id"]
|
| 603 |
+
print(f" ✓ Created job: {job_id[:8]}...")
|
| 604 |
+
|
| 605 |
+
# Try to reset a pending job - should fail
|
| 606 |
+
reset_resp = await client.post(f"/api/jobs/{job_id}/reset")
|
| 607 |
+
assert reset_resp.status_code == 400
|
| 608 |
+
print(f" ✓ Cannot reset pending job (expected)")
|
| 609 |
+
|
| 610 |
+
# Start the job (will likely fail but status changes)
|
| 611 |
+
await client.post(f"/api/jobs/{job_id}/start")
|
| 612 |
+
|
| 613 |
+
# Check status
|
| 614 |
+
status_resp = await client.get(f"/api/jobs/{job_id}")
|
| 615 |
+
job_status = status_resp.json()["status"]
|
| 616 |
+
print(f" ✓ Job status after start: {job_status}")
|
| 617 |
+
|
| 618 |
+
# If job is now running or failed, we can reset it
|
| 619 |
+
if job_status in ["running", "failed"]:
|
| 620 |
+
reset_resp = await client.post(f"/api/jobs/{job_id}/reset")
|
| 621 |
+
assert reset_resp.status_code == 200
|
| 622 |
+
reset_job = reset_resp.json()
|
| 623 |
+
assert reset_job["status"] == "pending"
|
| 624 |
+
assert reset_job["started_at"] is None
|
| 625 |
+
assert reset_job["completed_experiments"] == 0
|
| 626 |
+
print(f" ✓ Job reset to pending successfully")
|
| 627 |
+
|
| 628 |
+
# Cleanup
|
| 629 |
+
await client.delete(f"/api/jobs/{job_id}")
|
| 630 |
+
print(f" ✓ Deleted job")
|
| 631 |
+
|
| 632 |
+
finally:
|
| 633 |
+
await client.delete(f"/api/tasks/{task['id']}")
|
| 634 |
+
await client.delete(f"/api/configs/{config['id']}")
|
| 635 |
+
print(f" ✓ Cleaned up config and task")
|
| 636 |
+
|
| 637 |
+
print("\n Job reset endpoint test passed!")
|
| 638 |
+
|
| 639 |
+
@pytest.mark.asyncio
|
| 640 |
+
async def test_job_cleanup_stuck_endpoint(self, client: httpx.AsyncClient):
|
| 641 |
+
"""Test the cleanup-stuck endpoint for marking old running jobs as failed."""
|
| 642 |
+
print("\n[Job Cleanup Stuck Endpoint Test]")
|
| 643 |
+
|
| 644 |
+
# Call cleanup with default parameters
|
| 645 |
+
# This won't find anything in a fresh test, but verifies the endpoint works
|
| 646 |
+
resp = await client.post("/api/jobs/cleanup-stuck", params={"max_age_hours": 2})
|
| 647 |
+
assert resp.status_code == 200
|
| 648 |
+
stuck_jobs = resp.json()
|
| 649 |
+
assert isinstance(stuck_jobs, list)
|
| 650 |
+
print(f" ✓ Cleanup endpoint returned {len(stuck_jobs)} stuck jobs")
|
| 651 |
+
|
| 652 |
+
print("\n Job cleanup stuck endpoint test passed!")
|
| 653 |
+
|
| 654 |
|
| 655 |
if __name__ == "__main__":
|
| 656 |
# Run tests directly
|
src/flow/ui/ui/assets/index-AqV2bzyn.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-AwuECPjC.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-B08BtjW2.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-B0QnqJdr.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-BFk_2IKX.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-BI-UHUHQ.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-BMvcolSw.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-0\.5{left:.125rem}.left-3{left:.75rem}.left-\[calc\(100\%-26px\)\]{left:calc(100% - 26px)}.top-0{top:0}.top-0\.5{top:.125rem}.top-1\/2{top:50%}.z-10{z-index:10}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-14{width:3.5rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.opacity-0{opacity:0}.opacity-40{opacity:.4}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f7f8f9;--bg-tertiary: #eef0f2;--text-primary: #1a1a1a;--text-secondary: #4a4a4a;--accent: #16a34a;--accent-dim: #dcfce7;--border: #d1d5db;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:border-\[var\(--text-secondary\)\]:hover{border-color:var(--text-secondary)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-\[var\(--bg-primary\)\]:focus{--tw-ring-offset-color: var(--bg-primary)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}}
|
src/flow/ui/ui/assets/index-BZQeHPg9.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-0\.5{left:.125rem}.left-3{left:.75rem}.left-\[calc\(100\%-26px\)\]{left:calc(100% - 26px)}.top-0{top:0}.top-0\.5{top:.125rem}.top-1\/2{top:50%}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-14{width:3.5rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.opacity-0{opacity:0}.opacity-40{opacity:.4}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f7f8f9;--bg-tertiary: #eef0f2;--text-primary: #1a1a1a;--text-secondary: #4a4a4a;--accent: #16a34a;--accent-dim: #dcfce7;--border: #d1d5db;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:border-\[var\(--text-secondary\)\]:hover{border-color:var(--text-secondary)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-\[var\(--bg-primary\)\]:focus{--tw-ring-offset-color: var(--bg-primary)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}}
|
src/flow/ui/ui/assets/index-BcDpPA04.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-BvMZNuv9.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-C4qbqk6k.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-0\.5{left:.125rem}.left-3{left:.75rem}.left-\[calc\(100\%-26px\)\]{left:calc(100% - 26px)}.top-0{top:0}.top-0\.5{top:.125rem}.top-1\/2{top:50%}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-14{width:3.5rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.opacity-0{opacity:0}.opacity-40{opacity:.4}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f7f8f9;--bg-tertiary: #eef0f2;--text-primary: #1a1a1a;--text-secondary: #4a4a4a;--accent: #16a34a;--accent-dim: #dcfce7;--border: #d1d5db;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:border-\[var\(--text-secondary\)\]:hover{border-color:var(--text-secondary)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-\[var\(--bg-primary\)\]:focus{--tw-ring-offset-color: var(--bg-primary)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}}
|
src/flow/ui/ui/assets/index-CJQ5Um8o.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-0\.5{left:.125rem}.left-3{left:.75rem}.left-\[calc\(100\%-26px\)\]{left:calc(100% - 26px)}.top-0{top:0}.top-0\.5{top:.125rem}.top-1\/2{top:50%}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-14{width:3.5rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.opacity-0{opacity:0}.opacity-40{opacity:.4}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f7f8f9;--bg-tertiary: #eef0f2;--text-primary: #1a1a1a;--text-secondary: #4a4a4a;--accent: #16a34a;--accent-dim: #dcfce7;--border: #d1d5db;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:border-\[var\(--text-secondary\)\]:hover{border-color:var(--text-secondary)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-\[var\(--bg-primary\)\]:focus{--tw-ring-offset-color: var(--bg-primary)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}}
|
src/flow/ui/ui/assets/index-CcMPwNeP.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-CgNfNUQi.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-CpjX3LAH.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-0\.5{left:.125rem}.left-3{left:.75rem}.left-\[calc\(100\%-26px\)\]{left:calc(100% - 26px)}.top-0{top:0}.top-0\.5{top:.125rem}.top-1\/2{top:50%}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-14{width:3.5rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.opacity-0{opacity:0}.opacity-40{opacity:.4}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f7f8f9;--bg-tertiary: #eef0f2;--text-primary: #1a1a1a;--text-secondary: #4a4a4a;--accent: #16a34a;--accent-dim: #dcfce7;--border: #d1d5db;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:border-\[var\(--text-secondary\)\]:hover{border-color:var(--text-secondary)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-\[var\(--bg-primary\)\]:focus{--tw-ring-offset-color: var(--bg-primary)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}}
|
src/flow/ui/ui/assets/index-D0u7uw0T.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-DlCyCyh_.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-3{left:.75rem}.top-0{top:0}.top-1\/2{top:50%}.z-10{z-index:10}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f7f8f9;--bg-tertiary: #eef0f2;--text-primary: #1a1a1a;--text-secondary: #4a4a4a;--accent: #16a34a;--accent-dim: #dcfce7;--border: #d1d5db;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}}
|
src/flow/ui/ui/assets/index-DpPRF4Ru.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-3{left:.75rem}.top-0{top:0}.top-1\/2{top:50%}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-900\/50{background-color:#1e3a8a80}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-900\/50{background-color:#14532d80}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-900\/50{background-color:#7f1d1d80}.bg-yellow-900\/50{background-color:#713f1280}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}}
|
src/flow/ui/ui/assets/index-FbRR3o1w.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-0\.5{left:.125rem}.left-3{left:.75rem}.left-\[calc\(100\%-26px\)\]{left:calc(100% - 26px)}.top-0{top:0}.top-0\.5{top:.125rem}.top-1\/2{top:50%}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-14{width:3.5rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.opacity-0{opacity:0}.opacity-40{opacity:.4}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f7f8f9;--bg-tertiary: #eef0f2;--text-primary: #1a1a1a;--text-secondary: #4a4a4a;--accent: #16a34a;--accent-dim: #dcfce7;--border: #d1d5db;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:border-\[var\(--text-secondary\)\]:hover{border-color:var(--text-secondary)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-\[var\(--bg-primary\)\]:focus{--tw-ring-offset-color: var(--bg-primary)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}}
|
src/flow/ui/ui/assets/index-I6VIoTE8.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/flow/ui/ui/assets/index-k-MesJnS.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-0\.5{left:.125rem}.left-3{left:.75rem}.left-\[calc\(100\%-26px\)\]{left:calc(100% - 26px)}.top-0{top:0}.top-0\.5{top:.125rem}.top-1\/2{top:50%}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[80vh\]{max-height:80vh}.min-h-\[100px\]{min-height:100px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-14{width:3.5rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[90px\]{min-width:90px}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-blue-500\/30{border-color:#3b82f64d}.border-green-500\/30{border-color:#22c55e4d}.border-red-500\/30{border-color:#ef44444d}.border-red-500\/50{border-color:#ef444480}.bg-\[var\(--accent\)\]{background-color:var(--accent)}.bg-\[var\(--bg-primary\)\]{background-color:var(--bg-primary)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-\[var\(--bg-tertiary\)\]{background-color:var(--bg-tertiary)}.bg-\[var\(--error\)\]{background-color:var(--error)}.bg-black\/80{background-color:#000c}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-900\/50{background-color:#1e3a8a80}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-900\/50{background-color:#14532d80}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-900\/50{background-color:#7f1d1d80}.bg-yellow-900\/50{background-color:#713f1280}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-10{padding-left:2.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--error\)\]{color:var(--error)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-\[var\(--text-tertiary\)\]{color:var(--text-tertiary)}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-800{--tw-text-opacity: 1;color:rgb(22 101 52 / var(--tw-text-opacity, 1))}.text-orange-800{--tw-text-opacity: 1;color:rgb(154 52 18 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-purple-800{--tw-text-opacity: 1;color:rgb(107 33 168 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.accent-\[var\(--accent\)\]{accent-color:var(--accent)}.opacity-0{opacity:0}.opacity-40{opacity:.4}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}:root{--bg-primary: #0a0a0a;--bg-secondary: #141414;--bg-tertiary: #1a1a1a;--text-primary: #f5f5f5;--text-secondary: #a3a3a3;--accent: #22c55e;--accent-dim: #166534;--border: #262626;--error: #ef4444}[data-theme=light]{--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-tertiary: #f1f3f4;--text-primary: #1a1a1a;--text-secondary: #5f6368;--accent: #16a34a;--accent-dim: #bbf7d0;--border: #e0e0e0;--error: #dc2626}*{box-sizing:border-box}body{margin:0;background-color:var(--bg-primary);color:var(--text-primary);font-family:JetBrains Mono,ui-monospace,monospace;font-size:14px;line-height:1.6}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#404040}[data-theme=light] ::-webkit-scrollbar-thumb:hover{background:silver}.last\:border-0:last-child{border-width:0px}.hover\:border-\[var\(--accent-dim\)\]:hover{border-color:var(--accent-dim)}.hover\:border-\[var\(--text-secondary\)\]:hover{border-color:var(--text-secondary)}.hover\:bg-\[\#16a34a\]:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.hover\:bg-\[var\(--bg-primary\)\]:hover{background-color:var(--bg-primary)}.hover\:bg-\[var\(--bg-tertiary\)\]:hover{background-color:var(--bg-tertiary)}.hover\:bg-\[var\(--border\)\]:hover{background-color:var(--border)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:text-\[var\(--accent\)\]:hover{color:var(--accent)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:opacity-80:hover{opacity:.8}.focus\:border-\[var\(--accent\)\]:focus{border-color:var(--accent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[var\(--accent\)\]:focus{--tw-ring-color: var(--accent)}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-\[var\(--bg-primary\)\]:focus{--tw-ring-offset-color: var(--bg-primary)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme: dark){.dark\:bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.dark\:bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-orange-900{--tw-bg-opacity: 1;background-color:rgb(124 45 18 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.dark\:text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.dark\:text-orange-200{--tw-text-opacity: 1;color:rgb(254 215 170 / var(--tw-text-opacity, 1))}.dark\:text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.dark\:text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}}
|
src/flow/ui/ui/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
| 11 |
-
<script type="module" crossorigin src="/assets/index-
|
| 12 |
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
| 13 |
</head>
|
| 14 |
<body>
|
| 15 |
<div id="root"></div>
|
|
|
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
| 11 |
+
<script type="module" crossorigin src="/assets/index-BFk_2IKX.js"></script>
|
| 12 |
+
<link rel="stylesheet" crossorigin href="/assets/index-DlCyCyh_.css">
|
| 13 |
</head>
|
| 14 |
<body>
|
| 15 |
<div id="root"></div>
|