victordibia commited on
Commit
c1ec9a0
·
1 Parent(s): 87372fb

Deploy 2026-01-27 14:43:06

Browse files
Files changed (43) hide show
  1. README.md +57 -130
  2. src/flow/cli/app.py +56 -1
  3. src/flow/experiments/data/tasks/coding.jsonl +10 -0
  4. src/flow/experiments/data/tasks/core.jsonl +5 -0
  5. src/flow/experiments/data/tasks/quick.jsonl +3 -0
  6. src/flow/experiments/optimizer.py +3 -24
  7. src/flow/experiments/types.py +69 -146
  8. src/flow/ui/api/configs.py +167 -3
  9. src/flow/ui/api/jobs.py +164 -6
  10. src/flow/ui/api/tasks.py +30 -12
  11. src/flow/ui/auth/__init__.py +20 -0
  12. src/flow/ui/auth/config.py +155 -0
  13. src/flow/ui/auth/middleware.py +175 -0
  14. src/flow/ui/auth/router.py +323 -0
  15. src/flow/ui/auth/tokens.py +153 -0
  16. src/flow/ui/database.py +44 -0
  17. src/flow/ui/main.py +63 -8
  18. src/flow/ui/models/config.py +5 -0
  19. src/flow/ui/schemas/config.py +12 -0
  20. src/flow/ui/services/optimizer_service.py +17 -0
  21. src/flow/ui/tests/test_e2e_user_journey.py +126 -0
  22. src/flow/ui/ui/assets/index-AqV2bzyn.js +0 -0
  23. src/flow/ui/ui/assets/index-AwuECPjC.js +0 -0
  24. src/flow/ui/ui/assets/index-B08BtjW2.js +0 -0
  25. src/flow/ui/ui/assets/index-B0QnqJdr.js +0 -0
  26. src/flow/ui/ui/assets/index-BFk_2IKX.js +0 -0
  27. src/flow/ui/ui/assets/index-BI-UHUHQ.js +0 -0
  28. src/flow/ui/ui/assets/index-BMvcolSw.css +1 -0
  29. src/flow/ui/ui/assets/index-BZQeHPg9.css +1 -0
  30. src/flow/ui/ui/assets/index-BcDpPA04.js +0 -0
  31. src/flow/ui/ui/assets/index-BvMZNuv9.js +0 -0
  32. src/flow/ui/ui/assets/index-C4qbqk6k.css +1 -0
  33. src/flow/ui/ui/assets/index-CJQ5Um8o.css +1 -0
  34. src/flow/ui/ui/assets/index-CcMPwNeP.js +0 -0
  35. src/flow/ui/ui/assets/index-CgNfNUQi.js +0 -0
  36. src/flow/ui/ui/assets/index-CpjX3LAH.css +1 -0
  37. src/flow/ui/ui/assets/index-D0u7uw0T.js +0 -0
  38. src/flow/ui/ui/assets/index-DlCyCyh_.css +1 -0
  39. src/flow/ui/ui/assets/index-DpPRF4Ru.css +1 -0
  40. src/flow/ui/ui/assets/index-FbRR3o1w.css +1 -0
  41. src/flow/ui/ui/assets/index-I6VIoTE8.js +0 -0
  42. src/flow/ui/ui/assets/index-k-MesJnS.css +1 -0
  43. src/flow/ui/ui/index.html +2 -2
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Flow - Autonomous Coding Agent
3
  emoji: 🔄
4
  colorFrom: blue
5
  colorTo: purple
@@ -10,37 +10,32 @@ pinned: false
10
 
11
  # Flow
12
 
13
- **Autonomous Coding Agent with a Polished CLI**
14
 
15
- Flow is a standalone coding agent that can read, write, and execute code autonomously. It features a clean CLI interface similar to Claude Code, with support for multiple agent runtime harnesses.
16
 
17
- ## Features
18
 
19
- - **Autonomous Execution**: Flow doesn't just tell you what to do—it does it. Write code, run tests, fix errors, iterate.
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
- ## Installation
 
 
 
 
26
 
27
- ```bash
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
- # With all optional features
35
- pip install flow-agent[all]
36
 
37
- # Development installation
38
- pip install flow-agent[dev]
 
 
 
39
  ```
40
 
41
- ## Quick Start
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
- ### 2. Initialize Flow
52
 
53
  ```bash
54
- flow init
 
 
 
 
55
  ```
56
 
57
- ### 3. Run a Task
58
 
59
  ```bash
60
- # Single task
61
- flow run "Create a Python script that calculates fibonacci numbers"
62
-
63
- # Interactive mode
64
- flow run -i
65
  ```
66
 
67
  ## CLI Commands
68
 
69
  ```bash
70
- flow run [TASK] # Run a task or start interactive mode
71
- flow config # Show current configuration
72
- flow init # Initialize Flow directories
73
- flow --help # Show help
 
74
  ```
75
 
76
- ## Usage as a Library
77
-
78
- ```python
79
- import asyncio
80
- from flow import FlowAgent
81
-
82
- async def main():
83
- agent = FlowAgent()
84
 
85
- # Run a task
86
- response = await agent.run("Create a hello world script")
87
- print(response)
88
 
89
- # Stream events
90
- async for event in agent.run_stream("List files in the workspace"):
91
- print(event.type, event.content)
 
 
92
 
93
- await agent.close()
94
-
95
- asyncio.run(main())
96
- ```
97
 
98
- ## Configuration
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
- ~/.flow/
117
- ├── workspace/ # Agent's working directory
118
- ├── memory/ # Persistent memory storage
119
- │ ├── patterns/ # Reusable code patterns
120
- │ ├── projects/ # Per-project notes
121
- │ └── decisions/ # Architecture decisions
122
- └── skills/ # Domain-specific expertise
123
  ```
124
 
125
- ## Architecture
126
 
127
- ### Harness System
128
 
129
- Flow uses a harness abstraction to support multiple agent runtimes:
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
- # Clone the repository
175
- git clone https://github.com/victordibia/flow
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
+ ![Flow UI](docs/flow.png)
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
- console.print(f"\n[bold blue]Flow UI[/] starting on [cyan]http://{host}:{port}[/]\n")
 
 
 
 
 
 
 
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
- tasks = []
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 tasks
 
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 for Optimization
106
  # =============================================================================
107
 
108
- TASK_SUITES: dict[str, list[Task]] = {
109
- "quick": [
110
- Task(
111
- name="fizzbuzz",
112
- prompt="Create a Python file fizzbuzz.py that prints FizzBuzz from 1-100. Then run it.",
113
- criteria=[
114
- EvalCriterion(name="file_created", instruction="fizzbuzz.py file was created"),
115
- EvalCriterion(name="correct_output", instruction="Output shows correct FizzBuzz pattern"),
116
- ],
117
- metadata={"category": "short", "expected_duration": 60},
118
- ),
119
- Task(
120
- name="hello_api",
121
- prompt="Create a FastAPI app in api.py with a /hello endpoint that returns {'message': 'hello'}.",
122
- criteria=[
123
- EvalCriterion(name="file_created", instruction="api.py file was created"),
124
- EvalCriterion(name="has_endpoint", instruction="Contains a /hello GET endpoint"),
125
- ],
126
- metadata={"category": "short", "expected_duration": 90},
127
- ),
128
- Task(
129
- name="file_counter",
130
- prompt="Create a Python script count_files.py that counts .py files in current directory and prints the count.",
131
- criteria=[
132
- EvalCriterion(name="file_created", instruction="count_files.py was created"),
133
- EvalCriterion(name="runs_correctly", instruction="Script runs and outputs a number"),
134
- ],
135
- metadata={"category": "short", "expected_duration": 60},
136
- ),
137
- ],
138
- "core": [
139
- Task(
140
- name="fizzbuzz",
141
- prompt="Create a Python file fizzbuzz.py that prints FizzBuzz from 1-100. Then run it.",
142
- criteria=[
143
- EvalCriterion(name="file_created", instruction="fizzbuzz.py file was created"),
144
- EvalCriterion(name="correct_output", instruction="Output shows correct FizzBuzz pattern"),
145
- ],
146
- metadata={"category": "short"},
147
- ),
148
- Task(
149
- name="rest_api",
150
- prompt="Create a FastAPI app with CRUD endpoints for a TODO list (in-memory storage). Include GET /todos, POST /todos, DELETE /todos/{id}.",
151
- criteria=[
152
- EvalCriterion(name="file_created", instruction="API file was created"),
153
- EvalCriterion(name="has_crud", instruction="Contains GET, POST, DELETE endpoints"),
154
- ],
155
- metadata={"category": "medium"},
156
- ),
157
- Task(
158
- name="data_analysis",
159
- prompt="Create a Python script that generates 100 random data points, calculates mean/median/std, and saves results to stats.json.",
160
- criteria=[
161
- EvalCriterion(name="script_created", instruction="Python script was created"),
162
- EvalCriterion(name="json_output", instruction="stats.json was created with results"),
163
- ],
164
- metadata={"category": "medium"},
165
- ),
166
- Task(
167
- name="cli_tool",
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
- if suite_name not in TASK_SUITES:
264
- available = ", ".join(TASK_SUITES.keys())
 
265
  raise ValueError(f"Unknown suite '{suite_name}'. Available: {available}")
266
- return TASK_SUITES[suite_name]
 
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(session: AsyncSession = Depends(get_session)) -> list[AgentConfig]:
27
- """List all agent configurations."""
28
- result = await session.execute(select(AgentConfig).order_by(desc(AgentConfig.created_at)))
 
 
 
 
 
 
 
 
 
 
 
 
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
- service = OptimizerService()
113
- async for progress in service.run_job(job_id):
114
- yield f"data: {progress.model_dump_json()}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- created_tasks = []
104
  for t in suite_tasks:
105
- task = TaskModel(
106
- name=t.name,
107
- prompt=t.prompt,
108
- criteria_json=[{"name": c.name, "instruction": c.instruction, "weight": c.weight} for c in t.criteria],
109
- category=t.metadata.get("category", "default"),
110
- suite=suite_name,
111
  )
112
- session.add(task)
113
- created_tasks.append(task)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  await session.commit()
116
- for task in created_tasks:
 
117
  await session.refresh(task)
118
 
119
- return created_tasks
 
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
- # API routes
 
 
 
 
 
 
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
- return {"status": "ok", "service": "flow-ui"}
 
 
 
 
 
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(host: str = "0.0.0.0", port: int = 8091) -> None: # noqa: S104
82
- """Run the FastAPI server."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  import uvicorn
84
 
 
 
 
 
 
 
 
 
85
  uvicorn.run(
86
  "flow.ui.main:app",
87
  host=host,
88
  port=port,
89
- reload=False,
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-22uP37s1.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-C9mp4cep.css">
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>