diff --git a/.gitignore b/.gitignore index bcbeb2b0e..8dd8d997a 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,9 @@ datasUFO # Ignore mkdocs build output documents/site/ +# Ignore frontend environment files with auto-generated content +galaxy/webui/frontend/.env.development.local + # Ignore config files with sensitive data (API keys) config/*/agents.yaml config/*/agent.yaml diff --git a/README.md b/README.md index 21ad7f9d6..486843469 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ ![Python Version](https://img.shields.io/badge/Python-3776AB?&logo=python&logoColor=white-blue&label=3.10%20%7C%203.11)  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)  [![Documentation](https://img.shields.io/badge/Documentation-%230ABAB5?style=flat&logo=readthedocs&logoColor=black)](https://microsoft.github.io/UFO/)  -[![YouTube](https://img.shields.io/badge/YouTube-white?logo=youtube&logoColor=%23FF0000)](https://www.youtube.com/watch?v=QT_OhygMVXU)  +[![YouTube](https://img.shields.io/badge/YouTube-white?logo=youtube&logoColor=%23FF0000)](https://www.youtube.com/watch?v=NGrVWGcJL8o)  diff --git a/README_ZH.md b/README_ZH.md index 0db72437f..07b6969a1 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -21,7 +21,7 @@ ![Python Version](https://img.shields.io/badge/Python-3776AB?&logo=python&logoColor=white-blue&label=3.10%20%7C%203.11)  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)  [![Documentation](https://img.shields.io/badge/Documentation-%230ABAB5?style=flat&logo=readthedocs&logoColor=black)](https://microsoft.github.io/UFO/)  -[![YouTube](https://img.shields.io/badge/YouTube-white?logo=youtube&logoColor=%23FF0000)](https://www.youtube.com/watch?v=QT_OhygMVXU)  +[![YouTube](https://img.shields.io/badge/YouTube-white?logo=youtube&logoColor=%23FF0000)](https://www.youtube.com/watch?v=NGrVWGcJL8o) 

diff --git a/documents/docs/aip/endpoints.md b/documents/docs/aip/endpoints.md index 22238c043..7e9dfcd8e 100644 --- a/documents/docs/aip/endpoints.md +++ b/documents/docs/aip/endpoints.md @@ -1,7 +1,6 @@ # AIP Endpoints -!!!quote "Complete Client/Server Implementations" - Endpoints combine protocol, transport, and resilience components to provide production-ready AIP communication for servers, clients, and orchestrators. +Endpoints combine protocol, transport, and resilience components to provide production-ready AIP communication for servers, clients, and orchestrators. ## Endpoint Types at a Glance @@ -38,15 +37,14 @@ graph TB The dashed arrows indicate capabilities that the base class provides to all subclasses. This inheritance design ensures consistent behavior across all endpoint types while allowing specialization for server, client, and orchestrator roles. -!!!info "Base Endpoint Components" - All endpoints inherit from `AIPEndpoint`, which provides: - - - **Protocol**: Message serialization and handling - - **Reconnection Strategy**: Automatic reconnection with backoff - - **Timeout Manager**: Operation timeout management - - **Session Handlers**: Per-session state tracking +**Base Endpoint Components:** ---- +All endpoints inherit from `AIPEndpoint`, which provides: + +- **Protocol**: Message serialization and handling +- **Reconnection Strategy**: Automatic reconnection with backoff +- **Timeout Manager**: Operation timeout management +- **Session Handlers**: Per-session state tracking ## Base Endpoint: AIPEndpoint @@ -83,8 +81,7 @@ await endpoint.stop() ## DeviceServerEndpoint -!!!tip "Server-Side Device Management" - Wraps UFO's server-side WebSocket handler with AIP protocol support for managing multiple device connections simultaneously. +Wraps UFO's server-side WebSocket handler with AIP protocol support for managing multiple device connections simultaneously. ### Configuration @@ -92,9 +89,9 @@ await endpoint.stop() from aip.endpoints import DeviceServerEndpoint endpoint = DeviceServerEndpoint( - client_manager=client_manager, # WebSocket connection manager - session_manager=session_manager, # Session state manager - local=False # Local vs remote deployment + ws_manager=ws_manager, # WebSocket connection manager + session_manager=session_manager, # Session state manager + local=False # Local vs remote deployment ) ``` @@ -105,7 +102,7 @@ from fastapi import FastAPI, WebSocket from aip.endpoints import DeviceServerEndpoint app = FastAPI() -endpoint = DeviceServerEndpoint(client_manager, session_manager) +endpoint = DeviceServerEndpoint(ws_manager, session_manager) @app.websocket("/ws") async def websocket_route(websocket: WebSocket): @@ -122,8 +119,9 @@ async def websocket_route(websocket: WebSocket): | **Result Aggregation** | Collect and format execution results | Unified response handling | | **Auto Task Cancellation** | Cancel tasks on disconnect | Prevent orphaned tasks | -!!!success "Backward Compatibility" - The Device Server Endpoint maintains full compatibility with UFO's existing WebSocket handler. +**Backward Compatibility:** + +The Device Server Endpoint maintains full compatibility with UFO's existing WebSocket handler. ### Task Cancellation on Disconnection @@ -139,8 +137,7 @@ await endpoint.cancel_device_tasks( ## DeviceClientEndpoint -!!!tip "Client-Side Device Operations" - Wraps UFO's client-side WebSocket client with AIP protocol support, automatic reconnection, and heartbeat management. +Wraps UFO's client-side WebSocket client with AIP protocol support, automatic reconnection, and heartbeat management. ### Configuration @@ -159,7 +156,7 @@ endpoint = DeviceClientEndpoint( | Feature | Default Behavior | Configuration | |---------|------------------|---------------| -| **Heartbeat** | Starts on connection | 20s interval (configurable) | +| **Heartbeat** | Starts on connection | 20s interval (fixed) | | **Reconnection** | Exponential backoff | `max_retries=3`, `initial_backoff=2.0` | | **Message Routing** | Auto-routes to UFO client | Handled internally | | **Connection Management** | Auto-connect on start | Transparent to user | @@ -199,8 +196,7 @@ endpoint = DeviceClientEndpoint( ## ConstellationEndpoint -!!!tip "Orchestrator-Side Multi-Device Coordination" - Enables the ConstellationClient to communicate with multiple devices simultaneously, managing connections, tasks, and queries. +Enables the ConstellationClient to communicate with multiple devices simultaneously, managing connections, tasks, and queries. ### Configuration @@ -234,6 +230,8 @@ connection = await endpoint.connect_to_device( ) ``` +Learn more about [AgentProfile configuration](../galaxy/client/device_manager.md) in the Galaxy documentation. + ### Sending Tasks ```python @@ -398,8 +396,8 @@ await endpoint.stop() ## Resilience Features -!!!success "Built-In Resilience" - All endpoints include automatic reconnection, timeout management, and heartbeat monitoring. +!!!warning "Built-In Resilience" + All endpoints include automatic reconnection, timeout management, and heartbeat monitoring for production reliability. ### Resilience Configuration @@ -484,12 +482,13 @@ class CustomEndpoint(DeviceClientEndpoint): ## Best Practices -!!!tip "Endpoint Selection" - | Use Case | Endpoint Type | - |----------|---------------| - | Device agent server | `DeviceServerEndpoint` | - | Device agent client | `DeviceClientEndpoint` | - | Multi-device orchestrator | `ConstellationEndpoint` | +**Endpoint Selection:** + +| Use Case | Endpoint Type | +|----------|---------------| +| Device agent server | `DeviceServerEndpoint` | +| Device agent client | `DeviceClientEndpoint` | +| Multi-device orchestrator | `ConstellationEndpoint` | !!!warning "Configuration Guidelines" - **Set appropriate timeouts** based on deployment environment @@ -539,4 +538,7 @@ from aip.endpoints import ( - [Resilience](./resilience.md) - Reconnection and heartbeat management - [Messages](./messages.md) - Message types and validation - [Overview](./overview.md) - System architecture and design +- [Galaxy Client](../galaxy/client/overview.md) - Multi-device orchestration with ConstellationClient +- [UFO Server](../server/websocket_handler.md) - WebSocket server implementation +- [UFO Client](../client/websocket_client.md) - WebSocket client implementation diff --git a/documents/docs/aip/messages.md b/documents/docs/aip/messages.md index fc63bf735..5c5067615 100644 --- a/documents/docs/aip/messages.md +++ b/documents/docs/aip/messages.md @@ -1,7 +1,6 @@ # AIP Message Reference -!!!quote "Strongly-Typed Communication" - AIP uses **Pydantic-based messages** for automatic validation, serialization, and type safety. All messages transmit as JSON over WebSocket. +AIP uses **Pydantic-based messages** for automatic validation, serialization, and type safety. All messages transmit as JSON over WebSocket. ## Message Overview @@ -59,8 +58,7 @@ Unidirectional arrows indicate request-response patterns, while bidirectional ar ## Core Data Structures -!!!info "Foundation Types" - These Pydantic models form the building blocks for all AIP messages. +These Pydantic models form the building blocks for all AIP messages. ### Essential Types Summary @@ -105,8 +103,7 @@ control = ControlInfo( ) ``` -

-Complete Field List +**Complete Field List:** | Field | Type | Description | |-------|------|-------------| @@ -123,8 +120,6 @@ control = ControlInfo( | `source` | str? | Data source identifier | | `text_content` | str? | Text content | -
- ### WindowInfo Window metadata (extends ControlInfo). @@ -143,8 +138,9 @@ Window metadata (extends ControlInfo). MCP tool capability definition. -!!!tip "Tool Advertisement" - Device agents use `MCPToolInfo` to advertise their capabilities during registration. +**Tool Advertisement:** + +Device agents use `MCPToolInfo` to advertise their capabilities during registration. ```python tool_info = MCPToolInfo( @@ -174,6 +170,8 @@ tool_info = MCPToolInfo( | `meta` | dict? | Metadata | | `annotations` | dict? | Additional annotations | +Learn more about [MCP tools and capabilities](../mcp/overview.md). + --- ## Command and Result Structures @@ -200,8 +198,9 @@ cmd = Command( | `tool_type` | str | ✅ | `"data_collection"` or `"action"` | | `call_id` | str | | Unique identifier for correlation | -!!!tip "Call ID Correlation" - Use `call_id` to match commands with their results in the `Result` object. +**Call ID Correlation:** + +Use `call_id` to match commands with their results in the `Result` object. ### ResultStatus @@ -281,8 +280,9 @@ The `CONTINUE → CONTINUE` self-loop represents multi-turn execution where task | `OK` | ✓ Acknowledgment | Heartbeat, health check passed | | `ERROR` | ⚠️ Protocol error | Protocol-level error | -!!!success "Multi-Turn Execution" - `CONTINUE` enables agents to request additional commands before marking a task as complete, supporting complex multi-step workflows. +**Multi-Turn Execution:** + +`CONTINUE` enables agents to request additional commands before marking a task as complete, supporting complex multi-step workflows. --- @@ -320,8 +320,7 @@ constellation_msg = ClientMessage( ## ClientMessage (Client → Server) -!!!tip "Client-Initiated Messages" - Devices and constellation clients use `ClientMessage` to communicate with the server. +Devices and constellation clients use `ClientMessage` to communicate with the server. ### Message Types @@ -394,8 +393,7 @@ results_msg = ClientMessage( ## ServerMessage (Server → Client) -!!!tip "Server-Initiated Messages" - Device services use `ServerMessage` to assign tasks and send commands to clients. +Device services use `ServerMessage` to assign tasks and send commands to clients. ### Message Types @@ -490,8 +488,8 @@ task_end_msg = ServerMessage( ## Message Validation -!!!success "Built-In Validation" - AIP provides `MessageValidator` class for ensuring message integrity. +!!!warning "Built-In Validation" + AIP provides `MessageValidator` class for ensuring message integrity. Always validate messages before processing to prevent protocol errors. ### Validation Methods @@ -520,15 +518,11 @@ if MessageValidator.validate_command_results(client_message): await process_results(client_message) ``` -!!!warning "Validation Best Practice" - Always validate messages before processing to prevent protocol errors and ensure data integrity. - --- ## Message Correlation -!!!info "Request-Response Chaining" - AIP uses identifier chains to maintain conversation context across multiple message exchanges. +AIP uses identifier chains to maintain conversation context across multiple message exchanges. ### Correlation Pattern @@ -561,8 +555,9 @@ Each new request includes `prev_response_id` pointing to the previous server res ### Session Tracking -!!!tip "Session-Based Grouping" - All messages within a task execution share the same `session_id` for traceability. +**Session-Based Grouping:** + +All messages within a task execution share the same `session_id` for traceability. ```python # All messages use same session_id @@ -596,10 +591,11 @@ task_end_msg.session_id = SESSION_ID - Always provide meaningful error messages - Use `ResultStatus.FAILURE` with descriptive `error` field -!!!tip "Extensibility" - - Use `metadata` field for custom data without breaking protocol - - Leverage Pydantic's validation for type safety - - Always correlate messages with `prev_response_id` +**Extensibility:** + +- Use `metadata` field for custom data without breaking protocol +- Leverage Pydantic's validation for type safety +- Always correlate messages with `prev_response_id` --- @@ -627,3 +623,6 @@ from aip.messages import ( - [Protocol Guide](./protocols.md) - How protocols construct and use messages - [Endpoints](./endpoints.md) - How endpoints handle messages - [Overview](./overview.md) - High-level message flow in system architecture +- [Transport Layer](./transport.md) - WebSocket transport for message delivery +- [Resilience](./resilience.md) - Message retry and timeout handling +- [MCP Integration](../mcp/overview.md) - How MCP tools integrate with AIP messages diff --git a/documents/docs/aip/overview.md b/documents/docs/aip/overview.md index 775845d18..1c08611ab 100644 --- a/documents/docs/aip/overview.md +++ b/documents/docs/aip/overview.md @@ -6,13 +6,14 @@ The orchestration model requires a communication substrate that remains **correc AIP serves as the **nervous system** of UFO², connecting the ConstellationClient, device agent services, and device clients under a unified, event-driven control plane. It is designed as a lightweight yet evolution-tolerant protocol to satisfy six goals: -!!!success "Design Goals" - - **(G1)** Maintain persistent bidirectional sessions to eliminate per-request overhead - - **(G2)** Unify heterogeneous capability discovery via multi-source profiling - - **(G3)** Ensure fine-grained reliability through heartbeats and timeout managers for disconnection and failure detection - - **(G4)** Preserve deterministic command ordering within sessions - - **(G5)** Support composable extensibility for new message types and resilience strategies - - **(G6)** Provide transparent reconnection and task continuity under transient failures +**Design Goals:** + +- **(G1)** Maintain persistent bidirectional sessions to eliminate per-request overhead +- **(G2)** Unify heterogeneous capability discovery via multi-source profiling +- **(G3)** Ensure fine-grained reliability through heartbeats and timeout managers for disconnection and failure detection +- **(G4)** Preserve deterministic command ordering within sessions +- **(G5)** Support composable extensibility for new message types and resilience strategies +- **(G6)** Provide transparent reconnection and task continuity under transient failures | Legacy HTTP Coordination | AIP WebSocket-Based Design | |--------------------------|----------------------------| @@ -25,10 +26,7 @@ AIP serves as the **nervous system** of UFO², connecting the ConstellationClien ## Five-Layer Architecture -To meet these requirements, AIP adopts a persistent, bidirectional WebSocket transport and decomposes the orchestration substrate into **five** logical strata, each responsible for a distinct aspect of reliability and adaptability: - -!!!tip "Vertically Integrated Stack" - AIP's architecture establishes a complete substrate where **L1** defines semantic contracts, **L2** provides transport flexibility, **L3** implements protocol logic, **L4** ensures operational resilience, and **L5** delivers deployment-ready orchestration primitives. +To meet these requirements, AIP adopts a persistent, bidirectional WebSocket transport and decomposes the orchestration substrate into **five** logical strata, each responsible for a distinct aspect of reliability and adaptability. The architecture establishes a complete substrate where **L1** defines semantic contracts, **L2** provides transport flexibility, **L3** implements protocol logic, **L4** ensures operational resilience, and **L5** delivers deployment-ready orchestration primitives. **Architecture Diagram:** @@ -38,7 +36,7 @@ The following diagram illustrates the five-layer architecture and the roles of e ### Layer 1: Message Schema Layer -**Defines strongly-typed, Pydantic-validated contracts** (`ClientMessage`, `ServerMessage`) for message direction, purpose, and task transitions. +Defines strongly-typed, Pydantic-validated contracts (`ClientMessage`, `ServerMessage`) for message direction, purpose, and task transitions. All messages are validated at schema level, preventing malformed messages from entering the protocol pipeline, enabling early error detection and simplifying debugging. | Responsibility | Implementation | Supports | |----------------|----------------|----------| @@ -46,12 +44,9 @@ The following diagram illustrates the five-layer architecture and the roles of e | Structured metadata | System info, capabilities | Unified capability discovery (G2) | | ID correlation | Explicit request/response linking | Deterministic ordering (G4) | -!!!info "Type Safety & Validation" - All messages are validated at schema level, preventing malformed messages from entering the protocol pipeline. This enables early error detection and simplifies debugging. - ### Layer 2: Transport Abstraction Layer -**Provides protocol-agnostic `Transport` interface** with production-grade WebSocket implementation. +Provides protocol-agnostic `Transport` interface with production-grade WebSocket implementation. The abstraction layer allows swapping transports without changing protocol logic, supporting future protocol evolution. | Feature | Benefit | Goals | |---------|---------|-------| @@ -60,12 +55,9 @@ The following diagram illustrates the five-layer architecture and the roles of e | Decoupled transport logic | Future extensibility (HTTP/3, gRPC) | G5 | | Low-latency persistent sessions | Eliminates per-request overhead | G1 | -!!!tip "Transport Flexibility" - The abstraction layer allows swapping transports without changing protocol logic, supporting future protocol evolution. - ### Layer 3: Protocol Orchestration Layer -**Implements modular handlers** for registration, task execution, heartbeat, and command dispatch. +Implements modular handlers for registration, task execution, heartbeat, and command dispatch. Each handler is independently testable and replaceable, supporting composable extensibility (G5) while maintaining ordered state transitions (G4). | Component | Purpose | Design | |-----------|---------|--------| @@ -74,14 +66,16 @@ The following diagram illustrates the five-layer architecture and the roles of e | Middleware hooks | Logging, metrics, authentication | Composable extensions (G5) | | State transitions | Ordered message processing | Deterministic ordering (G4) | -!!!success "Modular Design" - Each handler is independently testable and replaceable, supporting composable extensibility (G5) while maintaining ordered state transitions (G4). - -[→ Complete message reference](./messages.md) +**Related Documentation:** +- [Complete message reference](./messages.md) +- [Protocol implementation details](./protocols.md) ### Layer 4: Resilience and Health Management Layer -**Encapsulates reliability mechanisms** ensuring operational continuity under failures. +!!!warning "Fault Tolerance" + This layer guarantees fine-grained reliability (G3) and seamless task continuity under transient disconnections (G6), preventing cascade failures. + +Encapsulates reliability mechanisms ensuring operational continuity under failures: | Component | Mechanism | Goals | |-----------|-----------|-------| @@ -90,14 +84,11 @@ The following diagram illustrates the five-layer architecture and the roles of e | `ReconnectionStrategy` | Exponential backoff with jitter | G6 | | Session recovery | Automatic state restoration | G6 | -!!!warning "Fault Tolerance" - This layer guarantees fine-grained reliability (G3) and seamless task continuity under transient disconnections (G6), preventing cascade failures. - [→ Resilience implementation details](./resilience.md) ### Layer 5: Endpoint Orchestration Layer -**Provides role-specific facades** integrating lower layers into deployable components. +Provides role-specific facades integrating lower layers into deployable components. These endpoints unify connection lifecycle, task routing, and health monitoring across roles, reinforcing G1–G6 through consistent implementation of lower-layer capabilities. | Endpoint | Role | Responsibilities | |----------|------|------------------| @@ -105,9 +96,6 @@ The following diagram illustrates the five-layer architecture and the roles of e | `DeviceServerEndpoint` | Server | WebSocket connection management, task dispatch, result aggregation | | `DeviceClientEndpoint` | Executor | Local task execution, MCP tool invocation, telemetry reporting | -!!!example "Unified Orchestration" - These endpoints unify connection lifecycle, task routing, and health monitoring across roles, reinforcing G1–G6 through consistent implementation of lower-layer capabilities. - **Endpoint Integration Benefits:** - ✅ Connection lifecycle management (G1, G6) @@ -117,8 +105,6 @@ The following diagram illustrates the five-layer architecture and the roles of e [→ Endpoint setup guide](./endpoints.md) ---- - ## Architecture Benefits Together, these layers form a vertically integrated stack that enables UFO² to maintain **correctness and availability** under challenging conditions: @@ -131,17 +117,13 @@ Together, these layers form a vertically integrated stack that enables UFO² to | **Transient Failures** | Timeout management, automatic recovery | L4 (G3, G6) | | **Protocol Evolution** | Transport abstraction, middleware hooks | L2, L3 (G5) | -!!!quote "Design Philosophy" - AIP transforms distributed workflow execution into a **coherent, safe, and adaptive system** where reasoning and execution converge seamlessly across diverse agents and environments. - ---- +AIP transforms distributed workflow execution into a **coherent, safe, and adaptive system** where reasoning and execution converge seamlessly across diverse agents and environments. ## Core Capabilities -### 1️⃣ Agent Registration & Profiling +### Agent Registration & Profiling -!!!info "Multi-Source Agent Profiles (G2)" - Each agent is represented by an **AgentProfile** combining data from three sources for comprehensive capability discovery, supporting heterogeneous capability unification. +Each agent is represented by an **AgentProfile** combining data from three sources for comprehensive capability discovery, supporting heterogeneous capability unification (G2): | Source | Provider | Information | |--------|----------|-------------| @@ -161,12 +143,9 @@ Together, these layers form a vertically integrated stack that enables UFO² to [→ See detailed registration flow](./protocols.md) ---- +### Task Dispatch & Result Delivery -### 2️⃣ Task Dispatch & Result Delivery - -!!!success "Persistent Sessions (G1)" - AIP uses **long-lived WebSocket sessions** that span multiple task executions, eliminating per-request connection overhead and preserving context. +AIP uses **long-lived WebSocket sessions** that span multiple task executions, eliminating per-request connection overhead and preserving context (G1). **Task Execution Sequence:** @@ -178,7 +157,7 @@ sequenceDiagram participant DAS as Device Service participant DAC as Device Client - CC->>DAS: TASK message (Task★) + CC->>DAS: TASK message (TaskStar) DAS->>DAC: Stream task payload DAC->>DAC: Execute using MCP tools DAC->>DAS: Stream execution logs @@ -191,30 +170,30 @@ Each arrow represents a message exchange, with vertical lifelines showing the te | Stage | Message Type | Content | |-------|-------------|---------| -| Assignment | `TASK` | Task★ definition, target device, commands | +| Assignment | `TASK` | TaskStar definition, target device, commands | | Execution | (internal) | MCP tool invocations, local computation | | Reporting | `TASK_END` | Status, logs, evaluator outputs, results | !!!warning "Asynchronous Execution" Tasks execute asynchronously. The orchestrator may assign multiple tasks to different devices simultaneously, with results arriving in non-deterministic order. -[→ See message format details](./messages.md) - ---- +**Related Documentation:** +- [Message format details](./messages.md) +- [TaskConstellation documentation](../galaxy/constellation/task_constellation.md) +- [TaskStar (task nodes) documentation](../galaxy/constellation/task_star.md) -### 3️⃣ Command Execution +### Command Execution -!!!info "Fine-Grained Control (G4)" - Within each task, AIP executes **individual commands** deterministically with preserved ordering, enabling precise control and error handling. +Within each task, AIP executes **individual commands** deterministically with preserved ordering, enabling precise control and error handling (G4). **Command Structure:** | Field | Purpose | Example | |-------|---------|---------| -| `id` | Unique identifier | `"cmd_001"` | -| `function` | Tool/action name | `"click_input"` | -| `args` | Typed arguments | `["Save Button", "left"]` | +| `tool_name` | Tool/action name | `"click_input"` | +| `parameters` | Typed arguments | `{"target": "Save Button", "button": "left"}` | | `tool_type` | Category | `"action"` or `"data_collection"` | +| `call_id` | Unique identifier | `"cmd_001"` | **Execution Guarantees:** @@ -227,10 +206,10 @@ Each arrow represents a message exchange, with vertical lifelines showing the te ```json { - "commands": [ - {"id": "1", "function": "click", "args": ["File"]}, - {"id": "2", "function": "click", "args": ["Save As"]}, - {"id": "3", "function": "type", "args": ["document.pdf"]} + "actions": [ + {"tool_name": "click", "parameters": {"target": "File"}, "call_id": "1"}, + {"tool_name": "click", "parameters": {"target": "Save As"}, "call_id": "2"}, + {"tool_name": "type", "parameters": {"text": "document.pdf"}, "call_id": "3"} ] } ``` @@ -239,12 +218,9 @@ All three commands sent in one message, executed sequentially. [→ See command execution protocol](./protocols.md) ---- - ## Message Protocol Overview -!!!tip "Strongly-Typed Messages" - All AIP messages use **Pydantic models** for automatic validation, serialization, and type safety. +All AIP messages use **Pydantic models** for automatic validation, serialization, and type safety. ### Bidirectional Message Types @@ -261,17 +237,16 @@ All three commands sent in one message, executed sequentially. | | `HEARTBEAT` | Keepalive acknowledgment | | **Bidirectional** | `ERROR` | Error condition reporting | -!!!info "Message Correlation" - Every message includes: - - - `timestamp`: ISO 8601 formatted - - `request_id` / `response_id`: Unique identifier - - `prev_response_id`: Links responses to requests - - `session_id`: Session context +**Message Correlation:** -[→ Complete message reference](./messages.md) +Every message includes: + +- `timestamp`: ISO 8601 formatted +- `request_id` / `response_id`: Unique identifier +- `prev_response_id`: Links responses to requests +- `session_id`: Session context ---- +[→ Complete message reference](./messages.md) ## Resilient Connection Protocol @@ -304,7 +279,7 @@ The `DISCONNECTED` state acts as a quarantine zone where the device is temporari |-------|---------------------|---------------| | **Device disconnects** | Mark as `DISCONNECTED`
Exclude from scheduling
Trigger auto-reconnect (G6) | N/A | | **Reconnection succeeds** | Mark as `CONNECTED`
Resume scheduling | Session restored (G6) | -| **Disconnect during task** | Mark tasks as `TASK_FAILED`
Propagate to ConstellationAgent
Trigger DAG edit | N/A | +| **Disconnect during task** | Mark tasks as `FAILED`
Propagate to ConstellationAgent
Trigger DAG edit | N/A | ### ConstellationClient Disconnection @@ -325,12 +300,9 @@ The `DISCONNECTED` state acts as a quarantine zone where the device is temporari [→ See resilience implementation](./resilience.md) ---- - ## Extensibility Mechanisms -!!!tip "Customization Points (G5)" - AIP provides multiple extension points for domain-specific needs without modifying the core protocol, supporting composable extensibility. +AIP provides multiple extension points for domain-specific needs without modifying the core protocol, supporting composable extensibility (G5). ### 1. Protocol Middleware @@ -343,6 +315,10 @@ class AuditMiddleware(ProtocolMiddleware): async def process_outgoing(self, msg): log_to_audit_trail(msg) return msg + + async def process_incoming(self, msg): + log_to_audit_trail(msg) + return msg ``` ### 2. Custom Message Handlers @@ -359,13 +335,11 @@ Pluggable transport (default: WebSocket) (G5): ```python from aip.transport import CustomTransport -protocol.set_transport(CustomTransport(config)) +protocol.transport = CustomTransport(config) ``` [→ See extensibility guide](./protocols.md) ---- - ## Integration with UFO² Ecosystem | Component | Integration Point | Benefit | @@ -375,12 +349,14 @@ protocol.set_transport(CustomTransport(config)) | **Configuration System** | Agent endpoints, capabilities managed via UFO² config | Centralized management, type-safe validation | | **Logging & Monitoring** | Comprehensive logging at all protocol layers | Debugging, performance monitoring, audit trails | -!!!success "Seamless Ecosystem Integration" - AIP abstracts network/device heterogeneity, allowing the orchestrator to treat all agents as **first-class citizens** in a single event-driven control plane. - ---- +AIP abstracts network/device heterogeneity, allowing the orchestrator to treat all agents as **first-class citizens** in a single event-driven control plane. +**Related Documentation:** +- [TaskConstellation (DAG orchestrator)](../galaxy/constellation/task_constellation.md) +- [ConstellationAgent (orchestration agent)](../galaxy/constellation_agent/overview.md) +- [MCP Integration Guide](../mcp/overview.md) +- [Configuration System](../configuration/system/system_config.md) **Next Steps:** - 📖 [Message Reference](./messages.md) - Complete message type documentation @@ -389,12 +365,9 @@ protocol.set_transport(CustomTransport(config)) - 🔌 [Endpoints](./endpoints.md) - Endpoint setup and usage patterns - 🛡️ [Resilience](./resilience.md) - Connection management and fault tolerance ---- - ## Summary -!!!quote "AIP's Value Proposition" - AIP transforms distributed workflow execution into a **coherent, safe, and adaptive system** where reasoning and execution converge seamlessly across diverse agents and environments. +AIP transforms distributed workflow execution into a **coherent, safe, and adaptive system** where reasoning and execution converge seamlessly across diverse agents and environments. **Key Takeaways:** diff --git a/documents/docs/aip/protocols.md b/documents/docs/aip/protocols.md index 22957d364..9ac3d4b77 100644 --- a/documents/docs/aip/protocols.md +++ b/documents/docs/aip/protocols.md @@ -1,13 +1,8 @@ # AIP Protocol Reference -!!!quote "Specialized Protocol Layers" - AIP's protocol stack provides specialized handlers for registration, task execution, commands, heartbeat, and device info—each optimized for its specific lifecycle phase. - ## Protocol Stack Overview -**Layered Protocol Architecture:** - -AIP uses a three-layer architecture where specialized protocols handle domain-specific concerns, the core protocol manages message processing, and the transport layer provides network communication: +AIP uses a three-layer architecture where specialized protocols handle domain-specific concerns, the core protocol manages message processing, and the transport layer provides network communication. ```mermaid graph TB @@ -59,8 +54,7 @@ This layered design enables clean separation of concerns: specialized protocols ## Core Protocol: AIPProtocol -!!!info "Foundation Layer" - `AIPProtocol` provides transport-agnostic message handling with middleware support and automatic serialization. +`AIPProtocol` provides transport-agnostic message handling with middleware support and automatic serialization. ### Quick Start @@ -84,8 +78,7 @@ protocol = AIPProtocol(transport) ### Middleware Pipeline -!!!tip "Extensible Processing" - Add middleware for logging, authentication, metrics, or custom transformations. +Add middleware for logging, authentication, metrics, or custom transformations. ```python from aip.protocol.base import ProtocolMiddleware @@ -126,13 +119,10 @@ await protocol.dispatch_message(server_msg) ## RegistrationProtocol {#registration-protocol} -!!!success "Agent Onboarding" - Handles initial registration and capability advertisement when agents join the constellation. +Handles initial registration and capability advertisement when agents join the constellation. ### Registration Flow -**Registration Handshake Sequence:** - The following diagram shows the two-way handshake for device registration, including validation and acknowledgment: ```mermaid @@ -178,6 +168,8 @@ success = await reg_protocol.register_as_device( - `timestamp`: Registration time (ISO 8601) - `client_type`: Set to `ClientType.DEVICE` +[→ See ClientType and ClientMessage in Message Reference](./messages.md) + ### Constellation Registration **Orchestrator Registration:** @@ -207,13 +199,10 @@ success = await reg_protocol.register_as_constellation( ## TaskExecutionProtocol {#task-execution-protocol} -!!!info "Multi-Turn Task Orchestration" - Manages the complete task lifecycle: assignment → command execution → result reporting → completion. +Manages the complete task lifecycle: assignment → command execution → result reporting → completion. ### Task Lifecycle -**Task State Machine:** - This state diagram shows the complete task execution lifecycle, including the multi-turn command loop where agents can request additional commands before completion: ```mermaid @@ -265,8 +254,7 @@ await task_protocol.send_task_assignment( ### Server → Client: Command Dispatch -!!!tip "Batch Commands" - Send multiple commands in one message to reduce network overhead. +Send multiple commands in one message to reduce network overhead. **Method 1: Using ServerMessage** @@ -324,6 +312,8 @@ await task_protocol.send_command_results( ) ``` +[→ See Result and ResultStatus definitions in Message Reference](./messages.md) + ### Task Completion **Server → Client: Success** @@ -353,8 +343,6 @@ await task_protocol.send_task_end( ### Complete Task Flow -**End-to-End Multi-Turn Task Execution:** - This comprehensive sequence diagram shows the complete flow from task request to completion, including the multi-turn command loop where the agent iteratively executes commands and requests follow-up actions: ```mermaid @@ -390,8 +378,7 @@ The loop in the middle represents iterative task execution where the agent can p ## CommandProtocol -!!!info "Command Validation Layer" - Provides validation utilities for commands and results before transmission. +Provides validation utilities for commands and results before transmission. ### Validation Methods @@ -424,20 +411,17 @@ if cmd_protocol.validate_results(results): await task_protocol.send_command_results(results, ...) ``` -!!!warning "Always Validate" - Validation catches protocol errors early, preventing runtime failures and debugging headaches. +!!!warning "Validation Best Practice" + Always validate commands and results before transmission to catch protocol errors early and prevent runtime failures. --- ## HeartbeatProtocol {#heartbeat-protocol} -!!!success "Connection Health Monitoring" - Periodic keepalive messages detect broken connections and network issues. +Periodic keepalive messages detect broken connections and network issues. ### Heartbeat Flow -**Periodic Keepalive Mechanism:** - The heartbeat protocol uses a simple ping-pong pattern to verify connection health at regular intervals: ```mermaid @@ -465,15 +449,14 @@ heartbeat_protocol = HeartbeatProtocol(transport) await heartbeat_protocol.send_heartbeat( client_id="windows_agent_001", - session_id="session_123" # Optional + metadata={"custom_info": "value"} # Optional ) ``` ### Server-Side Response ```python -await heartbeat_protocol.send_heartbeat_response( - session_id="session_123", +await heartbeat_protocol.send_heartbeat_ack( response_id="resp_hb_001" ) ``` @@ -487,13 +470,10 @@ await heartbeat_protocol.send_heartbeat_response( ## DeviceInfoProtocol -!!!info "Telemetry Exchange" - Request and report device hardware/software information for informed scheduling. +Request and report device hardware/software information for informed scheduling. ### Info Request Flow -**On-Demand Telemetry Collection:** - The server can request fresh device information at any time to make informed scheduling decisions: ```mermaid @@ -508,7 +488,7 @@ sequenceDiagram This pull-based telemetry model allows the orchestrator to query device capabilities on-demand (e.g., before assigning a GPU-intensive task) rather than relying on stale registration data. -### Server → Client: Request Info +### Constellation → Server: Request Info ```python from aip.protocol import DeviceInfoProtocol @@ -516,12 +496,15 @@ from aip.protocol import DeviceInfoProtocol info_protocol = DeviceInfoProtocol(transport) await info_protocol.request_device_info( - device_id="windows_agent_001", + constellation_id="orchestrator_001", + target_device="windows_agent_001", request_id="req_info_001" ) ``` -### Client → Server: Provide Info +### Server → Client: Provide Info + +The server responds with device information (or an error if collection failed): ```python device_info = { @@ -537,11 +520,13 @@ device_info = { await info_protocol.send_device_info_response( device_info=device_info, request_id="req_info_001", - client_id="windows_agent_001" + error=None # Set to error message string if info collection failed ) ``` -!!!success "Use Cases" +### Use Cases + +!!!success "Device-Aware Task Scheduling" - **GPU-aware scheduling**: Check GPU availability before assigning vision tasks - **Load balancing**: Distribute tasks based on CPU/RAM usage - **Health monitoring**: Track device status over time @@ -552,10 +537,7 @@ await info_protocol.send_device_info_response( ### Multi-Turn Conversations -!!!tip "Correlation Chain" - Use `prev_response_id` to maintain conversation context across multiple exchanges. - -**Request-Response Correlation Pattern:** +Use `prev_response_id` to maintain conversation context across multiple exchanges. This diagram shows how messages are chained together using `prev_response_id` to maintain conversation context: @@ -587,8 +569,7 @@ await protocol.send_message(ClientMessage( ### Session-Based Communication -!!!info "Session Grouping" - All messages in a task share the same `session_id` for traceability. +All messages in a task share the same `session_id` for traceability. ```python SESSION_ID = "session_abc123" @@ -627,31 +608,35 @@ await protocol.send_error( ## Best Practices -!!!success "Protocol Selection" - **Use specialized protocols** instead of manually constructing messages with `AIPProtocol`. - - | Task | Protocol | - |------|----------| - | Agent registration | `RegistrationProtocol` | - | Task execution | `TaskExecutionProtocol` | - | Command validation | `CommandProtocol` | - | Keepalive | `HeartbeatProtocol` | - | Device telemetry | `DeviceInfoProtocol` | - -!!!warning "Validation" - - Always validate commands/results before transmission - - Use `MessageValidator` for message integrity checks - - Catch validation errors early - -!!!info "Session Management" - - **Always set `session_id`** for task-related messages - - Use **correlation IDs** (`prev_response_id`) for multi-turn conversations - - **Generate unique IDs** with `uuid.uuid4()` - -!!!tip "Error Handling" - - **Distinguish** protocol errors (connection) from application errors (task failure) - - **Propagate errors** explicitly through error messages - - **Leverage middleware** for cross-cutting concerns (logging, metrics, auth) +### Protocol Selection + +Use specialized protocols instead of manually constructing messages with `AIPProtocol`. + +| Task | Protocol | +|------|----------| +| Agent registration | `RegistrationProtocol` | +| Task execution | `TaskExecutionProtocol` | +| Command validation | `CommandProtocol` | +| Keepalive | `HeartbeatProtocol` | +| Device telemetry | `DeviceInfoProtocol` | + +### Validation + +- Always validate commands/results before transmission +- Use `MessageValidator` for message integrity checks +- Catch validation errors early + +### Session Management + +- **Always set `session_id`** for task-related messages +- Use **correlation IDs** (`prev_response_id`) for multi-turn conversations +- **Generate unique IDs** with `uuid.uuid4()` + +### Error Handling + +- **Distinguish** protocol errors (connection) from application errors (task failure) +- **Propagate errors** explicitly through error messages +- **Leverage middleware** for cross-cutting concerns (logging, metrics, auth) !!!danger "Resource Cleanup" Always close protocols when done to release transport resources. diff --git a/documents/docs/aip/resilience.md b/documents/docs/aip/resilience.md index b393586f1..f38136cd1 100644 --- a/documents/docs/aip/resilience.md +++ b/documents/docs/aip/resilience.md @@ -1,7 +1,6 @@ # AIP Resilience -!!!quote "Fault-Tolerant Communication" - AIP's resilience layer ensures stable communication and consistent orchestration across distributed agent constellations through automatic reconnection, heartbeat monitoring, and timeout management. +AIP's resilience layer ensures stable communication and consistent orchestration across distributed agent constellations through automatic reconnection, heartbeat monitoring, and timeout management. ## Resilience Components @@ -16,13 +15,10 @@ ## Resilient Connection Protocol -!!!warning "Connection State Management" - The Resilient Connection Protocol governs how connection disruptions are detected, handled, and recovered between ConstellationClient and Device Agents. +The Resilient Connection Protocol governs how connection disruptions are detected, handled, and recovered between ConstellationClient and Device Agents. ### Connection State Diagram -**Device Connection Lifecycle with Failure Handling:** - This state diagram shows how devices transition between connection states and the internal sub-states during disconnection recovery: ```mermaid @@ -66,15 +62,12 @@ The nested states within `DISCONNECTED` show the cleanup and recovery sequence: ```python # Automatically called on disconnection -await device_server.cancel_all_tasks_for_client(client_id) +await device_server.cancel_device_tasks(client_id, reason="device_disconnected") ``` ### ConstellationClient Disconnection -!!!success "Bidirectional Fault Handling" - When ConstellationClient disconnects, Device Agent Servers proactively clean up to prevent orphaned tasks. - -**Orphaned Task Prevention:** +When ConstellationClient disconnects, Device Agent Servers proactively clean up to prevent orphaned tasks. This sequence diagram shows the proactive cleanup sequence when the orchestrator disconnects, ensuring all running tasks are properly aborted: @@ -106,8 +99,7 @@ The `x` marker on the connection arrow indicates an abnormal termination. The se ## ReconnectionStrategy -!!!info "Automatic Reconnection" - Manages reconnection attempts with configurable backoff policies to handle transient network failures. +Manages reconnection attempts with configurable backoff policies to handle transient network failures. ### Configuration @@ -123,10 +115,11 @@ strategy = ReconnectionStrategy( ) ``` +[→ See how ReconnectionStrategy is used in endpoints](./endpoints.md) + ### Backoff Policies -!!!tip "Choose Based on Network Conditions" - Select the policy that matches your deployment environment's network characteristics. +Select the policy that matches your deployment environment's network characteristics. | Policy | Backoff Pattern | Best For | Example Sequence | |--------|----------------|----------|------------------| @@ -140,8 +133,6 @@ strategy = ReconnectionStrategy( ### Reconnection Workflow -**Automated Reconnection Decision Tree:** - This flowchart shows the complete reconnection logic from failure detection through recovery or permanent failure: ```mermaid @@ -207,8 +198,7 @@ await strategy.handle_disconnection( ## HeartbeatManager {#heartbeat-manager} -!!!success "Connection Health Monitoring" - Sends periodic keepalive messages to detect broken connections before they cause failures. +Sends periodic keepalive messages to detect broken connections before they cause failures. ### Configuration @@ -223,6 +213,8 @@ heartbeat_manager = HeartbeatManager( ) ``` +[→ See HeartbeatProtocol reference](./protocols.md#heartbeat-protocol) + ### Lifecycle Management | Operation | Method | Description | @@ -255,27 +247,33 @@ await heartbeat_manager.stop_all() ### Heartbeat Loop Internals +The heartbeat manager automatically sends periodic heartbeats. If the protocol is not connected, it logs a warning and continues the loop: + ```python async def _heartbeat_loop(client_id: str, interval: float): """Internal heartbeat loop (automatic)""" - while True: - await asyncio.sleep(interval) - - if protocol.is_connected(): - await protocol.send_heartbeat(client_id) - else: - # Connection lost, exit loop - break + try: + while True: + await asyncio.sleep(interval) + + if protocol.is_connected(): + try: + await protocol.send_heartbeat(client_id) + except Exception as e: + logger.error(f"Error sending heartbeat: {e}") + # Continue loop, connection manager handles disconnection + else: + logger.warning("Protocol not connected, skipping heartbeat") + + except asyncio.CancelledError: + logger.debug("Heartbeat loop cancelled") ``` ### Failure Detection -!!!warning "Automatic Disconnection Trigger" - If heartbeat fails to send (connection closed), the loop exits and triggers disconnection handling. - -**Heartbeat Failure Detection:** +When the transport layer fails to send a heartbeat (connection closed), errors are logged but the loop continues running. The connection manager is responsible for detecting the disconnection through transport-level errors and triggering the reconnection strategy. -This sequence diagram shows how the heartbeat loop detects connection failures and triggers disconnection handling: +This sequence diagram shows how heartbeat errors are handled: ```mermaid sequenceDiagram @@ -291,31 +289,29 @@ sequenceDiagram P-->>HM: Continue else Connection dead T-xP: ConnectionError - P-xHM: Error - HM->>HM: Exit loop - HM->>HM: Trigger disconnection + P-xHM: Error (caught) + HM->>HM: Log error, continue loop + Note over HM: Connection manager
handles disconnection
at transport level end end ``` -The `x` markers indicate error paths. When the transport layer fails to send a heartbeat, the error propagates back to the manager, which exits the loop and initiates disconnection handling. +The `x` markers indicate error paths. When the transport layer fails to send a heartbeat, the error is caught and logged. The heartbeat loop continues, while the connection manager detects the disconnection at the transport level and initiates recovery. ### Interval Guidelines -!!!tip "Interval Selection" - | Environment | Recommended Interval | Rationale | - |-------------|---------------------|-----------| - | **Local network** | 10-20s | Quick failure detection, low latency | - | **Internet** | 30-60s | Balance overhead vs detection speed | - | **Mobile/Unreliable** | 60-120s | Reduce battery/bandwidth usage | - | **Critical systems** | 5-10s | Fastest failure detection | +| Environment | Recommended Interval | Rationale | +|-------------|---------------------|-----------| +| **Local network** | 10-20s | Quick failure detection, low latency | +| **Internet** | 30-60s | Balance overhead vs detection speed | +| **Mobile/Unreliable** | 60-120s | Reduce battery/bandwidth usage | +| **Critical systems** | 5-10s | Fastest failure detection | --- ## TimeoutManager -!!!info "Operation Timeout Enforcement" - Prevents operations from hanging indefinitely by enforcing configurable timeouts with automatic cancellation. +Prevents operations from hanging indefinitely by enforcing configurable timeouts with automatic cancellation. ### Configuration @@ -327,6 +323,8 @@ timeout_manager = TimeoutManager( ) ``` +[→ See how timeouts are used in protocol operations](./protocols.md) + ### Usage Patterns **Default Timeout:** @@ -365,43 +363,39 @@ except TimeoutError: ### Recommended Timeouts -!!!success "Timeout Guidelines by Operation" - | Operation | Timeout | Rationale | - |-----------|---------|-----------| - | **Registration** | 10-30s | Simple message exchange | - | **Task Dispatch** | 30-60s | May involve scheduling logic | - | **Command Execution** | 60-300s | Depends on command complexity | - | **Heartbeat** | 5-10s | Fast failure detection needed | - | **Disconnection** | 5-15s | Clean shutdown | - | **Device Info Query** | 15-30s | Telemetry collection | +| Operation | Timeout | Rationale | +|-----------|---------|-----------| +| **Registration** | 10-30s | Simple message exchange | +| **Task Dispatch** | 30-60s | May involve scheduling logic | +| **Command Execution** | 60-300s | Depends on command complexity | +| **Heartbeat** | 5-10s | Fast failure detection needed | +| **Disconnection** | 5-15s | Clean shutdown | +| **Device Info Query** | 15-30s | Telemetry collection | --- ## Integration with Endpoints -!!!tip "Automatic Resilience" - Endpoints automatically integrate all resilience components—no manual wiring needed. +Endpoints automatically integrate all resilience components—no manual wiring needed. ### Example: DeviceClientEndpoint ```python from aip.endpoints import DeviceClientEndpoint -from aip.resilience import ReconnectionStrategy endpoint = DeviceClientEndpoint( ws_url="ws://localhost:8000/ws", ufo_client=client, - reconnection_strategy=ReconnectionStrategy( - max_retries=3, - initial_backoff=2.0, - max_backoff=60.0 - ) + max_retries=3, # Reconnection retries + timeout=120.0 # Connection timeout ) # Resilience handled automatically on start await endpoint.start() ``` +**Note**: The endpoint creates its own `ReconnectionStrategy` internally with the specified `max_retries`. + ### Built-In Features | Feature | Behavior | Configuration | @@ -412,6 +406,7 @@ await endpoint.start() | **Task Cancellation** | Auto-cancel on disconnect | Built-in to endpoint | [→ See endpoint documentation](./endpoints.md) +[→ See WebSocket transport details](./transport.md) --- @@ -462,47 +457,43 @@ timeout_default = 180.0 ### Scenario 1: Transient Network Failure -!!!example "Brief Network Glitch" - **Problem**: Network glitch disconnects client for 3 seconds. - - **Resolution**: - 1. ✅ Disconnection detected via heartbeat timeout - 2. ✅ Automatic reconnection triggered (1st attempt after 2s) - 3. ✅ Connection restored successfully - 4. ✅ Heartbeat resumes - 5. ✅ Tasks continue +**Problem**: Network glitch disconnects client for 3 seconds. + +**Resolution**: +1. ✅ Disconnection detected via heartbeat timeout +2. ✅ Automatic reconnection triggered (1st attempt after 2s) +3. ✅ Connection restored successfully +4. ✅ Heartbeat resumes +5. ✅ Tasks continue ### Scenario 2: Prolonged Outage -!!!failure "Extended Disconnection" - **Problem**: Device offline for 10 minutes. - - **Resolution**: - 1. ❌ Initial disconnection detected - 2. ⏳ Multiple reconnection attempts (exponential backoff: 2s, 4s, 8s, 16s, 32s) - 3. ❌ All attempts fail (max retries reached) - 4. ⚠️ Tasks marked as FAILED - 5. 📢 ConstellationAgent notified - 6. ♻️ Tasks reassigned to other devices +**Problem**: Device offline for 10 minutes. + +**Resolution**: +1. ❌ Initial disconnection detected +2. ⏳ Multiple reconnection attempts (exponential backoff: 2s, 4s, 8s, 16s, 32s) +3. ❌ All attempts fail (max retries reached) +4. ⚠️ Tasks marked as FAILED +5. 📢 ConstellationAgent notified +6. ♻️ Tasks reassigned to other devices ### Scenario 3: Server Restart -!!!warning "All Clients Disconnect Simultaneously" - **Problem**: Server restarts, causing all clients to disconnect at once. - - **Resolution**: - 1. ⚠️ All clients detect disconnection - 2. ⏳ Each client begins reconnection (with jitter to avoid thundering herd) - 3. ✅ Server restarts and accepts connections - 4. ✅ Clients reconnect and re-register - 5. ✅ Task execution resumes +**Problem**: Server restarts, causing all clients to disconnect at once. + +**Resolution**: +1. ⚠️ All clients detect disconnection +2. ⏳ Each client begins reconnection (with jitter to avoid thundering herd) +3. ✅ Server restarts and accepts connections +4. ✅ Clients reconnect and re-register +5. ✅ Task execution resumes ### Scenario 4: Heartbeat Timeout -!!!danger "Missing Heartbeat Response" - **Problem**: Heartbeat not received within timeout period. - - **Resolution**: +**Problem**: Heartbeat not received within timeout period. + +**Resolution**: 1. ⏰ HeartbeatManager detects missing pong 2. ⚠️ Connection marked as potentially dead 3. 🔄 Disconnection handling triggered @@ -561,8 +552,7 @@ if not await strategy.attempt_reconnection(endpoint, device_id): ## Testing Resilience -!!!tip "Simulate Failures" - Test resilience by simulating network failures and verifying recovery. +Test resilience by simulating network failures and verifying recovery. ```python # Simulate disconnection diff --git a/documents/docs/aip/transport.md b/documents/docs/aip/transport.md index 54355b8e5..fc8715c48 100644 --- a/documents/docs/aip/transport.md +++ b/documents/docs/aip/transport.md @@ -1,12 +1,9 @@ # AIP Transport Layer -!!!quote "Network Communication Foundation" - The transport layer provides a pluggable abstraction for AIP's network communication, decoupling protocol logic from underlying network implementations through a unified Transport interface. +The transport layer provides a pluggable abstraction for AIP's network communication, decoupling protocol logic from underlying network implementations through a unified Transport interface. ## Transport Architecture -**Pluggable Transport Layer Design:** - AIP uses a transport abstraction pattern that allows different network protocols to be swapped without changing higher-level protocol logic. The current implementation focuses on WebSocket, with future support planned for HTTP/3 and gRPC: ```mermaid @@ -39,8 +36,7 @@ The unified adapter bridges client and server WebSocket libraries, providing a c ## Transport Interface -!!!info "Abstract Base Class" - All transport implementations must implement the `Transport` interface for interoperability. +All transport implementations must implement the `Transport` interface for interoperability. ### Core Operations @@ -50,6 +46,7 @@ The unified adapter bridges client and server WebSocket libraries, providing a c | `send(data)` | Send raw bytes | `None` | | `receive()` | Receive raw bytes | `bytes` | | `close()` | Close connection gracefully | `None` | +| `wait_closed()` | Wait for connection to fully close | `None` | | `is_connected` (property) | Check connection status | `bool` | ### Interface Definition @@ -74,6 +71,10 @@ class Transport(ABC): async def close(self) -> None: """Close connection""" + @abstractmethod + async def wait_closed(self) -> None: + """Wait for connection to fully close""" + @property @abstractmethod def is_connected(self) -> bool: @@ -84,8 +85,7 @@ class Transport(ABC): ## WebSocket Transport -!!!success "Primary Implementation" - `WebSocketTransport` provides persistent, full-duplex, bidirectional communication over WebSocket protocol (RFC 6455). +`WebSocketTransport` provides persistent, full-duplex, bidirectional communication over WebSocket protocol (RFC 6455). ### Quick Start @@ -130,8 +130,9 @@ async def websocket_endpoint(websocket: WebSocket): await transport.send(b"Response") ``` -!!!tip "Automatic Adapter Selection" - WebSocketTransport automatically detects whether it's wrapping a FastAPI WebSocket or a client connection and selects the appropriate adapter. +**Note**: WebSocketTransport automatically detects whether it's wrapping a FastAPI WebSocket or a client connection and selects the appropriate adapter. + +[→ See how endpoints use WebSocketTransport](./endpoints.md) ### Configuration Parameters @@ -154,8 +155,6 @@ async def websocket_endpoint(websocket: WebSocket): ### Connection States -**Transport State Machine:** - WebSocket connections transition through multiple states during their lifecycle. This diagram shows all possible states and transitions: ```mermaid @@ -201,12 +200,7 @@ else: ### Ping/Pong Keepalive -!!!info "Automatic Health Monitoring" - WebSocket automatically sends ping frames at `ping_interval` to detect broken connections. - -**Keepalive Flow:** - -**WebSocket Ping/Pong Health Check:** +WebSocket automatically sends ping frames at `ping_interval` to detect broken connections. This sequence diagram shows the automatic ping/pong mechanism for detecting broken connections: @@ -277,19 +271,17 @@ except Exception as e: logger.error(f"Error during shutdown: {e}") ``` -!!!success "Close Frame Exchange" - The transport sends a WebSocket close frame and waits for the peer's close frame within `close_timeout` before terminating the connection. +**Note**: The transport sends a WebSocket close frame and waits for the peer's close frame within `close_timeout` before terminating the connection. ### Adapter Pattern -!!!tip "Transparent Multi-Implementation Support" - AIP uses adapters to provide a unified interface across different WebSocket libraries without exposing implementation details. +AIP uses adapters to provide a unified interface across different WebSocket libraries without exposing implementation details. **Supported WebSocket Implementations:** | Implementation | Use Case | Adapter | |----------------|----------|---------| -| **websockets library** | Client-side connections | `WebSocketClientAdapter` | +| **websockets library** | Client-side connections | `WebSocketsLibAdapter` | | **FastAPI WebSocket** | Server-side endpoints | `FastAPIWebSocketAdapter` | **Automatic Detection:** @@ -298,7 +290,7 @@ except Exception as e: # Server-side: Automatically uses FastAPIWebSocketAdapter transport = WebSocketTransport(websocket=fastapi_websocket) -# Client-side: Automatically uses WebSocketClientAdapter +# Client-side: Automatically uses WebSocketsLibAdapter transport = WebSocketTransport() await transport.connect("ws://server:8000/ws") ``` @@ -314,13 +306,10 @@ await transport.connect("ws://server:8000/ws") ## Message Encoding -!!!info "UTF-8 JSON Serialization" - AIP uses UTF-8 encoded JSON for all messages, leveraging Pydantic for serialization/deserialization. +AIP uses UTF-8 encoded JSON for all messages, leveraging Pydantic for serialization/deserialization. ### Encoding Flow -**Message Serialization Pipeline:** - This diagram shows the transformation steps from Pydantic model to network bytes: ```mermaid @@ -402,64 +391,70 @@ print(f"Task ID: {msg.task_id}") ### Optimization Strategies -!!!success "Large Messages Strategy" - For messages approaching `max_size`: - - **Option 1: Compression** - ```python +**Large Messages Strategy:** + +For messages approaching `max_size`: + +**Option 1: Compression** +```python import gzip compressed = gzip.compress(large_data) await transport.send(compressed) ``` - - **Option 2: Chunking** - ```python + +**Option 2: Chunking** +```python chunk_size = 1024 * 1024 # 1MB chunks for i in range(0, len(large_data), chunk_size): chunk = large_data[i:i+chunk_size] await transport.send(chunk) ``` - - **Option 3: Streaming Protocol** - Consider implementing a custom streaming protocol for very large payloads. -!!!tip "High Throughput Strategy" - For high message rates: - - **Batch Messages:** - ```python +**Option 3: Streaming Protocol** + +Consider implementing a custom streaming protocol for very large payloads. + +[→ See message encoding details in Protocol Reference](./protocols.md) + +**High Throughput Strategy:** + +For high message rates: + +**Batch Messages:** +```python batch = [msg1, msg2, msg3, msg4] batch_json = json.dumps([msg.model_dump() for msg in batch]) await transport.send(batch_json.encode('utf-8')) ``` - - **Reduce Ping Frequency:** - ```python - transport = WebSocketTransport( - ping_interval=60.0 # Less overhead - ) - ``` -!!!info "Low Latency Strategy" - For real-time applications: - - **Fast Failure Detection:** - ```python +**Reduce Ping Frequency:** +```python +transport = WebSocketTransport( + ping_interval=60.0 # Less overhead +) +``` + +**Low Latency Strategy:** + +For real-time applications: + +**Fast Failure Detection:** +```python transport = WebSocketTransport( ping_interval=10.0, # Quick detection ping_timeout=30.0 ) ``` - - **Dedicated Connections:** - ```python - # One transport per device (no sharing) - device_transports = { - device_id: WebSocketTransport() - for device_id in devices - } - ``` + +**Dedicated Connections:** +```python +# One transport per device (no sharing) +device_transports = { + device_id: WebSocketTransport() + for device_id in devices +} +``` --- @@ -500,8 +495,7 @@ print(f"Task ID: {msg.task_id}") ### Custom Transport Implementation -!!!example "Extend Transport Interface" - Implement custom transports for specialized protocols: +Implement custom transports for specialized protocols: ```python from aip.transport.base import Transport @@ -527,24 +521,29 @@ class CustomTransport(Transport): **Integration:** +Custom transports can be used directly with protocols: + ```python -from aip.endpoints import DeviceClientEndpoint +from aip.protocol import AIPProtocol -# Use custom transport -endpoint = DeviceClientEndpoint( - transport=CustomTransport(), - ufo_client=client -) +# Use custom transport with protocol +transport = CustomTransport() +await transport.connect("custom://server:port") + +protocol = AIPProtocol(transport) +await protocol.send_message(message) ``` +[→ See Transport interface specification above](#transport-interface) +[→ See Protocol usage examples](./protocols.md) + --- ## Best Practices ### Environment-Specific Configuration -!!!success "Network-Aware Configuration" - Adapt transport settings to your deployment environment's characteristics. +Adapt transport settings to your deployment environment's characteristics. | Environment | ping_interval | ping_timeout | max_size | close_timeout | |-------------|--------------|--------------|----------|---------------| @@ -585,8 +584,7 @@ transport = WebSocketTransport( ### Connection Health Monitoring -!!!tip "Proactive Health Checks" - Always verify connection status before critical operations: +Always verify connection status before critical operations: ```python # Check before sending @@ -600,8 +598,7 @@ await transport.send(data) ### Resilience Integration -!!!info "Combine with Reconnection Strategy" - Transport alone provides low-level communication. Combine with resilience components for production readiness: +Transport alone provides low-level communication. Combine with resilience components for production readiness: ```python from aip.resilience import ReconnectionStrategy @@ -616,6 +613,7 @@ except ConnectionError: ``` [→ See Resilience documentation](./resilience.md) +[→ See HeartbeatManager for connection health monitoring](./resilience.md#heartbeat-manager) ### Logging and Observability diff --git a/documents/docs/client/computer.md b/documents/docs/client/computer.md index 821dbbb03..16d98ac18 100644 --- a/documents/docs/client/computer.md +++ b/documents/docs/client/computer.md @@ -1,54 +1,39 @@ -# Computer - MCP Tool Execution Layer +# Computer -## Overview +The **Computer** class is the core execution layer of the UFO client. It manages MCP (Model Context Protocol) tool execution, maintains tool registries, and provides thread-isolated execution for reliability. Each Computer instance represents a distinct execution context with its own namespace and resource management. -The **Computer** class is the core execution layer that manages MCP (Model Context Protocol) servers and provides tool execution capabilities for UFO² agents. It acts as an abstraction layer between high-level commands and low-level MCP tool calls. +## Architecture Overview +The Computer layer provides the execution engine for MCP tools with three main components: + +```mermaid +graph TB + CommandRouter["CommandRouter
Command Routing"] + ComputerManager["ComputerManager
Instance Management"] + Computer["Computer
Core Execution Layer"] + MCPServerManager["MCP Server Manager
Process Isolation"] + + CommandRouter -->|Routes To| ComputerManager + ComputerManager -->|Creates & Manages| Computer + Computer -->|Data Collection| DataServers["Data Collection Servers
screenshot, ui_detection, etc."] + Computer -->|Actions| ActionServers["Action Servers
gui_automation, file_operations, etc."] + Computer -->|Uses| ToolsRegistry["Tools Registry
tool_type::tool_name → MCPToolCall"] + Computer -->|Provides| MetaTools["Meta Tools
list_tools built-in introspection"] + Computer -->|Delegates To| MCPServerManager ``` -┌─────────────────────────────────────────────────────┐ -│ CommandRouter │ -│ (Routes commands to computers) │ -└───────────────────┬─────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────┐ -│ ComputerManager │ -│ (Creates & manages Computer instances) │ -└───────────────────┬─────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────┐ -│ Computer │ -│ ┌─────────────────────────────────────────────┐ │ -│ │ Data Collection Servers (namespace1, ...) │ │ -│ │ - screenshot, ui_detection, etc. │ │ -│ └─────────────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────┐ │ -│ │ Action Servers (namespace2, ...) │ │ -│ │ - gui_automation, file_operations, etc. │ │ -│ └─────────────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────┐ │ -│ │ Tools Registry │ │ -│ │ - tool_type::tool_name → MCPToolCall │ │ -│ └─────────────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────┐ │ -│ │ Meta Tools │ │ -│ │ - list_tools (built-in introspection) │ │ -│ └─────────────────────────────────────────────┘ │ -└────────────────────┬────────────────────────────────┘ - │ - ▼ - ┌─────────────────────┐ - │ MCP Server Manager │ - │ (Process isolation) │ - └─────────────────────┘ -``` -!!!info "Key Responsibilities" - - **Tool Registration**: Register tools from multiple MCP servers with namespace isolation - - **Command Routing**: Convert high-level commands to MCP tool calls - - **Execution Management**: Execute tools in isolated thread pools with timeout protection - - **Meta Tools**: Provide introspection capabilities (e.g., `list_tools`) +**Computer** manages MCP tool execution with thread isolation and timeout control (6000-second timeout, 10-worker thread pool). +**ComputerManager** handles multiple Computer instances with namespace-based routing. +**CommandRouter** routes and executes commands across Computer instances with early-exit support. + +### Key Responsibilities + +- **Tool Registration**: Register tools from multiple MCP servers with namespace isolation +- **Command Routing**: Convert high-level commands to MCP tool calls +- **Execution Management**: Execute tools in isolated thread pools with timeout protection +- **Meta Tools**: Provide introspection capabilities (e.g., `list_tools`) + +## Table of Contents ## Core Components @@ -84,8 +69,7 @@ Computer supports two types of tool namespaces: "action::type_text" # Type text ``` -!!!tip "Namespace Isolation" - Different namespaces allow the same tool name to exist in both data collection and action contexts. For example, both `data_collection::get_file_info` and `action::get_file_info` can coexist. +> **Note:** Different namespaces allow the same tool name to exist in both data collection and action contexts. For example, both `data_collection::get_file_info` and `action::get_file_info` can coexist. ### 2. ComputerManager Class @@ -127,10 +111,11 @@ mcp: reset: false ``` -!!!warning "Configuration Requirements" - - Each agent must have at least a `default` root configuration - - If `root_name` is not found, the manager falls back to `default` - - Missing configurations will raise a `ValueError` +**Configuration Requirements** + +- Each agent must have at least a `default` root configuration +- If `root_name` is not found, the manager falls back to `default` +- Missing configurations will raise a `ValueError` ### 3. CommandRouter Class @@ -138,14 +123,13 @@ The `CommandRouter` executes commands on the appropriate `Computer` instance by #### Execution Flow -``` -Command → CommandRouter → ComputerManager → Computer → MCP Tool - │ - └─ get_or_create(agent, process, root) - │ - └─ computer.command2tool(command) - │ - └─ computer.run_actions([tool_call]) +```mermaid +graph LR + Command --> CommandRouter + CommandRouter --> ComputerManager + ComputerManager -->|get_or_create| Computer + Computer -->|command2tool| ToolCall[MCPToolCall] + ToolCall -->|run_actions| Result[MCP Tool Result] ``` ## Initialization @@ -184,8 +168,7 @@ computer = Computer( await computer.async_init() ``` -!!!danger "Async Initialization Required" - You **must** call `await computer.async_init()` after creating a `Computer` instance. This registers all MCP servers and their tools asynchronously. +> **⚠️ Important:** You **must** call `await computer.async_init()` after creating a `Computer` instance. This registers all MCP servers and their tools asynchronously. ### ComputerManager Initialization @@ -255,8 +238,7 @@ tool_call = computer.command2tool(command) results = await computer.run_actions([tool_call]) ``` -!!!tip "Automatic Tool Type Detection" - If `tool_type` is not specified in the command, the `command2tool()` method will automatically detect whether the tool is registered as `data_collection` or `action`. +If `tool_type` is not specified in the command, the `command2tool()` method will automatically detect whether the tool is registered as `data_collection` or `action`. ### Batch Tool Execution @@ -310,14 +292,14 @@ result = await asyncio.wait_for( ) ``` -!!!warning "Timeout Handling" - If a tool execution exceeds 6000 seconds, it will be cancelled and return a timeout error: - ```python - CallToolResult( - is_error=True, - content=[TextContent(text="Tool execution timed out after 6000s")] - ) - ``` +If a tool execution exceeds 6000 seconds, it will be cancelled and return a timeout error: + +```python +CallToolResult( + is_error=True, + content=[TextContent(text="Tool execution timed out after 6000s")] +) +``` ## Meta Tools @@ -354,19 +336,20 @@ result = await computer.run_actions([tool_call]) tools = result[0].data # List of available action tools ``` -!!!example "Meta Tool Example" - ```python - # List all tools in "screenshot" namespace - result = await computer.run_actions([ - MCPToolCall( - tool_key="data_collection::list_tools", - tool_name="list_tools", - parameters={"namespace": "screenshot", "remove_meta": True} - ) - ]) - - # Returns: [{"tool_name": "take_screenshot", "description": "...", ...}] - ``` +**Example:** + +```python +# List all tools in "screenshot" namespace +result = await computer.run_actions([ + MCPToolCall( + tool_key="data_collection::list_tools", + tool_name="list_tools", + parameters={"namespace": "screenshot", "remove_meta": True} + ) +]) + +# Returns: [{"tool_name": "take_screenshot", "description": "...", ...}] +``` ## Dynamic Server Management @@ -404,10 +387,11 @@ await computer.delete_server( ) ``` -!!!tip "Use Cases for Dynamic Server Management" - - Add specialized tools for specific tasks - - Remove servers to reduce memory footprint - - Hot-reload MCP servers during development +**Use cases for dynamic server management:** + +- Add specialized tools for specific tasks +- Remove servers to reduce memory footprint +- Hot-reload MCP servers during development ## Command Routing @@ -463,8 +447,7 @@ results = await router.execute( ) ``` -!!!warning "Early Exit Behavior" - When `early_exit=True`, if a command fails, subsequent commands will **not** be executed, and their results will be set to `ResultStatus.FAILED`. +> **⚠️ Warning:** When `early_exit=True`, if a command fails, subsequent commands will **not** be executed, and their results will be set to `ResultStatus.SKIPPED`. ## Tool Registry @@ -498,23 +481,27 @@ print(tool_info.mcp_server) # Reference to MCP server ## Best Practices -!!!success "Configuration Best Practices" - 1. **Use namespaces wisely**: Group related tools under meaningful namespaces - 2. **Separate concerns**: Use `data_collection` for read-only operations, `action` for state changes - 3. **Configure timeouts**: Adjust `_tool_timeout` for long-running operations - 4. **Use default root**: Always provide a `default` root configuration as fallback - -!!!tip "Performance Optimization" - 1. **Register servers in parallel**: The `async_init()` method already does this via `asyncio.gather()` - 2. **Reuse Computer instances**: Let `ComputerManager` cache instances rather than creating new ones - 3. **Limit concurrent tools**: The thread pool has 10 workers; excessive parallel tools may queue - 4. **Reset servers carefully**: Setting `reset=True` in server config will restart the MCP server process - -!!!danger "Common Pitfalls" - - **Forgetting `async_init()`**: Always call after creating a `Computer` instance - - **Tool key collisions**: Ensure tool names are unique within each `tool_type` - - **Timeout too short**: Some operations (e.g., file downloads) may need longer timeouts - - **Blocking in meta tools**: Meta tools should be fast; avoid I/O operations +### Configuration + +1. **Use namespaces wisely**: Group related tools under meaningful namespaces +2. **Separate concerns**: Use `data_collection` for read-only operations, `action` for state changes +3. **Configure timeouts**: Adjust `_tool_timeout` for long-running operations +4. **Use default root**: Always provide a `default` root configuration as fallback + +### Performance Optimization + +1. **Register servers in parallel**: The `async_init()` method already does this via `asyncio.gather()` +2. **Reuse Computer instances**: Let `ComputerManager` cache instances rather than creating new ones +3. **Limit concurrent tools**: The thread pool has 10 workers; excessive parallel tools may queue +4. **Reset servers carefully**: Setting `reset=True` in server config will restart the MCP server process + +### Common Pitfalls + +> **⚠️ Important:** Avoid these common mistakes: +> - **Forgetting `async_init()`**: Always call after creating a `Computer` instance +> - **Tool key collisions**: Ensure tool names are unique within each `tool_type` +> - **Timeout too short**: Some operations (e.g., file downloads) may need longer timeouts +> - **Blocking in meta tools**: Meta tools should be fast; avoid I/O operations ## Error Handling diff --git a/documents/docs/client/computer_manager.md b/documents/docs/client/computer_manager.md index 8e3b9e72d..4b8adc03f 100644 --- a/documents/docs/client/computer_manager.md +++ b/documents/docs/client/computer_manager.md @@ -1,17 +1,15 @@ -# 🖥️ Computer Manager & Computer +# Computer Manager & Computer -!!!quote "Multi-Namespace Tool Execution" - The **Computer Manager** orchestrates multiple **Computer** instances, each representing an isolated execution namespace with dedicated MCP servers and tools. This enables context-specific tool routing and fine-grained control over data collection vs. action execution. +The **Computer Manager** orchestrates multiple **Computer** instances, each representing an isolated execution namespace with dedicated MCP servers and tools. This enables context-specific tool routing and fine-grained control over data collection vs. action execution. --- -## 📋 Overview +## Overview -!!!info "Two-Layer Architecture" - The Computer layer consists of two components working together: - - - **ComputerManager**: High-level orchestrator managing multiple Computer instances - - **Computer**: Individual execution namespace with its own MCP servers and tool registry +The Computer layer consists of two components working together: + +- **ComputerManager**: High-level orchestrator managing multiple Computer instances +- **Computer**: Individual execution namespace with its own MCP servers and tool registry ### Computer Manager Responsibilities @@ -70,7 +68,7 @@ graph TB --- -## 🏗️ Computer Manager Architecture +## 🏗�?Computer Manager Architecture ### Computer Instance Management @@ -106,12 +104,13 @@ graph LR | **Data Collection** | Gathering information, non-invasive queries | Screenshots, UI element detection, app state | | **Action** | Performing actions, invasive operations | GUI automation, file operations, app control | -!!!success "Separation of Concerns" - Data collection tools can't modify state, while action tools have full control. This prevents accidental state changes during information gathering. +Data collection tools are designed for non-invasive information gathering, while action tools have full control for state-changing operations. --- -## 🖥️ Computer (Instance) Architecture +## Computer Manager Architecture + +## 🖥�?Computer (Instance) Architecture ### Internal Structure @@ -131,9 +130,9 @@ graph TB subgraph "Tool Registry" Registry --> TR[_tools_registry Dict] - TR -->|key: action.click| T1[MCPToolCall] - TR -->|key: data.screenshot| T2[MCPToolCall] - TR -->|key: action.list_tools| T3[Meta Tool] + TR -->|key: action::click| T1[MCPToolCall] + TR -->|key: data_collection::screenshot| T2[MCPToolCall] + TR -->|key: action::list_tools| T3[Meta Tool] end subgraph "Execution Engine" @@ -158,56 +157,57 @@ graph TB |-----------|------|---------| | `_name` | `str` | Computer name (identifier) | | `_process_name` | `str` | Associated process (e.g., "notepad.exe") | -| `_data_collection_servers` | `Dict[str, BaseMCPServer]` | Namespace → MCP server mapping (data collection) | -| `_action_servers` | `Dict[str, BaseMCPServer]` | Namespace → MCP server mapping (actions) | -| `_tools_registry` | `Dict[str, MCPToolCall]` | Tool key → tool info mapping | +| `_data_collection_servers` | `Dict[str, BaseMCPServer]` | Namespace �?MCP server mapping (data collection) | +| `_action_servers` | `Dict[str, BaseMCPServer]` | Namespace �?MCP server mapping (actions) | +| `_tools_registry` | `Dict[str, MCPToolCall]` | Tool key �?tool info mapping | | `_meta_tools` | `Dict[str, Callable]` | Built-in meta tools | | `_executor` | `ThreadPoolExecutor` | Thread pool for tool execution (10 workers) | | `_tool_timeout` | `int` | Tool execution timeout: **6000 seconds (100 minutes)** | -!!!warning "Tool Timeout: 100 Minutes" - Based on source code: `self._tool_timeout = 6000` (6000 seconds = 100 minutes). This allows very long-running operations but prevents indefinite hangs. +> **Note:** The tool execution timeout is 6000 seconds (100 minutes), allowing for very long-running operations while preventing indefinite hangs. --- -## 🚀 Initialization +## Initialization ### Computer Manager Initialization -!!!example "Creating Computer Manager" - ```python - from ufo.client.computer import ComputerManager - from ufo.client.mcp.mcp_server_manager import MCPServerManager - from config.config_loader import get_ufo_config - - # 1. Get UFO configuration - ufo_config = get_ufo_config() - - # 2. Initialize MCP server manager - mcp_server_manager = MCPServerManager() - - # 3. Create computer manager - computer_manager = ComputerManager( - ufo_config.to_dict(), - mcp_server_manager - ) - ``` +**Creating Computer Manager:** + +```python +from ufo.client.computer import ComputerManager +from ufo.client.mcp.mcp_server_manager import MCPServerManager +from config.config_loader import get_ufo_config + +# 1. Get UFO configuration +ufo_config = get_ufo_config() + +# 2. Initialize MCP server manager +mcp_server_manager = MCPServerManager() + +# 3. Create computer manager +computer_manager = ComputerManager( + ufo_config.to_dict(), + mcp_server_manager +) +``` ### Computer Instance Initialization -!!!example "Computer Async Initialization" - ```python - computer = Computer( - name="default_agent", - process_name="explorer.exe", - mcp_server_manager=mcp_server_manager, - data_collection_servers_config=[...], - action_servers_config=[...] - ) - - # Async initialization (required) - await computer.async_init() - ``` +**Computer Async Initialization:** + +```python +computer = Computer( + name="default_agent", + process_name="explorer.exe", + mcp_server_manager=mcp_server_manager, + data_collection_servers_config=[...], + action_servers_config=[...] +) + +# Async initialization (required) +await computer.async_init() +``` **Initialization Flow:** @@ -266,8 +266,7 @@ action_servers: ### CommandRouter -!!!info "Smart Command Resolution" - The CommandRouter resolves which Computer instance should handle each command based on agent/process/root context. +The CommandRouter resolves which Computer instance should handle each command based on agent/process/root context. **Routing Signature:** @@ -320,8 +319,7 @@ graph TD ### Tool Execution Pipeline -!!!success "Thread Isolation for Blocking Operations" - MCP tools are executed in isolated threads to prevent blocking operations (like `time.sleep`) from blocking the main event loop and causing WebSocket disconnections. +MCP tools are executed in isolated threads to prevent blocking operations (like `time.sleep`) from blocking the main event loop and causing WebSocket disconnections. **Execution Flow:** @@ -398,23 +396,22 @@ result = await asyncio.wait_for( --- -## 🛠️ Tool Registry +## 🛠�?Tool Registry ### Tool Registration -!!!info "Dynamic Tool Discovery" - Tools are discovered from MCP servers during initialization and registered with unique keys. +Tools are discovered from MCP servers during initialization and registered with unique keys. **Tool Key Format:** ``` -. +:: Examples: -- action.click -- action.type_text -- data_collection.screenshot -- data_collection.get_ui_elements +- action::click +- action::type_text +- data_collection::screenshot +- data_collection::get_ui_elements ``` **Registration Process:** @@ -446,7 +443,7 @@ async def register_one_mcp_server( | Field | Type | Description | |-------|------|-------------| -| `tool_key` | `str` | Unique key (e.g., "action.click") | +| `tool_key` | `str` | Unique key (e.g., "action::click") | | `tool_name` | `str` | Tool name (e.g., "click") | | `title` | `str` | Display title | | `namespace` | `str` | Server namespace | @@ -458,10 +455,9 @@ async def register_one_mcp_server( --- -## 🎯 Meta Tools +## Meta Tools -!!!tip "Built-In Computer Operations" - Meta tools are built-in methods decorated with `@meta_tool` that provide computer-level operations. +Meta tools are built-in methods decorated with `@meta_tool` that provide computer-level operations. **Example: list_tools Meta Tool** @@ -524,589 +520,40 @@ computer.reset() --- -## ✅ Best Practices - -!!!tip "Production Tips" - - **1. Monitor Tool Execution Times** - ```python - import time - start = time.time() - result = await computer._run_action(tool_call) - duration = time.time() - start - if duration > 300: # 5 minutes - logger.warning(f"Slow tool: {tool_call.tool_name} took {duration}s") - ``` - - **2. Handle Timeouts Gracefully** - ```python - # 100-minute timeout is generous but not infinite - # Design tools to complete within reasonable time - ``` - - **3. Use Namespace Isolation** - ```python - # Separate data collection from actions - data_tools = await computer.list_tools(tool_type="data_collection") - action_tools = await computer.list_tools(tool_type="action") - ``` - ---- - -## 🚀 Next Steps - -👉 [Device Info Provider](./device_info.md) - System profiling -👉 [MCP Integration](./mcp_integration.md) - MCP server details -👉 [UFO Client](./ufo_client.md) - Execution orchestration - -## Architecture - -``` -┌────────────────────────────────────────────────────┐ -│ ComputerManager │ -├────────────────────────────────────────────────────┤ -│ Computer Instances: │ -│ • default_agent - Default computer instance │ -│ • process_specific - Per-process computers │ -│ • custom_contexts - Custom computer instances │ -├────────────────────────────────────────────────────┤ -│ Command Routing: │ -│ • CommandRouter - Route by agent/process/root │ -│ • Namespace resolution │ -│ • Tool lookup and execution │ -├────────────────────────────────────────────────────┤ -│ Configuration: │ -│ • Data collection servers config │ -│ • Action servers config │ -│ • MCP server manager integration │ -└────────────────────────────────────────────────────┘ -``` - -## Computer Instances - -Each computer instance is an isolated execution environment with its own: - -- **MCP Servers** - Data collection and action servers -- **Tool Registry** - Available tools and their configurations -- **Meta Tools** - Built-in computer operations -- **Execution Context** - Agent name, process name, root name - -### Computer Namespaces - -**Data Collection Namespace:** -- Tools for gathering information -- Screenshot capture -- UI element detection -- Application state queries - -**Action Namespace:** -- Tools for performing actions -- GUI automation (click, type, etc.) -- Application control -- File system operations - -!!!info "Namespace Isolation" - Each namespace can have different MCP servers, allowing fine-grained control over data collection vs. action execution. - -## Initialization - -```python -from ufo.client.computer import ComputerManager -from ufo.client.mcp.mcp_server_manager import MCPServerManager -from config.config_loader import get_ufo_config - -# Get UFO configuration -ufo_config = get_ufo_config() - -# Initialize MCP server manager -mcp_server_manager = MCPServerManager() - -# Create computer manager -computer_manager = ComputerManager( - ufo_config.to_dict(), - mcp_server_manager -) -``` - -**Configuration Structure:** - -The `ufo_config` contains: - -```python -{ - "data_collection_servers": [ - { - "namespace": "screenshot_collector", - "type": "local", - "module": "ufo.client.mcp.local_servers.screenshot_server", - "reset": False - }, - { - "namespace": "ui_collector", - "type": "local", - "module": "ufo.client.mcp.local_servers.ui_server", - "reset": False - } - ], - "action_servers": [ - { - "namespace": "gui_automator", - "type": "local", - "module": "ufo.client.mcp.local_servers.automation_server", - "reset": False - } - ] -} -``` - -## Command Router - -The `CommandRouter` routes commands to the appropriate computer instance and tool: - -```python -from ufo.client.computer import CommandRouter -from aip.messages import Command, Result - -router = CommandRouter(computer_manager=computer_manager) - -# Execute commands -results = await router.execute( - agent_name="HostAgent", - process_name="explorer.exe", - root_name="navigate_folder", - commands=[ - Command(action="click", parameters={"label": "File"}), - Command(action="type_text", parameters={"text": "Hello"}) - ] -) -``` - -### Routing Logic - -``` -Command → Resolve Computer → Lookup Tool → Execute → Return Result - │ │ │ │ │ - │ │ │ │ │ -Agent/ Computer MCP Tool Isolated Result -Process/ Instance Registry Thread Object -Root Name Pool -``` - -**Step-by-Step:** - -1. **Resolve Computer Instance** - - Based on `agent_name`, `process_name`, `root_name` - - Falls back to default computer if not found - -2. **Lookup Tool** - - Search in tool registry by action name - - Raise error if tool not found - -3. **Execute Tool** - - Run in isolated thread pool with timeout - - Capture result or error - -4. **Return Result** - - Structured Result object with status, observation, error - -### Command Execution - -```python -async def execute( - self, - agent_name: str, - process_name: str, - root_name: str, - commands: List[Command] -) -> List[Result]: - """Execute a list of commands.""" - - results = [] - - for command in commands: - try: - # Get computer instance - computer = self.computer_manager.get_computer( - agent_name, process_name, root_name - ) - - # Execute command - result = await computer.execute_command(command) - results.append(result) - - except Exception as e: - # Create error result - error_result = Result( - action=command.action, - status=ResultStatus.ERROR, - error_message=str(e), - observation=f"Failed to execute {command.action}" - ) - results.append(error_result) - - return results -``` - -## Computer Lifecycle - -### Creation - -Computers are created on demand: - -```python -def get_or_create_computer( - self, - agent_name: str, - process_name: str, - root_name: str -) -> Computer: - """Get existing computer or create new one.""" - - key = f"{agent_name}_{process_name}_{root_name}" - - if key not in self.computers: - computer = Computer( - name=key, - process_name=process_name, - mcp_server_manager=self.mcp_server_manager, - data_collection_servers_config=self.data_collection_config, - action_servers_config=self.action_config - ) - await computer.async_init() # Initialize MCP servers - self.computers[key] = computer - - return self.computers[key] -``` - -### Initialization - -Each computer initializes its MCP servers asynchronously: - -```python -class Computer: - async def async_init(self) -> None: - """Asynchronous initialization.""" - - # Initialize data collection servers - self._data_collection_servers = self._init_data_collection_servers() - - # Initialize action servers - self._action_servers = self._init_action_servers() - - # Register MCP servers in parallel - await asyncio.gather( - self.register_mcp_servers( - self._data_collection_servers, - tool_type="data_collection" - ), - self.register_mcp_servers( - self._action_servers, - tool_type="action" - ) - ) -``` - -!!!tip "Parallel Initialization" - MCP servers are registered in parallel for faster startup. - -### Reset - -Reset clears all computer instances and their state: - -```python -def reset(self): - """Reset all computer instances.""" - - # Clear all computers - for computer in self.computers.values(): - computer.cleanup() - - self.computers.clear() - - self.logger.info("Computer manager has been reset.") -``` - -**When to Reset:** - -- Before starting a new task -- On task completion/failure -- On client reconnection - -## Tool Registry - -Each computer maintains a registry of available tools: - -```python -class Computer: - def __init__(self, ...): - self._tools_registry: Dict[str, MCPToolCall] = {} - - async def register_mcp_servers( - self, - servers: Dict[str, BaseMCPServer], - tool_type: str - ): - """Register MCP servers and their tools.""" - - for namespace, server in servers.items(): - # Get tools from server - tools = await server.list_tools() - - # Register each tool - for tool in tools: - tool_name = f"{namespace}.{tool.name}" - self._tools_registry[tool_name] = MCPToolCall( - mcp_server=server, - tool_name=tool.name, - namespace=namespace, - tool_type=tool_type - ) -``` - -### Tool Lookup - -```python -def get_tool(self, action: str) -> MCPToolCall: - """Look up tool by action name.""" - - if action in self._tools_registry: - return self._tools_registry[action] - - # Try namespace-qualified lookup - for namespace in self._action_servers.keys(): - qualified_name = f"{namespace}.{action}" - if qualified_name in self._tools_registry: - return self._tools_registry[qualified_name] - - raise ValueError(f"Tool '{action}' not found in registry") -``` - -## Tool Execution - -### Isolated Execution - -Tools are executed in isolated thread pools to prevent blocking: - -```python -class Computer: - def __init__(self, ...): - # Thread pool for isolating blocking MCP tool calls - self._executor = concurrent.futures.ThreadPoolExecutor( - max_workers=10, - thread_name_prefix="mcp_tool_" - ) - - # Tool execution timeout (seconds) - self._tool_timeout = 6000 # 100 minutes -``` - -### Execute Command - -```python -async def execute_command(self, command: Command) -> Result: - """Execute a single command.""" - - try: - # Lookup tool - tool = self.get_tool(command.action) - - # Execute with timeout - loop = asyncio.get_event_loop() - future = loop.run_in_executor( - self._executor, - tool.execute, - command.parameters - ) - - result = await asyncio.wait_for( - future, - timeout=self._tool_timeout - ) - - return Result( - action=command.action, - status=ResultStatus.SUCCESS, - observation=result.observation, - data=result.data - ) - - except asyncio.TimeoutError: - return Result( - action=command.action, - status=ResultStatus.ERROR, - error_message=f"Tool execution timeout ({self._tool_timeout}s)", - observation="Tool execution exceeded time limit" - ) - - except Exception as e: - return Result( - action=command.action, - status=ResultStatus.ERROR, - error_message=str(e), - observation=f"Tool execution failed: {e}" - ) -``` - -!!!warning "Timeout Protection" - Tools have a default 100-minute timeout. Adjust `_tool_timeout` for different needs. - -## Meta Tools - -Computers support meta tools - built-in operations that don't require MCP servers: - -```python -class Computer: - @Computer.meta_tool("get_system_info") - def get_system_info(self) -> Dict[str, Any]: - """Get system information.""" - return { - "cpu_count": os.cpu_count(), - "memory_gb": psutil.virtual_memory().total / (1024**3), - "platform": platform.system() - } - - @Computer.meta_tool("ping") - def ping(self) -> str: - """Health check.""" - return "pong" -``` - -**Meta Tool Registration:** - -```python -def __init__(self, ...): - # Register meta tools - for attr in dir(self): - method = getattr(self, attr) - if callable(method) and hasattr(method, "_meta_tool_name"): - name = getattr(method, "_meta_tool_name") - self._meta_tools[name] = method -``` - -## Configuration - -### MCP Server Configuration - -**Data Collection Servers:** - -```yaml -data_collection_servers: - - namespace: screenshot_collector - type: local - module: ufo.client.mcp.local_servers.screenshot_server - reset: false - - - namespace: ui_detector - type: local - module: ufo.client.mcp.local_servers.ui_detection_server - reset: false -``` - -**Action Servers:** - -```yaml -action_servers: - - namespace: gui_automator - type: local - module: ufo.client.mcp.local_servers.automation_server - reset: false - - - namespace: file_ops - type: local - module: ufo.client.mcp.local_servers.file_server - reset: false -``` - -### Server Types - -**Local MCP Servers:** -- Run in same process via FastMCP -- Fastest execution -- Shared memory with client - -**Remote MCP Servers:** -- Connect via HTTP -- Can run on different machines -- Useful for resource-intensive tools - -See [MCP Integration](./mcp_integration.md) for MCP server details. - ## Best Practices -**Use Unique Namespaces** - -```python -# Good - clear namespace separation -data_collection_servers: - - namespace: screenshot_collector - - namespace: ui_detector - -# Bad - generic namespaces -data_collection_servers: - - namespace: default - - namespace: default_2 -``` - -**Configure Appropriate Timeouts** +### Monitor Tool Execution Times ```python -# For long-running operations -computer._tool_timeout = 600 # 10 minutes - -# For quick operations -computer._tool_timeout = 30 # 30 seconds +import time +start = time.time() +result = await computer._run_action(tool_call) +duration = time.time() - start +if duration > 300: # 5 minutes + logger.warning(f"Slow tool: {tool_call.tool_name} took {duration}s") ``` -**Handle Tool Failures Gracefully** +### Handle Timeouts Gracefully ```python -result = await computer.execute_command(command) - -if result.status == ResultStatus.ERROR: - logger.error(f"Tool failed: {result.error_message}") - # Implement recovery logic +# 100-minute timeout is generous but not infinite +# Design tools to complete within reasonable time ``` -**Reset Between Tasks** +### Use Namespace Isolation ```python -computer_manager.reset() # Clear all computers and state +# Separate data collection from actions +data_tools = await computer.list_tools(tool_type="data_collection") +action_tools = await computer.list_tools(tool_type="action") ``` -## Integration Points - -### UFO Client - -The UFO Client uses the Command Router: - -```python -action_results = await self.command_router.execute( - agent_name=self.agent_name, - process_name=self.process_name, - root_name=self.root_name, - commands=commands -) -``` - -See [UFO Client](./ufo_client.md) for execution details. - -### MCP Server Manager - -The Computer Manager uses MCP Server Manager to create servers: - -```python -mcp_server = self.mcp_server_manager.create_or_get_server( - mcp_config=server_config, - reset=False, - process_name=process_name -) -``` - -See [MCP Integration](./mcp_integration.md) for MCP details. +--- -## Next Steps +## 🚀 Next Steps -- [UFO Client](./ufo_client.md) - Execution orchestration -- [MCP Integration](./mcp_integration.md) - MCP server management -- [Quick Start](./quick_start.md) - Get started with client -- [Configuration](../configuration/system/overview.md) - UFO configuration +👉 [Device Info Provider](./device_info.md) - System profiling +👉 [MCP Integration](./mcp_integration.md) - MCP server details +👉 [UFO Client](./ufo_client.md) - Execution orchestration +👉 [Quick Start](./quick_start.md) - Get started with client +👉 [Configuration](../configuration/system/overview.md) - UFO configuration diff --git a/documents/docs/client/device_info.md b/documents/docs/client/device_info.md index 5c27e2b25..90483a7d6 100644 --- a/documents/docs/client/device_info.md +++ b/documents/docs/client/device_info.md @@ -1,15 +1,13 @@ # 📱 Device Info Provider -!!!quote "Know Your Device" - The **Device Info Provider** collects comprehensive system information from client devices during registration, enabling intelligent task assignment and device selection in constellation (multi-device) scenarios. +The **Device Info Provider** collects comprehensive system information from client devices during registration, enabling intelligent task assignment and device selection in constellation (multi-device) scenarios. + +Device information is proactively collected during client registration and pushed to the server, reducing latency and enabling immediate task routing decisions. --- ## 📋 Overview -!!!info "Push Model Device Profiling" - Device information is **proactively collected** during client registration and pushed to the server. This reduces latency for constellation clients and enables immediate task routing decisions. - **Core Capabilities:** | Capability | Description | Use Case | @@ -36,8 +34,7 @@ ### DeviceSystemInfo Dataclass -!!!success "Lightweight & Essential" - The device info structure captures only essential information to minimize registration overhead. +The device info structure captures essential information to minimize registration overhead: ```mermaid classDiagram @@ -93,9 +90,6 @@ classDiagram ### Automatic Collection -!!!example "Collection at Registration" - Device info is collected automatically when the client registers with the server: - ```python from ufo.client.device_info_provider import DeviceInfoProvider @@ -157,8 +151,7 @@ sequenceDiagram ### Platform-Specific Features -!!!info "Intelligent Capability Detection" - Features are automatically detected based on the platform to enable capability-based device selection. +Features are automatically detected based on the platform to enable capability-based device selection. **Windows Features:** @@ -279,8 +272,7 @@ json_str = json.dumps(device_dict, indent=2) ### Graceful Degradation -!!!success "Fail-Safe Collection" - If any detection method fails, the provider returns minimal info instead of crashing. +If any detection method fails, the provider returns minimal info instead of crashing. **Error Handling Strategy:** @@ -387,6 +379,8 @@ return "unknown" ### WebSocket Client Registration +The WebSocket client uses the Device Info Provider during registration: + ```python # In websocket client's register_client() from ufo.client.device_info_provider import DeviceInfoProvider @@ -408,494 +402,55 @@ await self.registration_protocol.register_as_device( ) ``` -See [WebSocket Client](./websocket_client.md) for registration details. - ---- - -## ✅ Best Practices - -!!!tip "Production Recommendations" - - **1. Add Custom Metadata for Environment Tracking** - ```python - custom_meta = { - "environment": os.getenv("ENVIRONMENT", "development"), - "version": "1.0.0", - "deployment_region": "us-west-2" - } - ``` - - **2. Install psutil for Accurate Memory Detection** - ```bash - pip install psutil - ``` - - **3. Use Descriptive Client IDs** - ```python - # Include environment and location in client_id - client_id = f"device_{platform}_{env}_{location}_{instance_id}" - ``` - ---- - -## 🚀 Next Steps - -👉 [WebSocket Client](./websocket_client.md) - See how device info is used in registration -👉 [MCP Integration](./mcp_integration.md) - Understand tool capabilities -👉 [Server Overview](../server/overview.md) - Learn how servers use device info +See [WebSocket Client](./websocket_client.md) for complete registration flow details. -## Device System Info +### Agent Server -The core data structure: +The server receives device info during registration and stores it in the agent profile: ```python -@dataclass -class DeviceSystemInfo: - """Device system information - lightweight and essential.""" - - # Basic identification - device_id: str # Unique device identifier - platform: str # windows, linux, darwin, android, ios - os_version: str # OS version string - - # Hardware information - cpu_count: int # Number of CPU cores - memory_total_gb: float # Total RAM in GB - - # Network information - hostname: str # Device hostname - ip_address: str # Device IP address - - # Capability information - supported_features: List[str] # Feature list - platform_type: str # computer, mobile, web, iot - - # Metadata - schema_version: str = "1.0" # Schema version - custom_metadata: Dict[str, Any] # Custom metadata +# Server-side AgentProfile integration +device_info = registration_data["metadata"]["system_info"] +agent_profile.add_device(device_id, device_info) ``` -## Collecting System Info +See [Server Quick Start](../server/quick_start.md) for server-side processing details. -### Basic Collection - -```python -from ufo.client.device_info_provider import DeviceInfoProvider - -# Collect device information -system_info = DeviceInfoProvider.collect_system_info( - client_id="device_windows_001", - custom_metadata=None -) +--- -# Access information -print(f"Platform: {system_info.platform}") -print(f"CPU Cores: {system_info.cpu_count}") -print(f"Memory: {system_info.memory_total_gb}GB") -print(f"Hostname: {system_info.hostname}") -print(f"IP: {system_info.ip_address}") -print(f"Features: {system_info.supported_features}") -``` +## ✅ Best Practices -### With Custom Metadata +**1. Add Custom Metadata for Environment Tracking** ```python -# Add custom metadata from configuration -custom_metadata = { - "datacenter": "US-EAST-1", - "rack": "A-42", - "role": "production", - "tags": ["gpu-enabled", "high-memory"] +custom_meta = { + "environment": os.getenv("ENVIRONMENT", "development"), + "version": "1.0.0", + "deployment_region": "us-west-2", + "cost_center": "engineering" } system_info = DeviceInfoProvider.collect_system_info( - client_id="device_windows_prod_01", - custom_metadata=custom_metadata + client_id="device_001", + custom_metadata=custom_meta ) ``` -### Serialization - -```python -# Convert to dictionary for transmission -info_dict = system_info.to_dict() - -# Example output: -{ - "device_id": "device_windows_001", - "platform": "windows", - "os_version": "10.0.22631", - "cpu_count": 8, - "memory_total_gb": 16.0, - "hostname": "DESKTOP-ABC123", - "ip_address": "192.168.1.100", - "supported_features": [ - "gui", - "cli", - "browser", - "file_system", - "office", - "windows_apps" - ], - "platform_type": "computer", - "schema_version": "1.0", - "custom_metadata": {} -} -``` - -## Platform Detection - -### Auto-Detection - -The provider automatically detects the platform: - -```python -@staticmethod -def _get_platform() -> str: - """Get platform name (windows, linux, darwin, etc.)""" - try: - return platform.system().lower() # "windows", "linux", "darwin" - except Exception: - return "unknown" -``` - -**Supported Platforms:** - -| Platform | Identifier | Description | -|----------|------------|-------------| -| Windows | `windows` | Windows 10/11, Server | -| Linux | `linux` | Ubuntu, CentOS, Debian, etc. | -| macOS | `darwin` | macOS/OS X | -| Android | `android` | Android devices (future) | -| iOS | `ios` | iOS devices (future) | - -### OS Version - -```python -@staticmethod -def _get_os_version() -> str: - """Get OS version string""" - try: - return platform.version() - # Windows: "10.0.22631" - # Linux: "#1 SMP PREEMPT_DYNAMIC Wed Nov 1 15:36:23 UTC 2023" - # macOS: "Darwin Kernel Version 22.6.0" - except Exception: - return "unknown" -``` - -## Hardware Detection - -### CPU Information - -```python -@staticmethod -def _get_cpu_count() -> int: - """Get number of CPU cores""" - try: - import os - cpu_count = os.cpu_count() - return cpu_count if cpu_count is not None else 0 - except Exception: - return 0 -``` - -**Examples:** - -- Quad-core: `4` -- 8-core with hyperthreading: `16` (logical cores) -- Single-core: `1` - -### Memory Information - -```python -@staticmethod -def _get_memory_total_gb() -> float: - """Get total memory in GB""" - try: - import psutil - total_memory = psutil.virtual_memory().total - return round(total_memory / (1024**3), 2) - except ImportError: - logger.warning("psutil not installed, memory info unavailable") - return 0.0 - except Exception: - return 0.0 -``` - -!!!warning "psutil Required" - Memory detection requires the `psutil` package. Install with `pip install psutil`. - -**Examples:** - -- 16 GB RAM: `16.0` -- 32 GB RAM: `32.0` -- 8 GB RAM: `8.0` - -## Network Information - -### Hostname - -```python -@staticmethod -def _get_hostname() -> str: - """Get device hostname""" - try: - return socket.gethostname() - # Examples: - # Windows: "DESKTOP-ABC123" - # Linux: "ubuntu-server-01" - # macOS: "MacBook-Pro.local" - except Exception: - return "unknown" -``` - -### IP Address - -```python -@staticmethod -def _get_ip_address() -> str: - """Get device IP address""" - try: - # Get local IP by connecting to external address - # (doesn't actually send data) - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.connect(("8.8.8.8", 80)) - ip = s.getsockname()[0] - s.close() - return ip - except Exception: - try: - # Fallback to hostname resolution - return socket.gethostbyname(socket.gethostname()) - except Exception: - return "unknown" -``` - -**IP Detection Strategy:** - -1. **Primary**: Connect to external address (8.8.8.8) to get local IP -2. **Fallback**: Resolve hostname to IP -3. **Error**: Return "unknown" - -!!!tip "Network Detection" - This method reliably gets the local network IP, not the loopback address (127.0.0.1). - -## Feature Detection +**2. Install psutil for Accurate Memory Detection** -### Auto-Detected Features - -The provider automatically detects device capabilities: - -```python -@staticmethod -def _detect_features() -> List[str]: - """Auto-detect device capabilities based on platform.""" - - features = [] - sys_platform = platform.system().lower() - - if sys_platform in ["windows", "linux", "darwin"]: - # Desktop/laptop computers - features.extend([ - "gui", # Graphical user interface - "cli", # Command line interface - "browser", # Web browser support - "file_system", # File system operations - "office" # Office applications - ]) - - # Platform-specific features - if sys_platform == "windows": - features.append("windows_apps") - elif sys_platform == "linux": - features.append("linux_apps") - elif sys_platform == "darwin": - features.append("macos_apps") - - elif sys_platform in ["android", "ios"]: - # Mobile devices (future support) - features.extend([ - "mobile_touch", # Touch interface - "mobile_apps", # Mobile applications - "camera", # Camera support - "gps" # GPS/location services - ]) - - return features -``` - -### Feature Categories - -**Desktop/Laptop Features:** - -| Feature | Description | -|---------|-------------| -| `gui` | Graphical user interface available | -| `cli` | Command line interface available | -| `browser` | Web browser support | -| `file_system` | File system operations | -| `office` | Office applications (Word, Excel, etc.) | -| `windows_apps` | Windows-specific applications | -| `linux_apps` | Linux-specific applications | -| `macos_apps` | macOS-specific applications | - -**Mobile Features (Future):** - -| Feature | Description | -|---------|-------------| -| `mobile_touch` | Touch interface support | -| `mobile_apps` | Mobile application support | -| `camera` | Camera hardware available | -| `gps` | GPS/location services | - -!!!example "Feature-Based Selection" - Constellation clients can use features to select appropriate devices: - ```python - # Select device with GUI and browser support - devices = [d for d in all_devices - if "gui" in d.supported_features - and "browser" in d.supported_features] - ``` - -## Platform Type - -### Type Categorization - -```python -@staticmethod -def _get_platform_type() -> str: - """Categorize platform type.""" - - sys_platform = platform.system().lower() - - if sys_platform in ["windows", "linux", "darwin"]: - return "computer" - elif sys_platform in ["android", "ios"]: - return "mobile" - else: - return "unknown" -``` - -**Platform Types:** - -| Type | Platforms | Use Cases | -|------|-----------|-----------| -| `computer` | Windows, Linux, macOS | Desktop automation, office tasks | -| `mobile` | Android, iOS | Mobile app testing, touch automation | -| `web` | Browser-based | Web automation, testing | -| `iot` | Embedded devices | IoT device control | - -## Error Handling - -### Graceful Degradation - -If information collection fails, minimal info is returned: - -```python -try: - return DeviceSystemInfo( - device_id=client_id, - platform=DeviceInfoProvider._get_platform(), - os_version=DeviceInfoProvider._get_os_version(), - cpu_count=DeviceInfoProvider._get_cpu_count(), - memory_total_gb=DeviceInfoProvider._get_memory_total_gb(), - hostname=DeviceInfoProvider._get_hostname(), - ip_address=DeviceInfoProvider._get_ip_address(), - supported_features=DeviceInfoProvider._detect_features(), - platform_type=DeviceInfoProvider._get_platform_type(), - custom_metadata=custom_metadata or {} - ) -except Exception as e: - logger.error(f"Error collecting system info: {e}", exc_info=True) - # Return minimal info on error - return DeviceSystemInfo( - device_id=client_id, - platform="unknown", - os_version="unknown", - cpu_count=0, - memory_total_gb=0.0, - hostname="unknown", - ip_address="unknown", - supported_features=[], - platform_type="unknown", - custom_metadata=custom_metadata or {} - ) -``` - -!!!success "Always Returns" - The provider always returns a valid `DeviceSystemInfo` object, even if collection fails partially. - -### Individual Field Errors - -Each detection method handles errors independently: - -```python -@staticmethod -def _get_memory_total_gb() -> float: - try: - import psutil - total_memory = psutil.virtual_memory().total - return round(total_memory / (1024**3), 2) - except ImportError: - logger.warning("psutil not installed, memory info unavailable") - return 0.0 # Graceful fallback - except Exception: - return 0.0 # Graceful fallback -``` - -## Usage in Registration - -The WebSocket client uses the provider during registration: - -```python -from ufo.client.device_info_provider import DeviceInfoProvider - -async def register_client(self): - """Register with device information.""" - - # Collect device info - system_info = DeviceInfoProvider.collect_system_info( - client_id=self.ufo_client.client_id, - custom_metadata=None - ) - - # Prepare metadata - metadata = { - "system_info": system_info.to_dict(), - "registration_time": datetime.now(timezone.utc).isoformat() - } - - # Register via AIP - success = await self.registration_protocol.register_as_device( - device_id=self.ufo_client.client_id, - metadata=metadata, - platform=self.ufo_client.platform - ) +```bash +pip install psutil ``` -See [WebSocket Client](./websocket_client.md) for registration details. - -## Best Practices - -**Include Custom Metadata for Production** +**3. Use Descriptive Client IDs** ```python -custom_metadata = { - "environment": "production", - "region": "us-west-2", - "cluster": "gpu-cluster-01", - "capabilities": ["gpu", "high-memory"], - "cost_center": "engineering" -} - -system_info = DeviceInfoProvider.collect_system_info( - client_id="prod_device_001", - custom_metadata=custom_metadata -) +# Include environment and location in client_id +client_id = f"device_{platform}_{env}_{location}_{instance_id}" +# Example: "device_windows_prod_us-west_001" ``` -**Log Collection Results** +**4. Log Collection Results** ```python system_info = DeviceInfoProvider.collect_system_info(...) @@ -909,18 +464,7 @@ logger.info( ) ``` -**Handle Missing psutil** - -```python -try: - import psutil -except ImportError: - logger.warning("Installing psutil for memory detection...") - import subprocess - subprocess.check_call(["pip", "install", "psutil"]) -``` - -**Validate Before Sending** +**5. Validate Before Sending** ```python system_info = DeviceInfoProvider.collect_system_info(...) @@ -931,36 +475,11 @@ assert system_info.platform != "unknown", "Platform detection failed" assert system_info.cpu_count > 0, "CPU detection failed" ``` -## Integration Points - -### WebSocket Client - -Uses provider during registration: - -```python -system_info = DeviceInfoProvider.collect_system_info( - self.ufo_client.client_id, - custom_metadata=None -) -``` - -See [WebSocket Client](./websocket_client.md) for integration details. - -### Agent Server - -Receives device info during registration: - -```python -# Server-side AgentProfile integration -device_info = registration_data["metadata"]["system_info"] -agent_profile.add_device(device_id, device_info) -``` - -See [Server Quick Start](../server/quick_start.md) for server-side processing. +--- -## Next Steps +## 🚀 Next Steps -- [WebSocket Client](./websocket_client.md) - Registration process -- [Quick Start](./quick_start.md) - Connect your device -- [Server Quick Start](../server/quick_start.md) - Server-side registration -- [AIP Messages](../aip/messages.md) - Message structures +- [WebSocket Client](./websocket_client.md) - See how device info is used in registration +- [Quick Start](./quick_start.md) - Connect your device to the server +- [MCP Integration](./mcp_integration.md) - Understand client tool capabilities +- [Server Quick Start](../server/quick_start.md) - Learn server-side registration processing diff --git a/documents/docs/client/mcp_integration.md b/documents/docs/client/mcp_integration.md index 727550e73..5170e0d6b 100644 --- a/documents/docs/client/mcp_integration.md +++ b/documents/docs/client/mcp_integration.md @@ -1,23 +1,14 @@ # 🔌 MCP Integration -!!!quote "Unified Tool Execution" - **MCP (Model Context Protocol)** provides the tool execution layer in UFO² clients, enabling agents to collect system state and execute actions through a standardized interface. +**MCP (Model Context Protocol)** provides the tool execution layer in UFO clients, enabling agents to collect system state and execute actions through a standardized interface. This page provides a **client-focused overview** of how MCP integrates into the client architecture. -!!!info "📚 Complete MCP Documentation Available" - This page provides a **client-focused overview** of MCP integration. For comprehensive details: - - **📖 Core Concepts:** - - [MCP Overview](../mcp/overview.md) - Architecture, server types, deployment models - - [Configuration Guide](../mcp/configuration.md) - How to configure MCP servers - - **🛠️ Server Types:** - - [Data Collection Servers](../mcp/data_collection.md) - UI detection, screenshots, system info - - [Action Servers](../mcp/action.md) - Click, type, run commands - - **🚀 Deployment:** - - [Local Servers](../mcp/local_servers.md) - Built-in in-process servers - - [Remote Servers](../mcp/remote_servers.md) - HTTP/Stdio deployment - - [Creating MCP Servers](../tutorials/creating_mcp_servers.md) - Build your own tools +**Related Documentation:** + +- [MCP Overview](../mcp/overview.md) - Core MCP concepts and architecture +- [Configuration Guide](../mcp/configuration.md) - Server configuration details +- [Data Collection Servers](../mcp/data_collection.md) - Observation tools +- [Action Servers](../mcp/action.md) - Execution tools +- [Creating MCP Servers](../tutorials/creating_mcp_servers.md) - Build custom tools --- @@ -29,19 +20,19 @@ graph TB Server[Agent Server
via WebSocket] Client[UFO Client
Session Orchestration] - ComputerMgr[Computer Manager
Multi-App Routing] + Router[Command Router
Command Execution] Computer[Computer
MCP Tool Manager] - MCPMgr[MCP Server Manager
Lifecycle & Registry] + MCPMgr[MCP Server Manager
Server Lifecycle] - DataServers[Data Collection Servers
UICollector, ScreenCapture] - ActionServers[Action Servers
UIExecutor, CLIExecutor] + DataServers[Data Collection Servers
UICollector, etc.] + ActionServers[Action Servers
UIExecutor, CommandLineExecutor] Server -->|AIP Commands| Client - Client -->|Execute Step| ComputerMgr - ComputerMgr -->|Route to App| Computer + Client -->|Execute Actions| Router + Router -->|Route to Computer| Computer Computer -->|Manage Servers| MCPMgr - Computer -->|Auto-invoke| DataServers - Computer -->|Execute Tool| ActionServers + Computer -->|Register & Execute| DataServers + Computer -->|Register & Execute| ActionServers style Computer fill:#e1f5ff style MCPMgr fill:#fff4e6 @@ -51,12 +42,13 @@ graph TB **Key Components:** -| Component | Role | Responsibility | -|-----------|------|----------------| -| **Computer** | MCP Tool Manager | Registers MCP servers, routes tool calls, executes in thread pool | -| **MCP Server Manager** | Server Lifecycle | Creates/manages server instances, handles local/remote servers | -| **Data Collection Servers** | Observation Layer | Auto-invoked to gather UI state, screenshots, system info | -| **Action Servers** | Execution Layer | LLM-selected tools to perform actions (click, type, run) | +| Component | Location | Responsibility | +|-----------|----------|----------------| +| **Computer** | `ufo.client.computer.Computer` | Manages MCP servers, routes tool calls, executes in thread pool | +| **MCP Server Manager** | `ufo.client.mcp.mcp_server_manager.MCPServerManager` | Creates/manages server instances (local/http/stdio) | +| **Command Router** | `ufo.client.computer.CommandRouter` | Routes commands to appropriate Computer instances | +| **Data Collection Servers** | Various MCP servers | Tools for gathering system state (read-only) | +| **Action Servers** | Various MCP servers | Tools for performing state changes | --- @@ -68,41 +60,31 @@ graph TB sequenceDiagram participant Server as Agent Server participant Client as UFO Client + participant Router as Command Router participant Computer as Computer participant MCP as MCP Server - participant OS as Operating System - - Server->>Client: AIP Command
(tool_name, parameters) - Client->>Computer: execute_step() - alt Data Collection (Auto-Invoked) - Computer->>MCP: Invoke all data_collection tools - MCP->>OS: Take screenshot, detect UI - OS-->>MCP: Screen image, UI elements - MCP-->>Computer: Observation data - end - - Computer->>Computer: command2tool()
Convert to MCPToolCall - - alt Action Execution (LLM-Selected) - Computer->>MCP: call_tool(tool_name, parameters) - MCP->>OS: Perform action (click, type, etc.) - OS-->>MCP: Action result - MCP-->>Computer: Success/Failure - end - - Computer-->>Client: Result (status, outputs) + Server->>Client: AIP Command (tool_name, parameters) + Client->>Router: execute_actions(commands) + Router->>Computer: command2tool() + Computer->>Computer: Convert to MCPToolCall + Router->>Computer: run_actions([tool_call]) + Computer->>MCP: call_tool(tool_name, parameters) + MCP-->>Computer: CallToolResult + Computer-->>Router: Results + Router-->>Client: List[Result] Client-->>Server: AIP Result message ``` **Execution Stages:** -| Stage | Description | Tool Type | Trigger | -|-------|-------------|-----------|---------| -| **1. Data Collection** | Gather system/UI state | Data Collection | Automatic (every step) | -| **2. Command Conversion** | AIP Command → MCPToolCall | N/A | Client | -| **3. Action Execution** | Execute LLM-selected tool | Action | Explicit command | -| **4. Result Return** | Package result for server | N/A | Client | +| Stage | Component | Description | +|-------|-----------|-------------| +| **1. Command Reception** | UFO Client | Receives AIP Command from server | +| **2. Command Routing** | Command Router | Routes to appropriate Computer instance | +| **3. Command Conversion** | Computer | AIP Command → MCPToolCall | +| **4. Tool Execution** | Computer | Executes tool via MCP Server | +| **5. Result Return** | UFO Client | Packages result for server | --- @@ -110,8 +92,7 @@ sequenceDiagram ### Computer Class Overview -!!!success "Central MCP Hub" - The `Computer` class is the **client-side MCP manager**, handling server registration, tool discovery, and execution. +The `Computer` class is the **client-side MCP manager**, handling server registration, tool discovery, and execution. **Core Responsibilities:** @@ -140,12 +121,12 @@ await computer.async_init() | Step | Action | Result | |------|--------|--------| -| 1. Create MCPServerManager | Initialize server factory | Ready to create servers | -| 2. Load data_collection servers | Register observation tools | UICollector, ScreenCapture ready | -| 3. Load action servers | Register execution tools | UIExecutor, CLIExecutor ready | -| 4. Discover tools | Query each server for tools | Tool registry populated | +| 1. Create MCP Server Manager | Initialize server lifecycle manager | Ready to create servers | +| 2. Initialize data_collection servers | Register observation tools | UICollector ready | +| 3. Initialize action servers | Register execution tools | HostUIExecutor, CommandLineExecutor ready | +| 4. Register MCP servers | Query each server for tools | Tool registry populated | -See [Computer Manager](./computer_manager.md) for detailed architecture. +See [Computer](./computer.md) for detailed class documentation. --- @@ -153,8 +134,7 @@ See [Computer Manager](./computer_manager.md) for detailed architecture. ### Data Collection vs Action -!!!info "Critical Distinction" - Understanding the difference between server types is essential for proper MCP usage. +Understanding the difference between server types is essential for proper MCP usage: **Comparison:** @@ -162,35 +142,41 @@ See [Computer Manager](./computer_manager.md) for detailed architecture. |--------|------------------------|----------------| | **Purpose** | Observe system state | Modify system state | | **Examples** | `take_screenshot`, `detect_ui_elements` | `click`, `type_text`, `run_command` | -| **Invocation** | **Automatic** (every step) | **Explicit** (LLM selects) | +| **Invocation** | LLM-selected tools | LLM-selected tools | | **Side Effects** | ❌ None (read-only) | ✅ Yes (state changes) | | **Namespace** | `"data_collection"` | `"action"` | -| **LLM Selectable?** | ❌ No | ✅ Yes | +| **Tool Key Format** | `data_collection::tool_name` | `action::tool_name` | **Data Collection Example:** ```python -# Automatically invoked before each action -# Client doesn't explicitly call - framework handles it -screenshot = await data_collection_server.call_tool( - "take_screenshot", - region="active_window" -) +# Example: Take screenshot for UI analysis +result = await computer.run_actions([ + computer.command2tool(Command( + tool_name="take_screenshot", + tool_type="data_collection", + parameters={"region": "active_window"} + )) +]) ``` **Action Example:** ```python -# LLM selects tool based on task -# Client explicitly calls via AIP Command -result = await action_server.call_tool( - "click", - control_text="Save", - control_type="Button" -) +# Example: Click a button +result = await computer.run_actions([ + computer.command2tool(Command( + tool_name="click", + tool_type="action", + parameters={ + "control_text": "Save", + "control_type": "Button" + } + )) +]) ``` -See [MCP Overview - Server Types](../mcp/overview.md#key-concepts) for detailed comparison. +See [MCP Overview - Server Types](../mcp/overview.md#1-two-server-types) for detailed comparison. --- @@ -213,7 +199,7 @@ HostAgent: type: local reset: false - - namespace: CLIExecutor # Multiple servers allowed + - namespace: CommandLineExecutor # Multiple servers allowed type: local reset: false ``` @@ -239,25 +225,33 @@ HostAgent: ### Tool Discovery +The Computer automatically discovers and registers tools from all configured MCP servers during initialization: + **Automatic Registration:** ```python # During computer.async_init() async def register_mcp_servers(self, servers, tool_type): + """Register tools from all MCP servers""" for namespace, server in servers.items(): # Connect to MCP server async with Client(server.server) as client: # List available tools tools = await client.list_tools() - # Register each tool + # Register each tool with unique key for tool in tools: - self.tool_registry[tool.name] = MCPToolCall( - name=tool.name, + tool_key = self.make_tool_key(tool_type, tool.name) + self._tools_registry[tool_key] = MCPToolCall( + tool_key=tool_key, + tool_name=tool.name, + title=tool.title, + namespace=namespace, + tool_type=tool_type, description=tool.description, - parameters=tool.inputSchema, - mcp_server=server, - namespace=namespace + input_schema=tool.inputSchema, + output_schema=tool.outputSchema, + mcp_server=server ) ``` @@ -265,13 +259,30 @@ async def register_mcp_servers(self, servers, tool_type): | Field | Type | Description | |-------|------|-------------| -| `name` | `str` | Tool name (e.g., `"take_screenshot"`) | -| `description` | `str` | Tool description from docstring | -| `parameters` | `dict` | JSON schema of parameters | +| `tool_key` | `str` | Unique key: `"tool_type::tool_name"` | +| `tool_name` | `str` | Tool name (e.g., `"take_screenshot"`) | +| `title` | `str` | Display title | +| `namespace` | `str` | Server namespace (e.g., `"UICollector"`) | +| `tool_type` | `str` | `"data_collection"` or `"action"` | +| `description` | `str` | Tool description | +| `input_schema` | `dict` | JSON schema for parameters | +| `output_schema` | `dict` | JSON schema for results | | `mcp_server` | `BaseMCPServer` | Server instance | -| `namespace` | `str` | Server namespace | -See [Computer Manager](./computer_manager.md) for details. +### Tool Execution + +Tools execute in isolated threads with timeout protection (default: 6000 seconds = 100 minutes per tool): + +```python +# Thread pool configuration +self._executor = concurrent.futures.ThreadPoolExecutor( + max_workers=10, + thread_name_prefix="mcp_tool_" +) +self._tool_timeout = 6000 # 100 minutes +``` + +See [Computer](./computer.md) for execution details. --- @@ -280,10 +291,18 @@ See [Computer Manager](./computer_manager.md) for details. ### Basic Usage ```python -from ufo.client.computer_manager import ComputerManager +from ufo.client.computer import ComputerManager, CommandRouter +from ufo.client.mcp.mcp_server_manager import MCPServerManager +from aip.messages import Command + +# Create MCP server manager +mcp_server_manager = MCPServerManager() + +# Create computer manager (manages Computer instances) +computer_manager = ComputerManager(config, mcp_server_manager) -# Create computer manager (manages MCP servers internally) -computer_manager = ComputerManager(config) +# Create command router +command_router = CommandRouter(computer_manager) # Execute action through MCP command = Command( @@ -295,8 +314,13 @@ command = Command( } ) -# Computer routes to appropriate MCP server -result = await computer_manager.execute_command(command) +# Router creates Computer instance and executes +results = await command_router.execute( + agent_name="HostAgent", + process_name="notepad.exe", + root_name="default", + commands=[command] +) ``` ### Custom MCP Server @@ -319,8 +343,9 @@ async def custom_action(param: str) -> str: # reset: false ``` -!!!example "🛠️ Build Custom Servers" - See [Creating MCP Servers](../tutorials/creating_mcp_servers.md) for step-by-step guide to creating your own MCP servers. +**For step-by-step instructions:** + +- [Creating MCP Servers](../tutorials/creating_mcp_servers.md) - Build your own MCP tools --- @@ -330,19 +355,23 @@ async def custom_action(param: str) -> str: **UFO Client:** - Receives AIP Commands from server -- Delegates to Computer Manager +- Delegates to Command Router - Returns AIP Results -**Computer Manager:** -- Routes commands to correct Computer instance (by app/process) -- Manages multiple Computer instances +**Command Router:** +- Routes commands to appropriate Computer instance (by agent/process/root name) +- Manages command execution with early-exit support **Computer:** - **MCP entry point**: Manages all MCP servers - Executes tools via MCP Server Manager -- Aggregates results +- Maintains tool registry + +**MCP Server Manager:** +- Creates and manages MCP server instances +- Supports local, HTTP, and stdio deployment types -See [UFO Client](./ufo_client.md) and [Computer Manager](./computer_manager.md) for integration details. +See [UFO Client](./ufo_client.md) and [Computer](./computer.md) for integration details. --- @@ -352,7 +381,7 @@ See [UFO Client](./ufo_client.md) and [Computer Manager](./computer_manager.md) | Component | Description | Link | |-----------|-------------|------| -| **Computer Manager** | Multi-app MCP coordination | [Computer Manager](./computer_manager.md) | +| **Computer** | Core MCP execution layer | [Computer](./computer.md) | | **UFO Client** | Session orchestration | [UFO Client](./ufo_client.md) | | **WebSocket Client** | Server communication | [WebSocket Client](./websocket_client.md) | @@ -372,33 +401,33 @@ See [UFO Client](./ufo_client.md) and [Computer Manager](./computer_manager.md) ## 🎯 Key Takeaways -!!!success "MCP in Client - Summary" - - **1. Computer is the MCP Manager** - - Manages all MCP server instances - - Routes tool calls to appropriate servers - - Executes in thread pool for isolation - - **2. Two Server Types** - - **Data Collection**: Auto-invoked, read-only, observation - - **Action**: LLM-selected, state-changing, execution - - **3. Configuration-Driven** - - Servers configured in `config/ufo/mcp.yaml` - - Supports local, HTTP, and stdio deployment - - **4. Automatic Registration** - - Tools auto-discovered during initialization - - Tool registry built from server metadata - - **5. Detailed Docs Available** - - Full MCP section at [../mcp/](../mcp/overview.md) - - Custom server guides, examples, troubleshooting +**MCP in Client - Summary** + +**1. Computer is the MCP Manager** +- Manages all MCP server instances +- Routes tool calls to appropriate servers +- Executes in thread pool for isolation + +**2. Two Server Types** +- **Data Collection**: Read-only, observation tools +- **Action**: State-changing, execution tools + +**3. Configuration-Driven** +- Servers configured in `config/ufo/mcp.yaml` +- Supports local, HTTP, and stdio deployment + +**4. Automatic Registration** +- Tools auto-discovered during initialization +- Tool registry built from server metadata + +**5. Detailed Docs Available** +- Full MCP section at [MCP Overview](../mcp/overview.md) +- Custom server guides, examples, troubleshooting --- ## 🚀 Next Steps -👉 [MCP Overview](../mcp/overview.md) - Understand MCP architecture in depth -👉 [Computer Manager](./computer_manager.md) - See how MCP servers are managed -👉 [Creating MCP Servers](../tutorials/creating_mcp_servers.md) - Build your own MCP tools +- [MCP Overview](../mcp/overview.md) - Understand MCP architecture in depth +- [Computer](./computer.md) - See how MCP servers are managed +- [Creating MCP Servers](../tutorials/creating_mcp_servers.md) - Build your own MCP tools diff --git a/documents/docs/client/overview.md b/documents/docs/client/overview.md index 0483c93c0..816a5734f 100644 --- a/documents/docs/client/overview.md +++ b/documents/docs/client/overview.md @@ -1,14 +1,12 @@ -# Agent Client Overview +# UFO Client Overview -!!!quote "The Execution Engine" - The **Agent Client** runs on target devices and serves as the **execution layer** of UFO's distributed agent system. It manages MCP (Model Context Protocol) servers, executes commands deterministically, and communicates with the Agent Server through the Agent Interaction Protocol (AIP). +The **UFO Client** runs on target devices and serves as the **execution layer** of UFO's distributed agent system. It manages MCP (Model Context Protocol) servers, executes commands deterministically, and communicates with the Agent Server through the Agent Interaction Protocol (AIP). -!!!tip "Quick Start" - Ready to run a client? Jump to the [Quick Start Guide](./quick_start.md) to connect your device in minutes. Make sure the [Agent Server](../server/quick_start.md) is running first. +**Quick Start:** Jump to the [Quick Start Guide](./quick_start.md) to connect your device. Make sure the [Agent Server](../server/quick_start.md) is running first. --- -## 🎯 What is the Agent Client? +## 🎯 What is the UFO Client? ```mermaid graph LR @@ -46,7 +44,7 @@ graph LR style Tools fill:#fff9c4 ``` -**The Agent Client is a stateless execution agent that:** +**The UFO Client is a stateless execution agent that:** | Capability | Description | Benefit | |------------|-------------|---------| @@ -56,64 +54,44 @@ graph LR | **📡 Communicates via AIP** | Maintains persistent WebSocket connection | Real-time bidirectional communication | | **🚫 Remains Stateless** | Executes directives without high-level reasoning | Independent updates, simple architecture | -!!!info "Stateless Design Philosophy" - The client focuses **purely on execution**. All reasoning and decision-making happens on the server, allowing: - - - ✅ Independent updates to server logic and client tools - - ✅ Simple client architecture (easier to maintain) - - ✅ Server can orchestrate multiple clients intelligently - - ✅ Clients can be lightweight and resource-efficient +**Stateless Design Philosophy:** The client focuses purely on execution. All reasoning and decision-making happens on the server, allowing independent updates to server logic and client tools, simple client architecture, intelligent orchestration of multiple clients, and resource-efficient operation. -!!!tip "Server-Client Architecture" - The Agent Client is part of UFO's distributed **server-client architecture**, where it handles command execution and resource access while the [Agent Server](../server/overview.md) handles orchestration and decision-making. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md) for the complete design rationale, communication protocols, and deployment patterns. +**Architecture:** The UFO Client is part of UFO's distributed **server-client architecture**, where it handles command execution and resource access while the [Agent Server](../server/overview.md) handles orchestration and decision-making. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md) for the complete design rationale, communication protocols, and deployment patterns. --- ## 🏗️ Architecture -!!!success "Layered Design" - The client implements a **layered architecture** separating communication, execution, and tool management for maximum flexibility and maintainability. +The client implements a **layered architecture** separating communication, execution, and tool management for maximum flexibility and maintainability. ```mermaid graph TB - subgraph "Communication Layer" - WSC[WebSocket Client] - AIP[AIP Protocol Handler] + subgraph "Communication" + WSC[WebSocket Client
AIP Protocol] end - subgraph "Orchestration Layer" + subgraph "Orchestration" UFC[UFO Client] CM[Computer Manager] end - subgraph "Execution Layer" - COMP[Computer Instance] - TR[Tool Registry] + subgraph "Execution" + COMP[Computer] + MCPM[MCP Manager] end - subgraph "Integration Layer" - MCPM[MCP Server Manager] + subgraph "Tools" LOCAL[Local MCP Servers] REMOTE[Remote MCP Servers] end - subgraph "Support Layer" - DI[Device Info Provider] - CONFIG[Configuration Loader] - end - - WSC --> AIP - AIP --> UFC + WSC --> UFC UFC --> CM CM --> COMP - COMP --> TR - TR --> MCPM + COMP --> MCPM MCPM --> LOCAL MCPM --> REMOTE - DI -.->|System Info| WSC - CONFIG -.->|Settings| MCPM - style WSC fill:#bbdefb style UFC fill:#c8e6c9 style COMP fill:#fff9c4 @@ -125,10 +103,10 @@ graph TB | Component | Responsibility | Key Features | Documentation | |-----------|---------------|--------------|---------------| | **WebSocket Client** | AIP communication | • Connection management
• Registration
• Heartbeat monitoring
• Message routing | [Details →](./websocket_client.md) | -| **UFO Client** | Execution orchestration | • Session tracking
• Command execution
• Result aggregation
• Error handling | [Details →](./ufo_client.md) | +| **UFO Client** | Execution orchestration | • Command execution
• Result aggregation
• Error handling
• Session management | [Details →](./ufo_client.md) | | **Computer Manager** | Multi-computer abstraction | • Computer instance management
• Namespace routing
• Resource isolation | [Details →](./computer_manager.md) | | **Computer** | Tool management | • MCP server registration
• Tool registry
• Execution isolation
• Thread pool management | [Details →](./computer.md) | -| **MCP Server Manager** | MCP lifecycle | • Server creation
• Configuration loading
• Connection pooling
• Health monitoring | [Details →](./mcp_integration.md) | +| **MCP Server Manager** | MCP lifecycle | • Server creation
• Configuration loading
• Connection pooling
• Health monitoring | [MCP Documentation →](../mcp/overview.md) | | **Device Info Provider** | System profiling | • Hardware detection
• Capability reporting
• Platform identification
• Feature enumeration | [Details →](./device_info.md) | For detailed component documentation: @@ -137,7 +115,7 @@ For detailed component documentation: - [UFO Client](./ufo_client.md) - Execution orchestration - [Computer Manager](./computer_manager.md) - Multi-computer management - [Device Info Provider](./device_info.md) - System profiling -- [MCP Integration](./mcp_integration.md) - MCP server management (brief overview) +- [MCP Integration](../mcp/overview.md) - MCP server management (comprehensive documentation) --- @@ -145,32 +123,22 @@ For detailed component documentation: ### 1. Deterministic Command Execution -!!!info "Pure Execution - No Interpretation" - The client executes commands **exactly as specified** without interpretation or reasoning, ensuring predictable behavior. +The client executes commands **exactly as specified** without interpretation or reasoning, ensuring predictable behavior. ```mermaid sequenceDiagram participant Server - participant WSClient as WebSocket Client - participant UFOClient as UFO Client - participant CompMgr as Computer Manager + participant Client as UFO Client participant Computer participant Tool as MCP Tool - Server->>WSClient: COMMAND (AIP) - WSClient->>UFOClient: Route Command - UFOClient->>CompMgr: Execute Command - CompMgr->>Computer: Route to Namespace - Computer->>Computer: Lookup Tool in Registry - Computer->>Tool: Execute Tool Call - - Note over Tool: Isolated Thread Pool
with Timeout - - Tool-->>Computer: Tool Result - Computer-->>CompMgr: Aggregated Result - CompMgr-->>UFOClient: Execution Result - UFOClient-->>WSClient: Format Response - WSClient-->>Server: COMMAND_RESULTS (AIP) + Server->>Client: COMMAND (AIP) + Client->>Computer: Execute Command + Computer->>Computer: Lookup Tool + Computer->>Tool: Execute with Timeout + Tool-->>Computer: Result + Computer-->>Client: Aggregated Result + Client-->>Server: COMMAND_RESULTS (AIP) ``` **Execution Flow:** @@ -184,22 +152,20 @@ sequenceDiagram | 5️⃣ **Aggregate** | Combine results from multiple tools | Structured response format | | 6️⃣ **Return** | Send results back to server via AIP | Complete the execution loop | -!!!success "Execution Guarantees" - - ✅ **Isolation**: Each tool runs in separate thread pool - - ✅ **Timeouts**: Configurable timeout (default: 100 minutes) - - ✅ **Fault Tolerance**: One failed tool doesn't crash entire client - - ✅ **Thread Safety**: Concurrent tool execution supported - - ✅ **Error Reporting**: Structured errors returned to server +**Execution Guarantees:** +- **Isolation**: Each tool runs in separate thread pool +- **Timeouts**: Configurable timeout (default: 6000 seconds/100 minutes) +- **Fault Tolerance**: One failed tool doesn't crash entire client +- **Thread Safety**: Concurrent tool execution supported +- **Error Reporting**: Structured errors returned to server ### 2. MCP Server Management -!!!tip "Extensible Tool Ecosystem" - The client manages a collection of **MCP (Model Context Protocol) servers** to provide diverse tool access for automation tasks. The client is responsible for registering, managing, and executing these tools, while the [Agent Server](../server/overview.md) handles command orchestration. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md#client-command-execution-and-resource-access) for how MCP integration fits into the overall architecture. +The client manages a collection of **MCP (Model Context Protocol) servers** to provide diverse tool access for automation tasks. The client is responsible for registering, managing, and executing these tools, while the [Agent Server](../server/overview.md) handles command orchestration. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md#client-command-execution-and-resource-access) for how MCP integration fits into the overall architecture. **MCP Server Categories:** -**Data Collection Servers:** -**Purpose**: Gather information from the device +**Data Collection Servers** gather information from the device: | Server Type | Tools Provided | Use Cases | |-------------|---------------|-----------| @@ -208,14 +174,9 @@ sequenceDiagram | **Screenshot** | Screen capture | Visual verification | | **UI Element Detection** | Control trees, accessibility | UI automation | -**Example Tools:** -- `get_system_info()` - System specifications -- `list_running_apps()` - Active applications -- `capture_screenshot()` - Screen snapshot -- `get_ui_tree()` - UI element hierarchy +Example Tools: `get_system_info()`, `list_running_apps()`, `capture_screenshot()`, `get_ui_tree()` -**Action Servers:** -**Purpose**: Perform actions on the device +**Action Servers** perform actions on the device: | Server Type | Tools Provided | Use Cases | |-------------|---------------|-----------| @@ -224,11 +185,7 @@ sequenceDiagram | **File System** | Read, write, delete | File operations | | **Command Execution** | Shell commands | System automation | -**Example Tools:** -- `click_button(label)` - UI interaction -- `type_text(text)` - Keyboard input -- `open_application(name)` - Launch app -- `execute_command(cmd)` - Shell execution +Example Tools: `click_button(label)`, `type_text(text)`, `open_application(name)`, `execute_command(cmd)` **Server Types:** @@ -237,32 +194,32 @@ sequenceDiagram | **Local MCP Servers** | Run in same process via FastMCP | Fast, no network overhead | Limited to local capabilities | | **Remote MCP Servers** | Connect via HTTP/SSE | Scalable, shared services | Network latency, external dependency | -!!!example "MCP Server Configuration" - ```yaml - mcp_servers: - data_collection: - - name: "system_info" - type: "local" - class: "SystemInfoServer" - - name: "ui_detector" - type: "local" - class: "UIDetectionServer" - - action: - - name: "gui_automation" - type: "local" - class: "GUIAutomationServer" - - name: "file_ops" - type: "remote" - url: "http://localhost:8080/mcp" - ``` - -See [MCP Integration](./mcp_integration.md) for comprehensive MCP server documentation. +**Example MCP Server Configuration:** + +```yaml +mcp_servers: + data_collection: + - name: "system_info" + type: "local" + class: "SystemInfoServer" + - name: "ui_detector" + type: "local" + class: "UIDetectionServer" + + action: + - name: "gui_automation" + type: "local" + class: "GUIAutomationServer" + - name: "file_ops" + type: "remote" + url: "http://localhost:8080/mcp" +``` + +See [MCP Integration](../mcp/overview.md) for comprehensive MCP server documentation. ### 3. Device Profiling -!!!info "Intelligent Task Assignment" - The client automatically collects and reports **device information** to enable the server to make intelligent task routing decisions. +The client automatically collects and reports **device information** to enable the server to make intelligent task routing decisions. **Device Profile Structure:** @@ -301,37 +258,17 @@ See [MCP Integration](./mcp_integration.md) for comprehensive MCP server documen ```mermaid graph LR - subgraph "Client Side" - Detect[Device Detection] - Collect[Info Collection] - Report[Profile Reporting] - end - - subgraph "Server Side" - Receive[Receive Profile] - Store[Store in Registry] - Route[Task Routing] - end + Client[Client Detects
Device Info] + Server[Server Stores
Profile] + Route[Server Routes
Tasks] - subgraph "Task Assignment" - Match[Match Requirements] - Select[Select Best Device] - Dispatch[Dispatch Task] - end - - Detect --> Collect - Collect --> Report - Report --> Receive - Receive --> Store - Store --> Route - - Route --> Match - Match --> Select - Select --> Dispatch + Client -->|Report Profile| Server + Server -->|Match Requirements| Route + Route -->|Dispatch Task| Client - style Collect fill:#bbdefb - style Store fill:#c8e6c9 - style Select fill:#fff9c4 + style Client fill:#bbdefb + style Server fill:#c8e6c9 + style Route fill:#fff9c4 ``` **Server Uses Profile For:** @@ -347,8 +284,7 @@ See [Device Info Provider](./device_info.md) for detailed profiling documentatio ### 4. Resilient Communication -!!!success "Built on AIP (Agent Interaction Protocol)" - Robust, fault-tolerant communication with the server using strongly-typed messages. +Robust, fault-tolerant communication with the server using strongly-typed AIP messages. **Connection Lifecycle:** @@ -411,26 +347,19 @@ See [WebSocket Client](./websocket_client.md) and [AIP Protocol](../aip/overview ```mermaid sequenceDiagram participant Main as Client Main - participant Config as Config Loader participant MCP as MCP Manager participant WSC as WebSocket Client participant Server - Note over Main: Client Startup - Main->>Config: Load Configuration - Config-->>Main: UFO Config - Main->>MCP: Initialize MCP Servers - MCP->>MCP: Create Data Collection Servers - MCP->>MCP: Create Action Servers - MCP-->>Main: Server Registry + MCP-->>Main: Server Registry Ready - Main->>WSC: Create WebSocket Client - WSC->>Server: Connect to ws://server:5000/ws + Main->>WSC: Create Client & Connect + WSC->>Server: WebSocket Connect Server-->>WSC: Connection Established WSC->>WSC: Collect Device Info - WSC->>Server: REGISTRATION
(device_id, platform, capabilities) + WSC->>Server: REGISTRATION Server-->>WSC: REGISTRATION_ACK WSC->>WSC: Start Heartbeat Loop @@ -440,7 +369,7 @@ sequenceDiagram Server-->>WSC: HEARTBEAT_ACK end - Note over WSC,Server: Ready to Receive Commands + Note over WSC,Server: Ready to Execute Commands ``` **Initialization Steps:** @@ -461,32 +390,23 @@ sequenceDiagram ```mermaid sequenceDiagram participant Server - participant WSC as WebSocket Client - participant UFC as UFO Client + participant Client as UFO Client participant Comp as Computer participant Tool as MCP Tool - Server->>WSC: COMMAND
{type: "click_button", args: {label: "Submit"}} - - WSC->>UFC: on_command_received() - UFC->>Comp: execute_command(command) - - Note over Comp: Lookup Tool in Registry + Server->>Client: COMMAND
{type: "click_button", args: {...}} + Client->>Comp: execute_command() Comp->>Comp: find_tool("click_button") alt Tool Found - Comp->>Tool: execute(label="Submit") - - Note over Tool: Execute in Thread Pool
(100 min timeout) - - Tool-->>Comp: Success: Button clicked - Comp-->>UFC: Result: {status: "success", output: "Clicked"} - UFC-->>WSC: format_result() - WSC-->>Server: COMMAND_RESULTS
{status: "completed", result: {...}} + Comp->>Tool: execute(args) + Note over Tool: Thread Pool Execution
6000s timeout + Tool-->>Comp: Success + Comp-->>Client: Result + Client-->>Server: COMMAND_RESULTS
{status: "completed"} else Tool Not Found - Comp-->>UFC: Error: Tool not found - UFC-->>WSC: format_error() - WSC-->>Server: ERROR
{error: "Tool 'click_button' not found"} + Comp-->>Client: Error + Client-->>Server: ERROR
{error: "Tool not found"} end ``` @@ -494,8 +414,7 @@ sequenceDiagram ## 🖥️ Platform Support -!!!info "Multi-Platform Execution" - The client supports multiple platforms with platform-specific tool implementations. +The client supports multiple platforms with platform-specific tool implementations. | Platform | Status | Features | Native Tools | |----------|--------|----------|--------------| @@ -510,26 +429,27 @@ sequenceDiagram - **Override**: Use `--platform` flag to specify manually - **Validation**: Server validates platform matches task requirements -!!!example "Platform-Specific Example" - **Windows:** - ```python - # Windows-specific tools - tools = [ - "open_windows_app(name='Excel')", - "execute_powershell(script='Get-Process')", - "read_registry(key='HKLM\\Software')" - ] - ``` - - **Linux:** - ```python - # Linux-specific tools - tools = [ - "execute_bash(command='ls -la')", - "install_package(name='vim')", - "control_systemd(service='nginx', action='restart')" - ] - ``` +**Platform-Specific Example:** + +**Windows:** +```python +# Windows-specific tools +tools = [ + "open_windows_app(name='Excel')", + "execute_powershell(script='Get-Process')", + "read_registry(key='HKLM\\Software')" +] +``` + +**Linux:** +```python +# Linux-specific tools +tools = [ + "execute_bash(command='ls -la')", + "install_package(name='vim')", + "control_systemd(service='nginx', action='restart')" +] +``` --- @@ -537,6 +457,8 @@ sequenceDiagram ### Command-Line Arguments +Start the UFO client with: + ```bash python -m ufo.client.client [OPTIONS] ``` @@ -550,21 +472,22 @@ python -m ufo.client.client [OPTIONS] | `--ws` | `flag` | `False` | **Enable WebSocket mode** (required) | `--ws` | | `--max-retries` | `int` | `5` | Connection retry limit | `--max-retries 10` | | `--platform` | `str` | Auto-detect | Platform override | `--platform windows` | -| `--log-level` | `str` | `INFO` | Logging verbosity | `--log-level DEBUG` | - -!!!tip "Quick Start Command" - ```bash - # Minimal command (default server) - python -m ufo.client.client --ws --client-id my_device - - # Production command (custom server) - python -m ufo.client.client \ - --ws \ - --client-id device_production_01 \ - --ws-server ws://ufo-server.company.com:5000/ws \ - --max-retries 10 \ - --log-level INFO - ``` +| `--log-level` | `str` | `WARNING` | Logging verbosity | `--log-level DEBUG` | + +**Quick Start Command:** + +```bash +# Minimal command (default server) +python -m ufo.client.client --ws --client-id my_device + +# Production command (custom server) +python -m ufo.client.client \ + --ws \ + --client-id device_production_01 \ + --ws-server ws://ufo-server.company.com:5000/ws \ + --max-retries 10 \ + --log-level INFO +``` ### UFO Configuration @@ -579,31 +502,32 @@ The client inherits settings from `config_dev.yaml`: | **Logging** | Log levels, formats, destinations | File logging, console output | | **Platform Settings** | OS-specific configurations | Windows UI automation settings | -!!!example "Sample Configuration" - ```yaml - client: - heartbeat_interval: 30 # seconds - command_timeout: 6000 # seconds (100 minutes) - max_concurrent_tools: 10 - - mcp_servers: - data_collection: - - name: system_info - type: local - enabled: true - action: - - name: gui_automation - type: local - enabled: true - settings: - click_delay: 0.5 - typing_speed: 100 # chars per minute - - logging: - level: INFO - format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - file: "logs/client.log" - ``` +**Sample Configuration:** + +```yaml +client: + heartbeat_interval: 30 # seconds + command_timeout: 6000 # seconds (100 minutes) + max_concurrent_tools: 10 + +mcp_servers: + data_collection: + - name: system_info + type: local + enabled: true + action: + - name: gui_automation + type: local + enabled: true + settings: + click_delay: 0.5 + typing_speed: 100 # chars per minute + +logging: + level: INFO + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + file: "logs/client.log" +``` See [Configuration Guide](../configuration/system/overview.md) for comprehensive documentation. @@ -611,8 +535,7 @@ See [Configuration Guide](../configuration/system/overview.md) for comprehensive ## ⚠️ Error Handling -!!!danger "Robust Fault Tolerance" - The client is designed to handle various failure scenarios gracefully without crashing. +The client is designed to handle various failure scenarios gracefully without crashing. ### Connection Failures @@ -653,21 +576,22 @@ stateDiagram-v2 | Mechanism | Purpose | Default Value | |-----------|---------|---------------| | **Thread Pool Isolation** | Prevent one tool from blocking others | Enabled | -| **Execution Timeout** | Kill hung tools | 100 minutes | +| **Execution Timeout** | Kill hung tools | 6000 seconds (100 minutes) | | **Exception Catching** | Graceful error handling | All tools wrapped | | **Error Reporting** | Notify server of failures | Structured error messages | -!!!example "Error Handling Example" - ```python - # Client automatically handles tool errors - try: - result = tool.execute(args) - return {"status": "success", "result": result} - except TimeoutError: - return {"status": "error", "error": "Tool execution timeout"} - except Exception as e: - return {"status": "error", "error": str(e)} - ``` +**Error Handling Example:** + +```python +# Client automatically handles tool errors +try: + result = tool.execute(args) + return {"status": "success", "result": result} +except TimeoutError: + return {"status": "error", "error": "Tool execution timeout"} +except Exception as e: + return {"status": "error", "error": str(e)} +``` ### Server Disconnection @@ -685,100 +609,102 @@ stateDiagram-v2 ### Development Best Practices -!!!tip "Development Tips" - - **1. Use Unique Client IDs** - ```bash - # Bad: Generic ID - --client-id client_001 - - # Good: Descriptive ID - --client-id device_win_dev_john_laptop - ``` - - **2. Start with INFO Logging** - ```bash - # Development: INFO for normal operation - --log-level INFO - - # Debugging: DEBUG for troubleshooting - --log-level DEBUG - ``` - - **3. Test MCP Connectivity First** - ```python - # Verify MCP servers are accessible before running client - from ufo.module.mcp_server import MCPServerManager - - manager = MCPServerManager() - servers = manager.create_servers_from_config() - print(f"Initialized {len(servers)} MCP servers") - ``` +**1. Use Unique Client IDs** + +```bash +# Bad: Generic ID +--client-id client_001 + +# Good: Descriptive ID +--client-id device_win_dev_john_laptop +``` + +**2. Start with INFO Logging** + +```bash +# Development: WARNING for normal operation (default) +--log-level WARNING + +# Debugging: DEBUG for troubleshooting +--log-level DEBUG +``` + +**3. Test MCP Connectivity First** + +```python +# Verify MCP servers are accessible before running client +from ufo.client.mcp.mcp_server_manager import MCPServerManager + +manager = MCPServerManager() +# Test server creation from configuration +``` ### Production Best Practices -!!!success "Production Deployment" - - **1. Use Descriptive Client IDs** - ```bash - # Include environment, location, purpose - --client-id device_windows_production_office_01 - --client-id device_linux_staging_lab_02 - ``` - - **2. Configure Automatic Restart** - - **systemd (Linux):** - ```ini - [Unit] - Description=UFO Agent Client - After=network.target - - [Service] - Type=simple - User=ufo - WorkingDirectory=/opt/ufo - ExecStart=/usr/bin/python3 -m ufo.client.client \ - --ws \ - --client-id device_linux_prod_01 \ - --ws-server ws://ufo-server.internal:5000/ws \ - --log-level INFO - Restart=always - RestartSec=10 - - [Install] - WantedBy=multi-user.target - ``` - - **PM2 (Cross-platform):** - ```json - { - "apps": [{ - "name": "ufo-client", - "script": "python", - "args": [ - "-m", "ufo.client.client", - "--ws", - "--client-id", "device_win_prod_01", - "--ws-server", "ws://ufo-server.internal:5000/ws", - "--log-level", "INFO" - ], - "cwd": "C:\\ufo", - "restart_delay": 5000, - "max_restarts": 10 - }] - } - ``` - - **3. Monitor Connection Health** - ```python - # Check logs for connection status - tail -f logs/client.log | grep -E "Connected|Disconnected|ERROR" - ``` +**1. Use Descriptive Client IDs** + +```bash +# Include environment, location, purpose +--client-id device_windows_production_office_01 +--client-id device_linux_staging_lab_02 +``` + +**2. Configure Automatic Restart** + +**systemd (Linux):** + +```ini +[Unit] +Description=UFO Agent Client +After=network.target + +[Service] +Type=simple +User=ufo +WorkingDirectory=/opt/ufo +ExecStart=/usr/bin/python3 -m ufo.client.client \ + --ws \ + --client-id device_linux_prod_01 \ + --ws-server ws://ufo-server.internal:5000/ws \ + --log-level INFO +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +``` + +**PM2 (Cross-platform):** + +```json +{ + "apps": [{ + "name": "ufo-client", + "script": "python", + "args": [ + "-m", "ufo.client.client", + "--ws", + "--client-id", "device_win_prod_01", + "--ws-server", "ws://ufo-server.internal:5000/ws", + "--log-level", "INFO" + ], + "cwd": "C:\\ufo", + "restart_delay": 5000, + "max_restarts": 10 + }] +} +``` + +**3. Monitor Connection Health** + +```bash +# Check logs for connection status +tail -f logs/client.log | grep -E "Connected|Disconnected|ERROR" +``` ### Security Best Practices -!!!warning "Security Considerations" +!!! warning "Security Considerations" | Practice | Description | Implementation | |----------|-------------|----------------| @@ -822,8 +748,7 @@ stateDiagram-v2 ## 🔄 Client vs. Server -!!!quote "Separation of Concerns" - Understanding the **clear division** between client and server responsibilities is crucial for effective system design. +Understanding the **clear division** between client and server responsibilities is crucial for effective system design. **Responsibility Matrix:** @@ -875,53 +800,48 @@ graph TB style C3 fill:#c8e6c9 ``` -!!!info "Decoupled Architecture Benefits" - - ✅ **Independent Updates**: Modify server logic without touching clients - - ✅ **Flexible Deployment**: Run clients on any platform - - ✅ **Scalability**: Add more clients without server changes - - ✅ **Maintainability**: Simpler client code, easier debugging - - ✅ **Testability**: Test client and server independently +**Decoupled Architecture Benefits:** +- Independent Updates: Modify server logic without touching clients +- Flexible Deployment: Run clients on any platform +- Scalability: Add more clients without server changes +- Maintainability: Simpler client code, easier debugging +- Testability: Test client and server independently --- ## 🚀 Next Steps -!!!tip "Get Started with UFO Client" - - **1. Run Your First Client** - ```bash - # Follow the quick start guide - python -m ufo.client.client \ - --ws \ - --client-id my_first_device \ - --ws-server ws://localhost:5000/ws - ``` - 👉 [Quick Start Guide](./quick_start.md) - - **2. Understand Registration Process** - - How clients register with the server - - Device profile structure - - Registration acknowledgment - - 👉 [Server Quick Start](../server/quick_start.md) - Start server and connect clients - - **3. Explore MCP Integration** - - Learn about MCP servers - - Configure custom tools - - Create your own MCP servers - - 👉 [MCP Integration](./mcp_integration.md) - - **4. Configure for Your Environment** - - Customize MCP servers - - Adjust timeouts and retries - - Platform-specific settings - - 👉 [Configuration Guide](../configuration/system/overview.md) - - **5. Master the Protocol** - - Deep dive into AIP messages - - Understand message flow - - Error handling patterns - - 👉 [AIP Protocol](../aip/overview.md) +**1. Run Your First Client** + +```bash +# Follow the quick start guide +python -m ufo.client.client \ + --ws \ + --client-id my_first_device \ + --ws-server ws://localhost:5000/ws +``` +👉 [Quick Start Guide](./quick_start.md) + +**2. Understand Registration Process** + +Learn how clients register with the server, device profile structure, and registration acknowledgment. + +👉 [Server Quick Start](../server/quick_start.md) - Start server and connect clients + +**3. Explore MCP Integration** + +Learn about MCP servers, configure custom tools, and create your own MCP servers. + +👉 [MCP Integration](../mcp/overview.md) + +**4. Configure for Your Environment** + +Customize MCP servers, adjust timeouts and retries, and configure platform-specific settings. + +👉 [Configuration Guide](../configuration/system/overview.md) + +**5. Master the Protocol** + +Deep dive into AIP messages, understand message flow, and error handling patterns. + +👉 [AIP Protocol](../aip/overview.md) diff --git a/documents/docs/client/quick_start.md b/documents/docs/client/quick_start.md index 7763ca38d..395bfb7a7 100644 --- a/documents/docs/client/quick_start.md +++ b/documents/docs/client/quick_start.md @@ -1,25 +1,21 @@ # ⚡ Quick Start -!!!quote "Plug and Play Execution" - Get your device connected to the UFO² Agent Server in under **5 minutes**. No complex setup—just run a single command and start executing tasks. - -Get your device connected to the UFO² Agent Server and executing tasks in minutes. +Get your device connected to the UFO Agent Server and start executing tasks in minutes. No complex setup—just run a single command. --- ## 📋 Prerequisites -!!!info "What You Need" - Before connecting a client device, ensure these requirements are met. +Before connecting a client device, ensure these requirements are met: | Requirement | Version/Details | Verification Command | |-------------|-----------------|----------------------| | **Python** | 3.10 or higher | `python --version` | -| **UFO² Installation** | Latest version with dependencies | `python -c "import ufo; print('✅ Installed')"` | +| **UFO Installation** | Latest version with dependencies | `python -c "import ufo; print('✅ Installed')"` | | **Running Server** | Agent server accessible on network | `curl http://server:5000/api/health` | | **Network Access** | Client can reach server WebSocket endpoint | Test connectivity to server | -!!!tip "Server First!" +!!! tip "Server First!" **Always start the Agent Server before connecting clients.** The server must be running and accessible for clients to register successfully. 👉 [Server Quick Start Guide](../server/quick_start.md) @@ -50,8 +46,7 @@ wscat -c ws://localhost:5000/ws ### Minimal Command (Local Server) -!!!example "Simplest Connection" - Connect to a server running on the same machine with default settings: +Connect to a server running on the same machine with default settings: ```bash python -m ufo.client.client --ws --client-id my_device @@ -69,8 +64,7 @@ python -m ufo.client.client --ws --client-id my_device ### Connect to Remote Server -!!!example "Production Setup" - Connect to a server running on a different machine in your network: +Connect to a server running on a different machine in your network: ```bash python -m ufo.client.client \ @@ -87,7 +81,7 @@ python -m ufo.client.client \ ### Override Platform Detection -!!!tip "When to Override" +!!! tip "When to Override" Normally, the client auto-detects the platform (`windows` or `linux`). Override when: - Running in container/VM with mismatched OS @@ -104,29 +98,29 @@ python -m ufo.client.client \ ### Complete Command (All Options) -!!!example "Production-Ready Configuration" - ```bash - python -m ufo.client.client \ - --ws \ - --ws-server ws://192.168.1.100:5000/ws \ - --client-id device_windows_prod_01 \ - --platform windows \ - --max-retries 10 \ - --log-level INFO - ``` - - **Enhancements:** - - - 🔁 **10 retries**: Resilient to temporary network issues - - 📋 **INFO logging**: Balanced verbosity (not DEBUG spam) - - 🏷️ **Descriptive ID**: `device_windows_prod_01` clearly identifies environment +Production-ready configuration with all available options: + +```bash +python -m ufo.client.client \ + --ws \ + --ws-server ws://192.168.1.100:5000/ws \ + --client-id device_windows_prod_01 \ + --platform windows \ + --max-retries 10 \ + --log-level WARNING +``` + +**Enhancements:** + +- 🔁 **10 retries**: Resilient to temporary network issues +- 📋 **WARNING logging**: Default level (less verbose than INFO) +- 🏷️ **Descriptive ID**: `device_windows_prod_01` clearly identifies environment --- ## 📝 Connection Parameters Reference -!!!info "CLI Argument Details" - All available command-line options for the UFO client. +All available command-line options for the UFO client. ### Required Parameters @@ -152,9 +146,9 @@ python -m ufo.client.client \ | Parameter | Type | Default | Description | Example | |-----------|------|---------|-------------|---------| -| `--log-level` | `str` | `INFO` | Logging verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` | `--log-level DEBUG` | +| `--log-level` | `str` | `WARNING` | Logging verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`, `OFF` | `--log-level DEBUG` | -!!!warning "Unique Client IDs - Critical!" +!!! warning "Unique Client IDs - Critical!" **Each device MUST have a unique `--client-id`.** Duplicate IDs will cause: - ❌ Connection conflicts (devices disconnecting each other) @@ -175,8 +169,7 @@ python -m ufo.client.client \ ### Client Logs -!!!success "Connection Established" - When the client connects successfully, you'll see this sequence: +When the client connects successfully, you'll see this sequence: ```log INFO - Platform detected/specified: windows @@ -195,28 +188,21 @@ sequenceDiagram participant C as Client participant S as Server - Note over C: 1. Startup - C->>C: Load Config - C->>C: Initialize MCP Servers - - Note over C,S: 2. Connection + C->>C: Load Config & Initialize MCP C->>S: WebSocket Connect S-->>C: Connection Ack - Note over C,S: 3. Registration C->>C: Collect Device Info - C->>S: REGISTRATION
(client_id, platform, capabilities) + C->>S: REGISTRATION
(id, platform, capabilities) S->>S: Validate & Store S-->>C: REGISTRATION_ACK - Note over C,S: 4. Keep-Alive loop Every 30s C->>S: HEARTBEAT S-->>C: HEARTBEAT_ACK end - Note over C,S: 5. Ready - S->>C: COMMAND (when task dispatched) + Note over C,S: Ready for Commands ``` ### Server Logs @@ -293,8 +279,7 @@ print(response.json()) ### Monitor Heartbeats -!!!info "Keep-Alive Mechanism" - The client sends **heartbeat messages every 30 seconds** to prove it's still alive. +The client sends **heartbeat messages every 30 seconds** to prove it's still alive. **Client Logs (DEBUG level):** @@ -314,8 +299,7 @@ DEBUG - [WS] Heartbeat acknowledged for device_windows_001 ## 🎯 Running Your First Task -!!!example "Test Task Execution" - Once the client is connected, dispatch a simple task from the server to verify end-to-end functionality. +Once the client is connected, dispatch a simple task from the server to verify end-to-end functionality. ### Dispatch Task via HTTP API @@ -393,23 +377,16 @@ sequenceDiagram participant API as HTTP API participant Server participant Client - participant Notepad + participant App as Notepad - API->>Server: POST /api/dispatch
{client_id, request} + API->>Server: POST /dispatch Server->>Server: Create Session Server-->>API: {session_id, status} - Server->>Client: COMMAND
(open notepad, type text) - - Client->>Client: Parse Command - Client->>Notepad: Launch Notepad - Notepad-->>Client: Window Opened - - Client->>Notepad: Type "Hello from UFO" - Notepad-->>Client: Text Entered - - Client->>Server: COMMAND_RESULTS
(success) - Server->>Server: Update Session + Server->>Client: COMMAND + Client->>App: Launch & Type + App-->>Client: Done + Client->>Server: COMMAND_RESULTS ``` --- @@ -418,11 +395,11 @@ sequenceDiagram ### 1. Connection Refused -!!!failure "Symptom" - ```log - ERROR - [WS] Unexpected error: [Errno 10061] Connect call failed - ERROR - [WS] Max retries reached. Exiting. - ``` +**Symptom:** +```log +ERROR - [WS] Unexpected error: [Errno 10061] Connect call failed +ERROR - [WS] Max retries reached. Exiting. +``` **Root Causes:** @@ -462,11 +439,11 @@ netstat -tuln | grep :5000 ### 2. Registration Failed -!!!failure "Symptom" - ```log - ERROR - [WS] [AIP] ❌ Failed to register as device_windows_001 - RuntimeError: Registration failed for device_windows_001 - ``` +**Symptom:** +```log +ERROR - [WS] [AIP] ❌ Failed to register as device_windows_001 +RuntimeError: Registration failed for device_windows_001 +``` **Root Causes:** @@ -497,11 +474,11 @@ python -m ufo.client.client --ws --client-id NEW_UNIQUE_ID ### 3. Platform Detection Issues -!!!warning "Symptom" - ```log - WARNING - Platform not detected correctly - WARNING - Defaulting to platform: unknown - ``` +**Symptom:** +```log +WARNING - Platform not detected correctly +WARNING - Defaulting to platform: unknown +``` **Solution:** @@ -524,11 +501,11 @@ python -m ufo.client.client \ ### 4. Heartbeat Timeout -!!!failure "Symptom" - ```log - ERROR - [WS] Connection closed: ConnectionClosedError - INFO - [WS] Reconnecting... (attempt 2/5) - ``` +**Symptom:** +```log +ERROR - [WS] Connection closed: ConnectionClosedError +INFO - [WS] Reconnecting... (attempt 2/5) +``` **Root Causes:** @@ -568,8 +545,7 @@ curl http://server:5000/api/health ## 🌐 Multiple Devices -!!!info "Multi-Device Deployment" - Connect multiple devices to the same server for **fleet management** and **task distribution**. +Connect multiple devices to the same server for **fleet management** and **task distribution**. ### Example Configuration @@ -650,13 +626,12 @@ Examples: ## 🔧 Running as Background Service -!!!tip "Production Deployment" +!!! tip "Production Deployment" For production use, run the client as a **system service** that starts automatically and restarts on failure. ### Linux (systemd) -!!!example "Systemd Service Configuration" - Create `/etc/systemd/system/ufo-client.service`: +Create `/etc/systemd/system/ufo-client.service`: ```ini [Unit] @@ -733,8 +708,7 @@ sudo journalctl -u ufo-client -f ### Windows (NSSM) -!!!example "Windows Service with NSSM" - **NSSM** (Non-Sucking Service Manager) wraps any application as a Windows service. +**NSSM** (Non-Sucking Service Manager) wraps any application as a Windows service. **1. Download NSSM:** @@ -798,8 +772,7 @@ Register-ScheduledTask -TaskName "UFOClient" ` ### PM2 (Cross-Platform) -!!!example "Node.js PM2 Process Manager" - **PM2** is a cross-platform process manager with built-in load balancing, monitoring, and auto-restart. +**PM2** is a cross-platform process manager with built-in load balancing, monitoring, and auto-restart. **1. Install PM2:** @@ -875,8 +848,7 @@ pm2 logs ufo-client ## 🏭 Production Deployment Best Practices -!!!success "Enterprise-Grade Deployment" - Follow these best practices for reliable production deployments. +Follow these best practices for reliable production deployments. ### 1. Descriptive Client IDs @@ -945,36 +917,37 @@ journalctl -u ufo-client -f --since "1 hour ago" ### 4. Health Monitoring -!!!example "Monitoring Script" - ```bash - #!/bin/bash - # check-ufo-client.sh - - CLIENT_ID="device_prod_01" - SERVER_URL="http://192.168.1.100:5000" - - # Check if client is connected - response=$(curl -s "${SERVER_URL}/api/clients" | grep -c "${CLIENT_ID}") - - if [ "$response" -eq "0" ]; then - echo "ALERT: Client ${CLIENT_ID} is not connected!" - # Send alert (email, Slack, PagerDuty, etc.) - exit 1 - else - echo "OK: Client ${CLIENT_ID} is connected" - exit 0 - fi - ``` - - **Run via cron:** - ```cron - # Check every 5 minutes - */5 * * * * /usr/local/bin/check-ufo-client.sh - ``` +**Monitoring Script:** + +```bash +#!/bin/bash +# check-ufo-client.sh + +CLIENT_ID="device_prod_01" +SERVER_URL="http://192.168.1.100:5000" + +# Check if client is connected +response=$(curl -s "${SERVER_URL}/api/clients" | grep -c "${CLIENT_ID}") + +if [ "$response" -eq "0" ]; then + echo "ALERT: Client ${CLIENT_ID} is not connected!" + # Send alert (email, Slack, PagerDuty, etc.) + exit 1 +else + echo "OK: Client ${CLIENT_ID} is connected" + exit 0 +fi +``` + +**Run via cron:** +```cron +# Check every 5 minutes +*/5 * * * * /usr/local/bin/check-ufo-client.sh +``` ### 5. Secure Communication -!!!danger "Production Security" +!!! danger "Production Security" **Never expose clients to the internet without these security measures:** **Use WSS (WebSocket Secure):** @@ -1010,8 +983,7 @@ python -m ufo.server.app \ ## 🔧 Troubleshooting Commands -!!!tip "Diagnostic Tools" - Use these commands to diagnose connection and execution issues. +Use these commands to diagnose connection and execution issues. ### Test Server Connectivity @@ -1091,14 +1063,14 @@ curl -X POST http://localhost:5000/api/dispatch \ ## 🚀 Next Steps -!!!tip "Continue Learning" +!!! tip "Continue Learning" Now that your client is connected and running tasks: **1. Understand Registration Flow** Learn how clients register with the server and exchange device profiles: -👉 [Server Overview - Client Registration](../server/overview.md) +👉 [UFO Client Overview](./overview.md) **2. Explore Device Information** @@ -1116,7 +1088,7 @@ Understand the AIP protocol and WebSocket message flow: Learn how to add custom tools and configure MCP servers: -👉 [MCP Integration](./mcp_integration.md) +👉 [MCP Integration](../mcp/overview.md) **5. Study the AIP Protocol** diff --git a/documents/docs/client/ufo_client.md b/documents/docs/client/ufo_client.md index 8989944b3..ff1c73236 100644 --- a/documents/docs/client/ufo_client.md +++ b/documents/docs/client/ufo_client.md @@ -1,16 +1,10 @@ # 🎯 UFO Client -!!!quote "The Execution Orchestrator" - The **UFO Client** is the pure execution engine that receives commands from the server, routes them to appropriate tools via the CommandRouter, and aggregates results—**no reasoning, just deterministic execution**. - -The UFO Client is the bridge between network communication and local tool execution. - ---- +The **UFO Client** is the execution engine that receives commands from the server, routes them to appropriate tools via the CommandRouter, and aggregates results. It focuses on stateless command execution, delegating all decision-making to the server. ## 📋 Overview -!!!info "Core Responsibilities" - The UFO Client focuses exclusively on **stateless command execution**, delegating all decision-making to the server. +The UFO Client bridges network communication and local tool execution. **Key Capabilities:** @@ -23,57 +17,53 @@ The UFO Client is the bridge between network communication and local tool execut | **State Management** | Maintains agent, process, and root names | Property setters with validation | | **Manager Coordination** | Orchestrates ComputerManager and MCPServerManager | `reset()` cascades to all managers | -!!!success "Stateless Execution Philosophy" - The UFO Client **never makes decisions**. It: - - - ✅ Executes commands sent by the server - - ✅ Routes commands to the appropriate tools - - ✅ Returns execution results - - ❌ Does **not** decide which commands to run - - ❌ Does **not** interpret user requests - - ❌ Does **not** store long-term state +The UFO Client follows a stateless execution philosophy: + +- Executes commands sent by the server +- Routes commands to the appropriate tools +- Returns execution results +- Does **not** decide which commands to run +- Does **not** interpret user requests +- Does **not** store long-term state **Architectural Position:** ```mermaid graph LR - subgraph "Server Side (Orchestration)" - Server[Agent Server] + subgraph Server["Server Side (Orchestration)"] + SRV[Agent Server] LLM[LLM Reasoning] end - subgraph "Network Layer" + subgraph Network["Network Layer"] WSC[WebSocket Client] end - subgraph "Client Side (Execution)" + subgraph Client["Client Side (Execution)"] UFC[UFO Client] CR[Command Router] Tools[MCP Tools] end - Server -->|Commands| WSC + SRV -->|Commands| WSC WSC -->|execute_step| UFC UFC -->|execute| CR CR -->|tool calls| Tools Tools -->|results| CR CR -->|results| UFC UFC -->|results| WSC - WSC -->|results| Server + WSC -->|results| SRV - LLM -->|planning| Server + LLM -->|planning| SRV - style Server fill:#ffe0b2 + style SRV fill:#ffe0b2 style UFC fill:#bbdefb style Tools fill:#c8e6c9 ``` ---- - ## 🏗️ Architecture -!!!success "Simple and Focused Design" - The UFO Client has a minimal API surface—just initialization, execution, and reset. +The UFO Client has a minimal API surface—just initialization, execution, and reset. ### Component Structure @@ -138,18 +128,15 @@ graph TB | `command_router` | `CommandRouter` | Routes commands to appropriate computers | | `task_lock` | `asyncio.Lock` | Ensures thread-safe execution | | `client_id` | `str` | Unique identifier for this client (default: `"client_001"`) | -| `platform` | `str` | Platform type (`"windows"` or `"linux"`) | +| `platform` | `str` | Platform type (`"windows"` or `"linux"`) - auto-detected if not provided | | `session_id` | `Optional[str]` | Current session identifier | | `agent_name` | `Optional[str]` | Active agent (e.g., `"HostAgent"`, `"AppAgent"`) | | `process_name` | `Optional[str]` | Process context (e.g., `"notepad.exe"`) | | `root_name` | `Optional[str]` | Root operation name | ---- - ## 🚀 Initialization -!!!example "Creating a UFO Client" - The UFO Client requires two manager instances: MCPServerManager and ComputerManager. +Creating a UFO Client requires two manager instances: MCPServerManager and ComputerManager. ```python from ufo.client.ufo_client import UFOClient @@ -190,12 +177,9 @@ client = UFOClient( 2. Initializes `task_lock` (`asyncio.Lock()`) 3. Sets session state to `None` (session_id, agent_name, process_name, root_name) ---- - ## 📊 Session State Management -!!!info "Metadata Tracking" - The UFO Client maintains contextual metadata for the current execution session. +The UFO Client maintains contextual metadata for the current execution session. ### Session ID @@ -289,14 +273,11 @@ except ValueError as e: | `process_name` | `str`, `None` | `ValueError` | | `root_name` | `str`, `None` | `ValueError` | ---- - ## ⚙️ Command Execution ### Execute Step (Main Entry Point) -!!!info "Single-Step Execution" - `execute_step()` processes one complete server message, extracting metadata and executing all commands. +`execute_step()` processes one complete server message, extracting metadata and executing all commands. **Signature:** @@ -372,8 +353,7 @@ for result in action_results: ### Execute Actions -!!!info "Batch Command Execution" - `execute_actions()` executes a list of commands via the CommandRouter. +`execute_actions()` executes a list of commands via the CommandRouter. **Signature:** @@ -448,8 +428,6 @@ results = await client.execute_actions(commands) See [Computer Manager](./computer_manager.md) for command routing details. ---- - ## 🔄 State Reset !!!warning "Critical for Multi-Task Execution" @@ -512,21 +490,17 @@ graph TD | **On task failure** | Clean up failed state | | **On server disconnection** | Reset to known good state | -!!!tip "Automatic Reset" - The WebSocket client automatically calls `reset()` before starting new tasks: - - ```python - async with self.ufo_client.task_lock: - self.ufo_client.reset() # Automatic - await self.task_protocol.send_task_request(...) - ``` +**Note:** The WebSocket client automatically calls `reset()` before starting new tasks: ---- +```python +async with self.ufo_client.task_lock: + self.ufo_client.reset() # Automatic + await self.task_protocol.send_task_request(...) +``` ## 🔒 Thread Safety -!!!success "Async-Safe Execution" - The UFO Client uses `asyncio.Lock` to prevent concurrent state modifications. +The UFO Client uses `asyncio.Lock` to prevent concurrent state modifications. **Lock Implementation:** @@ -555,8 +529,6 @@ async with client.task_lock: !!!warning "Single Task Execution" The lock ensures only **one task executes at a time**. Attempting concurrent execution will block until the lock is released. ---- - ## 📋 Complete Execution Pipeline ```mermaid @@ -595,14 +567,11 @@ sequenceDiagram WSC->>Server: COMMAND_RESULTS (via AIP) ``` ---- - ## ⚠️ Error Handling ### Command Execution Errors -!!!info "Error Capture in Results" - Individual command failures are captured in `Result` objects, not thrown as exceptions. +Individual command failures are captured in `Result` objects, not thrown as exceptions. **Error Result Structure:** @@ -653,12 +622,9 @@ except ValueError as e: | Property validation error | Property setters | `ValueError` exception | | Unexpected errors | Any component | Logged, may propagate | ---- - ## 📝 Logging -!!!info "Execution Visibility" - The UFO Client logs all major events for debugging and monitoring. +The UFO Client logs all major events for debugging and monitoring. **Log Examples:** @@ -698,14 +664,11 @@ INFO - Client state has been reset. | Production | `INFO` | Monitor without spam | | Troubleshooting | `DEBUG` | Diagnose issues | ---- - ## 💡 Usage Example ### Complete Workflow -!!!example "End-to-End Execution" - This example shows how to use the UFO Client in a typical workflow. +This example shows how to use the UFO Client in a typical workflow. ```python import asyncio @@ -754,692 +717,98 @@ async def main(): asyncio.run(main()) ``` ---- - ## ✅ Best Practices ### Development Best Practices -!!!tip "Defensive Programming" - - **1. Always Reset Between Tasks** - ```python - async with client.task_lock: - client.reset() # Clear previous state - await client.execute_step(new_server_response) - ``` - - **2. Use Property Setters (Not Direct Assignment)** - ```python - # ✅ Good - validates input - client.session_id = "session_123" - - # ❌ Bad - bypasses validation - client._session_id = "session_123" - ``` - - **3. Log Execution Progress** - ```python - self.logger.info(f"Executing {len(commands)} actions for {self.agent_name}") - ``` - - **4. Handle Errors Gracefully** - ```python - try: - results = await client.execute_actions(commands) - except Exception as e: - self.logger.error(f"Execution failed: {e}", exc_info=True) - # Error is also captured in results - ``` - -### Production Best Practices - -!!!success "Reliability and Monitoring" - - **1. Use Thread Locks Consistently** - ```python - # Always use task_lock for state operations - async with client.task_lock: - client.reset() - results = await client.execute_step(msg) - ``` - - **2. Monitor Execution Times** - ```python - import time - - start = time.time() - results = await client.execute_actions(commands) - duration = time.time() - start - - if duration > 60: # Alert if > 1 minute - logger.warning(f"Slow execution: {duration}s for {len(commands)} commands") - ``` - - **3. Validate Results** - ```python - # Check for failures - failed_actions = [r for r in results if r.status == ResultStatus.ERROR] - if failed_actions: - logger.error(f"{len(failed_actions)} actions failed") - # Report to monitoring system - ``` - ---- - -## 🔗 Integration Points - -### WebSocket Client Integration - -!!!info "Network Communication Layer" - The WebSocket client uses UFO Client for all command execution. - -**Integration:** - -```python -# In WebSocket client -action_results = await self.ufo_client.execute_step(server_response) -``` - -See [WebSocket Client](./websocket_client.md) for communication details. - -### Command Router Integration - -!!!info "Command Delegation" - The UFO Client delegates all execution to the CommandRouter. - -**Integration:** +**1. Always Reset Between Tasks** ```python -action_results = await self.command_router.execute( - agent_name=self.agent_name, - process_name=self.process_name, - root_name=self.root_name, - commands=commands -) -``` - -See [Computer Manager](./computer_manager.md) for routing details. - -### Computer Manager Integration - -!!!info "Computer Instance Management" - The Computer Manager maintains computer instances for tool execution. - -**Integration:** - -```python -# Reset cascades to computer manager -self.computer_manager.reset() -``` - -See [Computer Manager](./computer_manager.md) for management details. - -### MCP Server Manager Integration - -!!!info "MCP Server Lifecycle" - The MCP Server Manager handles MCP server creation and cleanup. - -**Integration:** - -```python -# Reset cascades to MCP server manager -self.mcp_server_manager.reset() -``` - -See [MCP Integration](./mcp_integration.md) for MCP details. - ---- - -## 🚀 Next Steps - -!!!tip "Continue Learning" - - **1. Understand Network Communication** - - Learn how the WebSocket client uses UFO Client: - - 👉 [WebSocket Client](./websocket_client.md) - - **2. Explore Command Routing** - - See how commands are routed to the right tools: - - 👉 [Computer Manager](./computer_manager.md) - - **3. Study Device Profiling** - - Understand device information collection: - - 👉 [Device Info Provider](./device_info.md) - - **4. Learn About MCP Integration** - - Deep dive into MCP server management: - - 👉 [MCP Integration](./mcp_integration.md) - - **5. Master AIP Messages** - - Understand message structures: - - 👉 [AIP Messages](../aip/messages.md) - -## Architecture - -``` -┌────────────────────────────────────────────────┐ -│ UFOClient │ -├────────────────────────────────────────────────┤ -│ Session State: │ -│ • session_id - Current session identifier │ -│ • agent_name - Active agent name │ -│ • process_name - Process context │ -│ • root_name - Root operation name │ -├────────────────────────────────────────────────┤ -│ Execution: │ -│ • execute_step() - Process server message │ -│ • execute_actions() - Run command list │ -│ • reset() - Clear session state │ -├────────────────────────────────────────────────┤ -│ Dependencies: │ -│ • CommandRouter - Route commands to tools │ -│ • ComputerManager - Manage computer instances │ -│ • MCPServerManager - Manage MCP servers │ -└────────────────────────────────────────────────┘ -``` - -## Initialization - -```python -from ufo.client.ufo_client import UFOClient -from ufo.client.computer import ComputerManager -from ufo.client.mcp.mcp_server_manager import MCPServerManager - -# Initialize managers -mcp_server_manager = MCPServerManager() -computer_manager = ComputerManager( - ufo_config.to_dict(), - mcp_server_manager -) - -# Create UFO client -client = UFOClient( - mcp_server_manager=mcp_server_manager, - computer_manager=computer_manager, - client_id="device_windows_001", - platform="windows" -) -``` - -**Parameters:** - -| Parameter | Type | Description | -|-----------|------|-------------| -| `mcp_server_manager` | MCPServerManager | Manages MCP server lifecycle | -| `computer_manager` | ComputerManager | Manages computer instances | -| `client_id` | str (optional) | Unique client identifier (default: `"client_001"`) | -| `platform` | str (optional) | Platform type: `"windows"` or `"linux"` | - -## Session State - -The client maintains state for the current session: - -### Session ID - -```python -# Set session ID -client.session_id = "session_20251104_143022_abc123" - -# Get session ID -current_session = client.session_id - -# Clear session ID -client.reset() # Sets session_id to None -``` - -!!!warning "Thread Safety" - Session state is protected by `task_lock` to prevent concurrent modifications. - -### Agent Name - -Identifies the active agent (HostAgent, AppAgent, etc.): - -```python -# Set agent name -client.agent_name = "HostAgent" - -# Get agent name -agent = client.agent_name -``` - -### Process Name - -Identifies the process context: - -```python -# Set process name -client.process_name = "notepad.exe" - -# Get process name -process = client.process_name -``` - -### Root Name - -Identifies the root operation: - -```python -# Set root name -client.root_name = "open_application" - -# Get root name -root = client.root_name -``` - -## Command Execution - -### Execute Step - -Processes a complete step from the server: - -```python -from aip.messages import ServerMessage - -# Receive server message -server_response = ServerMessage.model_validate_json(msg) - -# Execute step -action_results = await client.execute_step(server_response) - -# action_results is a list of Result objects -``` - -**Workflow:** - -``` -Server Message → Extract Metadata → Execute Actions → Return Results - │ │ │ │ - │ │ │ │ - Actions List agent_name CommandRouter Result[] - process_name Execution - root_name -``` - -**Implementation:** - -```python -async def execute_step(self, response: ServerMessage) -> List[Result]: - """Perform a single step execution.""" - - # Extract metadata from server response - self.agent_name = response.agent_name - self.process_name = response.process_name - self.root_name = response.root_name - - # Execute actions - action_results = await self.execute_actions(response.actions) - - return action_results -``` - -### Execute Actions - -Executes a list of commands: - -```python -from aip.messages import Command - -commands = [ - Command( - action="click", - parameters={"control_label": "Start", "x": 10, "y": 10} - ), - Command( - action="type_text", - parameters={"text": "notepad"} - ), - Command( - action="press_key", - parameters={"key": "enter"} - ) -] - -# Execute all commands -results = await client.execute_actions(commands) - -# results contains Result object for each command -``` - -**Delegation to Command Router:** - -```python -async def execute_actions(self, commands: Optional[List[Command]]) -> List[Result]: - """Execute the actions provided by the server.""" - - action_results = [] - - if commands: - self.logger.info(f"Executing {len(commands)} actions in total") - - # Delegate to CommandRouter - action_results = await self.command_router.execute( - agent_name=self.agent_name, - process_name=self.process_name, - root_name=self.root_name, - commands=commands - ) - - return action_results -``` - -See [Computer Manager](./computer_manager.md) for command routing details. - -## State Reset - -Resets all session state and dependent managers: - -```python -def reset(self): - """Reset session state and dependent managers.""" - - # Clear session state - self._session_id = None - self._agent_name = None - self._process_name = None - self._root_name = None - - # Reset managers - self.computer_manager.reset() - self.mcp_server_manager.reset() - - self.logger.info("Client state has been reset.") -``` - -**When to Reset:** - -- Before starting a new task -- On task completion -- On task failure -- On server disconnection - -!!!tip "Automatic Reset" - The WebSocket client automatically calls `reset()` before starting new tasks. - -## Thread Safety - -The client uses an asyncio lock for thread-safe execution: - -```python -# In WebSocket client async with client.task_lock: - client.reset() - await client.execute_step(server_response) + client.reset() # Clear previous state + await client.execute_step(new_server_response) ``` -**Protected Operations:** - -- Session state modifications -- Command execution -- State reset - -## Property Validation - -All session properties validate their inputs: +**2. Use Property Setters (Not Direct Assignment)** ```python -# Valid assignment -client.session_id = "session_123" # ✅ - -# Invalid assignment -client.session_id = 12345 # ❌ ValueError: Session ID must be a string or None - -# Valid clearing -client.session_id = None # ✅ - -# Similar validation for agent_name, process_name, root_name -``` - -**Error Example:** +# ✅ Good - validates input +client.session_id = "session_123" -```python -try: - client.agent_name = 123 # Not a string -except ValueError as e: - print(e) # "Agent name must be a string or None." +# ❌ Bad - bypasses validation +client._session_id = "session_123" ``` -## Integration with Command Router - -The client delegates execution to the `CommandRouter`: +**3. Log Execution Progress** ```python -from ufo.client.computer import CommandRouter - -self.command_router = CommandRouter( - computer_manager=self.computer_manager -) - -# Execute commands -results = await self.command_router.execute( - agent_name="HostAgent", - process_name="explorer.exe", - root_name="open_folder", - commands=[...] -) -``` - -**Command Router Responsibilities:** - -- Route commands to appropriate computer instances -- Execute commands via MCP tools -- Handle tool failures and timeouts -- Aggregate results - -See [Computer Manager](./computer_manager.md) for routing details. - -## Execution Flow - -### Complete Execution Pipeline - -``` -1. WebSocket receives COMMAND message - │ - ▼ -2. WebSocket calls client.execute_step(server_response) - │ - ▼ -3. Client extracts metadata (agent_name, process_name, root_name) - │ - ▼ -4. Client calls execute_actions(commands) - │ - ▼ -5. Client delegates to CommandRouter.execute() - │ - ▼ -6. CommandRouter routes to Computer instances - │ - ▼ -7. Computer executes via MCP tools - │ - ▼ -8. Results bubble back up to client - │ - ▼ -9. Client returns results to WebSocket - │ - ▼ -10. WebSocket sends COMMAND_RESULTS via AIP +self.logger.info(f"Executing {len(commands)} actions for {self.agent_name}") ``` -## Error Handling - -### Command Execution Errors +**4. Handle Errors Gracefully** ```python try: results = await client.execute_actions(commands) except Exception as e: - logger.error(f"Command execution failed: {e}", exc_info=True) - # Error will be included in Result objects -``` - -**Error Results:** - -Individual command failures are captured in Result objects: - -```python -from aip.messages import Result, ResultStatus - -error_result = Result( - action="click", - status=ResultStatus.ERROR, - error_message="Control not found", - observation="Failed to locate control with label 'Start'" -) -``` - -### Property Validation Errors - -```python -try: - client.session_id = 12345 # Invalid type -except ValueError as e: - logger.error(f"Invalid session ID: {e}") -``` - -## Logging - -The client logs execution progress: - -**Initialization:** - -``` -INFO - UFO Client initialized for platform: windows -``` - -**Session State Changes:** - -``` -INFO - Session ID set to: session_20251104_143022_abc123 -INFO - Agent name set to: HostAgent -INFO - Process name set to: notepad.exe -INFO - Root name set to: open_application -``` - -**Execution:** - -``` -INFO - Executing 5 actions in total -``` - -**Reset:** - -``` -INFO - Client state has been reset. -``` - -## Usage Example - -### Complete Workflow - -```python -import asyncio -from ufo.client.ufo_client import UFOClient -from aip.messages import ServerMessage, Command - -async def main(): - # Initialize client - client = UFOClient( - mcp_server_manager=mcp_manager, - computer_manager=computer_manager, - client_id="device_windows_001", - platform="windows" - ) - - # Simulate server message - server_msg = ServerMessage( - type=ServerMessageType.COMMAND, - session_id="session_123", - response_id="resp_456", - agent_name="HostAgent", - process_name="explorer.exe", - root_name="navigate_folder", - actions=[ - Command(action="click", parameters={"label": "File"}), - Command(action="click", parameters={"label": "New Folder"}) - ], - status=TaskStatus.PROCESSING - ) - - # Execute step - results = await client.execute_step(server_msg) - - # Process results - for result in results: - print(f"Action: {result.action}") - print(f"Status: {result.status}") - print(f"Observation: {result.observation}") - - # Reset for next task - client.reset() - -asyncio.run(main()) + self.logger.error(f"Execution failed: {e}", exc_info=True) + # Error is also captured in results ``` -## Best Practices +### Production Best Practices -**Always Reset Between Tasks** +**1. Use Thread Locks Consistently** ```python +# Always use task_lock for state operations async with client.task_lock: client.reset() - await client.execute_step(new_server_response) + results = await client.execute_step(msg) ``` -**Use Property Setters** +**2. Monitor Execution Times** ```python -# Good - validates input -client.session_id = "session_123" - -# Bad - bypasses validation -client._session_id = "session_123" -``` +import time -**Log Execution Progress** +start = time.time() +results = await client.execute_actions(commands) +duration = time.time() - start -```python -self.logger.info(f"Executing {len(commands)} actions for {self.agent_name}") +if duration > 60: # Alert if > 1 minute + logger.warning(f"Slow execution: {duration}s for {len(commands)} commands") ``` -**Handle Errors Gracefully** +**3. Validate Results** ```python -try: - results = await client.execute_actions(commands) -except Exception as e: - # Log and report error - self.logger.error(f"Execution failed: {e}", exc_info=True) - # Error is captured in results +# Check for failures +failed_actions = [r for r in results if r.status == ResultStatus.ERROR] +if failed_actions: + logger.error(f"{len(failed_actions)} actions failed") + # Report to monitoring system ``` -## Integration Points +## 🔗 Integration Points + +### WebSocket Client Integration -### WebSocket Client +The WebSocket client uses UFO Client for all command execution. -The WebSocket client uses UFO Client for execution: +**Integration:** ```python +# In WebSocket client action_results = await self.ufo_client.execute_step(server_response) ``` See [WebSocket Client](./websocket_client.md) for communication details. -### Command Router +### Command Router Integration -The UFO Client delegates to the CommandRouter: +The UFO Client delegates all execution to the CommandRouter. + +**Integration:** ```python action_results = await self.command_router.execute( @@ -1452,30 +821,42 @@ action_results = await self.command_router.execute( See [Computer Manager](./computer_manager.md) for routing details. -### Computer Manager +### Computer Manager Integration + +The Computer Manager maintains computer instances for tool execution. -Manages computer instances that execute commands: +**Integration:** ```python -self.computer_manager.reset() # Called during client reset +# Reset cascades to computer manager +self.computer_manager.reset() ``` See [Computer Manager](./computer_manager.md) for management details. -### MCP Server Manager +### MCP Server Manager Integration -Manages MCP server lifecycle: +The MCP Server Manager handles MCP server creation and cleanup. + +**Integration:** ```python -self.mcp_server_manager.reset() # Called during client reset +# Reset cascades to MCP server manager +self.mcp_server_manager.reset() ``` See [MCP Integration](./mcp_integration.md) for MCP details. -## Next Steps +## 🚀 Next Steps + +**Continue Learning** + +1. **Understand Network Communication** - Learn how the WebSocket client uses UFO Client: [WebSocket Client](./websocket_client.md) + +2. **Explore Command Routing** - See how commands are routed to the right tools: [Computer Manager](./computer_manager.md) + +3. **Study Device Profiling** - Understand device information collection: [Device Info Provider](./device_info.md) + +4. **Learn About MCP Integration** - Deep dive into MCP server management: [MCP Integration](./mcp_integration.md) -- [WebSocket Client](./websocket_client.md) - Communication layer -- [Computer Manager](./computer_manager.md) - Command routing -- [Device Info Provider](./device_info.md) - System profiling -- [MCP Integration](./mcp_integration.md) - Tool management -- [AIP Messages](../aip/messages.md) - Message structures +5. **Master AIP Messages** - Understand message structures: [AIP Messages](../aip/messages.md) \ No newline at end of file diff --git a/documents/docs/client/websocket_client.md b/documents/docs/client/websocket_client.md index f2604041e..24abea8eb 100644 --- a/documents/docs/client/websocket_client.md +++ b/documents/docs/client/websocket_client.md @@ -1,16 +1,10 @@ # 🔌 WebSocket Client -!!!quote "The Communication Backbone" - The **WebSocket Client** implements the **AIP (Agent Interaction Protocol)** for reliable, bidirectional communication between device clients and the Agent Server. It's the transport layer that makes remote task execution possible. - -The WebSocket client provides the low-level communication infrastructure for UFO device clients. - ---- +The **WebSocket Client** implements the **AIP (Agent Interaction Protocol)** for reliable, bidirectional communication between device clients and the Agent Server. It provides the low-level communication infrastructure for UFO device clients. ## 📋 Overview -!!!info "Core Capabilities" - The WebSocket client handles all network communication aspects, allowing the UFO Client to focus on task execution. +The WebSocket client handles all network communication aspects, allowing the UFO Client to focus on task execution. **Key Responsibilities:** @@ -51,12 +45,9 @@ graph LR style Server fill:#ffe0b2 ``` ---- - ## 🏗️ Architecture -!!!success "Layered Design" - The WebSocket client is organized into distinct layers for connection management, protocol handling, and message routing. +The WebSocket client is organized into distinct layers for connection management, protocol handling, and message routing. ### Component Structure @@ -169,30 +160,31 @@ sequenceDiagram ### Initialization Code -!!!example "Creating a WebSocket Client" - ```python - from ufo.client.websocket import UFOWebSocketClient - from ufo.client.ufo_client import UFOClient - - # Create UFO client (execution engine) - ufo_client = UFOClient( - mcp_server_manager=mcp_manager, - computer_manager=computer_manager, - client_id="device_windows_001", - platform="windows" - ) - - # Create WebSocket client (communication layer) - ws_client = UFOWebSocketClient( - ws_url="ws://localhost:5000/ws", - ufo_client=ufo_client, - max_retries=3, # Default: 3 attempts - timeout=120 # Heartbeat interval parameter (actual default: 30s) - ) - - # Connect and start listening (blocking call) - await ws_client.connect_and_listen() - ``` +Creating a WebSocket client: + +```python +from ufo.client.websocket import UFOWebSocketClient +from ufo.client.ufo_client import UFOClient + +# Create UFO client (execution engine) +ufo_client = UFOClient( + mcp_server_manager=mcp_manager, +computer_manager=computer_manager, + client_id="device_windows_001", + platform="windows" +) + +# Create WebSocket client (communication layer) +ws_client = UFOWebSocketClient( + ws_url="ws://localhost:5000/ws", + ufo_client=ufo_client, + max_retries=3, # Default: 3 attempts + timeout=120 # Heartbeat interval in seconds (default: 120) +) + +# Connect and start listening (blocking call) +await ws_client.connect_and_listen() +``` **Constructor Parameters:** @@ -201,15 +193,13 @@ sequenceDiagram | `ws_url` | `str` | Required | WebSocket server URL (e.g., `ws://localhost:5000/ws`) | | `ufo_client` | `UFOClient` | Required | UFO client instance for command execution | | `max_retries` | `int` | `3` | Maximum connection retry attempts | -| `timeout` | `float` | `120` | Parameter passed to heartbeat_loop (note: function default is 30s) | +| `timeout` | `float` | `120` | Heartbeat interval in seconds (passed to `heartbeat_loop()`) | -!!!warning "Timeout Parameter Clarification" - The `timeout` parameter (default 120) is passed to `heartbeat_loop()`, but the function itself defaults to 30 seconds if not explicitly overridden in the call. See source code: `async def heartbeat_loop(self, interval: float = 30)`. +**Note:** The `timeout` parameter is passed to `heartbeat_loop(interval)` to control heartbeat frequency. While `heartbeat_loop()` has a default of 30s in its signature, the client constructor uses 120s which is passed when calling the method. ### Connection Establishment Details -!!!info "WebSocket Configuration" - The client uses specific WebSocket parameters optimized for long-running task execution: +The client uses specific WebSocket parameters optimized for long-running task execution: **WebSocket Connection Parameters:** @@ -233,17 +223,13 @@ async with websockets.connect( | `close_timeout` | **10 seconds** | Quick cleanup on intentional disconnect | | `max_size` | **100 MB** | Supports large screenshots, logs, file transfers | -!!!success "Optimized for Long Operations" - The 180-second `ping_timeout` ensures the connection stays alive during lengthy tool executions (up to 100 minutes per tool). - ---- +**Note:** The 180-second `ping_timeout` ensures the connection stays alive during lengthy tool executions (up to 100 minutes per tool). ## 📝 Registration Flow ### Device Information Collection -!!!info "Push Model" - UFO uses a **push model** for device information: clients proactively send their profile during registration, rather than waiting for the server to request it. This reduces latency for constellation (multi-client) scenarios. +UFO uses a **push model** for device information: clients proactively send their profile during registration, rather than waiting for the server to request it. This reduces latency for constellation (multi-client) scenarios. **Device Info Collection:** @@ -396,8 +382,7 @@ RuntimeError: Registration failed for device_windows_001 ## 💓 Heartbeat Mechanism -!!!success "Connection Health Monitoring" - Heartbeats prove the client is still alive and responsive, allowing the server to detect disconnected clients quickly. +Heartbeats prove the client is still alive and responsive, allowing the server to detect disconnected clients quickly. ### Heartbeat Loop Implementation @@ -431,16 +416,17 @@ async def heartbeat_loop(self, interval: float = 30) -> None: break # Exit loop if connection is closed ``` -!!!tip "Customizing Heartbeat Interval" - Adjust the interval when calling the heartbeat loop: - - ```python - # In handle_messages(): - await asyncio.gather( - self.recv_loop(), - self.heartbeat_loop(interval=60) # Custom 60-second interval - ) - ``` +**Customizing Heartbeat Interval:** + +Adjust the interval when calling the heartbeat loop: + +```python +# In handle_messages(): +await asyncio.gather( + self.recv_loop(), + self.heartbeat_loop(interval=60) # Custom 60-second interval +) +``` ### Heartbeat Message Structure @@ -493,8 +479,7 @@ stateDiagram-v2 ### Message Router -!!!info "Type-Based Dispatch" - All incoming messages are validated against the AIP schema and routed based on their `type` field. +All incoming messages are validated against the AIP schema and routed based on their `type` field. **Message Dispatcher Code:** @@ -620,8 +605,7 @@ async def start_task(self, request_text: str, task_name: str | None): ### Command Execution Handler -!!!info "Server-Driven Execution" - The server sends specific commands (tool calls) to execute, and the client returns results. +The server sends specific commands (tool calls) to execute, and the client returns results. **Command Execution Flow:** @@ -689,8 +673,7 @@ async def handle_task_end(self, server_response: ServerMessage): ### Connection Error Recovery -!!!success "Automatic Retry with Exponential Backoff" - The client automatically retries failed connections using exponential backoff to avoid overwhelming the server. +The client automatically retries failed connections using exponential backoff to avoid overwhelming the server. **Retry Logic:** @@ -743,16 +726,17 @@ async def _maybe_retry(self): | 3rd retry | 8 seconds | 14s | | **Max retries reached** | Exit | - | -!!!note "Default Max Retries = 3" - Based on source code: `max_retries: int = 3` in constructor. Increase for unreliable networks: - - ```python - ws_client = UFOWebSocketClient( - ws_url="ws://...", - ufo_client=ufo_client, - max_retries=10 # More resilient - ) - ``` +**Default Max Retries = 3** + +Based on source code: `max_retries: int = 3` in constructor. Increase for unreliable networks: + +```python +ws_client = UFOWebSocketClient( + ws_url="ws://...", + ufo_client=ufo_client, + max_retries=10 # More resilient +) +``` ### Message Parsing Errors @@ -767,8 +751,7 @@ except Exception as e: # Message is dropped, client continues listening ``` -!!!info "Error Isolation" - Message parsing errors don't crash the client—the error is logged and the receive loop continues. +Message parsing errors don't crash the client—the error is logged and the receive loop continues. ### Registration Error Handling @@ -786,15 +769,13 @@ except Exception as e: } ``` -!!!tip "Graceful Degradation" - If device info collection fails, registration still proceeds with minimal metadata (timestamp only). +If device info collection fails, registration still proceeds with minimal metadata (timestamp only). --- ## 🔌 AIP Protocol Integration -!!!info "Three Protocol Handlers" - The WebSocket client uses three specialized AIP protocols for different communication patterns. +The WebSocket client uses three specialized AIP protocols for different communication patterns. ### 1. Registration Protocol @@ -885,8 +866,7 @@ See [AIP Task Execution Protocol](../aip/protocols.md#task-execution-protocol) f ### State Checking -!!!info "Connection Status Verification" - Use `is_connected()` to check if the client is ready to send messages. +Use `is_connected()` to check if the client is ready to send messages. **Implementation:** @@ -911,8 +891,7 @@ else: ### Connected Event -!!!tip "Async Coordination" - The `connected_event` is an `asyncio.Event` that signals successful registration. +The `connected_event` is an `asyncio.Event` that signals successful registration. **Usage Pattern:** @@ -933,89 +912,90 @@ await ws_client.start_task("Open Notepad", "task_notepad") | Registered | **Set** | ✅ Ready to send/receive messages | | Disconnected | Cleared | Connection lost, will retry | ---- - ## ✅ Best Practices ### Development Best Practices -!!!tip "Debugging and Testing" - - **1. Enable DEBUG Logging** - ```python - import logging - logging.basicConfig(level=logging.DEBUG) - ``` - - **Output:** - ```log - DEBUG - [WS] \[AIP] Heartbeat sent - DEBUG - [WS] \[AIP] Heartbeat failed (connection closed): ... - INFO - [WS] Received message: ServerMessage(type='COMMAND', ...) - ``` - - **2. Test Connection Before Full Integration** - ```python - # Test just connection and registration - ws_client = UFOWebSocketClient(ws_url, ufo_client) - await ws_client.connect_and_listen() # Should register successfully - ``` - - **3. Handle Connection Loss Gracefully** - ```python - try: - await ws_client.connect_and_listen() - except Exception as e: - logger.error(f"WebSocket client error: {e}") - # Implement recovery (e.g., alert, restart) - ``` +**1. Enable DEBUG Logging** + +```python +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +**Output:** +```log +DEBUG - [WS] [AIP] Heartbeat sent +DEBUG - [WS] [AIP] Heartbeat failed (connection closed): ... +INFO - [WS] Received message: ServerMessage(type='COMMAND', ...) +``` + +**2. Test Connection Before Full Integration** + +```python +# Test just connection and registration +ws_client = UFOWebSocketClient(ws_url, ufo_client) +await ws_client.connect_and_listen() # Should register successfully +``` + +**3. Handle Connection Loss Gracefully** + +```python +try: + await ws_client.connect_and_listen() +except Exception as e: + logger.error(f"WebSocket client error: {e}") + # Implement recovery (e.g., alert, restart) +``` ### Production Best Practices -!!!success "Reliability and Monitoring" - - **1. Use Appropriate Retry Limits** - - For production networks with occasional instability: - ```python - ws_client = UFOWebSocketClient( - ws_url="wss://production-server.com/ws", - ufo_client=ufo_client, - max_retries=10 # More retries for resilience - ) - ``` - - **2. Monitor Connection Health** - - Log heartbeat success/failure for alerting: - ```python - # In heartbeat_loop (add custom monitoring): - try: - await self.heartbeat_protocol.send_heartbeat(...) - self.logger.debug("[WS] ✅ Heartbeat sent successfully") - # Update metrics: heartbeat_success_count++ - except Exception as e: - self.logger.error(f"[WS] ❌ Heartbeat failed: {e}") - # Trigger alert: connection_health_alert() - ``` - - **3. Use Secure WebSocket (WSS)** - ```python - # Production: Encrypted WebSocket - ws_client = UFOWebSocketClient( - ws_url="wss://ufo-server.company.com/ws", # WSS, not WS - ufo_client=ufo_client - ) - ``` - - **4. Clean State on Reconnection** - - The client automatically resets state: - ```python - async with self.ufo_client.task_lock: - self.ufo_client.reset() # Clears session state - # Send new task request - ``` +**1. Use Appropriate Retry Limits** + +For production networks with occasional instability: + +```python +ws_client = UFOWebSocketClient( + ws_url="wss://production-server.com/ws", + ufo_client=ufo_client, + max_retries=10 # More retries for resilience +) +``` + +**2. Monitor Connection Health** + +Log heartbeat success/failure for alerting: + +```python +# In heartbeat_loop (add custom monitoring): +try: + await self.heartbeat_protocol.send_heartbeat(...) + self.logger.debug("[WS] ✅ Heartbeat sent successfully") + # Update metrics: heartbeat_success_count++ +except Exception as e: + self.logger.error(f"[WS] ❌ Heartbeat failed: {e}") + # Trigger alert: connection_health_alert() +``` + +**3. Use Secure WebSocket (WSS)** + +```python +# Production: Encrypted WebSocket +ws_client = UFOWebSocketClient( + ws_url="wss://ufo-server.company.com/ws", # WSS, not WS + ufo_client=ufo_client +) +``` + +**4. Clean State on Reconnection** + +The client automatically resets state: + +```python +async with self.ufo_client.task_lock: + self.ufo_client.reset() # Clears session state + # Send new task request +``` ### Error Handling Best Practices @@ -1054,8 +1034,7 @@ await ws_client.start_task("Open Notepad", "task_notepad") ### UFO Client Integration -!!!info "Execution Delegation" - The WebSocket client delegates all command execution to the UFO Client. +The WebSocket client delegates all command execution to the UFO Client. **Execution Flow:** @@ -1077,8 +1056,7 @@ See [UFO Client](./ufo_client.md) for execution details. ### Device Info Provider Integration -!!!info "System Profiling" - Device information is collected once during registration. +Device information is collected once during registration. **Integration:** @@ -1095,8 +1073,7 @@ See [Device Info Provider](./device_info.md) for profiling details. ### AIP Transport Integration -!!!info "Low-Level Communication" - All messages go through the WebSocket transport layer. +All messages go through the WebSocket transport layer. **Transport Creation:** @@ -1113,444 +1090,16 @@ self.transport = WebSocketTransport(ws) See [AIP Transport Layer](../aip/transport.md) for transport details. ---- - ## 🚀 Next Steps -!!!tip "Continue Learning" - - **1. Connect Your Client** - - Follow the step-by-step guide to connect a device: - - 👉 [Quick Start Guide](./quick_start.md) - - **2. Understand Command Execution** - - Learn how the UFO Client executes commands: - - 👉 [UFO Client Documentation](./ufo_client.md) - - **3. Explore Device Profiling** - - See what device information is collected: - - 👉 [Device Info Provider](./device_info.md) - - **4. Master the AIP Protocol** - - Deep dive into message formats and protocol details: - - 👉 [AIP Protocol Guide](../aip/protocols.md) - - **5. Study Server-Side Registration** - - Understand how the server handles client registration: - - 👉 [Server Overview](../server/overview.md) - ) - self.logger.debug("[WS] \[AIP] Heartbeat sent") - except (ConnectionError, IOError) as e: - self.logger.debug(f"[WS] \[AIP] Heartbeat failed: {e}") - break # Exit loop if connection closed -``` - -**Default Interval:** 120 seconds - -**Heartbeat Message:** - -```json -{ - "type": "HEARTBEAT", - "client_id": "device_windows_001", - "timestamp": "2025-11-04T14:30:22.123Z" -} -``` - -!!!tip "Tuning Heartbeat" - Adjust the interval in the `handle_messages()` call: - ```python - await self.heartbeat_loop(interval=60) # 60 second heartbeats - ``` - -## Message Handling - -### Message Dispatcher - -The client routes incoming messages by type: - -```python -async def handle_message(self, msg: str): - """Dispatch messages based on their type.""" - data = ServerMessage.model_validate_json(msg) - msg_type = data.type - - if msg_type == ServerMessageType.TASK: - await self.start_task(data.user_request, data.task_name) - elif msg_type == ServerMessageType.HEARTBEAT: - self.logger.info("[WS] Heartbeat received") - elif msg_type == ServerMessageType.TASK_END: - await self.handle_task_end(data) - elif msg_type == ServerMessageType.ERROR: - self.logger.error(f"[WS] Server error: {data.error}") - elif msg_type == ServerMessageType.COMMAND: - await self.handle_commands(data) - else: - self.logger.warning(f"[WS] Unknown message type: {msg_type}") -``` - -### Task Start - -When the server requests a task: - -```python -async def start_task(self, request_text: str, task_name: str | None): - """Start a new task.""" - - # Check if another task is running - if self.current_task is not None and not self.current_task.done(): - self.logger.warning("[WS] Task still running, ignoring new task") - return - - # Reset client state - async with self.ufo_client.task_lock: - self.ufo_client.reset() - - # Send task request via AIP - await self.task_protocol.send_task_request( - request=request_text, - task_name=task_name or str(uuid4()), - session_id=self.ufo_client.session_id, - client_id=self.ufo_client.client_id, - metadata={"platform": self.ufo_client.platform} - ) -``` - -!!!warning "Single Task Execution" - The client only executes one task at a time. New task requests while a task is running are ignored. - -### Command Execution - -When the server sends commands to execute: - -```python -async def handle_commands(self, server_response: ServerMessage): - """Handle commands from server.""" - - response_id = server_response.response_id - task_status = server_response.status - self.session_id = server_response.session_id - - # Execute commands via UFO Client - action_results = await self.ufo_client.execute_step(server_response) - - # Send results back via AIP - await self.task_protocol.send_task_result( - session_id=self.session_id, - prev_response_id=response_id, - action_results=action_results, - status=task_status, - client_id=self.ufo_client.client_id - ) - - # Check for task completion - if task_status in [TaskStatus.COMPLETED, TaskStatus.FAILED]: - await self.handle_task_end(server_response) -``` - -### Task Completion - -```python -async def handle_task_end(self, server_response: ServerMessage): - """Handle task end messages.""" - - if server_response.status == TaskStatus.COMPLETED: - self.logger.info(f"[WS] Task {self.session_id} completed") - elif server_response.status == TaskStatus.FAILED: - self.logger.error(f"[WS] Task {self.session_id} failed: {server_response.error}") -``` - -## Error Handling - -### Connection Errors - -**Automatic Retry with Exponential Backoff:** - -```python -async def connect_and_listen(self): - """Connect with automatic retry.""" - while self.retry_count < self.max_retries: - try: - async with websockets.connect(...) as ws: - await self.register_client() - self.retry_count = 0 # Reset on success - await self.handle_messages() - except (ConnectionClosedError, ConnectionClosedOK) as e: - self.logger.error(f"[WS] Connection closed: {e}") - self.retry_count += 1 - await self._maybe_retry() - except Exception as e: - self.logger.error(f"[WS] Unexpected error: {e}", exc_info=True) - self.retry_count += 1 - await self._maybe_retry() - - self.logger.error("[WS] Max retries reached. Exiting.") -``` - -**Exponential Backoff:** - -```python -async def _maybe_retry(self): - """Exponential backoff before retry.""" - if self.retry_count < self.max_retries: - wait_time = 2 ** self.retry_count # 1s, 2s, 4s, 8s, 16s... - self.logger.info(f"[WS] Retrying in {wait_time}s...") - await asyncio.sleep(wait_time) -``` - -### Message Parsing Errors - -```python -try: - data = ServerMessage.model_validate_json(msg) - # Process message -except Exception as e: - self.logger.error(f"[WS] Error handling message: {e}", exc_info=True) - # Send error report via AIP - error_msg = ClientMessage( - type=ClientMessageType.ERROR, - error=str(e), - client_id=self.ufo_client.client_id, - timestamp=datetime.now(timezone.utc).isoformat() - ) - await self.transport.send(error_msg.model_dump_json().encode()) -``` - -### Registration Errors - -If device info collection fails: - -```python -try: - system_info = DeviceInfoProvider.collect_system_info(...) - metadata = {"system_info": system_info.to_dict()} -except Exception as e: - self.logger.error(f"[WS] \[AIP] Error collecting device info: {e}") - # Continue with minimal metadata - metadata = {"registration_time": datetime.now(timezone.utc).isoformat()} -``` - -## AIP Protocol Integration - -The WebSocket client uses three AIP protocols: - -### Registration Protocol - -```python -from aip.protocol.registration import RegistrationProtocol - -self.registration_protocol = RegistrationProtocol(self.transport) - -# Register as device -success = await self.registration_protocol.register_as_device( - device_id="device_windows_001", - metadata={"system_info": {...}}, - platform="windows" -) -``` +**Continue Learning** -See [AIP Registration Protocol](../aip/protocols.md#registration-protocol) for details. +1. **Connect Your Client** - Follow the step-by-step guide: [Quick Start Guide](./quick_start.md) -### Heartbeat Protocol - -```python -from aip.protocol.heartbeat import HeartbeatProtocol - -self.heartbeat_protocol = HeartbeatProtocol(self.transport) - -# Send heartbeat -await self.heartbeat_protocol.send_heartbeat("device_windows_001") -``` - -See [AIP Heartbeat Protocol](../aip/protocols.md#heartbeat-protocol) for details. - -### Task Execution Protocol - -```python -from aip.protocol.task_execution import TaskExecutionProtocol - -self.task_protocol = TaskExecutionProtocol(self.transport) - -# Send task request -await self.task_protocol.send_task_request( - request="Open Notepad", - task_name="task_001", - session_id=None, - client_id="device_windows_001" -) - -# Send task result -await self.task_protocol.send_task_result( - session_id="session_123", - prev_response_id="resp_456", - action_results=[...], - status=TaskStatus.COMPLETED, - client_id="device_windows_001" -) -``` +2. **Understand Command Execution** - Learn how the UFO Client executes commands: [UFO Client Documentation](./ufo_client.md) -See [AIP Task Execution Protocol](../aip/protocols.md#task-execution-protocol) for details. +3. **Explore Device Profiling** - See what device information is collected: [Device Info Provider](./device_info.md) -## Connection State +4. **Master the AIP Protocol** - Deep dive into message formats: [AIP Protocol Guide](../aip/protocols.md) -### State Checking - -```python -def is_connected(self) -> bool: - """Check if WebSocket is connected.""" - return ( - self.connected_event.is_set() - and self._ws is not None - and not self._ws.closed - ) -``` - -**Usage:** - -```python -if ws_client.is_connected(): - await ws_client.start_task("Open Calculator", "task_calc") -else: - logger.error("Not connected to server") -``` - -### Connected Event - -The `connected_event` is an `asyncio.Event` that signals successful registration: - -```python -# Wait for connection -await ws_client.connected_event.wait() - -# Now safe to send task requests -await ws_client.start_task("Open Notepad", "task_notepad") -``` - -## Best Practices - -**Handle Connection Loss Gracefully** - -```python -try: - await ws_client.connect_and_listen() -except Exception as e: - logger.error(f"WebSocket client error: {e}") - # Implement your recovery strategy -``` - -**Use Appropriate Retry Limits** - -For unreliable networks, increase max retries: - -```python -ws_client = UFOWebSocketClient( - ws_url="ws://...", - ufo_client=ufo_client, - max_retries=10 # More retries for unstable connections -) -``` - -**Monitor Connection Health** - -Log heartbeat success/failure: - -```python -try: - await self.heartbeat_protocol.send_heartbeat(...) - self.logger.debug("[WS] ✅ Heartbeat sent successfully") -except Exception as e: - self.logger.error(f"[WS] ❌ Heartbeat failed: {e}") -``` - -**Clean State on Reconnection** - -The client automatically resets state on new tasks: - -```python -async with self.ufo_client.task_lock: - self.ufo_client.reset() # Clear session state - # Send new task request -``` - -## Integration Points - -### UFO Client - -The WebSocket client delegates command execution to the UFO Client: - -```python -action_results = await self.ufo_client.execute_step(server_response) -``` - -See [UFO Client](./ufo_client.md) for execution details. - -### Device Info Provider - -Collects device information for registration: - -```python -system_info = DeviceInfoProvider.collect_system_info( - client_id=self.ufo_client.client_id, - custom_metadata=None -) -``` - -See [Device Info Provider](./device_info.md) for profiling details. - -### AIP Transport - -All communication goes through the WebSocket transport: - -```python -from aip.transport.websocket import WebSocketTransport - -self.transport = WebSocketTransport(ws) -``` - -See [AIP Transport Layer](../aip/transport.md) for transport details. - ---- - -## 🚀 Next Steps - -!!!tip "Continue Learning" - - **1. Connect Your Client** - - Follow the step-by-step guide to connect a device: - - 👉 [Quick Start Guide](./quick_start.md) - - **2. Understand Command Execution** - - Learn how the UFO Client executes commands: - - 👉 [UFO Client Documentation](./ufo_client.md) - - **3. Explore Device Profiling** - - See what device information is collected: - - 👉 [Device Info Provider](./device_info.md) - - **4. Master the AIP Protocol** - - Deep dive into message formats and protocol details: - - 👉 [AIP Protocol Guide](../aip/protocols.md) - - **5. Study Server-Side Registration** - - Understand how the server handles client registration: - - 👉 [Server Overview](../server/overview.md) +5. **Study Server-Side Registration** - Understand how the server handles registration: [Server Overview](../server/overview.md) diff --git a/documents/docs/configuration/models/azure_openai.md b/documents/docs/configuration/models/azure_openai.md index b3f6a5e26..ee8381a44 100644 --- a/documents/docs/configuration/models/azure_openai.md +++ b/documents/docs/configuration/models/azure_openai.md @@ -1,31 +1,96 @@ # Azure OpenAI (AOAI) -## Step 1 -To use the Azure OpenAI API, you need to create an account on the [Azure OpenAI website](https://azure.microsoft.com/en-us/products/ai-services/openai-service). After creating an account, you can deploy the AOAI API and access the API key. +## Step 1: Create Azure OpenAI Resource -## Step 2 -After obtaining the API key, you can configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Azure OpenAI API. The following is an example configuration for the Azure OpenAI API: +To use the Azure OpenAI API, create an account on the [Azure OpenAI website](https://azure.microsoft.com/en-us/products/ai-services/openai-service). After creating an account, deploy a model and obtain your API key and endpoint. + +## Step 2: Configure Agent Settings + +Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the Azure OpenAI API. + +If the file doesn't exist, copy it from the template: + +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml +``` + +Edit `config/ufo/agents.yaml` with your Azure OpenAI configuration: + +### Option 1: API Key Authentication (Recommended for Development) ```yaml -VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions -API_TYPE: "aoai" , # The API type, "openai" for the OpenAI API, "aoai" for the AOAI API, 'azure_ad' for the ad authority of the AOAI API. -API_BASE: "YOUR_ENDPOINT", # The AOAI API address. Format: https://{your-resource-name}.openai.azure.com -API_KEY: "YOUR_KEY", # The aoai API key -API_VERSION: "2024-02-15-preview", # The version of the API, "2024-02-15-preview" by default -API_MODEL: "gpt-4-vision-preview", # The OpenAI model name, "gpt-4-vision-preview" by default. You may also use "gpt-4o" for using the GPT-4O model. -API_DEPLOYMENT_ID: "YOUR_AOAI_DEPLOYMENT", # The deployment id for the AOAI API +HOST_AGENT: + VISUAL_MODE: True # Enable visual mode to understand screenshots + REASONING_MODEL: False # Set to True for o-series models + API_TYPE: "aoai" # Use Azure OpenAI API + API_BASE: "https://YOUR_RESOURCE.openai.azure.com" # Your Azure endpoint + API_KEY: "YOUR_AOAI_KEY" # Your Azure OpenAI API key + API_VERSION: "2024-02-15-preview" # API version + API_MODEL: "gpt-4o" # Model name + API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" # Your deployment name + +APP_AGENT: + VISUAL_MODE: True + REASONING_MODEL: False + API_TYPE: "aoai" + API_BASE: "https://YOUR_RESOURCE.openai.azure.com" + API_KEY: "YOUR_AOAI_KEY" + API_VERSION: "2024-02-15-preview" + API_MODEL: "gpt-4o-mini" # Use gpt-4o-mini for cost efficiency + API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" ``` -If you want to use AAD for authentication, you should also set the following configuration: +### Option 2: Azure AD Authentication (Recommended for Production) + +For Azure Active Directory authentication, use `API_TYPE: "azure_ad"`: ```yaml - AAD_TENANT_ID: "YOUR_TENANT_ID", # Set the value to your tenant id for the llm model - AAD_API_SCOPE: "YOUR_SCOPE", # Set the value to your scope for the llm model - AAD_API_SCOPE_BASE: "YOUR_SCOPE_BASE" # Set the value to your scope base for the llm model, whose format is API://YOUR_SCOPE_BASE, and the only need is the YOUR_SCOPE_BASE +HOST_AGENT: + VISUAL_MODE: True + REASONING_MODEL: False + API_TYPE: "azure_ad" # Use Azure AD authentication + API_BASE: "https://YOUR_RESOURCE.openai.azure.com" # Your Azure endpoint + API_VERSION: "2024-02-15-preview" + API_MODEL: "gpt-4o" + API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" + + # Azure AD Configuration + AAD_TENANT_ID: "YOUR_TENANT_ID" # Your Azure tenant ID + AAD_API_SCOPE: "YOUR_SCOPE" # Your API scope + AAD_API_SCOPE_BASE: "YOUR_SCOPE_BASE" # Scope base (without api:// prefix) + +APP_AGENT: + VISUAL_MODE: True + REASONING_MODEL: False + API_TYPE: "azure_ad" + API_BASE: "https://YOUR_RESOURCE.openai.azure.com" + API_VERSION: "2024-02-15-preview" + API_MODEL: "gpt-4o-mini" + API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" + AAD_TENANT_ID: "YOUR_TENANT_ID" + AAD_API_SCOPE: "YOUR_SCOPE" + AAD_API_SCOPE_BASE: "YOUR_SCOPE_BASE" ``` -!!! tip - If you set `VISUAL_MODE` to `True`, make sure the `API_DEPLOYMENT_ID` supports visual inputs. +**Configuration Fields:** + +- **`VISUAL_MODE`**: Set to `True` to enable vision capabilities. Ensure your deployment supports visual inputs +- **`API_TYPE`**: Use `"aoai"` for API key auth or `"azure_ad"` for Azure AD auth +- **`API_BASE`**: Your Azure OpenAI endpoint URL (format: `https://{resource-name}.openai.azure.com`) +- **`API_KEY`**: Your Azure OpenAI API key (not needed for Azure AD auth) +- **`API_VERSION`**: Azure API version (e.g., `"2024-02-15-preview"`) +- **`API_MODEL`**: Model identifier (e.g., `gpt-4o`, `gpt-4o-mini`) +- **`API_DEPLOYMENT_ID`**: Your Azure deployment name (required for AOAI) +- **`AAD_TENANT_ID`**: Azure tenant ID (required for Azure AD auth) +- **`AAD_API_SCOPE`**: Azure AD API scope (required for Azure AD auth) +- **`AAD_API_SCOPE_BASE`**: Scope base without `api://` prefix (required for Azure AD auth) + +**For detailed configuration options, see:** + +- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference +- [Model Configuration Overview](overview.md) - Compare different LLM providers +- [OpenAI](openai.md) - Standard OpenAI API setup + +## Step 3: Start Using UFO -## Step 3 -After configuring the `HOST_AGENT` and `APP_AGENT` with the OpenAI API, you can start using UFO to interact with the AOAI API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO. \ No newline at end of file +After configuration, you can start using UFO with the Azure OpenAI API. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks. \ No newline at end of file diff --git a/documents/docs/configuration/models/claude.md b/documents/docs/configuration/models/claude.md index 0c2196ed0..162d0b35a 100644 --- a/documents/docs/configuration/models/claude.md +++ b/documents/docs/configuration/models/claude.md @@ -1,29 +1,69 @@ # Anthropic Claude -## Step 1 -To use the Claude API, you need to create an account on the [Claude website](https://www.anthropic.com/) and access the API key. +## Step 1: Obtain API Key -## Step 2 -You may need to install additional dependencies to use the Claude API. You can install the dependencies using the following command: +To use the Claude API, create an account on the [Anthropic Console](https://console.anthropic.com/) and access your API key from the API keys section. + +## Step 2: Install Dependencies + +Install the required Anthropic Python package: ```bash pip install -U anthropic==0.37.1 ``` -## Step 3 -Configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Claude API. The following is an example configuration for the Claude API: +## Step 3: Configure Agent Settings + +Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the Claude API. + +If the file doesn't exist, copy it from the template: + +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml +``` + +Edit `config/ufo/agents.yaml` with your Claude configuration: ```yaml -VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions -API_TYPE: "Claude" , -API_KEY: "YOUR_KEY", -API_MODEL: "YOUR_MODEL" +HOST_AGENT: + VISUAL_MODE: True # Enable visual mode to understand screenshots + API_TYPE: "claude" # Use Claude API + API_BASE: "https://api.anthropic.com" # Claude API endpoint + API_KEY: "YOUR_CLAUDE_API_KEY" # Your Claude API key + API_MODEL: "claude-3-5-sonnet-20241022" # Model name + API_VERSION: "2023-06-01" # API version + +APP_AGENT: + VISUAL_MODE: True + API_TYPE: "claude" + API_BASE: "https://api.anthropic.com" + API_KEY: "YOUR_CLAUDE_API_KEY" + API_MODEL: "claude-3-5-sonnet-20241022" + API_VERSION: "2023-06-01" ``` -!!! tip - If you set `VISUAL_MODE` to `True`, make sure the `API_MODEL` supports visual inputs. -!!! tip - `API_MODEL` is the model name of Claude LLM API. You can find the model name in the [Claude LLM model](https://www.anthropic.com/pricing#anthropic-api) list. +**Configuration Fields:** + +- **`VISUAL_MODE`**: Set to `True` to enable vision capabilities. Most Claude 3+ models support visual inputs (see [Claude models](https://www.anthropic.com/pricing#anthropic-api)) +- **`API_TYPE`**: Use `"claude"` for Claude API (case-sensitive in code: lowercase) +- **`API_BASE`**: Claude API endpoint - `https://api.anthropic.com` +- **`API_KEY`**: Your Anthropic API key from the console +- **`API_MODEL`**: Model identifier (e.g., `claude-3-5-sonnet-20241022`, `claude-3-opus-20240229`) +- **`API_VERSION`**: API version identifier + +**Available Models:** + +- **Claude 3.5 Sonnet**: `claude-3-5-sonnet-20241022` - Best balance of intelligence and speed +- **Claude 3 Opus**: `claude-3-opus-20240229` - Most capable model +- **Claude 3 Sonnet**: `claude-3-sonnet-20240229` - Balanced performance +- **Claude 3 Haiku**: `claude-3-haiku-20240307` - Fast and cost-effective + +**For detailed configuration options, see:** + +- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference +- [Model Configuration Overview](overview.md) - Compare different LLM providers +- [Anthropic Documentation](https://docs.anthropic.com/) - Official Claude API docs + +## Step 4: Start Using UFO -## Step 4 -After configuring the `HOST_AGENT` and `APP_AGENT` with the Claude API, you can start using UFO to interact with the Claude API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO. \ No newline at end of file +After configuration, you can start using UFO with the Claude API. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks. \ No newline at end of file diff --git a/documents/docs/configuration/models/custom_model.md b/documents/docs/configuration/models/custom_model.md index b9a2ec4bd..3b893c624 100644 --- a/documents/docs/configuration/models/custom_model.md +++ b/documents/docs/configuration/models/custom_model.md @@ -1,49 +1,121 @@ # Customized LLM Models -We support and welcome the integration of custom LLM models in UFO. If you have a custom LLM model that you would like to use with UFO, you can follow the steps below to configure the model in UFO. +UFO supports and welcomes the integration of custom LLM models. If you have a custom LLM model that you would like to use with UFO, follow the steps below to configure it. -## Step 1 - Create a custom LLM model and serve it on your local environment. +## Step 1: Create and Serve Your Model -## Step 2 - Create a python script under the `ufo/llm` directory, and implement your own LLM model class by inheriting the `BaseService` class in the `ufo/llm/base.py` file. We leave a `PlaceHolderService` class in the `ufo/llm/placeholder.py` file as an example. You must implement the `chat_completion` method in your LLM model class to accept a list of messages and return a list of completions for each message. +Create a custom LLM model and serve it on your local or remote environment. Ensure your model has an accessible API endpoint. + +## Step 2: Implement Model Service Class + +Create a Python script under the `ufo/llm` directory and implement your own LLM model class by inheriting the `BaseService` class from `ufo/llm/base.py`. + +**Reference Example:** See `PlaceHolderService` in `ufo/llm/placeholder.py` as a template. + +You must implement the `chat_completion` method: ```python def chat_completion( self, - messages, - n, + messages: List[Dict[str, str]], + n: int = 1, temperature: Optional[float] = None, max_tokens: Optional[int] = None, top_p: Optional[float] = None, **kwargs: Any, -): +) -> Tuple[List[str], Optional[float]]: """ Generates completions for a given list of messages. + Args: - messages (List[str]): The list of messages to generate completions for. - n (int): The number of completions to generate for each message. - temperature (float, optional): Controls the randomness of the generated completions. Higher values (e.g., 0.8) make the completions more random, while lower values (e.g., 0.2) make the completions more focused and deterministic. If not provided, the default value from the model configuration will be used. - max_tokens (int, optional): The maximum number of tokens in the generated completions. If not provided, the default value from the model configuration will be used. - top_p (float, optional): Controls the diversity of the generated completions. Higher values (e.g., 0.8) make the completions more diverse, while lower values (e.g., 0.2) make the completions more focused. If not provided, the default value from the model configuration will be used. - **kwargs: Additional keyword arguments to be passed to the underlying completion method. + messages: The list of messages to generate completions for. + n: The number of completions to generate for each message. + temperature: Controls the randomness (higher = more random). + max_tokens: The maximum number of tokens in completions. + top_p: Controls diversity (higher = more diverse). + **kwargs: Additional keyword arguments. + Returns: - List[str], None:A list of generated completions for each message and the cost set to be None. + Tuple[List[str], Optional[float]]: + - List of generated completions for each message + - Cost of the API call (None if not applicable) + Raises: Exception: If an error occurs while making the API request. """ + # Your implementation here pass ``` -## Step 3 -After implementing the LLM model class, you can configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the custom LLM model. The following is an example configuration for the custom LLM model: +**Key Implementation Points:** + +- Handle message formatting according to your model's API +- Process visual inputs if `VISUAL_MODE` is enabled +- Implement retry logic for failed requests +- Calculate and return cost if applicable + +## Step 3: Configure Agent Settings + +Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use your custom model. + +If the file doesn't exist, copy it from the template: + +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml +``` + +Edit `config/ufo/agents.yaml` with your custom model configuration: ```yaml -VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions -API_TYPE: "custom_model" , # The API type, "openai" for the OpenAI API, "aoai" for the AOAI API, 'azure_ad' for the ad authority of the AOAI API. -API_BASE: "YOUR_ENDPOINT", # The custom LLM API address. -API_MODEL: "YOUR_MODEL", # The custom LLM model name. +HOST_AGENT: + VISUAL_MODE: True # Set based on your model's capabilities + API_TYPE: "custom_model" # Use custom model type + API_BASE: "http://your-endpoint:port" # Your model's API endpoint + API_KEY: "YOUR_API_KEY" # Your API key (if required) + API_MODEL: "your-model-name" # Your model identifier + +APP_AGENT: + VISUAL_MODE: True + API_TYPE: "custom_model" + API_BASE: "http://your-endpoint:port" + API_KEY: "YOUR_API_KEY" + API_MODEL: "your-model-name" ``` -## Step 4 -After configuring the `HOST_AGENT` and `APP_AGENT` with the custom LLM model, you can start using UFO to interact with the custom LLM model for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO. \ No newline at end of file +**Configuration Fields:** + +- **`VISUAL_MODE`**: Set to `True` if your model supports visual inputs +- **`API_TYPE`**: Use `"custom_model"` for custom implementations +- **`API_BASE`**: Your custom model's API endpoint URL +- **`API_KEY`**: Authentication key (if your model requires it) +- **`API_MODEL`**: Model identifier or name + +**For detailed configuration options, see:** + +- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference +- [Model Configuration Overview](overview.md) - Compare different LLM providers + +## Step 4: Register Your Model + +Update the model factory in `ufo/llm/__init__.py` to include your custom model class: + +```python +from ufo.llm.your_model import YourModelService + +# Add to the model factory mapping +MODEL_FACTORY = { + # ... existing models ... + "custom_model": YourModelService, +} +``` + +## Step 5: Start Using UFO + +After configuration, you can start using UFO with your custom model. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks. + +**Testing Your Integration:** + +1. Test with simple requests first +2. Verify visual mode works (if applicable) +3. Check error handling and retry logic +4. Monitor response quality and latency \ No newline at end of file diff --git a/documents/docs/configuration/models/deepseek.md b/documents/docs/configuration/models/deepseek.md index 7611099b0..d76a4f3fa 100644 --- a/documents/docs/configuration/models/deepseek.md +++ b/documents/docs/configuration/models/deepseek.md @@ -1,20 +1,54 @@ # DeepSeek Model -## Step 1 -DeepSeek is developed by Alibaba DAMO Academy. To use the DeepSeek models, Go to [DeepSeek](https://www.deepseek.com/) and register an account and get the API key. +## Step 1: Obtain API Key -## Step 2 -Configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the DeepSeek model. The following is an example configuration for the DeepSeek model: +DeepSeek is developed by DeepSeek AI. To use DeepSeek models, go to [DeepSeek Platform](https://www.deepseek.com/), register an account, and obtain your API key from the API management console. + +## Step 2: Configure Agent Settings + +Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the DeepSeek model. + +If the file doesn't exist, copy it from the template: + +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml +``` + +Edit `config/ufo/agents.yaml` with your DeepSeek configuration: ```yaml - VISUAL_MODE: False, # Whether to use visual mode to understand screenshots and take actions - API_TYPE: "deepseek" , # The API type, "deepseek" for the DeepSeek model. - API_KEY: "YOUR_KEY", # The DeepSeek API key - API_MODEL: "YOUR_MODEL" # The DeepSeek model name +HOST_AGENT: + VISUAL_MODE: False # DeepSeek models typically don't support visual inputs + API_TYPE: "deepseek" # Use DeepSeek API + API_KEY: "YOUR_DEEPSEEK_API_KEY" # Your DeepSeek API key + API_MODEL: "deepseek-chat" # Model name + +APP_AGENT: + VISUAL_MODE: False + API_TYPE: "deepseek" + API_KEY: "YOUR_DEEPSEEK_API_KEY" + API_MODEL: "deepseek-chat" ``` -!!! tip - Most DeepSeek models don't support visual inputs, rembmer to set `VISUAL_MODE` to `False`. +**Configuration Fields:** + +- **`VISUAL_MODE`**: Set to `False` - Most DeepSeek models don't support visual inputs +- **`API_TYPE`**: Use `"deepseek"` for DeepSeek API (case-sensitive in code: lowercase) +- **`API_KEY`**: Your DeepSeek API key +- **`API_MODEL`**: Model identifier (e.g., `deepseek-chat`, `deepseek-coder`) + +**Available Models:** + +- **DeepSeek-Chat**: `deepseek-chat` - General conversation model +- **DeepSeek-Coder**: `deepseek-coder` - Code-specialized model + +**For detailed configuration options, see:** + +- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference +- [Model Configuration Overview](overview.md) - Compare different LLM providers + +## Step 3: Start Using UFO + +After configuration, you can start using UFO with the DeepSeek model. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks. -## Step 3 -After configuring the `HOST_AGENT` and `APP_AGENT` with the DeepSeek model, you can start using UFO to interact with the DeepSeek model for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO. +**Note:** Since DeepSeek models don't support visual mode, UFO will operate in text-only mode, which may limit some UI automation capabilities that rely on screenshot understanding. diff --git a/documents/docs/configuration/models/gemini.md b/documents/docs/configuration/models/gemini.md index 0e7fc706a..e4d02ff35 100644 --- a/documents/docs/configuration/models/gemini.md +++ b/documents/docs/configuration/models/gemini.md @@ -1,30 +1,78 @@ # Google Gemini -## Step 1 -To use the Google Gemini API, you need to create an account on the [Google Gemini website](https://ai.google.dev/) and access the API key. +## Step 1: Obtain API Key -## Step 2 -You may need to install additional dependencies to use the Google Gemini API. You can install the dependencies using the following command: +To use the Google Gemini API, create an account on [Google AI Studio](https://ai.google.dev/) and generate your API key from the API keys section. + +## Step 2: Install Dependencies + +Install the required Google GenAI Python package: ```bash pip install -U google-genai==1.12.1 ``` -## Step 3 -Configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Google Gemini API. The following is an example configuration for the Google Gemini API: +## Step 3: Configure Agent Settings + +Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the Google Gemini API. + +If the file doesn't exist, copy it from the template: + +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml +``` + +Edit `config/ufo/agents.yaml` with your Gemini configuration: ```yaml -VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions -JSON_SCHEMA: True, # Whether to use JSON schema for response completion -API_TYPE: "gemini" , -API_KEY: "YOUR_KEY", -API_MODEL: "YOUR_MODEL" +HOST_AGENT: + VISUAL_MODE: True # Enable visual mode to understand screenshots + JSON_SCHEMA: True # Enable JSON schema for structured responses + API_TYPE: "gemini" # Use Gemini API + API_BASE: "https://generativelanguage.googleapis.com" # Gemini API endpoint + API_KEY: "YOUR_GEMINI_API_KEY" # Your Gemini API key + API_MODEL: "gemini-2.0-flash-exp" # Model name + API_VERSION: "v1beta" # API version + +APP_AGENT: + VISUAL_MODE: True + JSON_SCHEMA: True + API_TYPE: "gemini" + API_BASE: "https://generativelanguage.googleapis.com" + API_KEY: "YOUR_GEMINI_API_KEY" + API_MODEL: "gemini-2.0-flash-exp" + API_VERSION: "v1beta" ``` -!!! tip - If you set `VISUAL_MODE` to `True`, make sure the `API_MODEL` supports visual inputs. -!!! tip - `API_MODEL` is the model name of Gemini LLM API. You can find the model name in the [Gemini LLM model](https://ai.google.dev/gemini-api) list. If you meet the `429` Resource has been exhausted (e.g. check quota)., it may because the rate limit of your Gemini API. +**Configuration Fields:** + +- **`VISUAL_MODE`**: Set to `True` to enable vision capabilities. Most Gemini models support visual inputs (see [Gemini models](https://ai.google.dev/gemini-api/docs/models/gemini)) +- **`JSON_SCHEMA`**: Set to `True` to enable structured JSON output formatting +- **`API_TYPE`**: Use `"gemini"` for Google Gemini API (case-sensitive in code: lowercase) +- **`API_BASE`**: Gemini API endpoint - `https://generativelanguage.googleapis.com` +- **`API_KEY`**: Your Google AI API key +- **`API_MODEL`**: Model identifier (e.g., `gemini-2.0-flash-exp`, `gemini-1.5-pro`) +- **`API_VERSION`**: API version (typically `v1beta`) + +**Available Models:** + +- **Gemini 2.0 Flash**: `gemini-2.0-flash-exp` - Latest experimental model with multimodal capabilities +- **Gemini 1.5 Pro**: `gemini-1.5-pro` - Advanced reasoning and long context +- **Gemini 1.5 Flash**: `gemini-1.5-flash` - Fast and efficient + +**Rate Limits:** + +If you encounter `429 Resource has been exhausted` errors, you've hit the rate limit of your Gemini API quota. Consider: +- Reducing request frequency +- Upgrading your API tier +- Using exponential backoff for retries + +**For detailed configuration options, see:** + +- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference +- [Model Configuration Overview](overview.md) - Compare different LLM providers +- [Gemini API Documentation](https://ai.google.dev/gemini-api) - Official Gemini API docs + +## Step 4: Start Using UFO -## Step 4 -After configuring the `HOST_AGENT` and `APP_AGENT` with the Gemini API, you can start using UFO to interact with the Gemini API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO. \ No newline at end of file +After configuration, you can start using UFO with the Gemini API. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks. \ No newline at end of file diff --git a/documents/docs/configuration/models/ollama.md b/documents/docs/configuration/models/ollama.md index 6b61b909f..27a58e367 100644 --- a/documents/docs/configuration/models/ollama.md +++ b/documents/docs/configuration/models/ollama.md @@ -1,48 +1,104 @@ # Ollama -## Step 1 -If you want to use the Ollama model, Go to [Ollama](https://github.com/jmorganca/ollama) and follow the instructions to serve a LLM model on your local environment. We provide a short example to show how to configure the ollama in the following, which might change if ollama makes updates. +## Step 1: Install and Start Ollama + +Go to [Ollama](https://github.com/jmorganca/ollama) and follow the installation instructions for your platform. + +**For Linux & WSL2:** ```bash -## Install ollama on Linux & WSL2 +# Install Ollama curl https://ollama.ai/install.sh | sh -## Run the serving + +# Start the Ollama server ollama serve ``` -## Step 2 -Open another terminal and run the following command to test the ollama model: +**For Windows/Mac:** Download and install from the [Ollama website](https://ollama.ai/). + +## Step 2: Pull and Test a Model + +Open a new terminal and pull a model: ```bash -ollama run YOUR_MODEL +# Pull a model (e.g., llama2) +ollama pull llama2 + +# Test the model +ollama run llama2 +``` + +By default, Ollama starts a server at `http://localhost:11434`, which will be used as the API base in your configuration. + +## Step 3: Configure Agent Settings + +Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use Ollama. + +If the file doesn't exist, copy it from the template: + +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml ``` -!!!info - When serving LLMs via Ollama, it will by default start a server at `http://localhost:11434`, which will later be used as the API base in `config.yaml`. +Edit `config/ufo/agents.yaml` with your Ollama configuration: + +```yaml +HOST_AGENT: + VISUAL_MODE: True # Enable if model supports vision (e.g., llava) + API_TYPE: "ollama" # Use Ollama API + API_BASE: "http://localhost:11434" # Ollama server endpoint + API_KEY: "ollama" # Placeholder (not used but required) + API_MODEL: "llama2" # Model name (must match pulled model) + +APP_AGENT: + VISUAL_MODE: True + API_TYPE: "ollama" + API_BASE: "http://localhost:11434" + API_KEY: "ollama" + API_MODEL: "llama2" +``` + +**Configuration Fields:** + +- **`VISUAL_MODE`**: Set to `True` only for vision-capable models like `llava` +- **`API_TYPE`**: Use `"ollama"` for Ollama API (case-sensitive in code: lowercase) +- **`API_BASE`**: Ollama server URL (default: `http://localhost:11434`) +- **`API_KEY`**: Placeholder value (not used but required in config) +- **`API_MODEL`**: Model name matching your pulled model + +**Important: Increase Context Length** + +UFO requires at least 20,000 tokens to function properly. Ollama's default context length is 2048 tokens, which is insufficient. You must create a custom model with increased context: + +1. Create a `Modelfile`: + +```text +FROM llama2 +PARAMETER num_ctx 32768 +``` + +2. Build the custom model: + +```bash +ollama create llama2-max-ctx -f Modelfile +``` -## Step 3 -After obtaining the API key, you can configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Ollama API. The following is an example configuration for the Ollama API: +3. Use the custom model in your config: ```yaml -VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions -API_TYPE: "ollama" , -API_BASE: "YOUR_ENDPOINT", -API_KEY: "ollama", # not used but required -API_MODEL: "YOUR_MODEL" +API_MODEL: "llama2-max-ctx" ``` +For more details, see [Ollama's Modelfile documentation](https://github.com/ollama/ollama/blob/main/docs/modelfile.md). -!!! tip - `API_BASE` is the URL started in the Ollama LLM server and `API_MODEL` is the model name of Ollama LLM, it should be same as the one you served before. In addition, due to model token limitations, you can use lite version of prompt to have a taste on UFO which can be configured in `config_dev.yaml`. +**For detailed configuration options, see:** -!!! note - To run UFO successfully with Ollama, you must increase the default token limit of 2048 tokens by creating a custom model with a modified Modelfile. Create a new Modelfile that specifies `PARAMETER num_ctx 32768` (or your model's maximum context length), then build your custom model with `ollama create [model]-max-ctx -f Modelfile`. UFO requires at least 20,000 tokens to function properly, so setting the `num_ctx` parameter to your model's maximum supported context length will ensure optimal performance. For more details on Modelfile configuration, refer to [Ollama's official documentation](https://github.com/ollama/ollama/blob/main/docs/modelfile.md). +- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference +- [Model Configuration Overview](overview.md) - Compare different LLM providers -!!! tip - If you set `VISUAL_MODE` to `True`, make sure the `API_MODEL` supports visual inputs. +## Step 4: Start Using UFO -## Step 4 -After configuring the `HOST_AGENT` and `APP_AGENT` with the Ollama API, you can start using UFO to interact with the Ollama API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO. +After configuration, you can start using UFO with Ollama. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks. diff --git a/documents/docs/configuration/models/openai.md b/documents/docs/configuration/models/openai.md index 5ca058974..2955218aa 100644 --- a/documents/docs/configuration/models/openai.md +++ b/documents/docs/configuration/models/openai.md @@ -1,28 +1,57 @@ # OpenAI -## Step 1 +## Step 1: Obtain API Key -To use the OpenAI API, you need to create an account on the [OpenAI website](https://platform.openai.com/signup). After creating an account, you can access the API key from the [API keys page](https://platform.openai.com/account/api-keys). +To use the OpenAI API, create an account on the [OpenAI website](https://platform.openai.com/signup). After creating an account, you can access your API key from the [API keys page](https://platform.openai.com/account/api-keys). -## Step 2 +## Step 2: Configure Agent Settings -After obtaining the API key, you can configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the OpenAI API. The following is an example configuration for the OpenAI API: +After obtaining the API key, configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the OpenAI API. + +If the file doesn't exist, copy it from the template: + +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml +``` + +Edit `config/ufo/agents.yaml` with your OpenAI configuration: ```yaml -VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions -REASONING_MODEL: False, # Set this to true if you are using o-series models such as o1, o3 -JSON_SCHEMA: True, # Whether to use JSON schema for response completion -API_TYPE: "openai" , # The API type, "openai" for the OpenAI API, "aoai" for the AOAI API, 'azure_ad' for the ad authority of the AOAI API. -API_BASE: "https://api.openai.com/v1/chat/completions", # The the OpenAI API endpoint, "https://api.openai.com/v1/chat/completions" for the OpenAI API. -API_KEY: "sk-", # The OpenAI API key, begin with sk- -API_VERSION: "2024-12-01-preview", # The version of the API, "2024-12-01-preview" by default -API_MODEL: "gpt-4o", # The OpenAI model name, "gpt-4o" by default. +HOST_AGENT: + VISUAL_MODE: True # Enable visual mode to understand screenshots + REASONING_MODEL: False # Set to True for o-series models (o1, o3, o3-mini) + API_TYPE: "openai" # Use OpenAI API + API_BASE: "https://api.openai.com/v1" # OpenAI API endpoint + API_KEY: "sk-YOUR_KEY_HERE" # Your OpenAI API key (starts with sk-) + API_VERSION: "2025-02-01-preview" # API version + API_MODEL: "gpt-4o" # Model name (gpt-4o, gpt-4o-mini, etc.) + +APP_AGENT: + VISUAL_MODE: True + REASONING_MODEL: False + API_TYPE: "openai" + API_BASE: "https://api.openai.com/v1" + API_KEY: "sk-YOUR_KEY_HERE" + API_VERSION: "2025-02-01-preview" + API_MODEL: "gpt-4o-mini" # Use gpt-4o-mini for cost efficiency ``` -!!! tip - If you set `VISUAL_MODE` to `True`, make sure the `API_MODEL` supports visual inputs. You can find the list of models [here](https://platform.openai.com/docs/models). +**Configuration Fields:** + +- **`VISUAL_MODE`**: Set to `True` to enable vision capabilities. Ensure your selected model supports visual inputs (see [OpenAI models](https://platform.openai.com/docs/models)) +- **`REASONING_MODEL`**: Set to `True` when using o-series models (o1, o3, o3-mini) which have different behavior +- **`API_TYPE`**: Use `"openai"` for OpenAI API +- **`API_BASE`**: OpenAI API base URL - `https://api.openai.com/v1` +- **`API_KEY`**: Your OpenAI API key from the API keys page +- **`API_VERSION`**: API version identifier +- **`API_MODEL`**: Model identifier (e.g., `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`) + +**For detailed configuration options, see:** +- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference +- [Model Configuration Overview](overview.md) - Compare different LLM providers +- [Azure OpenAI](azure_openai.md) - Alternative Azure-hosted OpenAI setup +## Step 3: Start Using UFO -## Step 3 -After configuring the `HOST_AGENT` and `APP_AGENT` with the OpenAI API, you can start using UFO to interact with the OpenAI API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO. \ No newline at end of file +After configuration, you can start using UFO with the OpenAI API. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks. \ No newline at end of file diff --git a/documents/docs/configuration/models/operator.md b/documents/docs/configuration/models/operator.md index 726c1537f..c8bb9a6e6 100644 --- a/documents/docs/configuration/models/operator.md +++ b/documents/docs/configuration/models/operator.md @@ -1,37 +1,82 @@ # OpenAI CUA (Operator) -The [Opeartor](https://openai.com/index/computer-using-agent/) is a specialized agentic model tailored for Computer-Using Agents (CUA). We now support calling via the Azure OpenAI API (AOAI). The following sections provide a comprehensive guide on how to set up and use the AOAI API with UFO. Note that now AOAI only supports the [Response API](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/responses?tabs=python-secure) to invoke the model. +The [Operator](https://openai.com/index/computer-using-agent/) is a specialized agentic model tailored for Computer-Using Agents (CUA). It's currently available via the Azure OpenAI API (AOAI) using the [Response API](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/responses?tabs=python-secure). +## Step 1: Create Azure OpenAI Resource +To use the Operator model, create an account on the [Azure OpenAI website](https://azure.microsoft.com/en-us/products/ai-services/openai-service). After creating an account, deploy the Operator model and access your API key. -## Step 1 -To use the Azure OpenAI API, you need to create an account on the [Azure OpenAI website](https://azure.microsoft.com/en-us/products/ai-services/openai-service). After creating an account, you can deploy the AOAI API and access the API key. +## Step 2: Configure Operator Agent -## Step 2 -After obtaining the API key, you can configure the `OPERATOR` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Azure OpenAI API. The following is an example configuration for the Azure OpenAI API: +Configure the `OPERATOR` in the `config/ufo/agents.yaml` file to use the Azure OpenAI Operator model. + +If the file doesn't exist, copy it from the template: + +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml +``` + +Edit `config/ufo/agents.yaml` with your Operator configuration: ```yaml -OPERATOR: { - SCALER: [1024, 768], # The scaler for the visual input in a list format, [width, height] - API_TYPE: "azure_ad" , # The API type, "openai" for the OpenAI API, "aoai" for the AOAI API, 'azure_ad' for the ad authority of the AOAI API. - API_MODEL: "computer-use-preview-20250311", #"gpt-4o-mini-20240718", #"gpt-4o-20240513", # The only OpenAI model by now that accepts visual input - API_VERSION: "2025-03-01-preview", # "2024-02-15-preview" by default - API_BASE: "", # The the OpenAI API endpoint, "https://api.openai.com/v1/chat/completions" for the OpenAI API. As for the AAD, it should be your endpoints. -} +OPERATOR: + SCALER: [1024, 768] # Visual input resolution [width, height] + API_TYPE: "azure_ad" # Use Azure AD authentication + API_MODEL: "computer-use-preview-20250311" # Operator model name + API_VERSION: "2025-03-01-preview" # API version for Operator + API_BASE: "https://YOUR_RESOURCE.openai.azure.com" # Your Azure endpoint + + # Azure AD Authentication (required) + AAD_TENANT_ID: "YOUR_TENANT_ID" # Your Azure tenant ID + AAD_API_SCOPE: "YOUR_SCOPE" # Your API scope + AAD_API_SCOPE_BASE: "YOUR_SCOPE_BASE" # Scope base (without api:// prefix) ``` -If you want to use AAD for authentication, you should additionally set the following configuration: +**Configuration Fields:** + +- **`SCALER`**: Resolution for visual input `[width, height]` (recommended: `[1024, 768]`) +- **`API_TYPE`**: Use `"azure_ad"` for Azure AD authentication (or `"aoai"` for API key auth) +- **`API_MODEL`**: Operator model identifier (e.g., `computer-use-preview-20250311`) +- **`API_VERSION`**: API version for Operator (e.g., `2025-03-01-preview`) +- **`API_BASE`**: Your Azure OpenAI endpoint URL +- **`AAD_TENANT_ID`**: Azure tenant ID (required for Azure AD auth) +- **`AAD_API_SCOPE`**: Azure AD API scope (required for Azure AD auth) +- **`AAD_API_SCOPE_BASE`**: Scope base without `api://` prefix (required for Azure AD auth) + +**For API Key Authentication (Development):** + +If you prefer API key authentication instead of Azure AD: ```yaml - AAD_TENANT_ID: "YOUR_TENANT_ID", # Set the value to your tenant id for the llm model - AAD_API_SCOPE: "YOUR_SCOPE", # Set the value to your scope for the llm model - AAD_API_SCOPE_BASE: "YOUR_SCOPE_BASE" # Set the value to your scope base for the llm model, whose format is API://YOUR_SCOPE_BASE, and the only need is the YOUR_SCOPE_BASE +OPERATOR: + SCALER: [1024, 768] + API_TYPE: "aoai" # Use API key authentication + API_MODEL: "computer-use-preview-20250311" + API_VERSION: "2025-03-01-preview" + API_BASE: "https://YOUR_RESOURCE.openai.azure.com" + API_KEY: "YOUR_AOAI_KEY" # Your Azure OpenAI API key + API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" # Your deployment name ``` -## Step 3 +## Step 3: Run Operator in UFO + +UFO supports running Operator in two modes: + +1. **Standalone Agent**: Run Operator as a single agent +2. **As AppAgent**: Call Operator as a separate `AppAgent` from the `HostAgent` + +Operator uses a specialized visual-only workflow different from other models and currently does not support the standard `AppAgent` workflow. + +**For detailed usage instructions, see:** + +- [Operator as AppAgent](../../ufo2/advanced_usage/operator_as_app_agent.md) - How to integrate Operator into UFO workflows +- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference +- [Azure OpenAI](azure_openai.md) - General Azure OpenAI setup -Now UFO only support to run Operator as a single agent, or as a separate `AppAgent` that can be called by the `HostAgent`. Please refer to the [documents](../../ufo2/advanced_usage/operator_as_app_agent.md) for how to run Operator within UFO. +**Important Notes:** -!!!note - The Opeartor is a visual-only model and use different workflow from the other models. Currently, it does not support reuse the `AppAgent` workflow. Please refer to the documents for how to run Operator within UFO. +- Operator is a visual-only model optimized for computer control tasks +- It uses a different workflow from standard text-based models +- Best suited for direct UI manipulation and visual understanding tasks +- Requires Azure OpenAI deployment (not available via standard OpenAI API) diff --git a/documents/docs/configuration/models/overview.md b/documents/docs/configuration/models/overview.md index 048699f22..1927296fa 100644 --- a/documents/docs/configuration/models/overview.md +++ b/documents/docs/configuration/models/overview.md @@ -1,19 +1,96 @@ # Supported Models -UFO supports a variety of LLM models and APIs. You can customize the model and API used by the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file. Additionally, you can configure a `BACKUP_AGENT` to handle requests when the primary agent fails to respond. +UFO supports a wide variety of LLM models and APIs. You can configure different models for `HOST_AGENT`, `APP_AGENT`, `BACKUP_AGENT`, and `EVALUATION_AGENT` in the `config/ufo/agents.yaml` file to optimize for performance, cost, or specific capabilities. -Please refer to the following sections for more information on the supported models and APIs: +## Available Model Integrations -| LLMs | Documentation | -| --- | --- | -| `OPENAI` | [OpenAI API](./openai.md) | -| `Azure OpenAI (AOAI)` | [Azure OpenAI API](./azure_openai.md) | -| `Gemini` | [Gemini API](./gemini.md) | -| `Claude` | [Claude API](./claude.md) | -| `QWEN` | [QWEN API](./qwen.md) | -| `Ollama` | [Ollama API](./ollama.md) | -| `Custom` | [Custom API](./custom_model.md) | +| Provider | Documentation | Visual Support | Authentication | +| --- | --- | --- | --- | +| **OpenAI** | [OpenAI API](./openai.md) | ✅ | API Key | +| **Azure OpenAI (AOAI)** | [Azure OpenAI API](./azure_openai.md) | ✅ | API Key / Azure AD | +| **Google Gemini** | [Gemini API](./gemini.md) | ✅ | API Key | +| **Anthropic Claude** | [Claude API](./claude.md) | ✅ | API Key | +| **Qwen (Alibaba)** | [Qwen API](./qwen.md) | ✅ | API Key | +| **DeepSeek** | [DeepSeek API](./deepseek.md) | ❌ | API Key | +| **Ollama** | [Ollama API](./ollama.md) | ⚠️ Limited | Local | +| **OpenAI Operator** | [Operator (CUA)](./operator.md) | ✅ | Azure AD | +| **Custom Models** | [Custom API](./custom_model.md) | Depends | Varies | +## Model Selection Guide -!!! info - Each model is implemented as a separate class in the `ufo/llm` directory, and uses the functions `chat_completion` defined in the `BaseService` class of the `ufo/llm/base.py` file to obtain responses from the model. \ No newline at end of file +### By Use Case + +**For Production Deployments:** +- **Primary**: OpenAI GPT-4o or Azure OpenAI (enterprise features) +- **Cost-optimized**: GPT-4o-mini for APP_AGENT, GPT-4o for HOST_AGENT +- **Privacy-sensitive**: Ollama (local models) + +**For Development & Testing:** +- **Fast iteration**: Gemini 2.0 Flash (high speed, low cost) +- **Local testing**: Ollama with llama2 or similar +- **Budget-friendly**: DeepSeek or Qwen models + +**For Specialized Tasks:** +- **Computer control**: OpenAI Operator (CUA model) +- **Code generation**: DeepSeek-Coder or Claude +- **Long context**: Gemini 1.5 Pro (large context window) + +### By Capability + +**Vision Support (Screenshot Understanding):** +- ✅ OpenAI GPT-4o, GPT-4-turbo +- ✅ Azure OpenAI (vision-enabled deployments) +- ✅ Google Gemini (all 1.5+ models) +- ✅ Claude 3+ (all variants) +- ✅ Qwen-VL models +- ⚠️ Ollama (llava models only) +- ❌ DeepSeek (text-only) + +**JSON Schema Support:** +- ✅ OpenAI / Azure OpenAI +- ✅ Google Gemini +- ⚠️ Limited: Claude, Qwen, Ollama + +## Configuration Architecture + +Each model is implemented as a separate class in the `ufo/llm` directory, inheriting from the `BaseService` class in `ufo/llm/base.py`. All models implement the `chat_completion` method to maintain a consistent interface. + +**Key Configuration Files:** + +- **`config/ufo/agents.yaml`**: Primary agent configuration (HOST, APP, BACKUP, EVALUATION, OPERATOR) +- **`config/ufo/system.yaml`**: System-wide LLM parameters (MAX_TOKENS, TEMPERATURE, etc.) +- **`config/ufo/prices.yaml`**: Cost tracking for different models + +## Multi-Provider Setup + +You can mix and match providers for different agents to optimize cost and performance: + +```yaml +# Use OpenAI for planning +HOST_AGENT: + API_TYPE: "openai" + API_MODEL: "gpt-4o" + +# Use Azure OpenAI for execution (cost control) +APP_AGENT: + API_TYPE: "aoai" + API_MODEL: "gpt-4o-mini" + +# Use Claude for evaluation +EVALUATION_AGENT: + API_TYPE: "claude" + API_MODEL: "claude-3-5-sonnet-20241022" +``` + +## Getting Started + +1. Choose your LLM provider from the table above +2. Follow the provider-specific documentation to obtain API keys +3. Configure `config/ufo/agents.yaml` with your credentials +4. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) to begin + +**For detailed configuration options:** + +- [Agent Configuration Guide](../system/agents_config.md) - Complete configuration reference +- [System Configuration](../system/system_config.md) - LLM parameters and behavior +- [Quick Start Guide](../../getting_started/quick_start_ufo2.md) - Step-by-step setup \ No newline at end of file diff --git a/documents/docs/configuration/models/qwen.md b/documents/docs/configuration/models/qwen.md index 5c2a80626..9242e5240 100644 --- a/documents/docs/configuration/models/qwen.md +++ b/documents/docs/configuration/models/qwen.md @@ -1,23 +1,53 @@ # Qwen Model -## Step 1 -Qwen (Tongyi Qianwen) is developed by Alibaba DAMO Academy. To use the Qwen model, Go to [QWen](https://dashscope.aliyun.com/) and register an account and get the API key. More details can be found [here](https://help.aliyun.com/zh/dashscope/developer-reference/activate-dashscope-and-create-an-api-key?spm=a2c4g.11186623.0.0.7b5749d72j3SYU) (in Chinese). +## Step 1: Obtain API Key -## Step 2 -Configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Qwen model. The following is an example configuration for the Qwen model: +Qwen (Tongyi Qianwen) is developed by Alibaba DAMO Academy. To use Qwen models, go to [DashScope](https://dashscope.aliyun.com/), register an account, and obtain your API key. Detailed instructions are available in the [DashScope documentation](https://help.aliyun.com/zh/dashscope/developer-reference/activate-dashscope-and-create-an-api-key) (Chinese). + +## Step 2: Configure Agent Settings + +Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the Qwen model. + +If the file doesn't exist, copy it from the template: + +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml +``` + +Edit `config/ufo/agents.yaml` with your Qwen configuration: ```yaml - VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions - API_TYPE: "qwen" , # The API type, "qwen" for the Qwen model. - API_KEY: "YOUR_KEY", # The Qwen API key - API_MODEL: "YOUR_MODEL" # The Qwen model name +HOST_AGENT: + VISUAL_MODE: True # Enable visual mode for vision-capable models + API_TYPE: "qwen" # Use Qwen API + API_KEY: "YOUR_QWEN_API_KEY" # Your DashScope API key + API_MODEL: "qwen-vl-max" # Model name (e.g., qwen-vl-max, qwen-max) + +APP_AGENT: + VISUAL_MODE: True + API_TYPE: "qwen" + API_KEY: "YOUR_QWEN_API_KEY" + API_MODEL: "qwen-vl-max" ``` -!!! tip - If you set `VISUAL_MODE` to `True`, make sure the `API_MODEL` supports visual inputs. +**Configuration Fields:** + +- **`VISUAL_MODE`**: Set to `True` for vision-capable models (qwen-vl-*). Set to `False` for text-only models +- **`API_TYPE`**: Use `"qwen"` for Qwen API (case-sensitive in code: lowercase) +- **`API_KEY`**: Your DashScope API key +- **`API_MODEL`**: Model identifier (see [Qwen model list](https://help.aliyun.com/zh/dashscope/developer-reference/model-square/)) + +**Available Models:** + +- **Qwen-VL-Max**: `qwen-vl-max` - Vision and language model +- **Qwen-Max**: `qwen-max` - Text-only advanced model +- **Qwen-Plus**: `qwen-plus` - Balanced performance model + +**For detailed configuration options, see:** + +- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference +- [Model Configuration Overview](overview.md) - Compare different LLM providers -!!! tip - `API_MODEL` is the model name of Qwen LLM API. You can find the model name in the [Qwen LLM model](https://help.aliyun.com/zh/dashscope/developer-reference/model-square/?spm=a2c4g.11186623.0.0.35a36ffdt97ljI) list. +## Step 3: Start Using UFO -## Step 3 -After configuring the `HOST_AGENT` and `APP_AGENT` with the Qwen model, you can start using UFO to interact with the Qwen model for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO. +After configuration, you can start using UFO with the Qwen model. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks. diff --git a/documents/docs/configuration/system/agents_config.md b/documents/docs/configuration/system/agents_config.md index 25a85eb22..6f88b2405 100644 --- a/documents/docs/configuration/system/agents_config.md +++ b/documents/docs/configuration/system/agents_config.md @@ -1,9 +1,6 @@ # Agent Configuration (agents.yaml) -!!!quote "LLM and Agent Settings" - Configure all LLM models and agent-specific settings for UFO². Each agent type can use different models and API configurations for optimal performance. - ---- +Configure all LLM models and agent-specific settings for UFO². Each agent type can use different models and API configurations for optimal performance. ## Overview @@ -11,19 +8,16 @@ The `agents.yaml` file defines LLM settings for all agents in UFO². This is the **File Location**: `config/ufo/agents.yaml` -!!!warning "Initial Setup Required" - On first installation, you need to: - - 1. **Copy the template file**: - ```powershell - Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml - ``` - - 2. **Edit `config/ufo/agents.yaml`** with your API keys and settings - - 3. **Never commit `agents.yaml`** to version control (it contains secrets) +**Initial Setup Required:** ---- +1. **Copy the template file**: + ```powershell + Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml + ``` + +2. **Edit `config/ufo/agents.yaml`** with your API keys and settings + +3. **Never commit `agents.yaml`** to version control (it contains secrets) ## Quick Start @@ -122,14 +116,13 @@ UFO² uses different agents for different purposes. Each can be configured with | **APP_AGENT** | Action execution, UI interaction | GPT-4o-mini, GPT-4o | High (every action) | | **BACKUP_AGENT** | Fallback when others fail | GPT-4-vision-preview | Rare (errors) | | **EVALUATION_AGENT** | Task completion evaluation | GPT-4o | Low (end of task) | -| **OPERATOR** | CUA-based automation | operator-20250213 | Optional | +| **OPERATOR** | CUA-based automation | computer-use-preview | Optional | -!!!tip "Cost Optimization" - - Use **GPT-4o** for HOST_AGENT (complex planning) - - Use **GPT-4o-mini** for APP_AGENT (frequent actions, 60% cheaper) - - Same model can be used for BACKUP_AGENT and EVALUATION_AGENT +**Cost Optimization Tips:** ---- +- Use **GPT-4o** for HOST_AGENT (complex planning) +- Use **GPT-4o-mini** for APP_AGENT (frequent actions, 60% cheaper) +- Same model can be used for BACKUP_AGENT and EVALUATION_AGENT ## Configuration Fields @@ -149,9 +142,7 @@ These fields are available for `HOST_AGENT`, `APP_AGENT`, `BACKUP_AGENT`, `EVALU | `API_MODEL` | String | ✅ | varies | Model identifier | | `API_VERSION` | String | ❌ | `"2025-02-01-preview"` | API version | -!!!warning "Required vs Optional" - - ✅ **Required**: Must be set for the agent to work - - ❌ **Optional**: Has a default value, can be omitted +**Legend:** ✅ = Required (must be set), ❌ = Optional (has default value) #### API_TYPE Options @@ -209,7 +200,7 @@ HOST_AGENT: | `EXAMPLE_PROMPT` | String | ❌ | Path to example prompt template | | `API_PROMPT` | String | ❌ | Path to API usage prompt (APP_AGENT only) | -**Default Prompt Paths**: +**Default Prompt Paths:** ```yaml HOST_AGENT: PROMPT: "ufo/prompts/share/base/host_agent.yaml" @@ -221,10 +212,21 @@ APP_AGENT: API_PROMPT: "ufo/prompts/share/base/api.yaml" ``` -!!!info "Custom Prompts" - You can customize prompts by creating your own YAML files and updating these paths. +You can customize prompts by creating your own YAML files and updating these paths. See the [Customization Guide](../../tutorials/customization.md) for details. ---- +#### OPERATOR-Specific Fields + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `SCALER` | List[int] | ❌ | Screen dimensions for visual input `[width, height]`, default: `[1024, 768]` | + +**Example:** +```yaml +OPERATOR: + SCALER: [1920, 1080] # Full HD resolution + API_MODEL: "computer-use-preview-20250311" + # ... other settings +``` ## Complete Configuration Example @@ -278,17 +280,16 @@ EVALUATION_AGENT: # OPERATOR - OpenAI Operator (optional) OPERATOR: + SCALER: [1024, 768] # Screen resolution for visual input VISUAL_MODE: True REASONING_MODEL: False API_TYPE: "openai" API_BASE: "https://api.openai.com/v1/chat/completions" API_KEY: "sk-YOUR_KEY_HERE" - API_MODEL: "operator-20250213" - API_VERSION: "2025-02-01-preview" + API_MODEL: "computer-use-preview-20250311" + API_VERSION: "2025-03-01-preview" ``` ---- - ## Multi-Provider Configuration You can use different providers for different agents: @@ -317,8 +318,6 @@ EVALUATION_AGENT: API_MODEL: "claude-3-5-sonnet-20241022" ``` ---- - ## Model Recommendations ### For HOST_AGENT (Planning) @@ -342,9 +341,7 @@ EVALUATION_AGENT: | Model | Provider | Notes | |-------|----------|-------| -| **operator-20250213** | OpenAI | Only supported model for Operator mode | - ---- +| **computer-use-preview-20250311** | OpenAI | Supported model for Operator mode (Computer Use Agent) | ## Reasoning Models @@ -358,13 +355,7 @@ HOST_AGENT: # ... other settings ``` -!!!warning "Reasoning Model Limitations" - Reasoning models have different behavior: - - No streaming responses - - Different token limits - - May have different pricing - ---- +**Note:** Reasoning models have different behavior including no streaming responses, different token limits, and may have different pricing. ## Environment Variables @@ -397,8 +388,6 @@ export OPENAI_API_KEY="sk-your-key" export AZURE_OPENAI_KEY="your-azure-key" ``` ---- - ## Programmatic Access ```python @@ -422,80 +411,75 @@ else: print("Warning: HOST_AGENT API key not set") ``` ---- - ## Troubleshooting ### Issue 1: "agents.yaml not found" -!!!bug "Error Message" - ``` - FileNotFoundError: config/ufo/agents.yaml not found - ``` - - **Solution**: Copy the template file - ```powershell - Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml - ``` +**Error Message:** +``` +FileNotFoundError: config/ufo/agents.yaml not found +``` + +**Solution:** Copy the template file +```powershell +Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml +``` ### Issue 2: API Authentication Errors -!!!bug "Error Message" - ``` - openai.AuthenticationError: Invalid API key - ``` - - **Solutions**: - 1. Verify API key is correct - 2. Check for extra spaces or quotes - 3. Ensure API_TYPE matches your provider - 4. For Azure, verify API_DEPLOYMENT_ID is set +**Error Message:** +``` +openai.AuthenticationError: Invalid API key +``` + +**Solutions:** +1. Verify API key is correct +2. Check for extra spaces or quotes +3. Ensure API_TYPE matches your provider +4. For Azure, verify API_DEPLOYMENT_ID is set ### Issue 3: Model Not Found -!!!bug "Error Message" - ``` - openai.NotFoundError: The model 'gpt-4o' does not exist - ``` - - **Solutions**: - 1. Verify model name is correct (check provider's documentation) - 2. For Azure, ensure deployment exists and API_DEPLOYMENT_ID matches - 3. Check if you have access to the model +**Error Message:** +``` +openai.NotFoundError: The model 'gpt-4o' does not exist +``` + +**Solutions:** +1. Verify model name is correct (check provider's documentation) +2. For Azure, ensure deployment exists and API_DEPLOYMENT_ID matches +3. Check if you have access to the model ### Issue 4: Rate Limits -!!!bug "Error Message" - ``` - openai.RateLimitError: Rate limit exceeded - ``` - - **Solutions**: - 1. Add delays between requests (configure in `system.yaml`) - 2. Upgrade your API plan - 3. Use different API keys for different agents +**Error Message:** +``` +openai.RateLimitError: Rate limit exceeded +``` ---- +**Solutions:** +1. Add delays between requests (configure in `system.yaml`) +2. Upgrade your API plan +3. Use different API keys for different agents ## Security Best Practices -!!!danger "API Key Security" - 1. ✅ **Never commit `agents.yaml` to Git** - - Add to `.gitignore` - - Only commit `agents.yaml.template` - - 2. ✅ **Use environment variables** for production - ```yaml - API_KEY: "${OPENAI_API_KEY}" - ``` - - 3. ✅ **Rotate keys regularly** - - 4. ✅ **Use separate keys** for dev/prod environments - - 5. ✅ **Restrict key permissions** (e.g., read-only for evaluation agents) +**API Key Security Guidelines:** ---- +1. ✅ **Never commit `agents.yaml` to Git** + - Add to `.gitignore` + - Only commit `agents.yaml.template` + +2. ✅ **Use environment variables** for production + ```yaml + API_KEY: "${OPENAI_API_KEY}" + ``` + +3. ✅ **Rotate keys regularly** + +4. ✅ **Use separate keys** for dev/prod environments + +5. ✅ **Restrict key permissions** (e.g., read-only for evaluation agents) ## Related Documentation @@ -507,15 +491,14 @@ else: - **[Model Setup Guide](../models/overview.md)** - Provider-specific setup - **[Migration Guide](migration.md)** - Migrating from legacy config ---- - ## Summary -!!!success "Key Takeaways" - ✅ **Copy template first**: `Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml` - ✅ **Add your API keys**: Edit `agents.yaml` with your credentials - ✅ **Choose models wisely**: GPT-4o for planning, GPT-4o-mini for actions - ✅ **Never commit secrets**: Keep `agents.yaml` out of version control - ✅ **Use environment variables**: For production deployments - - **Your agents are now ready to work!** 🚀 +**Key Takeaways:** + +✅ **Copy template first**: `Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml` +✅ **Add your API keys**: Edit `agents.yaml` with your credentials +✅ **Choose models wisely**: GPT-4o for planning, GPT-4o-mini for actions +✅ **Never commit secrets**: Keep `agents.yaml` out of version control +✅ **Use environment variables**: For production deployments + +**Your agents are now ready to work!** 🚀 diff --git a/documents/docs/configuration/system/extending.md b/documents/docs/configuration/system/extending.md index 1cfca9195..09bffa80d 100644 --- a/documents/docs/configuration/system/extending.md +++ b/documents/docs/configuration/system/extending.md @@ -2,10 +2,11 @@ This guide shows you how to add custom configuration options to UFO2. -!!!info "Three Ways to Extend" - 1. **Simple YAML files** - Quick custom settings in existing files - 2. **New configuration files** - Organize new features separately - 3. **Typed configuration schemas** - Full type safety with Python dataclasses +**Three Ways to Extend:** + +1. **Simple YAML files** - Quick custom settings in existing files +2. **New configuration files** - Organize new features separately +3. **Typed configuration schemas** - Full type safety with Python dataclasses ## Method 1: Adding Fields to Existing Files @@ -22,7 +23,7 @@ For simple customizations, add fields directly to existing configuration files. FEATURE_FLAGS: enable_telemetry: false use_experimental_api: true - ``` +``` ### Accessing Custom Fields @@ -35,10 +36,9 @@ For simple customizations, add fields directly to existing configuration files. timeout = config.system.CUSTOM_TIMEOUT # 300 debug = config.system.DEBUG_MODE # True use_experimental = config.system.FEATURE_FLAGS['use_experimental_api'] # True - ``` +``` -!!!success "Zero Code Changes Required" - Custom fields are automatically discovered and loaded - no code modifications needed! +Custom fields are automatically discovered and loaded - no code modifications needed! --- @@ -64,17 +64,16 @@ For larger features, create dedicated configuration files. ### Automatic Discovery -!!!success "Zero Configuration Required" - The config loader automatically discovers and loads all YAML files in `config/ufo/`: - - ```python - # No registration needed! - config = get_ufo_config() - - # Your new file is automatically loaded - analytics_enabled = config.ANALYTICS['enabled'] - metrics = config.ANALYTICS['metrics'] - ``` +The config loader automatically discovers and loads all YAML files in `config/ufo/`: + +```python +# No registration needed! +config = get_ufo_config() + +# Your new file is automatically loaded +analytics_enabled = config.ANALYTICS['enabled'] +metrics = config.ANALYTICS['metrics'] +``` --- @@ -83,7 +82,7 @@ For larger features, create dedicated configuration files. For production features requiring type safety and validation, define typed schemas. ```python - # config/ufo/schemas/analytics_config.py + # config/config_schemas.py from dataclasses import dataclass, field from typing import List, Literal @@ -119,14 +118,13 @@ For production features requiring type safety and validation, define typed schem if self.batch_size <= 0: raise ValueError("batch_size must be positive") - ``` +``` ### Step 2: Integrate into UFOConfig ```python - # config/ufo/ufo_config.py + # config/config_schemas.py from dataclasses import dataclass - from .schemas.analytics_config import AnalyticsConfig @dataclass class UFOConfig: @@ -138,7 +136,7 @@ For production features requiring type safety and validation, define typed schem analytics: AnalyticsConfig # Add your new config # ... rest of implementation - ``` +``` ### Step 3: Use Typed Configuration @@ -154,7 +152,7 @@ For production features requiring type safety and validation, define typed schem # Validation happens automatically batch_size = config.analytics.batch_size # Guaranteed > 0 - ``` +``` --- @@ -177,7 +175,7 @@ For production features requiring type safety and validation, define typed schem LOG_LEVEL: "WARNING" CACHE_SIZE: 10000 MONITORING_ENABLED: true - ``` +``` ### Feature Flags @@ -196,7 +194,7 @@ For production features requiring type safety and validation, define typed schem app_agent: speculative_execution: true action_batching: true - ``` +``` ### Plugin Configuration @@ -219,30 +217,32 @@ For production features requiring type safety and validation, define typed schem enabled: false class: "plugins.custom.MyProcessor" priority: 100 - ``` +``` --- ## Best Practices -!!!tip "DO - Recommended Practices" - - ? **Group related settings** in dedicated files - - ? **Use typed schemas** for production features - - ? **Provide sensible defaults** for all optional fields - - ? **Add validation** in `__post_init__` methods - - ? **Document all fields** with docstrings - - ? **Use environment overrides** for deployment-specific settings - - ? **Version your config schemas** when making breaking changes - - ? **Test configuration loading** in CI/CD pipelines - -!!!danger "DON'T - Anti-Patterns" - - ? **Don't hardcode secrets** - use environment variables - - ? **Don't duplicate settings** across multiple files - - ? **Don't use dynamic field names** - breaks type safety - - ? **Don't skip validation** - catch errors early - - ? **Don't mix concerns** - keep configs focused - - ? **Don't ignore warnings** from config loader - - ? **Don't commit sensitive data** - use .env files +**DO - Recommended Practices** + +- ✅ **Group related settings** in dedicated files +- ✅ **Use typed schemas** for production features +- ✅ **Provide sensible defaults** for all optional fields +- ✅ **Add validation** in `__post_init__` methods +- ✅ **Document all fields** with docstrings +- ✅ **Use environment overrides** for deployment-specific settings +- ✅ **Version your config schemas** when making breaking changes +- ✅ **Test configuration loading** in CI/CD pipelines + +**DON'T - Anti-Patterns** + +- ❌ **Don't hardcode secrets** - use environment variables +- ❌ **Don't duplicate settings** across multiple files +- ❌ **Don't use dynamic field names** - breaks type safety +- ❌ **Don't skip validation** - catch errors early +- ❌ **Don't mix concerns** - keep configs focused +- ❌ **Don't ignore warnings** from config loader +- ❌ **Don't commit sensitive data** - use .env files --- @@ -263,19 +263,19 @@ For production features requiring type safety and validation, define typed schem api_key: "${API_KEY}" ``` -!!!tip "Environment Variables" - Use environment variables for secrets: - - ```python - import os - from config.config_loader import get_ufo_config - - config = get_ufo_config() - - # Resolve environment variables - db_password = os.getenv('DB_PASSWORD') - api_key = os.getenv('API_KEY') - ``` +### "Environment Variables" +Use environment variables for secrets: + +```python +import os +from config.config_loader import get_ufo_config + +config = get_ufo_config() + +# Resolve environment variables +db_password = os.getenv('DB_PASSWORD') +api_key = os.getenv('API_KEY') +``` --- @@ -321,15 +321,14 @@ For production features requiring type safety and validation, define typed schem # Verify custom configuration loaded assert hasattr(config, 'analytics') assert config.analytics.enabled in [True, False] - ``` +``` --- ## Next Steps -!!!note "Learn More" - - **[Agents Configuration](./agents_config.md)** - LLM and agent settings - - **[System Configuration](./system_config.md)** - Runtime and execution settings - - **[RAG Configuration](./rag_config.md)** - Knowledge retrieval settings - - **[Migration Guide](./migration.md)** - Migrate from legacy configuration - - **[Configuration Overview](./overview.md)** - Understand configuration system design +- **[Agents Configuration](./agents_config.md)** - LLM and agent settings +- **[System Configuration](./system_config.md)** - Runtime and execution settings +- **[RAG Configuration](./rag_config.md)** - Knowledge retrieval settings +- **[Migration Guide](./migration.md)** - Migrate from legacy configuration +- **[Configuration Overview](./overview.md)** - Understand configuration system design diff --git a/documents/docs/configuration/system/galaxy_agent.md b/documents/docs/configuration/system/galaxy_agent.md index b4d0228c4..b86f93fa1 100644 --- a/documents/docs/configuration/system/galaxy_agent.md +++ b/documents/docs/configuration/system/galaxy_agent.md @@ -1,11 +1,10 @@ -# ⚙️ Configuration - Galaxy Constellation Agent +# Galaxy Constellation Agent Configuration -!!!quote "LLM Configuration for Constellation Planning" - **agent.yaml** configures the **Constellation Agent** - the AI agent responsible for creating constellations (task decomposition) and editing them based on execution results. +**agent.yaml** configures the **Constellation Agent** - the AI agent responsible for creating constellations (task decomposition) and editing them based on execution results. --- -## 📋 Overview +## Overview The **agent.yaml** configuration file provides **LLM and API settings** for the Constellation Agent. This agent is responsible for: @@ -14,10 +13,11 @@ The **agent.yaml** configuration file provides **LLM and API settings** for the - **Device Selection**: Choosing appropriate devices for each sub-task - **Task Orchestration**: Coordinating multi-device workflows -!!!info "Configuration Separation" - - **agent.yaml** - LLM configuration for constellation agent (this document) - - **constellation.yaml** - Runtime settings for orchestrator ([Galaxy Constellation Configuration](./galaxy_constellation.md)) - - **devices.yaml** - Device definitions ([Galaxy Devices Configuration](./galaxy_devices.md)) +**Configuration Separation:** + +- **agent.yaml** - LLM configuration for constellation agent (this document) +- **constellation.yaml** - Runtime settings for orchestrator ([Galaxy Constellation Configuration](./galaxy_constellation.md)) +- **devices.yaml** - Device definitions ([Galaxy Devices Configuration](./galaxy_devices.md)) **Agent Role in System:** @@ -37,7 +37,7 @@ graph TB --- -## 📁 File Location +## File Location **Standard Location:** @@ -73,7 +73,7 @@ api_model = agent_config.api_model --- -## 📝 Configuration Schema +## Configuration Schema ### Complete Schema @@ -105,7 +105,7 @@ CONSTELLATION_AGENT: --- -## 🔧 Configuration Fields +## Configuration Fields ### Reasoning Capabilities @@ -238,7 +238,7 @@ CONSTELLATION_AGENT: --- -## 📚 Complete Examples +## Complete Examples ### Example 1: Production (Azure AD) @@ -321,7 +321,7 @@ CONSTELLATION_AGENT: --- -## 🔐 Security Best Practices +## Security Best Practices !!!danger "Never Commit Credentials" **DO NOT commit `agent.yaml` with real credentials to version control!** @@ -339,22 +339,22 @@ CONSTELLATION_AGENT: git commit -m "Update agent template" ``` -!!!tip "Credential Management" - **Use Environment Variables for Sensitive Data:** - ```yaml - # In agent.yaml - CONSTELLATION_AGENT: - API_KEY: ${GALAXY_API_KEY} # Read from environment variable - ``` - - ```bash - # In your shell - export GALAXY_API_KEY="sk-proj-..." - ``` +**Use Environment Variables for Sensitive Data:** + +```yaml +# In agent.yaml +CONSTELLATION_AGENT: + API_KEY: ${GALAXY_API_KEY} # Read from environment variable +``` + +```bash +# In your shell +export GALAXY_API_KEY="sk-proj-..." +``` --- -## 🔗 Integration with Other Configurations +## Integration with Other Configurations The agent configuration works together with other Galaxy configs: @@ -408,38 +408,38 @@ print(f" Devices: {len(devices_config['devices'])}") --- -## 💡 Best Practices - -!!!tip "Configuration Best Practices" - - **1. Use Templates for Team Collaboration** - ```bash - # Share template, not credentials - config/galaxy/agent.yaml.template # ✅ Commit this - config/galaxy/agent.yaml # ❌ Never commit this - ``` - - **2. Test with OpenAI, Deploy with Azure** - ```yaml - # Development: OpenAI (fast iteration) - API_TYPE: "openai" - - # Production: Azure (enterprise features) - API_TYPE: "azure_ad" - ``` - - **3. Use Reasoning Mode Selectively** - ```yaml - # For complex workflows - REASONING_MODEL: True - - # For simple tasks - REASONING_MODEL: False # Faster - ``` +## Best Practices + +**Configuration Best Practices:** + +1. **Use Templates for Team Collaboration** + ```bash + # Share template, not credentials + config/galaxy/agent.yaml.template # ✅ Commit this + config/galaxy/agent.yaml # ❌ Never commit this + ``` + +2. **Test with OpenAI, Deploy with Azure** + ```yaml + # Development: OpenAI (fast iteration) + API_TYPE: "openai" + + # Production: Azure (enterprise features) + API_TYPE: "azure_ad" + ``` + +3. **Use Reasoning Mode Selectively** + ```yaml + # For complex workflows + REASONING_MODEL: True + + # For simple tasks + REASONING_MODEL: False # Faster + ``` --- -## 🔗 Related Documentation +## Related Documentation | Topic | Document | Description | |-------|----------|-------------| @@ -449,7 +449,7 @@ print(f" Devices: {len(devices_config['devices'])}") --- -## 🚀 Next Steps +## Next Steps 1. **Copy Template**: `cp agent.yaml.template agent.yaml` 2. **Configure Credentials**: Fill in API_KEY or AAD settings @@ -459,7 +459,7 @@ print(f" Devices: {len(devices_config['devices'])}") --- -## 📚 Source Code References +## Source Code References - **ConstellationAgent**: `galaxy/agents/constellation_agent.py` - **Configuration Loading**: `config/config_loader.py` diff --git a/documents/docs/configuration/system/galaxy_constellation.md b/documents/docs/configuration/system/galaxy_constellation.md index c54ec750c..33cc7de69 100644 --- a/documents/docs/configuration/system/galaxy_constellation.md +++ b/documents/docs/configuration/system/galaxy_constellation.md @@ -1,11 +1,10 @@ -# ⚙️ Configuration - Galaxy Constellation Runtime +# Galaxy Constellation Runtime Configuration -!!!quote "Constellation-Wide Runtime Settings" - **constellation.yaml** defines constellation-wide runtime settings that control how the Galaxy orchestrator manages devices, tasks, and logging across the entire constellation system. +**constellation.yaml** defines constellation-wide runtime settings that control how the Galaxy orchestrator manages devices, tasks, and logging across the entire constellation system. --- -## 📋 Overview +## Overview The **constellation.yaml** configuration file provides **constellation-level runtime settings** that apply to the entire Galaxy system. These settings control: @@ -14,10 +13,11 @@ The **constellation.yaml** configuration file provides **constellation-level run - Task concurrency and step limits - Device configuration file path -!!!info "Configuration Separation" - - **constellation.yaml** - Runtime settings for the constellation orchestrator (this document) - - **devices.yaml** - Individual device definitions ([Galaxy Devices Configuration](./galaxy_devices.md)) - - **agent.yaml** - LLM configuration for constellation agent ([Galaxy Agent Configuration](./galaxy_agent.md)) +**Configuration Separation:** + +- **constellation.yaml** - Runtime settings for the constellation orchestrator (this document) +- **devices.yaml** - Individual device definitions ([Galaxy Devices Configuration](./galaxy_devices.md)) +- **agent.yaml** - LLM configuration for constellation agent ([Galaxy Agent Configuration](./galaxy_agent.md)) **Configuration Relationship:** @@ -36,7 +36,7 @@ graph TB --- -## 📁 File Location +## File Location **Standard Location:** @@ -78,7 +78,7 @@ for device in devices_config["devices"]: --- -## 📝 Configuration Schema +## Configuration Schema ### Complete Schema @@ -104,7 +104,7 @@ DEVICE_INFO: string # Path to devices.yaml file --- -## 🔧 Configuration Fields +## Configuration Fields ### Constellation Identity & Logging @@ -120,11 +120,12 @@ CONSTELLATION_ID: "production_constellation" LOG_TO_MARKDOWN: true ``` -!!!tip "Constellation ID Best Practices" - Use descriptive names that indicate environment and purpose: - - `production_main` - Main production constellation - - `dev_testing` - Development testing constellation - - `qa_regression` - QA regression testing constellation +**Constellation ID Best Practices:** + +Use descriptive names that indicate environment and purpose: +- `production_main` - Main production constellation +- `dev_testing` - Development testing constellation +- `qa_regression` - QA regression testing constellation --- @@ -217,10 +218,11 @@ effective_concurrency = min( DEVICE_INFO: "config/galaxy/devices.yaml" ``` -!!!info "Path Resolution" - - **Relative paths** are resolved from the UFO2 project root - - **Absolute paths** are supported for external configuration files - - The loader validates that the file exists and is readable +**Path Resolution:** + +- **Relative paths** are resolved from the UFO2 project root +- **Absolute paths** are supported for external configuration files +- The loader validates that the file exists and is readable **Example Paths:** @@ -237,7 +239,7 @@ DEVICE_INFO: "config/galaxy/devices_test.yaml" --- -## 📚 Complete Examples +## Complete Examples ### Example 1: Production Configuration @@ -317,7 +319,7 @@ DEVICE_INFO: "config/galaxy/devices_ci.yaml" --- -## 🔗 Integration with Device Configuration +## Integration with Device Configuration The constellation configuration works together with device configuration: @@ -389,48 +391,48 @@ print(f" Max concurrent tasks: {constellation_config['MAX_CONCURRENT_TASKS']}" --- -## 💡 Best Practices - -!!!tip "Configuration Best Practices" - - **1. Use Environment-Specific Configurations** - ```bash - config/galaxy/ - ├── constellation.yaml # Base production config - ├── constellation_dev.yaml # Development overrides - ├── constellation_test.yaml # Testing overrides - ``` - - **2. Tune Heartbeat for Your Network** - ```yaml - # Local network - fast heartbeats - HEARTBEAT_INTERVAL: 10.0 - - # WAN/Internet - slower heartbeats - HEARTBEAT_INTERVAL: 60.0 - ``` - - **3. Match Concurrency to Use Case** - ```yaml - # High-throughput automation - MAX_CONCURRENT_TASKS: 20 - - # Resource-constrained environment - MAX_CONCURRENT_TASKS: 3 - ``` - - **4. Set Reasonable Step Limits** - ```yaml - # Prevent runaway sessions - MAX_STEP: 30 - - # For debugging (see all steps) - MAX_STEP: 100 - ``` +## Best Practices + +**Configuration Best Practices:** + +1. **Use Environment-Specific Configurations** + ```bash + config/galaxy/ + ├── constellation.yaml # Base production config + ├── constellation_dev.yaml # Development overrides + ├── constellation_test.yaml # Testing overrides + ``` + +2. **Tune Heartbeat for Your Network** + ```yaml + # Local network - fast heartbeats + HEARTBEAT_INTERVAL: 10.0 + + # WAN/Internet - slower heartbeats + HEARTBEAT_INTERVAL: 60.0 + ``` + +3. **Match Concurrency to Use Case** + ```yaml + # High-throughput automation + MAX_CONCURRENT_TASKS: 20 + + # Resource-constrained environment + MAX_CONCURRENT_TASKS: 3 + ``` + +4. **Set Reasonable Step Limits** + ```yaml + # Prevent runaway sessions + MAX_STEP: 30 + + # For debugging (see all steps) + MAX_STEP: 100 + ``` --- -## 🔗 Related Documentation +## Related Documentation | Topic | Document | Description | |-------|----------|-------------| @@ -441,7 +443,7 @@ print(f" Max concurrent tasks: {constellation_config['MAX_CONCURRENT_TASKS']}" --- -## 🚀 Next Steps +## Next Steps 1. **Configure Devices**: See [Galaxy Devices Configuration](./galaxy_devices.md) 2. **Configure Agent**: See [Galaxy Agent Configuration](./galaxy_agent.md) @@ -450,7 +452,7 @@ print(f" Max concurrent tasks: {constellation_config['MAX_CONCURRENT_TASKS']}" --- -## 📚 Source Code References +## Source Code References - **ConstellationDeviceManager**: `galaxy/client/device_manager.py` - **Configuration Loading**: `config/config_loader.py` diff --git a/documents/docs/configuration/system/galaxy_devices.md b/documents/docs/configuration/system/galaxy_devices.md index 6484e7bb3..72a555fe5 100644 --- a/documents/docs/configuration/system/galaxy_devices.md +++ b/documents/docs/configuration/system/galaxy_devices.md @@ -1,11 +1,10 @@ -# ⚙️ Configuration - Galaxy Devices +# Galaxy Devices Configuration -!!!quote "Configuration as the Starting Point" - Device configuration in **devices.yaml** defines the constellation's device agents, providing device identity, capabilities, metadata, and connection parameters for each agent in the constellation. +Device configuration in **devices.yaml** defines the constellation's device agents, providing device identity, capabilities, metadata, and connection parameters for each agent in the constellation. --- -## 📋 Overview +## Overview The **devices.yaml** configuration file defines the **devices array** for the Galaxy constellation system. It provides: @@ -14,10 +13,11 @@ The **devices.yaml** configuration file defines the **devices array** for the Ga - Custom metadata and preferences - Connection and retry parameters -!!!info "Constellation vs Device Configuration" - - **devices.yaml** - Defines individual device agents (this document) - - **constellation.yaml** - Defines constellation-wide runtime settings - - See [Galaxy Constellation Configuration](./galaxy_constellation.md) for runtime settings +**Constellation vs Device Configuration:** + +- **devices.yaml** - Defines individual device agents (this document) +- **constellation.yaml** - Defines constellation-wide runtime settings +- See [Galaxy Constellation Configuration](./galaxy_constellation.md) for runtime settings **Configuration Flow:** @@ -44,10 +44,10 @@ graph LR ``` UFO2/ ├── config/ -│ └── galaxy/ -│ ├── devices.yaml # ← Device definitions (this file) -│ ├── constellation.yaml # ← Runtime settings -│ └── agent.yaml.template # ← Agent LLM configuration template +�? └── galaxy/ +�? ├── devices.yaml # �?Device definitions (this file) +�? ├── constellation.yaml # �?Runtime settings +�? └── agent.yaml.template # �?Agent LLM configuration template ``` **Loading in Code:** @@ -129,6 +129,11 @@ devices: # List of device configurations | `max_retries` | `int` | `5` | Maximum connection retries | `3`, `10` | | `auto_connect` | `bool` | `true` | Auto-connect after registration | `true`, `false` | +!!!danger "Required Fields" + `device_id` and `server_url` are **required** for every device. Registration will fail without them. + +--- + --- ### Metadata Fields @@ -540,12 +545,12 @@ def validate_device_config(device: dict) -> bool: **1. Use Meaningful device_id** ```yaml - # ✅ Good: Descriptive and unique + # �?Good: Descriptive and unique device_id: "windows_office_pc_01" device_id: "linux_prod_server_us_west_01" device_id: "gpu_ml_workstation_lab_a" - # ❌ Bad: Generic or ambiguous + # �?Bad: Generic or ambiguous device_id: "device1" device_id: "test" device_id: "agent" @@ -553,13 +558,13 @@ def validate_device_config(device: dict) -> bool: **2. Specify Granular Capabilities** ```yaml - # ✅ Good: Specific capabilities + # �?Good: Specific capabilities capabilities: - "web_browsing_chrome" - "office_excel_automation" - "email_outlook" - # ❌ Bad: Vague capabilities + # �?Bad: Vague capabilities capabilities: - "office" - "internet" @@ -567,7 +572,7 @@ def validate_device_config(device: dict) -> bool: **3. Include Rich Metadata** ```yaml - # ✅ Good: Comprehensive metadata + # �?Good: Comprehensive metadata metadata: location: "datacenter_us_west_rack_a42" performance: "very_high" @@ -577,7 +582,7 @@ def validate_device_config(device: dict) -> bool: gpu_type: "NVIDIA A100" gpu_count: 4 - # ❌ Bad: Minimal metadata + # �?Bad: Minimal metadata metadata: location: "server room" ``` diff --git a/documents/docs/configuration/system/mcp_reference.md b/documents/docs/configuration/system/mcp_reference.md index 53a7ac682..e1f2efb53 100644 --- a/documents/docs/configuration/system/mcp_reference.md +++ b/documents/docs/configuration/system/mcp_reference.md @@ -2,15 +2,12 @@ This document provides a quick reference for MCP (Model Context Protocol) server configuration in UFO². -!!!info "Complete MCP Documentation" - For comprehensive MCP configuration guide with examples, best practices, and detailed explanations, see: - - **[MCP Configuration Guide](../../mcp/configuration.md)** - Complete configuration documentation - - Other MCP resources: - - [MCP Overview](../../mcp/overview.md) - Architecture and concepts - - [Data Collection Servers](../../mcp/data_collection.md) - Observation tools - - [Action Servers](../../mcp/action.md) - Execution tools +For comprehensive MCP configuration guide with examples, best practices, and detailed explanations, see: + +- **[MCP Configuration Guide](../../mcp/configuration.md)** - Complete configuration documentation +- [MCP Overview](../../mcp/overview.md) - Architecture and concepts +- [Data Collection Servers](../../mcp/data_collection.md) - Observation tools +- [Action Servers](../../mcp/action.md) - Execution tools ## Quick Reference diff --git a/documents/docs/configuration/system/migration.md b/documents/docs/configuration/system/migration.md index 4cd9deeca..aa2ae317e 100644 --- a/documents/docs/configuration/system/migration.md +++ b/documents/docs/configuration/system/migration.md @@ -2,8 +2,7 @@ This guide helps you migrate from the legacy configuration system (`ufo/config/config.yaml`) to the new modular configuration system (`config/ufo/`). -!!!info "Migration Overview" - Migrating to the new configuration system is **optional but recommended**. Your existing configuration will continue to work, but the new system offers better organization, type safety, and IDE support. +**Migration Overview:** Migrating to the new configuration system is **optional but recommended**. Your existing configuration will continue to work, but the new system offers better organization, type safety, and IDE support. ## Why Migrate? diff --git a/documents/docs/configuration/system/overview.md b/documents/docs/configuration/system/overview.md index 4a0743f1a..72b151b3f 100644 --- a/documents/docs/configuration/system/overview.md +++ b/documents/docs/configuration/system/overview.md @@ -2,15 +2,14 @@ UFO² features a modern, modular configuration system designed for flexibility, maintainability, and backward compatibility. This guide explains the overall architecture and design principles. -!!!info "What's New in UFO²" - The configuration system has been completely redesigned in UFO² 2.0.0 with modular YAML files, type safety, and automatic discovery. If you're upgrading from UFO v1.x, see the [Migration Guide](./migration.md). - ## Design Philosophy -The new configuration system follows professional software engineering best practices: +The configuration system follows professional software engineering best practices: + +### Separation of Concerns -### ✅ **Separation of Concerns** Configuration files are organized by domain rather than monolithic structure: + - **Agent configurations** (`agents.yaml`) - LLM settings for different agents → [Agent Config Guide](./agents_config.md) - **System configurations** (`system.yaml`) - Execution and runtime settings → [System Config Guide](./system_config.md) - **RAG configurations** (`rag.yaml`) - Knowledge retrieval settings → [RAG Config Guide](./rag_config.md) @@ -18,12 +17,14 @@ Configuration files are organized by domain rather than monolithic structure: - **Pricing configurations** (`prices.yaml`) - Cost tracking for different models → [Pricing Config Guide](./prices_config.md) - **Third-party configurations** (`third_party.yaml`) - External agent integration (LinuxAgent, HardwareAgent) → [Third-Party Config Guide](./third_party_config.md) -### ✅ **Type Safety + Flexibility** +### Type Safety + Flexibility + Hybrid approach combining: + - **Fixed typed fields** - IDE autocomplete, type checking, and IntelliSense - **Dynamic YAML fields** - Add new settings without code changes -**Example - Hybrid Configuration Access**: +**Example:** ```python # Type-safe access (recommended) @@ -39,17 +40,20 @@ new_setting = config["NEW_YAML_KEY"] max_step_old = config["MAX_STEP"] ``` -### ✅ **Backward Compatibility** +### Backward Compatibility + Zero breaking changes - existing code continues to work: + - Old configuration paths still supported (`ufo/config/`) - Old access patterns still work (`config["MAX_STEP"]`) - Automatic migration warnings guide users to new structure -!!!success "Zero Breaking Changes" - Your existing code will continue to work without any modifications. The system automatically falls back to legacy paths and access patterns. +Your existing code will continue to work without any modifications. The system automatically falls back to legacy paths and access patterns. See the [Migration Guide](./migration.md) for details on upgrading to the new structure. + +### Auto-Discovery -### ✅ **Auto-Discovery** No manual file registration needed: + - All `*.yaml` files in `config/ufo/` are automatically loaded - Files are merged intelligently with deep merging - Environment-specific overrides (`*_dev.yaml`, `*_test.yaml`) supported @@ -57,7 +61,7 @@ No manual file registration needed: ## Directory Structure ``` -UFO2/ +UFO/ ├── config/ ← New Configuration Root (Recommended) │ ├── ufo/ ← UFO² Configurations │ │ ├── agents.yaml # LLM agent settings @@ -66,7 +70,7 @@ UFO2/ │ │ ├── rag.yaml # RAG knowledge settings │ │ ├── mcp.yaml # MCP server configurations │ │ ├── prices.yaml # Model pricing -│ │ └── third_party.yaml # Third-party agents (advanced, optional) +│ │ └── third_party.yaml # Third-party agents (optional) │ │ │ ├── galaxy/ ← Galaxy Configurations │ │ ├── agent.yaml # Constellation agent settings @@ -133,26 +137,25 @@ config/galaxy/ ```python # Load Galaxy configurations from config.config_loader import get_galaxy_config -import yaml -# 1. Load agent configuration (LLM settings) +# Load Galaxy configuration (includes agent and constellation settings) galaxy_config = get_galaxy_config() -agent_config = galaxy_config.constellation_agent -# 2. Load constellation runtime settings -with open("config/galaxy/constellation.yaml", "r") as f: - constellation_config = yaml.safe_load(f) +# Access agent configuration (LLM settings) +agent_config = galaxy_config.agent.constellation_agent -# 3. Load device definitions from path specified in constellation.yaml -device_config_path = constellation_config["DEVICE_INFO"] -with open(device_config_path, "r") as f: - devices_config = yaml.safe_load(f) +# Access constellation runtime settings +constellation_settings = galaxy_config.constellation + +# Or use raw dict access for backward compatibility +constellation_id = galaxy_config["CONSTELLATION_ID"] ``` -!!!info "Galaxy vs UFO Configuration" - - **UFO Configurations** (`config/ufo/`) - Single-agent automation settings - - **Galaxy Configurations** (`config/galaxy/`) - Multi-device constellation settings - - Both systems can coexist in the same project +**Galaxy vs UFO Configuration:** + +- **UFO Configurations** (`config/ufo/`) - Single-agent automation settings +- **Galaxy Configurations** (`config/galaxy/`) - Multi-device constellation settings +- Both systems can coexist in the same project --- @@ -166,8 +169,7 @@ The configuration system uses a clear priority chain (highest to lowest): 2. **Legacy monolithic config** - `{module}/config/config.yaml` 3. **Environment variables** - Runtime overrides -!!!warning "Configuration Priority" - When the same setting exists in multiple locations, the **new modular config** takes precedence over legacy configs. Values are merged with later sources overriding earlier ones. +When the same setting exists in multiple locations, the **new modular config** takes precedence over legacy configs. Values are merged with later sources overriding earlier ones. ### Loading Algorithm @@ -192,9 +194,7 @@ def load_config(): ### Deep Merging -Configuration files are merged recursively: - -**Example - Deep Merge**: +Configuration files are merged recursively, allowing you to split configurations across multiple files without duplication: ```yaml # config/ufo/agents.yaml @@ -213,9 +213,8 @@ Fields from later files are added to (not replacing) earlier configurations. ## File Organization Patterns -### Recommended Structure +### Split by Domain (Current Approach) -**Split by domain** (current approach): ``` config/ufo/ ├── agents.yaml # All agent LLM configs @@ -225,10 +224,7 @@ config/ufo/ └── prices.yaml # Model pricing ``` -**Advantages**: -- Easy to find related settings -- Clear separation of concerns -- Good for documentation +**Advantages:** Easy to find related settings, clear separation of concerns, good for documentation. ### Alternative: Split by Agent @@ -240,10 +236,7 @@ config/ufo/ └── rag.yaml # Shared RAG config ``` -**Advantages**: -- Agent-specific settings isolated -- Easy to customize per agent -- Good for multi-agent scenarios +**Advantages:** Agent-specific settings isolated, easy to customize per agent, good for multi-agent scenarios. Both patterns work! The loader auto-discovers and merges all YAML files. @@ -278,8 +271,6 @@ $env:UFO_ENV = "dev" # Windows PowerShell Provides IDE autocomplete and type safety: -**Example - Type-Safe Configuration**: - ```python @dataclass class SystemConfig: @@ -295,9 +286,9 @@ config.system.temperature # float ### Dynamic Types (Flexible) -For custom or experimental settings: +For custom or experimental settings. Learn more about adding custom fields in the [Extending Configuration guide](./extending.md). -**Example - Dynamic Configuration**: +**Example:** ```python # In YAML @@ -313,8 +304,6 @@ if config.MY_CUSTOM_FEATURE: Best of both worlds: -**Example - Hybrid Configuration Model**: - ```python class SystemConfig: # Fixed fields @@ -327,46 +316,46 @@ class SystemConfig: # Try extras for unknown fields return self._extras.get(name) ``` - ``` ## Migration Warnings The system provides clear warnings when using legacy paths: -!!!warning "Legacy Path Detection" - ⚠️ LEGACY CONFIG PATH DETECTED: UFO - - Using legacy config: ufo/config/ - Please migrate to: config/ufo/ - - Quick migration: - mkdir -p config/ufo - cp ufo/config/*.yaml config/ufo/ - - Or use migration tool: - python -m ufo.tools.migrate_config - - These warnings appear once per session and guide you to migrate to the new structure. +``` +⚠️ LEGACY CONFIG PATH DETECTED: UFO + +Using legacy config: ufo/config/ +Please migrate to: config/ufo/ + +Quick migration: + mkdir -p config/ufo + cp ufo/config/*.yaml config/ufo/ + +Or use migration tool: + python -m ufo.tools.migrate_config +``` + +These warnings appear once per session and guide you to migrate to the new structure. ## Best Practices -!!!tip "DO - Recommended Practices" - - **Use modular files** - Split by domain or agent - - **Use typed access** - `config.system.max_step` over `config["MAX_STEP"]` - - **Add templates** - Provide `.template` files for sensitive data - - **Document custom fields** - Add comments in YAML - - **Use environment overrides** - For dev/test/prod differences +**Recommended Practices:** -!!!danger "DON'T - Anti-Patterns" - - **Mix old and new** - Migrate fully to new structure - - **Put secrets in YAML** - Use environment variables instead - - **Duplicate settings** - Leverage deep merging - - **Break backward compat** - Keep `config["OLD_KEY"]` working - - **Hardcode paths** - Use config system +- **Use modular files** - Split by domain or agent +- **Use typed access** - `config.system.max_step` over `config["MAX_STEP"]` +- **Add templates** - Provide `.template` files for sensitive data +- **Document custom fields** - Add comments in YAML +- **Use environment overrides** - For dev/test/prod differences -## Configuration Lifecycle +**Anti-Patterns to Avoid:** -**Configuration Loading Process**: +- **Mix old and new** - Migrate fully to new structure +- **Put secrets in YAML** - Use environment variables instead +- **Duplicate settings** - Leverage deep merging +- **Break backward compat** - Keep `config["OLD_KEY"]` working +- **Hardcode paths** - Use config system + +## Configuration Lifecycle ```mermaid graph LR @@ -379,14 +368,6 @@ graph LR G --> H[Cache for Reuse] H --> I[Application Running] ``` - B --> C[Check for Legacy Config] - C --> D[Load New Modular Configs] - D --> E[Deep Merge All Sources] - E --> F[Apply Transformations] - F --> G[Create Typed Config Object] - G --> H[Cache for Reuse] - H --> I[Application Running] - ``` ## Next Steps @@ -407,8 +388,10 @@ graph LR ## API Reference -:::config_loader.ConfigLoader - -:::config_schemas.UFOConfig +For detailed API documentation of configuration classes and methods, see: -:::config_schemas.GalaxyConfig +- `config.config_loader.ConfigLoader` - Configuration loading and caching +- `config.config_schemas.UFOConfig` - UFO configuration schema +- `config.config_schemas.GalaxyConfig` - Galaxy configuration schema +- `config.config_loader.get_ufo_config()` - Get UFO configuration instance +- `config.config_loader.get_galaxy_config()` - Get Galaxy configuration instance diff --git a/documents/docs/configuration/system/prices_config.md b/documents/docs/configuration/system/prices_config.md index a7a4eb96a..5546ce857 100644 --- a/documents/docs/configuration/system/prices_config.md +++ b/documents/docs/configuration/system/prices_config.md @@ -1,7 +1,6 @@ # Pricing Configuration (prices.yaml) -!!!quote "LLM Cost Tracking" - Configure token pricing for different LLM models to track and estimate API costs during UFO² execution. +Configure token pricing for different LLM models to track and estimate API costs during UFO² execution. --- diff --git a/documents/docs/configuration/system/rag_config.md b/documents/docs/configuration/system/rag_config.md index 1658ab79a..297cc0478 100644 --- a/documents/docs/configuration/system/rag_config.md +++ b/documents/docs/configuration/system/rag_config.md @@ -1,7 +1,6 @@ # RAG Configuration (rag.yaml) -!!!quote "Knowledge Retrieval and Learning" - Configure Retrieval-Augmented Generation (RAG) to enhance UFO² with external knowledge sources, online search, experience learning, and demonstration-based learning. +Configure Retrieval-Augmented Generation (RAG) to enhance UFO² with external knowledge sources, online search, experience learning, and demonstration-based learning. --- @@ -16,8 +15,7 @@ The `rag.yaml` file configures knowledge retrieval systems that augment UFO²'s **File Location**: `config/ufo/rag.yaml` -!!!info "Optional Configuration" - RAG features are **optional**. UFO² works without them, but they can significantly improve performance on complex or domain-specific tasks. +**Optional Configuration:** RAG features are **optional**. UFO² works without them, but they can significantly improve performance on complex or domain-specific tasks. --- diff --git a/documents/docs/configuration/system/system_config.md b/documents/docs/configuration/system/system_config.md index 61fdc3f5e..08c092597 100644 --- a/documents/docs/configuration/system/system_config.md +++ b/documents/docs/configuration/system/system_config.md @@ -1,9 +1,6 @@ # System Configuration (system.yaml) -!!!quote "Runtime and Execution Settings" - Configure UFO²'s runtime behavior, execution limits, control backends, logging, and operational parameters. This file controls how UFO² interacts with the Windows environment. - ---- +Configure UFO²'s runtime behavior, execution limits, control backends, logging, and operational parameters. This file controls how UFO² interacts with the Windows environment. ## Overview @@ -11,10 +8,7 @@ The `system.yaml` file defines runtime settings that control UFO²'s behavior du **File Location**: `config/ufo/system.yaml` -!!!info "No Template Needed" - Unlike `agents.yaml`, the `system.yaml` file is **already present** in the repository with sensible defaults. You can use it as-is or customize it for your needs. - ---- +**Note:** Unlike `agents.yaml`, the `system.yaml` file is **already present** in the repository with sensible defaults. You can use it as-is or customize it for your needs. ## Quick Configuration @@ -37,7 +31,7 @@ MAX_STEP: 50 MAX_ROUND: 1 PRINT_LOG: True LOG_LEVEL: "DEBUG" -CONTROL_BACKEND: ["uia", "win32"] +CONTROL_BACKEND: ["uia"] ``` ### Recommended for Production @@ -46,14 +40,12 @@ CONTROL_BACKEND: ["uia", "win32"] # Optimized for reliability MAX_STEP: 100 MAX_ROUND: 3 -CONTROL_BACKEND: ["uia", "win32"] +CONTROL_BACKEND: ["uia"] USE_MCP: True SAFE_GUARD: True LOG_TO_MARKDOWN: True ``` ---- - ## Configuration Categories The `system.yaml` file is organized into logical sections: @@ -69,8 +61,6 @@ The `system.yaml` file is organized into logical sections: | **[Safety](#safety)** | Security controls | `SAFE_GUARD`, `CONTROL_LIST` | | **[Control Filtering](#control-filtering)** | UI element filtering | `CONTROL_FILTER_TYPE`, `CONTROL_FILTER_TOP_K` | ---- - ## LLM Parameters These settings control how UFO² communicates with LLM APIs. @@ -101,13 +91,12 @@ TIMEOUT: 60 # TOP_P: 0.9 ``` -!!!tip "When to Adjust" - - **Increase MAX_TOKENS** if responses are getting cut off - - **Increase TEMPERATURE** if you want more varied responses (not recommended) - - **Keep at 0.0** for consistent, repeatable automation - - **Increase TIMEOUT** for slow API connections +**When to Adjust:** ---- +- **Increase MAX_TOKENS** if responses are getting cut off +- **Increase TEMPERATURE** if you want more varied responses (not recommended) +- **Keep at 0.0** for consistent, repeatable automation +- **Increase TIMEOUT** for slow API connections ## Execution Limits @@ -139,13 +128,12 @@ RECTANGLE_TIME: 1 # SLEEP_TIME: 0 ``` -!!!warning "Step vs Round" - - **STEP**: Individual action (click, type, etc.) - - **ROUND**: Complete task attempt from start - - Example: If `MAX_ROUND: 3`, UFO² will retry the entire task up to 3 times if it fails. +**Note on Step vs Round:** ---- +- **STEP**: Individual action (click, type, etc.) +- **ROUND**: Complete task attempt from start + +Example: If `MAX_ROUND: 3`, UFO² will retry the entire task up to 3 times if it fails. ## Control Backend @@ -163,27 +151,22 @@ Configure how UFO² detects and interacts with UI elements. | Backend | Description | Pros | Cons | |---------|-------------|------|------| | `"uia"` | UI Automation | Fast, reliable, Windows native | May miss some controls | -| `"win32"` | Win32 API | Finds more controls | Slower, may have duplicates | | `"omniparser"` | Vision-based | Finds visual-only elements | Requires GPU, slow | +**Note:** `win32` backend is no longer supported. + ### Example ```yaml -# Recommended: Use both UIA and Win32 -CONTROL_BACKEND: ["uia", "win32"] +# Recommended: Use UIA (default) +CONTROL_BACKEND: ["uia"] IOU_THRESHOLD_FOR_MERGE: 0.1 -# Fast but may miss controls -# CONTROL_BACKEND: ["uia"] - -# Most comprehensive (slow) -# CONTROL_BACKEND: ["uia", "win32", "omniparser"] +# With vision-based parsing (slow) +# CONTROL_BACKEND: ["uia", "omniparser"] ``` -!!!tip "Best Practice" - Start with `["uia", "win32"]` for best balance of speed and coverage. - ---- +**Best Practice:** Use `["uia"]` as the default backend. Add `"omniparser"` only if you need vision-based control detection. ## Action Configuration @@ -445,7 +428,7 @@ Configure native API usage for Office applications. | Field | Type | Default | Description | |-------|------|---------|-------------| -| `USE_APIS` | Boolean | `True` | Enable Win32 COM API usage | +| `USE_APIS` | Boolean | `True` | Enable COM API usage for Office applications | | `API_PROMPT` | String | `"ufo/prompts/share/base/api.yaml"` | API prompt template | | `APP_API_PROMPT_ADDRESS` | Dict | See below | App-specific API prompts | @@ -498,7 +481,7 @@ SLEEP_TIME: 1 RECTANGLE_TIME: 1 # Control Backend -CONTROL_BACKEND: ["uia", "win32"] +CONTROL_BACKEND: ["uia"] IOU_THRESHOLD_FOR_MERGE: 0.1 # Action Configuration @@ -607,37 +590,34 @@ if config.system.use_mcp: ### Issue 2: Controls Not Detected -!!!bug "Symptom" - UFO² can't find UI elements - - **Solutions**: - 1. Add more backends: - ```yaml - CONTROL_BACKEND: ["uia", "win32"] - ``` - 2. Disable filtering: - ```yaml - CONTROL_FILTER_TYPE: [] - ``` +**Symptom:** UFO² can't find UI elements + +**Solutions:** +1. Try enabling omniparser for vision-based detection: + ```yaml + CONTROL_BACKEND: ["uia", "omniparser"] + ``` +2. Disable filtering: + ```yaml + CONTROL_FILTER_TYPE: [] + ``` ### Issue 3: Actions Too Fast -!!!bug "Symptom" - Actions execute before UI is ready - - **Solution**: Add delays - ```yaml - SLEEP_TIME: 2 - AFTER_CLICK_WAIT: 1 - ``` +**Symptom:** Actions execute before UI is ready + +**Solution:** Add delays +```yaml +SLEEP_TIME: 2 +AFTER_CLICK_WAIT: 1 +``` ### Issue 4: Logs Too Verbose -!!!bug "Symptom" - Too much console output - - **Solution**: Reduce logging - ```yaml +**Symptom:** Too much console output + +**Solution:** Reduce logging +```yaml PRINT_LOG: False LOG_LEVEL: "WARNING" ``` @@ -651,7 +631,7 @@ if config.system.use_mcp: ```yaml MAX_STEP: 50 SLEEP_TIME: 0 -CONTROL_BACKEND: ["uia"] # Faster than win32 +CONTROL_BACKEND: ["uia"] CONTROL_FILTER_TYPE: ["SEMANTIC"] # Reduce LLM input ACTION_SEQUENCE: True # Multi-action in one step ``` @@ -663,7 +643,7 @@ MAX_STEP: 100 MAX_ROUND: 3 SLEEP_TIME: 2 AFTER_CLICK_WAIT: 1 -CONTROL_BACKEND: ["uia", "win32"] # More coverage +CONTROL_BACKEND: ["uia"] CONTROL_FILTER_TYPE: [] # Don't filter out controls ``` @@ -685,16 +665,15 @@ MCP_LOG_EXECUTION: True - **[MCP Configuration](mcp_reference.md)** - Tool server configuration - **[RAG Configuration](rag_config.md)** - Knowledge retrieval ---- - ## Summary -!!!success "Key Takeaways" - ✅ **Default settings work** - Start with defaults, adjust as needed - ✅ **Increase MAX_STEP** for complex tasks - ✅ **Use ["uia", "win32"]** for best control detection - ✅ **Enable ACTION_SEQUENCE** for faster execution - ✅ **Adjust logging** based on dev vs production - ✅ **Enable MCP** for better Office automation - - **Fine-tune system settings for optimal performance!** ⚙️ +**Key Takeaways:** + +✅ **Default settings work** - Start with defaults, adjust as needed +✅ **Increase MAX_STEP** for complex tasks +✅ **Use ["uia"]** for control detection +✅ **Enable ACTION_SEQUENCE** for faster execution +✅ **Adjust logging** based on dev vs production +✅ **Enable MCP** for better Office automation + +**Fine-tune system settings for optimal performance!** ⚙️ diff --git a/documents/docs/configuration/system/third_party_config.md b/documents/docs/configuration/system/third_party_config.md index a75abfbe1..014d03946 100644 --- a/documents/docs/configuration/system/third_party_config.md +++ b/documents/docs/configuration/system/third_party_config.md @@ -1,7 +1,6 @@ # Third-Party Agent Configuration (third_party.yaml) -!!!quote "External Agent Integration" - Configure third-party agents that extend UFO²'s capabilities beyond Windows GUI automation, such as LinuxAgent for CLI operations and HardwareAgent for physical device control. +Configure third-party agents that extend UFO²'s capabilities beyond Windows GUI automation, such as LinuxAgent for CLI operations and HardwareAgent for physical device control. --- @@ -11,8 +10,7 @@ The `third_party.yaml` file configures external agents that integrate with UFO² **File Location**: `config/ufo/third_party.yaml` -!!!info "Advanced Feature" - Third-party agent configuration is an **advanced optional feature**. Most users only need the core agents (HostAgent, AppAgent). Configure third-party agents only when you need specialized capabilities. +**Advanced Feature:** Third-party agent configuration is an **advanced optional feature**. Most users only need the core agents (HostAgent, AppAgent). Configure third-party agents only when you need specialized capabilities. !!!tip "Creating Custom Third-Party Agents" Want to build your own third-party agent? See the **[Creating Custom Third-Party Agents Tutorial](../../tutorials/creating_third_party_agents.md)** for a complete step-by-step guide using HardwareAgent as an example. diff --git a/documents/docs/faq.md b/documents/docs/faq.md index 9f0ca0e7f..2f9c7d28d 100644 --- a/documents/docs/faq.md +++ b/documents/docs/faq.md @@ -1,31 +1,560 @@ -# FAQ +# Frequently Asked Questions (FAQ) -We provide answers to some frequently asked questions about the UFO. +Quick answers to common questions about UFO³ Galaxy, UFO², Linux Agents, and general troubleshooting. -## Q1: Why is it called UFO? +--- -A: UFO stands for **U**I **Fo**cused agent. The name is inspired by the concept of an unidentified flying object (UFO) that is mysterious and futuristic. +## 🎯 General Questions -## Q2: Can I use UFO on Linux or macOS? -A: UFO is currently only supported on Windows OS. +### Q: What is UFO³? -## Q3: Why the latency of UFO is high? -A: The latency of UFO depends on the response time of the LLMs and the network speed. If you are using GPT, it usually takes dozens of seconds to generate a response in one step. The workload of the GPT endpoint may also affect the latency. +**A:** UFO³ is the third iteration of the UFO project, encompassing three major frameworks: -## Q4: What models does UFO support? -A: UFO supports various language models, including OpenAI and Azure OpenAI models, QWEN, google Gimini, Ollama, and more. You can find the full list of supported models in the `Supported Models` section of the documentation. +- **UFO²** - Desktop AgentOS for Windows automation +- **UFO³ Galaxy** - Multi-device orchestration framework +- **Linux Agent** - Server and CLI automation for Linux -## Q5: Can I use non-vision models in UFO? -A: Yes, you can use non-vision models in UFO. You can set the `VISUAL_MODE` to `False` in the `config.yaml` file to disable the visual mode and use non-vision models. However, UFO is designed to work with vision models, and using non-vision models may affect the performance. +### Q: Why is it called UFO? -## Q6: Can I host my own LLM endpoint? -A: Yes, you can host your custom LLM endpoint and configure UFO to use it. Check the documentation in the `Supported Models` section for more details. +**A:** UFO stands for **U**I **Fo**cused agent. The name was given to the first version of the project and has been retained through all iterations (UFO v1, UFO², UFO³) as the project evolved from a simple UI-focused agent to a comprehensive multi-device orchestration framework. -## Q7: Can I use non-English requests in UFO? -A: It depends on the language model you are using. Most of LLMs support multiple languages, and you can specify the language in the request. However, the performance may vary for different languages. +### Q: Which version should I use? -## Q8: Why it shows the error `Error making API request: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))`? -A: This means the LLM endpoint is not accessible. You can check the network connection (e.g. VPN) and the status of the LLM endpoint. +**A:** Choose based on your needs: -!!! info - To get more support, please submit an issue on the [GitHub Issues](https://github.com/microsoft/UFO/issues), or send an email to [ufo-agent@microsoft.com](mailto:ufo-agent@microsoft.com). \ No newline at end of file +| Use Case | Recommended Version | +|----------|-------------------| +| Windows desktop automation only | [UFO²](getting_started/quick_start_ufo2.md) | +| Cross-device workflows (Windows + Linux) | [UFO³ Galaxy](getting_started/quick_start_galaxy.md) | +| Linux server management only | [Linux Agent](getting_started/quick_start_linux.md) | +| Multi-device orchestration | [UFO³ Galaxy](getting_started/quick_start_galaxy.md) | + +### Q: What's the difference between UFO² and UFO³ Galaxy? + +**A: UFO²** is for single Windows desktop automation with: +- Deep Windows OS integration (UIA, Win32, COM) +- Office application automation +- GUI + API hybrid execution + +**UFO³ Galaxy** orchestrates multiple devices with: +- Cross-platform support (Windows + Linux) +- Distributed task execution +- Device capability-based routing +- Constellation-based DAG orchestration + +See [Migration Guide](getting_started/migration_ufo2_to_galaxy.md) for details. + +### Q: Can I use UFO on Linux or macOS? + +**A:** Yes and No: + +- **✅ Linux:** Supported via Linux Agent for server/CLI automation +- **❌ macOS:** Not currently supported (Windows and Linux only) +- **Windows:** Full UFO² desktop automation support + +--- + +## 🔧 Installation & Setup + +### Q: Which Python version do I need? + +**A:** Python **3.10 or higher** is required for all UFO³ components. + +```bash +# Check your Python version +python --version +``` + +### Q: What models does UFO support? + +**A:** UFO³ supports multiple LLM providers: + +- **OpenAI** - GPT-4o, GPT-4, GPT-3.5 +- **Azure OpenAI** - All Azure-hosted models +- **Google Gemini** - Gemini Pro, Gemini Flash +- **Anthropic Claude** - Claude 3.5, Claude 3 +- **Qwen** - Local or API deployment +- **DeepSeek** - DeepSeek models +- **Ollama** - Local model hosting +- And more... + +See [Model Configuration Guide](configuration/models/overview.md) for the complete list and setup instructions. + +### Q: Can I use non-vision models in UFO? + +**A:** Yes! You can disable visual mode: + +```yaml +# config/ufo/system.yaml +VISUAL_MODE: false +``` + +However, UFO² is designed for vision models. Non-vision models may have reduced performance for GUI automation tasks. + +### Q: Can I host my own LLM endpoint? + +**A:** Yes! UFO³ supports custom endpoints: + +```yaml +# config/ufo/agents.yaml +HOST_AGENT: + API_TYPE: "openai" # Or compatible API + API_BASE: "http://your-endpoint.com/v1/chat/completions" + API_KEY: "your-key" + API_MODEL: "your-model-name" +``` + +See [Model Configuration](configuration/models/overview.md) for details. + +### Q: Do I need API keys for all agents? + +**A:** No, only for LLM-powered agents: + +| Component | Requires API Key | Purpose | +|-----------|-----------------|---------| +| **ConstellationAgent** (Galaxy) | ✅ Yes | Orchestration reasoning | +| **HostAgent** (UFO²) | ✅ Yes | Task planning | +| **AppAgent** (UFO²) | ✅ Yes | Action execution | +| **LinuxAgent** | ✅ Yes | Command planning | +| **Device Server** | ❌ No | Message routing only | +| **MCP Servers** | ❌ No | Tool provider only | + +--- + +## ⚙️ Configuration + +### Q: Where are configuration files located? + +**A:** UFO³ uses a modular configuration system in `config/`: + +``` +config/ +├── ufo/ # UFO² configuration +│ ├── agents.yaml # LLM and agent settings +│ ├── system.yaml # Runtime settings +│ ├── rag.yaml # Knowledge retrieval +│ └── mcp.yaml # MCP server configuration +└── galaxy/ # Galaxy configuration + ├── agent.yaml # ConstellationAgent LLM + ├── devices.yaml # Device pool + └── constellation.yaml # Runtime settings +``` + +### Q: Can I still use the old `ufo/config/config.yaml`? + +**A:** Yes, for backward compatibility, but we recommend migrating to the new modular system: + +```bash +# Check current configuration +python -m ufo.tools.validate_config ufo --show-config + +# Migrate from legacy to new +python -m ufo.tools.migrate_config +``` + +See [Configuration Migration Guide](configuration/system/migration.md) for details. + +### Q: How do I protect my API keys? + +**A:** Best practices for API key security: + +1. **Never commit `.yaml` files with keys** - Use `.template` files + ```bash + # Good pattern + config/ufo/agents.yaml.template # Commit this (with placeholders) + config/ufo/agents.yaml # DON'T commit (has real keys) + ``` + +2. **Use environment variables** for sensitive data: + ```yaml + # In agents.yaml + HOST_AGENT: + API_KEY: ${OPENAI_API_KEY} # Reads from environment + ``` + +3. **Add to `.gitignore`**: + ``` + config/**/agents.yaml + config/**/agent.yaml + !**/*.template + ``` + +--- + +## 🌌 UFO³ Galaxy Questions + +### Q: What's the minimum number of devices for Galaxy? + +**A:** Galaxy requires **at least 1 device agent** (Windows or Linux) to be useful, but you can start with just one device and add more later. + +```yaml +# Minimal Galaxy setup (1 device) +devices: + - device_id: "my_windows_pc" + server_url: "ws://localhost:5000/ws" + os: "windows" +``` + +### Q: Can Galaxy mix Windows and Linux devices? + +**A:** Yes! Galaxy can orchestrate heterogeneous devices: + +```yaml +devices: + - device_id: "windows_desktop" + os: "windows" + capabilities: ["office", "excel", "outlook"] + + - device_id: "linux_server" + os: "linux" + capabilities: ["server", "database", "log_analysis"] +``` + +Galaxy automatically routes tasks based on device capabilities. + +### Q: Do all devices need to be on the same network? + +**A:** No, devices can be distributed across networks using SSH tunneling: + +- **Same network:** Direct WebSocket connections +- **Different networks:** Use SSH tunnels (reverse/forward) +- **Cloud + local:** SSH tunnels with public gateways + +See [Linux Quick Start - SSH Tunneling](getting_started/quick_start_linux.md#network-connectivity-ssh-tunneling) for examples. + +### Q: How does Galaxy decide which device to use? + +**A:** Galaxy uses **capability-based routing**: + +1. Analyzes the task requirements +2. Matches against device `capabilities` in `devices.yaml` +3. Considers device `metadata` (OS, performance, etc.) +4. Selects the best-fit device(s) + +Example: +```yaml +# Task: "Analyze error logs on the production server" +# → Galaxy routes to device with: +capabilities: + - "log_analysis" + - "server_management" +os: "linux" +``` + +--- + +## 🐧 Linux Agent Questions + +### Q: Does the Linux Agent require a GUI? + +**A:** No! The Linux Agent is designed for headless servers: + +- Executes CLI commands via MCP +- No X11/desktop environment needed +- Works over SSH +- Perfect for remote servers + +### Q: Can I run multiple Linux Agents on one machine? + +**A:** Yes, using different ports and client IDs: + +```bash +# Agent 1 +python -m ufo.server.app --port 5001 +python -m ufo.client.client --ws --client-id linux_1 --platform linux + +# Agent 2 (same machine) +python -m ufo.server.app --port 5002 +python -m ufo.client.client --ws --client-id linux_2 --platform linux +``` + +### Q: What's the MCP service for? + +**A:** The MCP (Model Context Protocol) service provides the **actual command execution tools** for the Linux Agent: + +``` +Linux Agent (LLM reasoning) + ↓ +MCP Service (tool provider) + ↓ +Bash commands (actual execution) +``` + +Without MCP, the Linux Agent can't execute commands - it can only plan them. + +--- + +## 🪟 UFO² Questions + +### Q: Does UFO² work on Windows 10? + +**A:** Yes! UFO² supports: +- ✅ Windows 11 (recommended) +- ✅ Windows 10 (fully supported) +- ❌ Windows 8.1 or earlier (not tested) + +### Q: Can UFO² automate Office apps? + +**A:** Yes! UFO² has enhanced Office support through: +- **MCP Office servers** - Direct API access to Excel, Word, Outlook, PowerPoint +- **GUI automation** - Fallback for unsupported operations +- **Hybrid execution** - Automatically chooses API or GUI + +Enable MCP in `config/ufo/mcp.yaml` for better Office automation. + +### Q: Does UFO² interrupt my work? + +**A:** UFO² can run automation tasks on your current desktop. For non-disruptive operation, you can run it on a separate machine or virtual desktop environment. + +> **Note:** Picture-in-Picture mode is planned for future releases. + +### Q: Can I use UFO² without MCP? + +**A:** UFO² requires MCP (Model Context Protocol) servers for tool execution. MCP provides the interface between the LLM agents and system operations (Windows APIs, Office automation, etc.). Without MCP, UFO² cannot perform actions. + +--- + +## 🐛 Common Issues & Troubleshooting + +### Issue: "Configuration file not found" + +**Error:** +``` +FileNotFoundError: config/ufo/agents.yaml not found +``` + +**Solution:** +```bash +# Copy template files +cp config/ufo/agents.yaml.template config/ufo/agents.yaml + +# Edit with your API keys +notepad config/ufo/agents.yaml # Windows +nano config/ufo/agents.yaml # Linux +``` + +### Issue: "API Authentication Error" + +**Error:** +``` +openai.AuthenticationError: Invalid API key +``` + +**Solutions:** + +1. **Check API key format:** + ```yaml + API_KEY: "sk-..." # OpenAI starts with sk- + API_KEY: "..." # Azure uses deployment key + ``` + +2. **Verify API_TYPE matches your provider:** + ```yaml + API_TYPE: "openai" # For OpenAI + API_TYPE: "aoai" # For Azure OpenAI + ``` + +3. **Check for extra spaces/quotes** in YAML + +4. **For Azure:** Verify `API_DEPLOYMENT_ID` is set + +### Issue: "Connection aborted / Remote end closed connection" + +**Error:** +``` +Error making API request: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response')) +``` + +**Solutions:** + +- Check network connection (VPN, proxy, firewall) +- Verify LLM endpoint is accessible: `curl https://api.openai.com/v1/models` +- Check endpoint status (Azure, OpenAI, etc.) +- Try increasing timeout in config +- Verify API base URL is correct + +### Issue: "Device not connecting to Galaxy" + +**Error:** +``` +ERROR - [WS] Failed to connect to ws://localhost:5000/ws +Connection refused +``` + +**Checklist:** + +- [ ] Is the server running? (`curl http://localhost:5000/api/health`) +- [ ] Port number correct? (Server: `--port 5000`, Client: `ws://...:5000/ws`) +- [ ] Platform flag set? (`--platform windows` or `--platform linux`) +- [ ] Firewall blocking? (Allow port 5000) +- [ ] SSH tunnel established? (If using remote devices) + +### Issue: "device_id mismatch in Galaxy" + +**Error:** +``` +ERROR - Device 'linux_agent_1' not found in configuration +``` + +**Cause:** Mismatch between `devices.yaml` and client command + +**Solution:** Ensure exact match: + +| Location | Field | Example | +|----------|-------|---------| +| `devices.yaml` | `device_id:` | `"linux_agent_1"` | +| Client command | `--client-id` | `linux_agent_1` | + +**Critical:** IDs must match **exactly** (case-sensitive, no typos). + +### Issue: "MCP service not responding (Linux)" + +**Error:** +``` +ERROR - Cannot connect to MCP server at http://127.0.0.1:8010 +``` + +**Solutions:** + +1. **Check if MCP service is running:** + ```bash + curl http://localhost:8010/health + ps aux | grep linux_mcp_server + ``` + +2. **Restart MCP service:** + ```bash + pkill -f linux_mcp_server + python -m ufo.client.mcp.http_servers.linux_mcp_server + ``` + +3. **Check port conflict:** + ```bash + lsof -i :8010 + # If port taken, use different port: + python -m ufo.client.mcp.http_servers.linux_mcp_server --port 8011 + ``` + +### Issue: "Tasks failing after X steps" + +**Cause:** `MAX_STEP` limit reached + +**Solution:** Increase step limit in `config/ufo/system.yaml`: + +```yaml +# Default is 50 +MAX_STEP: 100 # For complex tasks + +# Or disable limit (not recommended) +MAX_STEP: -1 +``` + +### Issue: "Too many LLM calls / high cost" + +**Solutions:** + +1. **Enable action sequences** (bundles actions): + ```yaml + # config/ufo/system.yaml + ACTION_SEQUENCE: true + ``` + +2. **Use vision-capable models for GUI tasks:** + ```yaml + # config/ufo/agents.yaml + APP_AGENT: + API_MODEL: "gpt-4o" # Use vision models for GUI automation + ``` + + > **Note:** Non-vision models like gpt-3.5-turbo cannot process screenshots and should not be used for GUI automation tasks. + +3. **Enable experience learning** (reuse patterns): + ```yaml + # config/ufo/rag.yaml + RAG_EXPERIENCE: true + ``` + +### Issue: "Why is the latency high?" + +**A:** Latency depends on several factors: + +- **LLM response time** - GPT-4o typically takes 10-30 seconds per step +- **Network speed** - API calls to OpenAI/Azure endpoints +- **Endpoint workload** - Provider server load +- **Visual mode** - Image processing adds overhead + +**To reduce latency:** +- Use faster models (gpt-3.5-turbo vs gpt-4o) +- Enable action sequences to batch operations +- Use local models (Ollama) if acceptable +- Disable visual mode if not needed + +### Issue: "Can I use non-English requests?" + +**A:** Yes! Most modern LLMs support multiple languages: + +- GPT-4o, GPT-4: Excellent multilingual support +- Gemini: Good multilingual support +- Qwen: Excellent for Chinese +- Claude: Good multilingual support + +Performance may vary by language and model. Test with your specific language and model combination. + +--- + +## 📚 Where to Find More Help + +### Documentation + +| Topic | Link | +|-------|------| +| **Getting Started** | [UFO² Quick Start](getting_started/quick_start_ufo2.md), [Galaxy Quick Start](getting_started/quick_start_galaxy.md), [Linux Quick Start](getting_started/quick_start_linux.md) | +| **Configuration** | [Configuration Overview](configuration/system/overview.md) | +| **Troubleshooting** | Quick start guides have detailed troubleshooting sections | +| **Architecture** | [Project Structure](project_directory_structure.md) | +| **More Guidance** | [User & Developer Guide](getting_started/more_guidance.md) | + +### Community & Support + +- **GitHub Discussions:** [https://github.com/microsoft/UFO/discussions](https://github.com/microsoft/UFO/discussions) +- **GitHub Issues:** [https://github.com/microsoft/UFO/issues](https://github.com/microsoft/UFO/issues) +- **Email:** ufo-agent@microsoft.com + +### Debugging Tips + +1. **Enable debug logging:** + ```yaml + # config/ufo/system.yaml + LOG_LEVEL: "DEBUG" + ``` + +2. **Check log files:** + ``` + logs// + ├── request.log # Request logs + ├── response.log # Response logs + ├── action_step*.png # Screenshots at each step + └── action_step*_annotated.png # Annotated screenshots + ``` + +3. **Validate configuration:** + ```bash + python -m ufo.tools.validate_config ufo --show-config + python -m ufo.tools.validate_config galaxy --show-config + ``` + +4. **Test LLM connectivity:** + ```python + # Test your API key + from openai import OpenAI + client = OpenAI(api_key="your-key") + response = client.chat.completions.create( + model="gpt-4o", + messages=[{"role": "user", "content": "Hello"}] + ) + print(response.choices[0].message.content) + ``` + +--- + +> **💡 Still have questions?** Check the [More Guidance](getting_started/more_guidance.md) page for additional resources, or reach out to the community! diff --git a/documents/docs/galaxy/agent_registration/agent_profile.md b/documents/docs/galaxy/agent_registration/agent_profile.md index abf68dd7a..42a27712d 100644 --- a/documents/docs/galaxy/agent_registration/agent_profile.md +++ b/documents/docs/galaxy/agent_registration/agent_profile.md @@ -1,7 +1,6 @@ # 📊 AgentProfile - Comprehensive Agent Representation -!!!quote "The Identity Card of Constellation Agents" - The **AgentProfile** is a multi-source data structure that consolidates administrator configuration, service-level capabilities, and real-time client telemetry into a unified, dynamically updated representation of each constellation agent. +The **AgentProfile** is a multi-source data structure that consolidates administrator configuration, service-level capabilities, and real-time client telemetry into a unified, dynamically updated representation of each constellation agent. --- @@ -9,7 +8,10 @@ The **AgentProfile** is the primary data structure representing a registered constellation agent. It aggregates information from **three distinct sources** to provide a comprehensive view of each agent's identity, capabilities, operational status, and hardware characteristics. -**Purpose:** +For a complete understanding of how agents work in the constellation system, see: + +- [Constellation Overview](../constellation/overview.md) - Architecture and multi-device coordination +- [Constellation Agent](../constellation_agent/overview.md) - Agent behavior and lifecycle | Function | Description | |----------|-------------| @@ -47,12 +49,9 @@ class AgentProfile: Device information and capabilities. Consolidates information from three sources: - 1. User-specified registration (devices.yaml + constellation.yaml) + 1. User-specified registration (devices.yaml) 2. Service-level manifest (AIP registration) 3. Client-side telemetry (DeviceInfoProvider) - - Note: Runtime settings (heartbeat_interval, max_concurrent_tasks) are in - constellation.yaml, while device-specific settings are in devices.yaml. """ # === Identity === @@ -87,8 +86,7 @@ class AgentProfile: | `device_id` | `str` | User Config | Unique identifier for the device | `"windowsagent"`, `"linux_gpu_01"` | | `server_url` | `str` | User Config | WebSocket endpoint of device agent server | `"ws://localhost:5005/ws"` | -!!!warning "Unique Constraint" - The `device_id` must be unique across the entire constellation. Attempting to register a duplicate `device_id` will fail. +The `device_id` must be unique across the entire constellation. Attempting to register a duplicate `device_id` will fail. ### Platform & Capabilities @@ -134,8 +132,7 @@ DeviceStatus.FAILED # Connection or execution failed | `connection_attempts` | `int` | Runtime | Number of connection attempts made | `0`, `3` | | `max_retries` | `int` | User Config | Maximum reconnection attempts before giving up | `5`, `10` | -!!!info "Automatic Reconnection" - When a device disconnects, the system automatically retries connection up to `max_retries` times with exponential backoff. +When a device disconnects, the system automatically retries connection up to `max_retries` times with exponential backoff. ### Task Execution @@ -240,30 +237,13 @@ metadata = { ```mermaid graph LR - subgraph "Source 1: User Configuration" - A[devices.yaml] - A -->|device_id, server_url| D - A -->|capabilities| D - A -->|metadata.location| D - A -->|metadata.performance| D - end - - subgraph "Source 2: Service Manifest" - B[AIP Registration] - B -->|platform| D - B -->|registration_time| D - B -->|client_type| D - end - - subgraph "Source 3: Client Telemetry" - C[DeviceInfoProvider] - C -->|system_info.cpu_count| D - C -->|system_info.memory_total_gb| D - C -->|system_info.hostname| D - C -->|supported_features| D - end + A[User Config
devices.yaml] + B[AIP Registration
Service Manifest] + C[Device Telemetry
DeviceInfoProvider] - D[AgentProfile] + A -->|device_id, server_url
capabilities, metadata| D[AgentProfile] + B -->|platform, registration_time| D + C -->|system_info, features| D style A fill:#e1f5ff style B fill:#fff4e1 @@ -277,31 +257,23 @@ graph LR sequenceDiagram participant Config as devices.yaml participant Manager as DeviceManager - participant Registry as DeviceRegistry participant Server as UFO Server participant Telemetry as DeviceInfoProvider - Note over Config,Telemetry: Phase 1: User Configuration - Config->>Manager: Load config - Manager->>Registry: register_device(device_id, server_url, capabilities, metadata) - Registry->>Registry: Create AgentProfile (Source 1) - Note over Registry: AgentProfile v1
Has: device_id, server_url, capabilities, user metadata + Note over Config,Telemetry: Phase 1: Initial Registration + Config->>Manager: Load device config + Manager->>Manager: Create AgentProfile
(device_id, server_url, capabilities) Note over Config,Telemetry: Phase 2: Service Registration Manager->>Server: WebSocket REGISTER - Server->>Server: Process registration (platform, client_type) - Server-->>Manager: Registration confirmed - Registry->>Registry: Update metadata (Source 2) - Note over Registry: AgentProfile v2
Added: platform, registration_time + Server-->>Manager: Add platform, registration_time Note over Config,Telemetry: Phase 3: Telemetry Collection Manager->>Server: request_device_info() Server->>Telemetry: collect_system_info() - Telemetry-->>Server: DeviceSystemInfo + Telemetry-->>Server: system_info Server-->>Manager: system_info - Manager->>Registry: update_device_system_info(system_info) - Registry->>Registry: Merge system_info (Source 3) - Note over Registry: AgentProfile v3 (Complete)
Added: system_info, supported_features + Manager->>Manager: Update AgentProfile
(merge system_info & features) ``` ### Merging Strategy @@ -433,28 +405,24 @@ AgentProfile( ) ``` -**Visual Representation:** +### Profile Summary -``` -╔══════════════════════════════════════════════════════════════════════╗ -║ AgentProfile: gpu_workstation_01 ║ -╠══════════════════════════════════════════════════════════════════════╣ -║ Status: IDLE Last Heartbeat: 10:45:30 ║ -╠══════════════════════════════════════════════════════════════════════╣ -║ SYSTEM │ PERFORMANCE ║ -║ OS: Windows 10.0.22631 │ GPU: 2× NVIDIA RTX 4090 ║ -║ CPU: 16 cores │ Memory: 64.0 GB ║ -║ Hostname: DESKTOP-GPU01 │ Network: 192.168.1.100 ║ -║ │ ║ -║ CAPABILITIES │ METADATA ║ -║ • web_browsing │ Location: office_desktop ║ -║ • office_applications │ Performance: very_high ║ -║ • gpu_computation │ Tags: production, gpu, ml ║ -║ • model_training │ ║ -║ • gui, cli, browser, file_system │ Engineer: ml-team@example.com ║ -╠══════════════════════════════════════════════════════════════════════╣ -║ Server: ws://192.168.1.100:5005/ws │ Registered: 2025-11-06 10:30 ║ -╚══════════════════════════════════════════════════════════════════════╝ +```mermaid +graph TB + subgraph "AgentProfile: gpu_workstation_01" + A["Status: IDLE
Last Heartbeat: 10:45:30"] + + B["System
━━━━━
OS: Windows 10.0.22631
CPU: 16 cores
Memory: 64.0 GB
Host: DESKTOP-GPU01
IP: 192.168.1.100"] + + C["Capabilities
━━━━━
• web_browsing
• office_applications
• gpu_computation
• model_training
• gui, cli, browser
• file_system"] + + D["Metadata
━━━━━
Location: office_desktop
Performance: very_high
Tags: production, gpu, ml
GPU: 2× NVIDIA RTX 4090"] + end + + style A fill:#e3f2fd + style B fill:#f3e5f5 + style C fill:#e8f5e9 + style D fill:#fff3e0 ``` ### Example 2: Linux Server @@ -620,6 +588,8 @@ print(f"Attempts: {profile.connection_attempts}") # 0 ## 🎯 Usage Patterns +The following patterns demonstrate how AgentProfile is used for intelligent task routing and device management. For more details on task constellation concepts, see [Constellation Overview](../constellation/overview.md). + ### Task Assignment Decision ```python @@ -769,51 +739,55 @@ print(f"Errors: {health['errors']}") | **Galaxy Devices Config** | [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) | YAML configuration reference | | **Device Info** | [Device Info Provider](../../client/device_info.md) | Telemetry collection details | | **AIP Protocol** | [AIP Overview](../../aip/overview.md) | Agent Interaction Protocol | +| **Constellation System** | [Constellation Overview](../constellation/overview.md) | Multi-device coordination | +| **WebSocket Client** | [Client AIP Integration](../client/aip_integration.md) | Client-side implementation | --- ## 💡 Best Practices -!!!tip "AgentProfile Best Practices" - - **1. Meaningful Capabilities** - ```python - # ✅ Good: Specific, actionable capabilities - capabilities = ["web_browsing", "office_excel", "file_management", "email_sending"] - - # ❌ Bad: Vague capabilities - capabilities = ["desktop", "general"] - ``` - - **2. Rich Metadata** - ```python - # ✅ Good: Comprehensive metadata for smart routing - metadata = { - "location": "datacenter_us_west", - "performance": "high", - "description": "GPU workstation for ML training", - "tags": ["production", "gpu", "ml"], - "operation_engineer_email": "ml-team@example.com" - } - ``` - - **3. Monitor Heartbeats** - ```python - # Regularly check heartbeat freshness - if profile.last_heartbeat: - age = datetime.now(timezone.utc) - profile.last_heartbeat - if age > timedelta(minutes=5): - logger.warning(f"Device {profile.device_id} heartbeat stale") - ``` - - **4. Use System Info for Resource-Aware Routing** - ```python - # Check if device has enough resources - system_info = profile.metadata.get("system_info", {}) - if system_info.get("memory_total_gb", 0) >= 16: - # Assign memory-intensive task - pass - ``` +### 1. Meaningful Capabilities + +```python +# ✅ Good: Specific, actionable capabilities +capabilities = ["web_browsing", "office_excel", "file_management", "email_sending"] + +# ❌ Bad: Vague capabilities +capabilities = ["desktop", "general"] +``` + +### 2. Rich Metadata + +```python +# ✅ Good: Comprehensive metadata for smart routing +metadata = { + "location": "datacenter_us_west", + "performance": "high", + "description": "GPU workstation for ML training", + "tags": ["production", "gpu", "ml"], + "operation_engineer_email": "ml-team@example.com" +} +``` + +### 3. Monitor Heartbeats + +```python +# Regularly check heartbeat freshness +if profile.last_heartbeat: + age = datetime.now(timezone.utc) - profile.last_heartbeat + if age > timedelta(minutes=5): + logger.warning(f"Device {profile.device_id} heartbeat stale") +``` + +### 4. Use System Info for Resource-Aware Routing + +```python +# Check if device has enough resources +system_info = profile.metadata.get("system_info", {}) +if system_info.get("memory_total_gb", 0) >= 16: + # Assign memory-intensive task + pass +``` --- diff --git a/documents/docs/galaxy/agent_registration/device_registry.md b/documents/docs/galaxy/agent_registration/device_registry.md index 8695b4910..04d7ab300 100644 --- a/documents/docs/galaxy/agent_registration/device_registry.md +++ b/documents/docs/galaxy/agent_registration/device_registry.md @@ -1,13 +1,10 @@ # 🗄️ DeviceRegistry - Device Data Management -!!!quote "Single Responsibility: Device Information Storage" - The **DeviceRegistry** is a focused component that manages device registration and information storage, providing a clean separation of concerns in the constellation architecture. - ---- - ## 📋 Overview -The **DeviceRegistry** is responsible for **device data management only**. It stores, retrieves, and updates AgentProfile instances without handling networking, task execution, or protocol logic. +The **DeviceRegistry** is a focused component that manages device registration and information storage, providing a clean separation of concerns in the constellation architecture. It is responsible for **device data management only** - storing, retrieving, and updating AgentProfile instances without handling networking, task execution, or protocol logic. + +> For details on how devices connect and register using the AIP protocol, see [Registration Flow](./registration_flow.md). **Core Responsibilities:** @@ -19,14 +16,12 @@ The **DeviceRegistry** is responsible for **device data management only**. It st | **Information Retrieval** | Provide device information to other components | | **Task State Tracking** | Track which device is executing which task | -**What DeviceRegistry Does NOT Do:** +**Delegation to Other Components:** -- ❌ Network communication (handled by WebSocketConnectionManager) -- ❌ Message processing (handled by MessageProcessor) -- ❌ Task execution (handled by TaskQueueManager) -- ❌ Heartbeat monitoring (handled by HeartbeatManager) - ---- +- Network communication → [`WebSocketConnectionManager`](../client/components.md#websocketconnectionmanager-network-communication-handler) +- Message processing → [`MessageProcessor`](../client/components.md#messageprocessor-message-router-and-handler) +- Task execution → [`TaskQueueManager`](../client/components.md#taskqueuemanager-task-scheduling-and-queuing) +- Heartbeat monitoring → [`HeartbeatManager`](../client/components.md#heartbeatmanager-connection-health-monitor) ## 🏗️ Architecture @@ -199,18 +194,7 @@ print(f"Registered: {profile.device_id}") print(f"Status: {profile.status.value}") # "disconnected" ``` -**Characteristics:** - -- ✅ Creates defensive copy of capabilities list -- ✅ Creates defensive copy of metadata dict -- ✅ Sets initial status to `DISCONNECTED` -- ✅ Logs registration action -- ⚠️ Overwrites if device_id already exists (no duplicate check) - -!!!warning "Duplicate Registration" - If a device_id already exists, it will be overwritten. Consider adding validation in production use. - ---- +> **Note:** The `register_device()` method will overwrite an existing device if the same `device_id` is used. Consider adding validation if duplicate prevention is needed. ### 2. Device Retrieval @@ -549,6 +533,8 @@ def update_device_system_info( """ ``` +> **Note:** System information is collected from the device agent and retrieved via the server. See [Client Connection Manager](../../server/client_connection_manager.md) for server-side information management. + **Process:** ```mermaid @@ -611,6 +597,10 @@ device_info.metadata.update({ if "custom_metadata" in system_info: device_info.metadata["custom_metadata"] = system_info["custom_metadata"] +# 5. Add tags if present +if "tags" in system_info: + device_info.metadata["tags"] = system_info["tags"] + return True ``` @@ -691,8 +681,7 @@ def set_device_capabilities( device_info.metadata.update(capabilities["metadata"]) ``` -!!!info "Legacy Method" - This method primarily exists for backwards compatibility. Modern code should use `update_device_system_info()` instead. +> **Note:** This method is primarily for backwards compatibility. Modern code should use `update_device_system_info()` instead. #### Method: `get_device_capabilities()` @@ -869,6 +858,8 @@ def check_all_devices_health(registry: DeviceRegistry): ## 🔗 Integration with Other Components +DeviceRegistry is used internally by other components in the constellation system. See [Components Overview](../client/components.md) for details on the component architecture. + ### With ConstellationDeviceManager ```python @@ -922,48 +913,50 @@ class TaskQueueManager: | **AgentProfile** | [AgentProfile](./agent_profile.md) | Profile structure details | | **Registration Flow** | [Registration Flow](./registration_flow.md) | Step-by-step registration | | **Galaxy Devices Config** | [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) | YAML config reference | -| **Device Info** | [Device Info Provider](../../client/device_info.md) | Telemetry collection | +| **Components** | [Client Components](../client/components.md) | Component architecture | --- ## 💡 Best Practices -!!!tip "DeviceRegistry Best Practices" - - **1. Always Check Device Exists** - ```python +**1. Always Check Device Exists** + +```python +profile = registry.get_device(device_id) +if not profile: + logger.error(f"Device {device_id} not found") + return +``` + +**2. Use Defensive Copies for Lists/Dicts** + +```python +# Registry already creates copies, but be aware +capabilities = ["web", "office"] +registry.register_device(..., capabilities=capabilities) +# Modifying original list won't affect registry +capabilities.append("new") # Safe +``` + +**3. Monitor Heartbeats Regularly** + +```python +# Periodic check +for device_id in registry.get_all_devices(): profile = registry.get_device(device_id) - if not profile: - logger.error(f"Device {device_id} not found") - return - ``` - - **2. Use Defensive Copies for Lists/Dicts** - ```python - # Registry already creates copies, but be aware - capabilities = ["web", "office"] - registry.register_device(..., capabilities=capabilities) - # Modifying original list won't affect registry - capabilities.append("new") # Safe - ``` - - **3. Monitor Heartbeats Regularly** - ```python - # Periodic check - for device_id in registry.get_all_devices(): - profile = registry.get_device(device_id) - if profile.last_heartbeat: - age = datetime.now(timezone.utc) - profile.last_heartbeat - if age > timedelta(minutes=5): - logger.warning(f"Stale heartbeat: {device_id}") - ``` - - **4. Clear Task State After Completion** - ```python - # Always set to IDLE after task completes - registry.set_device_idle(device_id) - # This automatically clears current_task_id - ``` + if profile.last_heartbeat: + age = datetime.now(timezone.utc) - profile.last_heartbeat + if age > timedelta(minutes=5): + logger.warning(f"Stale heartbeat: {device_id}") +``` + +**4. Clear Task State After Completion** + +```python +# Always set to IDLE after task completes +registry.set_device_idle(device_id) +# This automatically clears current_task_id +``` --- diff --git a/documents/docs/galaxy/agent_registration/overview.md b/documents/docs/galaxy/agent_registration/overview.md index 5ada95b0c..fa4d1fda0 100644 --- a/documents/docs/galaxy/agent_registration/overview.md +++ b/documents/docs/galaxy/agent_registration/overview.md @@ -1,7 +1,6 @@ # 🌟 Agent Registration & Profiling - Overview -!!!quote "The Foundation of Constellation" - **Agent Registration** is the cornerstone of the AIP (Agent Interaction Protocol) initialization process. It enables dynamic discovery, capability advertisement, and intelligent task allocation across distributed constellation agents. +**Agent Registration** is the cornerstone of the AIP (Agent Interaction Protocol) initialization process. It enables dynamic discovery, capability advertisement, and intelligent task allocation across distributed constellation agents. --- @@ -12,8 +11,13 @@ At the core of AIP's initialization process is the **ConstellationClient** (implemented as `ConstellationDeviceManager`), which maintains a global registry of active agents. Any device agent service that exposes a WebSocket endpoint and implements the AIP task dispatch and result-return protocol can be seamlessly integrated into UFO, providing remarkable **extensibility**. -!!!success "Key Innovation" - The multi-source profiling pipeline enables **transparent capability discovery** and **safe adaptation** to environmental drift without direct administrator intervention. +The multi-source profiling pipeline enables **transparent capability discovery** and **safe adaptation** to environmental drift without direct administrator intervention. + +For a complete understanding of the constellation system, see: + +- [Constellation Overview](../constellation/overview.md) - Multi-device coordination architecture +- [Constellation Agent Overview](../constellation_agent/overview.md) - Agent behavior and patterns +- [AIP Protocol Overview](../../aip/overview.md) - Message protocol details --- @@ -32,39 +36,22 @@ The agent registry is a centralized store that tracks all active constellation a ### Multi-Source Profiling -!!!info "Three-Source Architecture" - Each **AgentProfile** consolidates information from **three distinct sources**, creating a comprehensive and dynamically updated view of each agent. +Each **AgentProfile** consolidates information from **three distinct sources**, creating a comprehensive and dynamically updated view of each agent. ```mermaid graph TB - subgraph "Agent Profile Construction" - AP[AgentProfile] - - subgraph "Source 1: User Configuration" - UC[devices.yaml] - UC --> |device_id, server_url| AP - UC --> |capabilities| AP - UC --> |metadata| AP - end - - subgraph "Source 2: Service Manifest" - SM[AIP Registration Protocol] - SM --> |client_type| AP - SM --> |platform| AP - SM --> |registration_time| AP - end - - subgraph "Source 3: Client Telemetry" - CT[DeviceInfoProvider] - CT --> |os_version, cpu_count| AP - CT --> |memory_total_gb| AP - CT --> |hostname, ip_address| AP - CT --> |supported_features| AP - end + subgraph Sources + UC[User Config
devices.yaml] + SM[AIP Registration
Service Manifest] + CT[Device Telemetry
DeviceInfoProvider] end + UC -->|device_id, capabilities
metadata| AP[AgentProfile] + SM -->|platform, client_type
registration_time| AP + CT -->|system_info
supported_features| AP + AP --> CR[ConstellationDeviceManager] - CR --> |Task Assignment| TA[Intelligent Routing] + CR --> TA[Intelligent Task Routing] style UC fill:#e1f5ff style SM fill:#fff4e1 @@ -80,6 +67,8 @@ graph TB | **2. Service Manifest** | Device Agent Service (AIP) | Client type, platform, registration metadata | On registration | | **3. Client Telemetry** | Device Client (DeviceInfoProvider) | Hardware specs, OS info, network status | On connection + periodic updates | +**Note:** While constellation.yaml contains runtime settings like heartbeat intervals, the device-specific configuration is in devices.yaml. + --- ## 🔄 Registration Flow @@ -95,40 +84,30 @@ The registration process follows a well-defined sequence that ensures comprehens sequenceDiagram participant Admin as Administrator participant CDM as ConstellationDeviceManager - participant DR as DeviceRegistry - participant WS as WebSocket Connection participant Server as UFO Server participant DIP as DeviceInfoProvider Note over Admin,DIP: Phase 1: User Configuration - Admin->>CDM: register_device(device_id, server_url, capabilities, metadata) - CDM->>DR: register_device(...) - DR->>DR: Create AgentProfile (Source 1) - DR-->>CDM: AgentProfile created + Admin->>CDM: register_device(device_id, capabilities) + CDM->>CDM: Create AgentProfile Note over Admin,DIP: Phase 2: WebSocket Connection - CDM->>WS: connect_device(device_id) - WS->>Server: WebSocket handshake - Server-->>WS: Connection accepted + CDM->>Server: connect_device() + Server-->>CDM: Connection established - Note over Admin,DIP: Phase 3: Service-Level Registration - WS->>Server: REGISTER message (ClientType, platform) - Server->>Server: Validate registration - Server-->>WS: Registration confirmation - CDM->>DR: update_device_status(CONNECTED) + Note over Admin,DIP: Phase 3: Service Registration + CDM->>Server: REGISTER message + Server-->>CDM: Registration confirmed - Note over Admin,DIP: Phase 4: Client Telemetry Collection - CDM->>Server: request_device_info(device_id) + Note over Admin,DIP: Phase 4: Telemetry Collection + CDM->>Server: request_device_info() Server->>DIP: collect_system_info() - DIP->>DIP: Detect hardware, OS, features - DIP-->>Server: DeviceSystemInfo + DIP-->>Server: system_info Server-->>CDM: system_info - CDM->>DR: update_device_system_info(system_info) - DR->>DR: Merge into AgentProfile (Source 3) + CDM->>CDM: Merge into AgentProfile Note over Admin,DIP: Phase 5: Ready for Tasks - CDM->>DR: set_device_idle(device_id) - DR->>DR: Status = IDLE + CDM->>CDM: Set device to IDLE ``` **Registration Phases:** @@ -141,13 +120,14 @@ sequenceDiagram | **4. Telemetry Collection** | Retrieve runtime system information from device | DeviceInfoProvider, DeviceInfoProtocol | Hardware, OS, and feature data merged | | **5. Activation** | Set device to IDLE state, ready for task assignment | DeviceRegistry | Agent ready for constellation tasks | -!!!tip "Automatic vs Manual Connection" - Devices can be registered with `auto_connect=True` to automatically establish connection, or `auto_connect=False` to require manual connection via `connect_device()`. +Devices can be registered with `auto_connect=True` to automatically establish connection, or `auto_connect=False` to require manual connection via `connect_device()`. --- ## 📊 AgentProfile Structure +The **AgentProfile** is the primary data structure representing a registered constellation agent. For detailed information about the AgentProfile and its lifecycle operations, see [Agent Profile Documentation](./agent_profile.md). + ### Core Fields The **AgentProfile** is the primary data structure representing a registered constellation agent: @@ -222,8 +202,7 @@ metadata = { } ``` -!!!example "Example AgentProfile" - See the complete example in [Agent Profile Documentation](./agent_profile.md#example-profiles). +For a complete example, see the [Agent Profile Documentation](./agent_profile.md#example-profiles). --- @@ -232,6 +211,8 @@ metadata = { ![Agent State Machine](../../img/agent_state.png) *Lifecycle state transitions of the Constellation Agent.* +The agent lifecycle is managed through a state machine that tracks connection, registration, and task execution states. For more details on agent behavior and state management, see [Constellation Agent State Management](../constellation_agent/state.md). + ### State Definitions ```python @@ -285,8 +266,7 @@ stateDiagram-v2 | Any | DISCONNECTED | Connection lost | Cleanup, schedule reconnection | | FAILED | CONNECTING | Retry timer | Attempt reconnection (if under max_retries) | -!!!warning "Automatic Reconnection" - When a device disconnects or enters FAILED state, the system automatically schedules reconnection attempts up to `max_retries` times with `reconnect_delay` interval. +**Important:** When a device disconnects or enters FAILED state, the system automatically schedules reconnection attempts up to `max_retries` times with `reconnect_delay` interval. --- @@ -421,7 +401,7 @@ See [Device Info Provider Documentation](../../client/device_info.md) for teleme **File:** `ufo/server/services/client_connection_manager.py` -Server-side client connection tracking and management. +Server-side client connection tracking and management. For detailed information about the server-side implementation, see [Client Connection Manager Documentation](../../server/client_connection_manager.md). **Responsibilities:** @@ -448,20 +428,19 @@ class ClientConnectionManager: ## 📝 Configuration -!!!info "Configuration Files" - Agent registration uses two configuration files: - - **1. `config/galaxy/devices.yaml`** - Device definitions: - - Device endpoints and identities - - User-specified capabilities and metadata - - Connection parameters (max retries, auto-connect) - - **2. `config/galaxy/constellation.yaml`** - Runtime settings: - - Constellation identification and logging - - Heartbeat interval and reconnection delay - - Task concurrency and step limits - - See [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) and [Galaxy Constellation Configuration](../../configuration/system/galaxy_constellation.md) for details. +Agent registration uses two configuration files: + +**1. `config/galaxy/devices.yaml`** - Device definitions: +- Device endpoints and identities +- User-specified capabilities and metadata +- Connection parameters (max retries, auto-connect) + +**2. `config/galaxy/constellation.yaml`** - Runtime settings: +- Constellation identification and logging +- Heartbeat interval and reconnection delay +- Task concurrency and step limits + +See [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) and [Galaxy Constellation Configuration](../../configuration/system/galaxy_constellation.md) for details. **Example Device Configuration (devices.yaml):** @@ -541,7 +520,9 @@ print(f"Task Status: {result.status}") print(f"Result: {result.result}") ``` -See [Registration Flow Documentation](./registration_flow.md) for detailed examples. +For more details on task assignment and execution, see: +- [Registration Flow Documentation](./registration_flow.md) - Detailed examples +- [Constellation Task Distribution](../constellation/overview.md) - Task routing strategies --- @@ -558,6 +539,8 @@ See [Registration Flow Documentation](./registration_flow.md) for detailed examp | **Registration Flow** | [Registration Flow](./registration_flow.md) | Step-by-step registration process | | **Galaxy Devices Config** | [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) | YAML configuration reference | | **Device Registry** | [Device Registry](./device_registry.md) | Registry component details | +| **Constellation System** | [Constellation Overview](../constellation/overview.md) | Multi-device coordination | +| **Client Connection Manager** | [Server Connection Manager](../../server/client_connection_manager.md) | Server-side connection tracking | ### Architecture Diagrams @@ -569,31 +552,31 @@ See [Registration Flow Documentation](./registration_flow.md) for detailed examp ## 💡 Key Benefits -!!!success "Multi-Source Profiling Advantages" - - **1. Improved Task Allocation Accuracy** - - - Administrators specify high-level capabilities - - Service manifests advertise supported tools - - Telemetry provides real-time hardware status - - **2. Transparent Capability Discovery** - - - No manual system info entry required - - Automatic feature detection based on platform - - Dynamic updates without configuration changes - - **3. Safe Adaptation to Environmental Drift** - - - System changes (upgrades, hardware additions) automatically reflected - - No administrator intervention needed for routine updates - - Consistent metadata across distributed agents - - **4. Reliable Scheduling Decisions** - - - Fresh and accurate information for task routing - - Hardware-aware task assignment (CPU/memory requirements) - - Platform-specific capability matching +The multi-source profiling approach provides several advantages: + +**1. Improved Task Allocation Accuracy** + +- Administrators specify high-level capabilities +- Service manifests advertise supported tools +- Telemetry provides real-time hardware status + +**2. Transparent Capability Discovery** + +- No manual system info entry required +- Automatic feature detection based on platform +- Dynamic updates without configuration changes + +**3. Safe Adaptation to Environmental Drift** + +- System changes (upgrades, hardware additions) automatically reflected +- No administrator intervention needed for routine updates +- Consistent metadata across distributed agents + +**4. Reliable Scheduling Decisions** + +- Fresh and accurate information for task routing +- Hardware-aware task assignment (CPU/memory requirements) +- Platform-specific capability matching --- @@ -614,5 +597,4 @@ See [Registration Flow Documentation](./registration_flow.md) for detailed examp - **Device Info**: `ufo/client/device_info_provider.py` - **Configuration**: `config/galaxy/devices.yaml` -!!!tip "Best Practice" - Always configure devices with meaningful metadata and capabilities to enable intelligent task routing. The system will automatically enhance this information with telemetry data. +**Best Practice:** Always configure devices with meaningful metadata and capabilities to enable intelligent task routing. The system will automatically enhance this information with telemetry data. diff --git a/documents/docs/galaxy/agent_registration/registration_flow.md b/documents/docs/galaxy/agent_registration/registration_flow.md index b26048946..0352e8d0e 100644 --- a/documents/docs/galaxy/agent_registration/registration_flow.md +++ b/documents/docs/galaxy/agent_registration/registration_flow.md @@ -1,25 +1,20 @@ # 🔄 Registration Flow - Complete Process Guide -!!!quote "From Configuration to Task-Ready" - The **Registration Flow** transforms a device configuration entry into a fully profiled, connected, and task-ready constellation agent through a coordinated multi-phase process. - ---- - ## 📋 Overview -The registration flow is a **5-phase process** that: +The registration flow transforms a device configuration entry into a fully profiled, connected, and task-ready constellation agent through a coordinated **5-phase process**: 1. **Loads user configuration** from YAML -2. **Establishes WebSocket connection** to device agent server +2. **Establishes WebSocket connection** to device agent server 3. **Performs AIP registration protocol** exchange 4. **Collects client telemetry** data 5. **Activates the agent** as task-ready +See [Agent Registration Overview](./overview.md) for architecture context and [DeviceRegistry](./device_registry.md) for data management details. + ![Agent Registration Flow](../../img/agent_registry.png) *Multi-source AgentProfile construction and registration flow.* ---- - ## 🎯 Registration Phases ### Phase Overview @@ -56,8 +51,6 @@ graph TB | **4. Telemetry Collection** | 1-3s | No (graceful degradation) | No | System info merged | | **5. Agent Activation** | < 1s | No | No | Status = IDLE | ---- - ## 📝 Phase 1: User Configuration ### Purpose @@ -161,16 +154,15 @@ AgentProfile( ) ``` -!!!success "Phase 1 Complete" - Device registered in local registry with user-specified configuration. Status: `DISCONNECTED` - ---- +> **Phase 1 Complete:** Device registered in local registry with user-specified configuration. Status: `DISCONNECTED` ## 🌐 Phase 2: WebSocket Connection ### Purpose -Establish a persistent WebSocket connection to the device agent's UFO server. +Establish a persistent WebSocket connection to the device agent's UFO server. This connection is managed by the `WebSocketConnectionManager` component. + +See [Client Components](../client/components.md) for component architecture details. ### Process @@ -289,8 +281,7 @@ graph TB | `reconnect_delay` | 5.0 seconds | Delay between attempts | | `retry_counter` | Per-device | Tracked in AgentProfile.connection_attempts | -!!!warning "Connection Timeout" - If a device fails to connect after `max_retries` attempts, it enters `FAILED` status and requires manual intervention (e.g., restarting the device agent server). +> **Warning:** If a device fails to connect after `max_retries` attempts, it enters `FAILED` status and requires manual intervention (e.g., restarting the device agent server). ### Output @@ -299,10 +290,7 @@ graph TB - **Heartbeat monitoring** started - **Status**: `CONNECTED` -!!!success "Phase 2 Complete" - WebSocket connection established. Message handler and heartbeat monitoring active. - ---- +> **Phase 2 Complete:** WebSocket connection established. Message handler and heartbeat monitoring active. ## 📡 Phase 3: Service Registration (AIP) @@ -314,6 +302,8 @@ Perform AIP registration protocol exchange to: - Advertise platform information - Validate registration with server +See [AIP Protocol Documentation](../../aip/protocols.md#registration-protocol) for detailed protocol specifications. + ### Process ```mermaid @@ -448,9 +438,7 @@ ClientMessage( ) ``` -!!!info "Device vs Constellation" - - **Device Client**: Actual device agent (Windows, Linux, etc.) that executes tasks - - **Constellation Client**: Orchestrator that dispatches tasks to multiple device agents +> **Note:** Device clients register as `ClientType.DEVICE`, while constellation orchestrators register as `ClientType.CONSTELLATION` with a `target_id` pointing to the device they want to control. ### Output @@ -459,16 +447,15 @@ ClientMessage( - Platform information stored - Registration confirmation received -!!!success "Phase 3 Complete" - AIP registration protocol completed. Client type and platform recorded on server. - ---- +> **Phase 3 Complete:** AIP registration protocol completed. Client type and platform recorded on server. ## 📊 Phase 4: Telemetry Collection ### Purpose -Collect real-time system information from the device client and merge it into the AgentProfile. +Collect real-time system information from the device client and merge it into the AgentProfile. The system information is collected by the device's `DeviceInfoProvider` during registration and sent to the server as part of the registration metadata. + +See [Device Info Provider](../../client/device_info.md) for details on telemetry collection. ### Process @@ -555,7 +542,7 @@ await self.registration_protocol.register_as_device( } ``` -See [Device Info Provider](../../client/device_info.md) for collection details. +See [Device Info Provider](../../client/device_info.md) for telemetry collection details. ### Merging Logic @@ -598,6 +585,10 @@ def update_device_system_info( if "custom_metadata" in system_info: device_info.metadata["custom_metadata"] = system_info["custom_metadata"] + # 5. Add tags if present + if "tags" in system_info: + device_info.metadata["tags"] = system_info["tags"] + return True ``` @@ -647,10 +638,7 @@ AgentProfile( ) ``` -!!!success "Phase 4 Complete" - System information collected and merged into AgentProfile. Capabilities expanded with auto-detected features. - ---- +> **Phase 4 Complete:** System information collected and merged into AgentProfile. Capabilities expanded with auto-detected features. ## ✅ Phase 5: Agent Activation @@ -732,10 +720,7 @@ AgentProfile( ) ``` -!!!success "Phase 5 Complete" - Agent fully registered, profiled, and activated. Status: `IDLE` - Ready to accept task assignments. - ---- +> **Phase 5 Complete:** Agent fully registered, profiled, and activated. Status: `IDLE` - Ready to accept task assignments. ## 🎯 Complete End-to-End Example @@ -898,56 +883,52 @@ except Exception as e: | **Device Info** | [Device Info Provider](../../client/device_info.md) | Telemetry collection | | **AIP Protocol** | [AIP Overview](../../aip/overview.md) | Protocol fundamentals | ---- - ## 💡 Best Practices -!!!tip "Registration Best Practices" - - **1. Use auto_connect for Production** - ```python - await manager.register_device(..., auto_connect=True) - # Automatically completes all 5 phases - ``` - - **2. Configure Appropriate max_retries** - ```python - # Critical devices: higher retries - max_retries=10 # For production servers - - # Test devices: lower retries - max_retries=3 # For development environments - ``` - - **3. Monitor Registration Status** - ```python - profile = manager.get_device_info(device_id) - if profile.status == DeviceStatus.FAILED: - logger.error(f"Device {device_id} failed to register") - # Take corrective action - ``` - - **4. Provide Rich Metadata** - ```python - metadata={ - "location": "datacenter_us_west", - "performance": "high", - "tags": ["production", "critical"], - "operation_engineer_email": "ops@example.com" - } - ``` +**1. Use auto_connect for Production** ---- +```python +await manager.register_device(..., auto_connect=True) +# Automatically completes all 5 phases +``` + +**2. Configure Appropriate max_retries** + +```python +# Critical devices: higher retries +max_retries=10 # For production servers + +# Test devices: lower retries +max_retries=3 # For development environments +``` + +**3. Monitor Registration Status** + +```python +profile = manager.get_device_info(device_id) +if profile.status == DeviceStatus.FAILED: + logger.error(f"Device {device_id} failed to register") + # Take corrective action +``` + +**4. Provide Rich Metadata** + +```python +metadata={ + "location": "datacenter_us_west", + "performance": "high", + "tags": ["production", "critical"], + "operation_engineer_email": "ops@example.com" +} +``` ## 🚀 Next Steps 1. **Configure Devices**: Read [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) 2. **Understand DeviceRegistry**: Check [Device Registry](./device_registry.md) -3. **Learn Task Assignment**: See [Task Execution Documentation] +3. **Learn Task Assignment**: See [Task Execution Documentation](../constellation_orchestrator/overview.md) 4. **Study AIP Messages**: Read [AIP Messages](../../aip/messages.md) ---- - ## 📚 Source Code References - **ConstellationDeviceManager**: `galaxy/client/device_manager.py` diff --git a/documents/docs/galaxy/constellation/constellation_editor.md b/documents/docs/galaxy/constellation/constellation_editor.md index d0bb68116..6fc4f5b00 100644 --- a/documents/docs/galaxy/constellation/constellation_editor.md +++ b/documents/docs/galaxy/constellation/constellation_editor.md @@ -1,16 +1,14 @@ # ConstellationEditor — Interactive DAG Editor +--- + ## 📋 Overview **ConstellationEditor** provides a high-level, command pattern-based interface for safe and comprehensive TaskConstellation manipulation. It offers undo/redo capabilities, batch operations, validation, and observer patterns for building, modifying, and managing complex workflow DAGs interactively. -!!!info "Command Pattern Architecture" - ConstellationEditor uses the **Command Pattern** to encapsulate all operations as reversible command objects, enabling: - - - **Undo/Redo**: Full command history with rollback capabilities - - **Transactional Safety**: Atomic operations with validation - - **Auditability**: Complete operation tracking - - **Extensibility**: Easy addition of new command types +The editor uses the **Command Pattern** to encapsulate all operations as reversible command objects, enabling undo/redo with full command history, transactional safety with atomic operations, complete operation tracking for auditability, and easy extensibility for new command types. + +**Usage in Galaxy**: The ConstellationEditor is primarily used by the [Constellation Agent](../constellation_agent/overview.md) to programmatically build task workflows, but can also be used directly for manual constellation creation and debugging. --- @@ -94,8 +92,8 @@ added_task = editor.add_task(task_dict) # Method 3: Create and add in one step task = editor.create_and_add_task( task_id="train_model", - name="Model Training", description="Train neural network on preprocessed data", + name="Model Training", target_device_id="gpu_server", priority="HIGH", timeout=3600.0, @@ -660,33 +658,38 @@ editor.save_constellation("ml_training_pipeline.json") # Step 6: Execute (via orchestrator) constellation = editor.constellation -# ... pass to orchestrator for execution ... +# Pass to ConstellationOrchestrator for distributed execution +# See: ../constellation_orchestrator/overview.md for execution details ``` +For details on executing the built constellation, see the [Constellation Orchestrator documentation](../constellation_orchestrator/overview.md). + --- ## 🎯 Best Practices -!!!tip "Editor Usage" - 1. **Enable history**: Always enable undo/redo for interactive editing - 2. **Validate frequently**: Run `validate_constellation()` after major changes - 3. **Use observers**: Add observers for logging, metrics, or UI updates - 4. **Batch operations**: Use `batch_operations()` for multiple related changes - 5. **Save incrementally**: Save constellation checkpoints during complex editing +### Editor Usage Guidelines -!!!example "Command Pattern Benefits" - The command pattern provides: - - - **Undo/Redo**: Full operation history - - **Audit trail**: Every change is recorded - - **Transaction safety**: Operations are atomic - - **Extensibility**: Easy to add new operation types +1. **Enable history**: Always enable undo/redo for interactive editing sessions +2. **Validate frequently**: Run `validate_constellation()` after major structural changes +3. **Use observers**: Add observers for logging, metrics tracking, or UI updates +4. **Batch operations**: Use `batch_operations()` for multiple related changes to improve efficiency +5. **Save incrementally**: Create constellation checkpoints during complex editing workflows + +### Command Pattern Benefits + +The command pattern architecture provides several key advantages: + +- **Undo/Redo**: Full operation history with rollback capabilities +- **Audit trail**: Every change is recorded and traceable +- **Transaction safety**: Operations are atomic and validated +- **Extensibility**: New operation types can be added easily !!!warning "Common Pitfalls" - - **Forgetting to validate**: Always validate before execution - - **Clearing history prematurely**: Can't undo after `clear_history()` - - **Modifying running constellations**: Editor operations fail if constellation is executing - - **Ignoring observer errors**: Observers should handle their own exceptions + - **Forgetting to validate**: Always validate before passing to orchestrator for execution + - **Clearing history prematurely**: Cannot undo operations after calling `clear_history()` + - **Modifying running constellations**: Editor operations will fail if constellation is currently executing + - **Ignoring observer errors**: Observers should handle their own exceptions to avoid breaking the editor --- @@ -728,10 +731,16 @@ result = editor.execute_command_by_name( ## 🔗 Related Components -- **[TaskStar](task_star.md)** — Tasks that are edited -- **[TaskStarLine](task_star_line.md)** — Dependencies that are managed -- **[TaskConstellation](task_constellation.md)** — Constellation being edited -- **[Overview](overview.md)** — Framework overview +- **[TaskStar](task_star.md)** — Individual tasks that can be edited and managed +- **[TaskStarLine](task_star_line.md)** — Dependencies between tasks that define execution order +- **[TaskConstellation](task_constellation.md)** — The constellation DAG being edited +- **[Overview](overview.md)** — Task Constellation framework overview + +### Related Documentation + +- **[Constellation Orchestrator](../constellation_orchestrator/overview.md)** — Learn how edited constellations are scheduled and executed +- **[Constellation Agent](../constellation_agent/overview.md)** — Understand how agents use the editor to build constellations +- **[Command Pattern](https://en.wikipedia.org/wiki/Command_pattern)** — More about the command design pattern --- @@ -751,83 +760,83 @@ ConstellationEditor( | Method | Description | |--------|-------------| -| `add_task(task)` | Add task (TaskStar or dict) | -| `create_and_add_task(task_id, description, name, **kwargs)` | Create and add task | -| `update_task(task_id, **updates)` | Update task properties | -| `remove_task(task_id)` | Remove task | -| `get_task(task_id)` | Get task by ID | -| `list_tasks()` | Get all tasks | +| `add_task(task)` | Add task (TaskStar or dict), returns TaskStar | +| `create_and_add_task(task_id, description, name, **kwargs)` | Create and add new task, returns TaskStar | +| `update_task(task_id, **updates)` | Update task properties, returns updated TaskStar | +| `remove_task(task_id)` | Remove task and related dependencies, returns removed task ID (str) | +| `get_task(task_id)` | Get task by ID, returns Optional[TaskStar] | +| `list_tasks()` | Get all tasks, returns List[TaskStar] | ### Dependency Operations | Method | Description | |--------|-------------| -| `add_dependency(dependency)` | Add dependency (TaskStarLine or dict) | -| `create_and_add_dependency(from_id, to_id, type, **kwargs)` | Create and add dependency | -| `update_dependency(dependency_id, **updates)` | Update dependency | -| `remove_dependency(dependency_id)` | Remove dependency | -| `get_dependency(dependency_id)` | Get dependency by ID | -| `list_dependencies()` | Get all dependencies | -| `get_task_dependencies(task_id)` | Get dependencies for task | +| `add_dependency(dependency)` | Add dependency (TaskStarLine or dict), returns TaskStarLine | +| `create_and_add_dependency(from_id, to_id, type, **kwargs)` | Create and add dependency, returns TaskStarLine | +| `update_dependency(dependency_id, **updates)` | Update dependency properties, returns updated TaskStarLine | +| `remove_dependency(dependency_id)` | Remove dependency, returns removed dependency ID (str) | +| `get_dependency(dependency_id)` | Get dependency by ID, returns Optional[TaskStarLine] | +| `list_dependencies()` | Get all dependencies, returns List[TaskStarLine] | +| `get_task_dependencies(task_id)` | Get dependencies for specific task, returns List[TaskStarLine] | ### Bulk Operations | Method | Description | |--------|-------------| -| `build_constellation(config, clear_existing)` | Build from schema | -| `build_from_tasks_and_dependencies(tasks, deps, ...)` | Build from lists | -| `clear_constellation()` | Remove all tasks and dependencies | -| `batch_operations(operations)` | Execute multiple operations | +| `build_constellation(config, clear_existing)` | Build constellation from TaskConstellationSchema | +| `build_from_tasks_and_dependencies(tasks, deps, ...)` | Build constellation from task and dependency lists (returns TaskConstellation) | +| `clear_constellation()` | Remove all tasks and dependencies from constellation | +| `batch_operations(operations)` | Execute multiple operations in sequence, returning list of results | ### File Operations | Method | Description | |--------|-------------| -| `save_constellation(file_path)` | Save to JSON file | -| `load_constellation(file_path)` | Load from JSON file | -| `load_from_dict(data)` | Load from dictionary | -| `load_from_json_string(json_string)` | Load from JSON string | +| `save_constellation(file_path)` | Save constellation to JSON file, returns file path | +| `load_constellation(file_path)` | Load constellation from JSON file, returns TaskConstellation | +| `load_from_dict(data)` | Load constellation from dictionary, returns TaskConstellation | +| `load_from_json_string(json_string)` | Load constellation from JSON string, returns TaskConstellation | ### History Operations | Method | Description | |--------|-------------| -| `undo()` | Undo last command | -| `redo()` | Redo next command | -| `can_undo()` | Check if undo available | -| `can_redo()` | Check if redo available | -| `get_undo_description()` | Get undo description | -| `get_redo_description()` | Get redo description | -| `clear_history()` | Clear command history | -| `get_history()` | Get command history | +| `undo()` | Undo last command, returns True if successful, False if no undo available | +| `redo()` | Redo next command, returns True if successful, False if no redo available | +| `can_undo()` | Check if undo is available (returns bool) | +| `can_redo()` | Check if redo is available (returns bool) | +| `get_undo_description()` | Get description of operation that would be undone (returns Optional[str]) | +| `get_redo_description()` | Get description of operation that would be redone (returns Optional[str]) | +| `clear_history()` | Clear command history (no return value) | +| `get_history()` | Get list of command descriptions (returns List[str]) | ### Validation | Method | Description | |--------|-------------| -| `validate_constellation()` | Validate DAG structure | -| `has_cycles()` | Check for cycles | -| `get_topological_order()` | Get topological ordering | -| `get_ready_tasks()` | Get tasks ready to execute | -| `get_statistics()` | Get constellation + editor stats | +| `validate_constellation()` | Validate DAG structure, returns tuple of (is_valid: bool, errors: List[str]) | +| `has_cycles()` | Check for cycles in the DAG, returns bool | +| `get_topological_order()` | Get topological ordering of tasks, returns List[str] of task IDs | +| `get_ready_tasks()` | Get tasks ready to execute (no pending dependencies), returns List[TaskStar] | +| `get_statistics()` | Get comprehensive constellation and editor statistics, returns Dict[str, Any] | ### Observers | Method | Description | |--------|-------------| -| `add_observer(observer)` | Add change observer | -| `remove_observer(observer)` | Remove observer | +| `add_observer(observer)` | Add change observer callable that receives (editor, command, result) | +| `remove_observer(observer)` | Remove previously added observer | ### Advanced | Method | Description | |--------|-------------| -| `create_subgraph(task_ids)` | Extract subgraph | -| `merge_constellation(other_editor, prefix)` | Merge another constellation | -| `display_constellation(mode)` | Display visualization | +| `create_subgraph(task_ids)` | Extract subgraph with specific tasks | +| `merge_constellation(other_editor, prefix)` | Merge another constellation with optional ID prefix | +| `display_constellation(mode)` | Display visualization (modes: 'overview', 'topology', 'details', 'execution') | + +For interactive web-based visualization and editing, see the [Galaxy WebUI](../webui.md). --- -
-

ConstellationEditor — Safe, interactive, and reversible constellation manipulation

-
+**ConstellationEditor** — Safe, interactive, and reversible constellation manipulation diff --git a/documents/docs/galaxy/constellation/overview.md b/documents/docs/galaxy/constellation/overview.md index c34386c88..64481a2cb 100644 --- a/documents/docs/galaxy/constellation/overview.md +++ b/documents/docs/galaxy/constellation/overview.md @@ -9,10 +9,12 @@ ## 🌌 Introduction -The **Task Constellation** is the central abstraction in UFO³ Galaxy that captures the concurrent and asynchronous structure of distributed task execution. It provides a formal, directed acyclic graph (DAG) representation of complex workflows, enabling consistent scheduling, fault-tolerant orchestration, and runtime dynamism across heterogeneous devices. +The **Task Constellation** is the central abstraction in Galaxy that captures the concurrent and asynchronous structure of distributed task execution. It provides a formal, directed acyclic graph (DAG) representation of complex workflows, enabling consistent scheduling, fault-tolerant orchestration, and runtime dynamism across heterogeneous devices. At its core, a Task Constellation decomposes complex user requests into interdependent subtasks connected through explicit dependency edges. This formalism not only enables correct distributed execution but also supports runtime adaptation—allowing new tasks or dependencies to be introduced as the workflow evolves. +For information on how Task Constellations are orchestrated and scheduled, see the [Constellation Orchestrator](../constellation_orchestrator/overview.md) documentation. To understand how agents interact with constellations, refer to the [Constellation Agent](../constellation_agent/overview.md) guide. + --- ## 🎯 Core Components @@ -22,7 +24,7 @@ The Task Constellation framework consists of four primary components: | Component | Purpose | Key Features | |-----------|---------|--------------| | **[TaskStar](task_star.md)** | Atomic execution unit | Self-contained task with description, device assignment, execution state, dependencies | -| **[TaskStarLine](task_star_line.md)** | Dependency relationship | Directed edge with conditional logic, success-only, or unconditional execution | +| **[TaskStarLine](task_star_line.md)** | Dependency relationship | Directed edge with conditional logic, success-only, completion-only, or unconditional execution | | **[TaskConstellation](task_constellation.md)** | DAG orchestrator | Complete workflow graph with validation, scheduling, and dynamic modification | | **[ConstellationEditor](constellation_editor.md)** | Interactive editor | Command pattern-based interface with undo/redo for safe constellation manipulation | @@ -44,19 +46,19 @@ where: ### TaskStar Representation -Each TaskStar $t_i \in \mathcal{T}$ is defined as: +Each TaskStar $t_i \in \mathcal{T}$ encapsulates a complete task specification: $$ -t_i = (\text{name}_ i, \text{description}_ i, \text{device}_ i, \text{tips}_ i, \text{status}_ i, \text{dependencies}_ i) +t_i = (\text{name}_ i, \text{description}_ i, \text{target\_device\_id}_ i, \text{tips}_ i, \text{status}_ i, \text{dependencies}_ i) $$ **Components:** -- **name**: Unique identifier for the task +- **name**: Short name for the task - **description**: Natural-language specification sent to the device agent -- **device**: Identifier of the device agent responsible for execution -- **tips**: Guidance list to help the device agent complete the task -- **status**: Current execution state (pending, running, completed, failed, cancelled) -- **dependencies**: References to prerequisite tasks that must complete first +- **target_device_id**: ID of the device agent responsible for execution +- **tips**: List of guidance hints to help the device agent complete the task +- **status**: Current execution state (pending, running, completed, failed, cancelled, waiting_dependency) +- **dependencies**: Set of prerequisite task IDs that must complete first ### TaskStarLine Representation @@ -258,7 +260,7 @@ editor = ConstellationEditor(constellation) diagnostic_task = editor.create_and_add_task( task_id="diag_1", description="Check server health", - device_type="LINUX" + name="Server Health Check" ) # Add conditional dependency @@ -328,7 +330,7 @@ graph LR ## 🎨 Visualization -The Task Constellation provides multiple visualization modes: +The Task Constellation provides multiple visualization modes for monitoring and debugging: ### Overview Mode High-level constellation structure with task counts and state @@ -347,23 +349,24 @@ Real-time execution flow with progress tracking constellation.display_dag(mode="overview") # or "topology", "details", "execution" ``` +For interactive web-based visualization, check out the [Galaxy WebUI](../webui.md). + --- ## 📚 Component Documentation Explore detailed documentation for each component: -### [TaskStar](task_star.md) -Atomic execution units representing individual tasks in the constellation - -### [TaskStarLine](task_star_line.md) -Dependency relationships connecting tasks with conditional logic +- **[TaskStar](task_star.md)** — Atomic execution units representing individual tasks in the constellation +- **[TaskStarLine](task_star_line.md)** — Dependency relationships connecting tasks with conditional logic +- **[TaskConstellation](task_constellation.md)** — Complete DAG orchestrator managing workflow execution and coordination +- **[ConstellationEditor](constellation_editor.md)** — Interactive editor with command pattern and undo/redo capabilities -### [TaskConstellation](task_constellation.md) -Complete DAG orchestrator managing workflow execution and coordination +### Related Documentation -### [ConstellationEditor](constellation_editor.md) -Interactive editor with command pattern and undo/redo capabilities +- **[Constellation Orchestrator](../constellation_orchestrator/overview.md)** — Learn how constellations are scheduled and executed across devices +- **[Constellation Agent](../constellation_agent/overview.md)** — Understand how agents plan and manage constellation lifecycles +- **[Evaluation & Metrics](../evaluation/performance_metrics.md)** — Monitor constellation performance and analyze execution patterns --- @@ -371,12 +374,12 @@ Interactive editor with command pattern and undo/redo capabilities The Task Constellation model is grounded in formal DAG theory and distributed systems research. Key properties include: -- **Acyclicity guarantees** through Kahn's algorithm +- **Acyclicity guarantees** through Kahn's algorithm for topological sorting - **Topological ordering** for consistent execution - **Critical path analysis** for performance optimization - **Dynamic graph evolution** without compromising consistency -For detailed research context, see the [UFO³ Research Paper](https://arxiv.org/) *(Coming Soon)*. +For more on Galaxy's architecture and design principles, see the [Galaxy Overview](../overview.md). --- @@ -389,11 +392,12 @@ For detailed research context, see the [UFO³ Research Paper](https://arxiv.org/ 4. **Validate early**: Run `validate_dag()` before execution 5. **Monitor metrics**: Track parallelism ratio to optimize workflow design -!!!example "Common Patterns" - - **Fan-out**: One task spawns multiple independent parallel tasks - - **Fan-in**: Multiple parallel tasks converge to a single task - - **Pipeline**: Sequential stages with parallel tasks within each stage - - **Conditional branching**: Use conditional dependencies for error handling paths +**Common Patterns:** + +- **Fan-out**: One task spawns multiple independent parallel tasks +- **Fan-in**: Multiple parallel tasks converge to a single task +- **Pipeline**: Sequential stages with parallel tasks within each stage +- **Conditional branching**: Use conditional dependencies for error handling paths --- @@ -403,9 +407,3 @@ For detailed research context, see the [UFO³ Research Paper](https://arxiv.org/ - Explore **[TaskStarLine](task_star_line.md)** — Dependency relationships - Master **[TaskConstellation](task_constellation.md)** — DAG orchestration - Try **[ConstellationEditor](constellation_editor.md)** — Interactive editing - ---- - -
-

Task Constellation — The formal foundation of distributed workflow orchestration

-
diff --git a/documents/docs/galaxy/constellation/task_constellation.md b/documents/docs/galaxy/constellation/task_constellation.md index 224b69226..a7ada24b1 100644 --- a/documents/docs/galaxy/constellation/task_constellation.md +++ b/documents/docs/galaxy/constellation/task_constellation.md @@ -1,21 +1,20 @@ # TaskConstellation — DAG Orchestrator -## 📋 Overview +## Overview **TaskConstellation** is the complete DAG (Directed Acyclic Graph) orchestration system that manages distributed workflows across heterogeneous devices. It provides comprehensive task management, dependency validation, execution scheduling, and runtime dynamism for complex cross-device orchestration. -!!!quote "Formal Definition" - A TaskConstellation $\mathcal{C}$ is a DAG defined as: - - $$ - \mathcal{C} = (\mathcal{T}, \mathcal{E}) - $$ - - where $\mathcal{T}$ is the set of TaskStars and $\mathcal{E}$ is the set of TaskStarLines. +**Formal Definition:** A TaskConstellation $\mathcal{C}$ is a DAG defined as: + +$$ +\mathcal{C} = (\mathcal{T}, \mathcal{E}) +$$ + +where $\mathcal{T}$ is the set of TaskStars and $\mathcal{E}$ is the set of TaskStarLines. --- -## 🏗️ Architecture +## Architecture ### Core Components @@ -40,7 +39,7 @@ --- -## 🔄 Constellation Lifecycle +## Constellation Lifecycle ```mermaid stateDiagram-v2 @@ -69,7 +68,7 @@ stateDiagram-v2 --- -## 💻 Core Operations +## Core Operations ### Creating a Constellation @@ -169,7 +168,7 @@ dep = constellation.get_dependency(dep1.line_id) --- -## 🔍 DAG Validation +## DAG Validation ### Cycle Detection @@ -207,7 +206,7 @@ except ValueError as e: --- -## 📊 Scheduling and Execution +## Scheduling and Execution ### Getting Ready Tasks @@ -263,7 +262,7 @@ if constellation.is_complete(): --- -## 📈 Parallelism Analysis +## Parallelism Analysis ### DAG Metrics @@ -296,9 +295,9 @@ print(f"Calculation Mode: {metrics['calculation_mode']}") # P = 3.5 → 3.5x parallelism on average ``` -!!!info "Calculation Modes" - - **node_count**: Used when tasks are incomplete (counts each task as 1 unit) - - **actual_time**: Used when all tasks are terminal (uses real execution durations) +**Note:** Calculation modes depend on task completion status: +- **node_count**: Used when tasks are incomplete (counts each task as 1 unit) +- **actual_time**: Used when all tasks are terminal (uses real execution durations) ### Time-Based Critical Path @@ -321,7 +320,7 @@ print(f"Speedup: {speedup:.2f}x") --- -## 📊 Statistics and Monitoring +## Statistics and Monitoring ### Comprehensive Statistics @@ -348,7 +347,7 @@ if stats['execution_duration']: --- -## 🔄 Dynamic Modification +## Dynamic Modification ### Modifiable Components @@ -389,7 +388,7 @@ constellation.add_dependency(fallback_dep) constellation.update_state() ``` -!!!warning "Modification Safety" +!!! warning "Modification Safety" The constellation enforces safe modification: - **RUNNING tasks**: Cannot be modified @@ -400,7 +399,7 @@ constellation.update_state() --- -## 💾 Serialization and Persistence +## Serialization and Persistence ### JSON Export/Import @@ -446,7 +445,7 @@ constellation_from_schema = TaskConstellation.from_basemodel(schema) --- -## 🎨 Visualization +## Visualization ### Display Modes @@ -466,7 +465,7 @@ constellation.display_dag(mode="execution") --- -## 🔍 Querying Dependencies +## Querying Dependencies ### Task-Specific Dependencies @@ -483,7 +482,7 @@ all_deps = constellation.get_all_dependencies() --- -## 📊 Example Workflows +## Example Workflows ### Simple Linear Pipeline @@ -593,7 +592,7 @@ assert longest_path_length == 3 # A → B/C → D --- -## 🛡️ Error Handling +## Error Handling ### Cycle Detection @@ -639,44 +638,46 @@ except ValueError as e: --- -## 🎯 Best Practices +## Best Practices -!!!tip "Constellation Design" - 1. **Validate early**: Run `validate_dag()` before execution - 2. **Minimize dependencies**: Reduce unnecessary edges to maximize parallelism - 3. **Use appropriate dependency types**: Match dependency type to workflow logic - 4. **Monitor metrics**: Track parallelism ratio to optimize design - 5. **Handle failures**: Use conditional dependencies for error recovery +### Constellation Design Guidelines -!!!example "Optimization Patterns" - **Before (Serial):** - - ```mermaid - graph LR - A[A] --> B[B] - B --> C[C] - C --> D[D] - D --> E[E] - E --> F[F] - ``` - - Parallelism Ratio: 1.0 - - **After (Optimized):** - - ```mermaid - graph LR - A[A] --> B[B] - A --> C[C] - A --> D[D] - B --> F[F] - C --> F - D --> E[E] - ``` - - Parallelism Ratio: 1.67 +1. **Validate early**: Run `validate_dag()` before execution +2. **Minimize dependencies**: Reduce unnecessary edges to maximize parallelism +3. **Use appropriate dependency types**: Match dependency type to workflow logic +4. **Monitor metrics**: Track parallelism ratio to optimize design +5. **Handle failures**: Use conditional dependencies for error recovery + +### Optimization Patterns + +**Before (Serial):** + +```mermaid +graph LR + A[A] --> B[B] + B --> C[C] + C --> D[D] + D --> E[E] + E --> F[F] +``` + +Parallelism Ratio: 1.0 + +**After (Optimized):** + +```mermaid +graph LR + A[A] --> B[B] + A --> C[C] + A --> D[D] + B --> F[F] + C --> F + D --> E[E] +``` + +Parallelism Ratio: 1.67 -!!!warning "Common Pitfalls" +!!! warning "Common Pitfalls" - **Over-parallelization**: Too many parallel tasks can overwhelm resources - **Tight coupling**: Excessive dependencies reduce parallelism - **Missing validation**: Always validate before execution @@ -684,7 +685,7 @@ except ValueError as e: --- -## 🔬 Formal Properties +## Formal Properties ### Acyclicity Guarantee @@ -712,7 +713,7 @@ The constellation provides **safe concurrent execution**: --- -## 🔗 Related Components +## Related Components - **[TaskStar](task_star.md)** — Atomic task execution units - **[TaskStarLine](task_star_line.md)** — Dependency relationships @@ -721,7 +722,7 @@ The constellation provides **safe concurrent execution**: --- -## 📚 API Reference +## API Reference ### Constructor @@ -762,9 +763,9 @@ TaskConstellation( | Method | Description | |--------|-------------| -| `validate_dag()` | Validate DAG structure, returns (bool, errors) | -| `has_cycle()` | Check for cycles | -| `get_topological_order()` | Get topological ordering (raises if cyclic) | +| `validate_dag()` | Validate DAG structure, returns `(bool, List[str])` with validation errors | +| `has_cycle()` | Check for cycles (returns `bool`) | +| `get_topological_order()` | Get topological ordering (returns `List[str]`, raises `ValueError` if cyclic) | ### Execution @@ -772,21 +773,21 @@ TaskConstellation( |--------|-------------| | `start_execution()` | Mark constellation as started | | `start_task(task_id)` | Start specific task | -| `mark_task_completed(task_id, success, result, error)` | Mark task done, returns newly ready tasks | +| `mark_task_completed(task_id, success, result, error)` | Mark task done, returns `List[TaskStar]` of newly ready tasks | | `complete_execution()` | Mark constellation as completed | -| `is_complete()` | Check if all tasks are terminal | +| `is_complete()` | Check if all tasks are terminal (returns `bool`) | | `update_state()` | Update constellation state based on task states | ### Analysis | Method | Description | |--------|-------------| -| `get_longest_path()` | Get critical path (node count) | -| `get_critical_path_length_with_time()` | Get critical path (actual time) | -| `get_max_width()` | Get maximum parallelism | -| `get_total_work()` | Get sum of execution durations | -| `get_parallelism_metrics()` | Get comprehensive parallelism metrics | -| `get_statistics()` | Get all constellation statistics | +| `get_longest_path()` | Get critical path using node count, returns `(int, List[str])` | +| `get_critical_path_length_with_time()` | Get critical path using actual time, returns `(float, List[str])` | +| `get_max_width()` | Get maximum parallelism (returns `int`) | +| `get_total_work()` | Get sum of execution durations (returns `float`) | +| `get_parallelism_metrics()` | Get comprehensive parallelism metrics (returns `Dict[str, Any]`) | +| `get_statistics()` | Get all constellation statistics (returns `Dict[str, Any]`) | ### Serialization @@ -807,6 +808,4 @@ TaskConstellation( --- -
-

TaskConstellation — Orchestrating distributed workflows across the digital galaxy

-
+*TaskConstellation — Orchestrating distributed workflows across the digital galaxy* diff --git a/documents/docs/galaxy/constellation/task_star.md b/documents/docs/galaxy/constellation/task_star.md index 0b15f7d74..864144dce 100644 --- a/documents/docs/galaxy/constellation/task_star.md +++ b/documents/docs/galaxy/constellation/task_star.md @@ -1,19 +1,18 @@ # TaskStar — Atomic Execution Unit -## 📋 Overview +## Overview -**TaskStar** represents the atomic unit of computation in UFO³ Galaxy—the smallest indivisible task scheduled on a device agent. Each TaskStar encapsulates complete context necessary for autonomous execution, including semantic description, assigned device, execution state, and dependency relationships. +**TaskStar** represents the atomic unit of computation in UFO Galaxy—the smallest indivisible task scheduled on a device agent. Each TaskStar encapsulates complete context necessary for autonomous execution, including semantic description, assigned device, execution state, and dependency relationships. -!!!quote "Formal Definition" - A TaskStar $t_i$ is formally defined as: - - $$ - t_i = (\text{name}_i, \text{description}_i, \text{device}_i, \text{tips}_i, \text{status}_i, \text{dependencies}_i) - $$ +**Formal Definition:** A TaskStar $t_i$ is formally defined as: + +$$ +t_i = (\text{name}_i, \text{description}_i, \text{device}_i, \text{tips}_i, \text{status}_i, \text{dependencies}_i) +$$ --- -## 🏗️ Architecture +## Architecture ### Core Properties @@ -29,6 +28,10 @@ | **priority** | `TaskPriority` | Priority level for scheduling (LOW, MEDIUM, HIGH, CRITICAL) | | **timeout** | `float` | Maximum execution time in seconds | | **retry_count** | `int` | Number of allowed retries on failure | +| **task_data** | `Dict[str, Any]` | Additional data needed for task execution | +| **expected_output_type** | `str` | Expected type/format of the output | + +**Note:** The property `task_description` is available as a backward compatibility alias for `description`. ### Execution Tracking @@ -42,9 +45,18 @@ | **created_at** | `datetime` | Task creation timestamp | | **updated_at** | `datetime` | Last modification timestamp | +**Note:** All execution tracking properties are read-only and automatically managed by the TaskStar lifecycle methods. + +### Computed Properties + +| Property | Type | Description | +|----------|------|-------------| +| **is_terminal** | `bool` | True if task is in a terminal state (COMPLETED, FAILED, or CANCELLED) | +| **is_ready_to_execute** | `bool` | True if task is PENDING and has no pending dependencies | + --- -## 🔄 Task Status Lifecycle +## Task Status Lifecycle ```mermaid stateDiagram-v2 @@ -72,12 +84,11 @@ stateDiagram-v2 | **FAILED** | Task encountered an error | ✅ | | **CANCELLED** | Task was cancelled by user | ✅ | -!!!info "Terminal States" - Terminal states (COMPLETED, FAILED, CANCELLED) are final—tasks in these states cannot transition to other states without explicit retry. +**Note:** Terminal states (COMPLETED, FAILED, CANCELLED) are final—tasks in these states cannot transition to other states without explicit retry. --- -## 🎯 Priority Levels +## Priority Levels Tasks are scheduled based on priority when multiple tasks are ready to execute: @@ -90,7 +101,7 @@ Tasks are scheduled based on priority when multiple tasks are ready to execute: --- -## 💻 Usage Examples +## Usage Examples ### Creating a TaskStar @@ -150,7 +161,7 @@ print(task.priority) # Default: TaskPriority.MEDIUM --- -## 🔧 Core Operations +## Core Operations ### Execution Management @@ -196,7 +207,7 @@ else: --- -## 📊 State Queries +## State Queries ### Checking Task State @@ -230,7 +241,7 @@ elif task.status == TaskStatus.FAILED: --- -## 🔄 Serialization +## Serialization ### JSON Export/Import @@ -271,7 +282,7 @@ task_from_schema = TaskStar.from_basemodel(schema) --- -## 🎨 Advanced Features +## Advanced Features ### Request String Formatting @@ -304,12 +315,12 @@ data = task.task_data print(data["additional_flags"]) ``` -!!!warning "Modification Restrictions" +!!! warning "Modification Restrictions" Task properties cannot be modified while the task is in `RUNNING` status. This prevents race conditions and ensures execution consistency. --- -## 🔍 Dependency Management +## Dependency Management ### Internal Dependency Tracking @@ -330,12 +341,12 @@ print(f"Dependencies: {task._dependencies}") print(f"Dependents: {task._dependents}") ``` -!!!note "Managed by TaskConstellation" - Dependency management methods are primarily used internally by `TaskConstellation`. Direct manipulation is not recommended—use `ConstellationEditor` instead. +!!! note "Managed by TaskConstellation" + Dependency management methods are primarily used internally by `TaskConstellation`. Direct manipulation is not recommended—use `ConstellationEditor` for safe editing with undo/redo support. --- -## 📈 Integration with Constellation +## Integration with Constellation ### Adding to Constellation @@ -359,21 +370,24 @@ from galaxy.client.device_manager import ConstellationDeviceManager # Execute task using device manager device_manager = ConstellationDeviceManager() -result = await task.execute(device_manager) +# Execute returns an ExecutionResult object +execution_result = await task.execute(device_manager) -print(f"Status: {result.status}") -print(f"Result: {result.result}") +print(f"Status: {execution_result.status}") +print(f"Result: {execution_result.result}") +print(f"Execution Time: {execution_result.execution_time}s") ``` --- -## 🛡️ Error Handling +## Error Handling ### Validation Errors ```python task = TaskStar( task_id="", # Invalid: empty ID + name="", # Invalid: empty name description="", # Invalid: empty description timeout=-1.0 # Invalid: negative timeout ) @@ -384,6 +398,7 @@ if not task.validate(): # Output: # ❌ Task ID must be a non-empty string +# ❌ Task name must be a non-empty string # ❌ Task description must be a non-empty string # ❌ Timeout must be a positive number ``` @@ -406,7 +421,7 @@ except ValueError as e: --- -## 📊 Example Workflows +## Example Workflows ### Simple Task Execution @@ -461,43 +476,44 @@ while attempt < max_attempts: --- -## 🎯 Best Practices +## Best Practices -!!!tip "Task Design" - 1. **Keep tasks atomic**: Each task should represent a single, well-defined operation - 2. **Provide clear descriptions**: Use natural language that device agents can understand - 3. **Include helpful tips**: Guide the agent with specific instructions or common pitfalls - 4. **Set appropriate timeouts**: Prevent hanging tasks with realistic timeout values - 5. **Use retry wisely**: Enable retries for transient failures, not logic errors +### Task Design Guidelines -!!!example "Good vs. Bad Task Descriptions" - ✅ **Good**: "Build the Docker image from the Dockerfile in /app directory and tag it as 'myapp:v1.2.3'" - - ❌ **Bad**: "Build stuff" - - --- - - ✅ **Good**: "Run pytest on the test/ directory and generate a coverage report in HTML format" - - ❌ **Bad**: "Test the code" +1. **Keep tasks atomic**: Each task should represent a single, well-defined operation +2. **Provide clear descriptions**: Use natural language that device agents can understand +3. **Include helpful tips**: Guide the agent with specific instructions or common pitfalls +4. **Set appropriate timeouts**: Prevent hanging tasks with realistic timeout values +5. **Use retry wisely**: Enable retries for transient failures, not logic errors + +### Good vs. Bad Task Descriptions + +✅ **Good**: "Build the Docker image from the Dockerfile in /app directory and tag it as 'myapp:v1.2.3'" + +❌ **Bad**: "Build stuff" + +✅ **Good**: "Run pytest on the test/ directory and generate a coverage report in HTML format" + +❌ **Bad**: "Test the code" -!!!warning "Common Pitfalls" +!!! warning "Common Pitfalls" - **Don't modify running tasks**: Attempting to change properties during execution raises `ValueError` - **Don't forget validation**: Always validate tasks before adding to constellation - **Don't ignore timeouts**: Set realistic timeouts to prevent resource exhaustion --- -## 🔗 Related Components +## Related Components - **[TaskStarLine](task_star_line.md)** — Dependency relationships between tasks - **[TaskConstellation](task_constellation.md)** — DAG orchestration and execution - **[ConstellationEditor](constellation_editor.md)** — Safe task editing with undo/redo +- **[ConstellationDeviceManager](../client/device_manager.md)** — Device management and task assignment - **[Overview](overview.md)** — Task Constellation framework overview --- -## 📚 API Reference +## API Reference ### Constructor @@ -522,19 +538,22 @@ TaskStar( | Method | Description | |--------|-------------| -| `execute(device_manager)` | Execute task using device manager (async) | -| `validate()` | Validate task configuration | +| `execute(device_manager)` | Execute task using device manager (async, returns `ExecutionResult`) | +| `validate()` | Validate task configuration (returns `bool`) | +| `get_validation_errors()` | Get list of validation errors (returns `List[str]`) | | `start_execution()` | Mark task as started | | `complete_with_success(result)` | Mark task as completed successfully | | `complete_with_failure(error)` | Mark task as failed | | `retry()` | Reset task for retry attempt | +| `cancel()` | Cancel the task | +| `should_retry()` | Check if task should be retried (returns `bool`) | | `to_dict()` | Convert to dictionary | | `to_json(save_path)` | Export to JSON string or file | | `from_dict(data)` | Create from dictionary (classmethod) | | `from_json(json_data, file_path)` | Create from JSON (classmethod) | +| `to_basemodel()` | Convert to Pydantic BaseModel schema | +| `from_basemodel(schema)` | Create from Pydantic schema (classmethod) | --- -
-

TaskStar — The atomic building block of distributed workflows

-
+*TaskStar — The atomic building block of distributed workflows* diff --git a/documents/docs/galaxy/constellation/task_star_line.md b/documents/docs/galaxy/constellation/task_star_line.md index 09ba2b638..90c001f9f 100644 --- a/documents/docs/galaxy/constellation/task_star_line.md +++ b/documents/docs/galaxy/constellation/task_star_line.md @@ -1,21 +1,20 @@ # TaskStarLine — Dependency Relationship -## 📋 Overview +## Overview **TaskStarLine** represents a directed dependency relationship between two TaskStars, forming an edge in the task constellation DAG. Each TaskStarLine defines how tasks depend on each other, with support for conditional logic, success-only execution, and custom condition evaluation. -!!!quote "Formal Definition" - A TaskStarLine $e_{i \rightarrow j}$ specifies a dependency from task $t_i$ to task $t_j$: - - $$ - e_{i \rightarrow j} = (\text{from\_task}_i, \text{to\_task}_j, \text{type}, \text{description}) - $$ - - Task $t_j$ cannot begin until certain conditions on $t_i$ are satisfied, based on the dependency type. +**Formal Definition:** A TaskStarLine $e_{i \rightarrow j}$ specifies a dependency from task $t_i$ to task $t_j$: + +$$ +e_{i \rightarrow j} = (\text{from\_task}_i, \text{to\_task}_j, \text{type}, \text{description}) +$$ + +Task $t_j$ cannot begin until certain conditions on $t_i$ are satisfied, based on the dependency type. --- -## 🏗️ Architecture +## Architecture ### Core Properties @@ -29,6 +28,8 @@ | **condition_evaluator** | `Callable` | Function to evaluate if condition is met | | **metadata** | `Dict[str, Any]` | Additional metadata for the dependency | +**Note:** The properties `source_task_id` and `target_task_id` are available as aliases for `from_task_id` and `to_task_id` respectively (for IDependency interface compatibility). + ### State Tracking | Property | Type | Description | @@ -39,9 +40,11 @@ | **created_at** | `datetime` | Dependency creation timestamp | | **updated_at** | `datetime` | Last modification timestamp | +**Note:** All state tracking properties are read-only and automatically managed by TaskStarLine methods. + --- -## 🎯 Dependency Types +## Dependency Types TaskStarLine supports four types of dependency relationships: @@ -75,7 +78,7 @@ dep = TaskStarLine.create_unconditional( ### 2. Success-Only (`SUCCESS_ONLY`) -Task $t_j$ proceeds **only if** $t_i$ completes successfully. +Task $t_j$ proceeds **only if** $t_i$ completes successfully (result is not `None`). ```mermaid graph LR @@ -101,6 +104,8 @@ dep = TaskStarLine.create_success_only( ) ``` +**Note:** Success is determined by the prerequisite task returning a non-`None` result. + --- ### 3. Completion-Only (`COMPLETION_ONLY`) @@ -172,9 +177,11 @@ dep = TaskStarLine.create_conditional( ) ``` +**Note:** If no `condition_evaluator` is provided for a CONDITIONAL dependency, it defaults to SUCCESS_ONLY behavior (checks if result is not `None`). + --- -## 🔄 Dependency Lifecycle +## Dependency Lifecycle ```mermaid stateDiagram-v2 @@ -189,7 +196,7 @@ stateDiagram-v2 --- -## 💻 Usage Examples +## Usage Examples ### Creating Dependencies @@ -234,7 +241,7 @@ dep4 = TaskStarLine( --- -## 🔧 Core Operations +## Core Operations ### Condition Evaluation @@ -275,20 +282,25 @@ if dep.is_satisfied(): --- -## 📊 State Queries +## State Queries ### Checking Dependency State ```python -# Check if dependency is satisfied (with completed tasks list) +# Method 1: Check using completed tasks list (for IDependency interface) +# Returns True if from_task_id is in the completed_tasks list completed_tasks = ["task_a", "task_b", "task_c"] if dep.is_satisfied(completed_tasks): - print("Dependency satisfied") + print("Prerequisite task is completed") + +# Method 2: Check internal satisfaction state (without parameter) +# Returns the internal _is_satisfied flag set by evaluate_condition +if dep.is_satisfied(): + print("Dependency condition is satisfied") # Get last evaluation details print(f"Last evaluated: {dep.last_evaluation_time}") print(f"Result: {dep.last_evaluation_result}") -print(f"Currently satisfied: {dep.is_satisfied()}") # Access metadata print(f"Metadata: {dep.metadata}") @@ -296,7 +308,7 @@ print(f"Metadata: {dep.metadata}") --- -## 🔄 Modification +## Modification ### Updating Dependency Properties @@ -320,12 +332,12 @@ dep.update_metadata({ }) ``` -!!!warning "Modification During Execution" +!!! warning "Modification During Execution" Changing `dependency_type` or `condition_evaluator` resets the satisfaction status. Be cautious when modifying dependencies during active constellation execution. --- -## 🔄 Serialization +## Serialization ### JSON Export/Import @@ -381,7 +393,7 @@ dep_from_schema = TaskStarLine.from_basemodel(schema) --- -## 📈 Integration with Constellation +## Integration with Constellation ### Adding to Constellation @@ -422,7 +434,7 @@ if not is_valid: --- -## 🎯 Advanced Patterns +## Advanced Patterns ### Conditional Error Handling @@ -494,7 +506,7 @@ cpu_dep = TaskStarLine.create_conditional( --- -## 🛡️ Error Handling +## Error Handling ### Validation @@ -535,7 +547,7 @@ print(dep.last_evaluation_result) # False --- -## 📊 Example Workflows +## Example Workflows ### Build Pipeline @@ -584,32 +596,36 @@ dep3 = TaskStarLine.create_success_only("task_c", "aggregate") --- -## 🎯 Best Practices - -!!!tip "Dependency Design" - 1. **Use the right type**: Choose the dependency type that matches your workflow logic - 2. **Keep conditions simple**: Condition evaluators should be fast and deterministic - 3. **Handle evaluator errors**: Ensure evaluators don't raise uncaught exceptions - 4. **Document conditions**: Use clear `condition_description` for debugging - 5. **Avoid cycles**: TaskConstellation validates, but design carefully to avoid attempts - -!!!example "Good vs. Bad Condition Evaluators" - ✅ **Good**: Simple, fast, defensive - ```python - def check_success(result): - return result is not None and result.get("status") == "success" - ``` - - ❌ **Bad**: Complex, slow, error-prone - ```python - def check_success(result): - # Slow database query - db_status = query_database(result["task_id"]) - # Complex logic with potential errors - return eval(result["complex_expression"]) and db_status - ``` - -!!!warning "Common Pitfalls" +## Best Practices + +### Dependency Design Guidelines + +1. **Use the right type**: Choose the dependency type that matches your workflow logic +2. **Keep conditions simple**: Condition evaluators should be fast and deterministic +3. **Handle evaluator errors**: Ensure evaluators don't raise uncaught exceptions (they're caught internally but logged) +4. **Document conditions**: Use clear `condition_description` for debugging +5. **Avoid cycles**: TaskConstellation validates, but design carefully to avoid attempts + +### Good vs. Bad Condition Evaluators + +✅ **Good**: Simple, fast, defensive + +```python +def check_success(result): + return result is not None and result.get("status") == "success" +``` + +❌ **Bad**: Complex, slow, error-prone + +```python +def check_success(result): + # Slow database query + db_status = query_database(result["task_id"]) + # Complex logic with potential errors + return eval(result["complex_expression"]) and db_status +``` + +!!! warning "Common Pitfalls" - **Cyclic dependencies**: Always validate DAG before execution - **Missing tasks**: Ensure both `from_task_id` and `to_task_id` exist in constellation - **Stateful evaluators**: Avoid evaluators that depend on external state @@ -617,7 +633,7 @@ dep3 = TaskStarLine.create_success_only("task_c", "aggregate") --- -## 🔗 Related Components +## Related Components - **[TaskStar](task_star.md)** — Atomic execution units that TaskStarLines connect - **[TaskConstellation](task_constellation.md)** — DAG manager that validates and executes dependencies @@ -626,7 +642,7 @@ dep3 = TaskStarLine.create_success_only("task_c", "aggregate") --- -## 📚 API Reference +## API Reference ### Constructor @@ -646,27 +662,27 @@ TaskStarLine( | Method | Description | |--------|-------------| -| `create_unconditional(from_id, to_id, desc)` | Create unconditional dependency | -| `create_success_only(from_id, to_id, desc)` | Create success-only dependency | -| `create_conditional(from_id, to_id, desc, evaluator)` | Create conditional dependency | +| `create_unconditional(from_id, to_id, desc)` | Create unconditional dependency (classmethod) | +| `create_success_only(from_id, to_id, desc)` | Create success-only dependency (classmethod) | +| `create_conditional(from_id, to_id, desc, evaluator)` | Create conditional dependency (classmethod) | ### Key Methods | Method | Description | |--------|-------------| -| `evaluate_condition(result)` | Evaluate if condition is satisfied | +| `evaluate_condition(result)` | Evaluate if condition is satisfied (returns `bool`) | | `mark_satisfied()` | Manually mark as satisfied | | `reset_satisfaction()` | Reset satisfaction status | -| `is_satisfied(completed_tasks)` | Check if dependency is satisfied | +| `is_satisfied(completed_tasks=None)` | Check if dependency is satisfied (returns `bool`); with parameter checks if from_task is completed, without checks internal state | | `set_condition_evaluator(evaluator)` | Set new condition evaluator | | `update_metadata(metadata)` | Update metadata | | `to_dict()` | Convert to dictionary | | `to_json(save_path)` | Export to JSON | | `from_dict(data)` | Create from dictionary (classmethod) | | `from_json(json_data, file_path)` | Create from JSON (classmethod) | +| `to_basemodel()` | Convert to Pydantic BaseModel schema | +| `from_basemodel(schema)` | Create from Pydantic schema (classmethod) | --- -
-

TaskStarLine — Connecting tasks with intelligent dependency logic

-
+*TaskStarLine — Connecting tasks with intelligent dependency logic* diff --git a/documents/docs/galaxy/webui.md b/documents/docs/galaxy/webui.md index 7792ad0a6..345910cf2 100644 --- a/documents/docs/galaxy/webui.md +++ b/documents/docs/galaxy/webui.md @@ -17,7 +17,8 @@ The Galaxy WebUI transforms the command-line Galaxy experience into a rich, visu - **📊 Visualize Constellations**: Watch task constellations form and execute as interactive DAG graphs - **🎯 Monitor Execution**: Track task status, device assignments, and real-time progress - **🔄 See Agent Reasoning**: Observe agent thoughts, plans, and decision-making processes -- **🖥️ Manage Devices**: View and monitor all connected devices and their capabilities +- **🖥️ Manage Devices**: View, monitor, and **add new devices** through the UI +- **➕ Add Device Agents**: Register new device agents dynamically without restarting - **📡 Stream Events**: Follow the event log to understand system behavior in real-time --- @@ -53,6 +54,33 @@ The WebUI will automatically: ## 🏗️ Architecture +### Design Principles + +The Galaxy WebUI backend follows **software engineering best practices**: + +**Separation of Concerns:** +- **Models Layer**: Pydantic models ensure type safety and validation +- **Services Layer**: Business logic isolated from presentation +- **Handlers Layer**: WebSocket message processing logic +- **Routers Layer**: HTTP endpoint definitions + +**Dependency Injection:** +- `AppState` class provides centralized state management +- `get_app_state()` dependency injection function +- Replaces global variables with type-safe properties + +**Type Safety:** +- Pydantic models for all API requests/responses +- Enums for constants (`WebSocketMessageType`, `RequestStatus`) +- `TYPE_CHECKING` pattern for forward references +- Comprehensive type annotations throughout + +**Modularity:** +- Clear module boundaries +- Easy to test individual components +- Simple to extend with new features +- Better code organization and maintainability + ### System Architecture The Galaxy WebUI follows a modern client-server architecture with real-time event streaming: @@ -69,10 +97,26 @@ graph TB end subgraph Backend["Backend (FastAPI + WebSocket)"] - B1[WebSocket Server] - B2[Event Observer] - B3[Request Handler] - B4[Static File Serving] + subgraph Presentation["Presentation Layer"] + B1[FastAPI App
server.py] + B2[Routers
health/devices/websocket] + end + + subgraph Business["Business Logic Layer"] + B3[Services
Config/Device/Galaxy] + B4[Handlers
WebSocket Message Handler] + end + + subgraph Data["Data & Models Layer"] + B5[Models
Requests/Responses] + B6[Enums
MessageType/Status] + B7[Dependencies
AppState] + end + + subgraph Events["Event Processing"] + B8[WebSocketObserver] + B9[EventSerializer] + end end subgraph Core["Galaxy Core"] @@ -82,12 +126,23 @@ graph TB C4[Event System] end - Frontend <-->|WebSocket| Backend + Frontend <-->|WebSocket| B2 + B2 --> B4 + B4 --> B3 + B3 --> B7 + B2 --> B5 + B8 --> B9 + B8 -->|Broadcast| Frontend + C4 -->|Publish Events| B8 + B3 <-->|State Access| B7 Backend <-->|Event Bus| Core end style Frontend fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#fff - style Backend fill:#16213e,stroke:#7b2cbf,stroke-width:2px,color:#fff + style Presentation fill:#16213e,stroke:#7b2cbf,stroke-width:2px,color:#fff + style Business fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#fff + style Data fill:#0f1419,stroke:#10b981,stroke-width:2px,color:#fff + style Events fill:#16213e,stroke:#ff006e,stroke-width:2px,color:#fff style Core fill:#0a0e27,stroke:#ff006e,stroke-width:2px,color:#fff ``` @@ -95,11 +150,56 @@ graph TB #### Backend Components -| Component | File | Responsibility | -|-----------|------|----------------| -| **FastAPI Server** | `galaxy/webui/server.py` | HTTP server, WebSocket endpoint, static file serving | -| **WebSocket Observer** | `galaxy/webui/websocket_observer.py` | Subscribes to Galaxy events, broadcasts to clients | -| **Event Serializer** | Built into observer | Converts Python objects to JSON for WebSocket | +The Galaxy WebUI backend follows a **modular architecture** with clear separation of concerns: + +| Component | File/Directory | Responsibility | +|-----------|----------------|----------------| +| **FastAPI Server** | `galaxy/webui/server.py` | Application initialization, middleware, router registration, lifespan management | +| **Models** | `galaxy/webui/models/` | Pydantic models for requests/responses, enums for type safety | +| **Services** | `galaxy/webui/services/` | Business logic layer (config, device, galaxy operations) | +| **Handlers** | `galaxy/webui/handlers/` | WebSocket message processing and routing | +| **Routers** | `galaxy/webui/routers/` | FastAPI endpoint definitions organized by feature | +| **Dependencies** | `galaxy/webui/dependencies.py` | Dependency injection for state management (AppState) | +| **WebSocket Observer** | `galaxy/webui/websocket_observer.py` | Event subscription and broadcasting to WebSocket clients | +| **Event Serializer** | Built into observer | Converts Python objects to JSON-compatible format | + +**Detailed Backend Structure:** + +``` +galaxy/webui/ +├── server.py # Main FastAPI application +├── dependencies.py # AppState and dependency injection +├── websocket_observer.py # EventSerializer + WebSocketObserver +├── models/ +│ ├── __init__.py # Export all models +│ ├── enums.py # WebSocketMessageType, RequestStatus enums +│ ├── requests.py # Pydantic request models +│ └── responses.py # Pydantic response models +├── services/ +│ ├── __init__.py +│ ├── config_service.py # Configuration management +│ ├── device_service.py # Device operations and snapshots +│ └── galaxy_service.py # Galaxy client interactions +├── handlers/ +│ ├── __init__.py +│ └── websocket_handlers.py # WebSocket message handler +├── routers/ +│ ├── __init__.py +│ ├── health.py # Health check endpoint +│ ├── devices.py # Device management endpoints +│ └── websocket.py # WebSocket endpoint +└── templates/ + └── index.html # Fallback HTML page +``` + +**Architecture Benefits:** + +✅ **Maintainability**: Each module has a single, clear responsibility +✅ **Testability**: Services and handlers can be unit tested independently +✅ **Type Safety**: Pydantic models validate all inputs/outputs +✅ **Extensibility**: Easy to add new endpoints, message types, or services +✅ **Readability**: Clear module boundaries improve code comprehension +✅ **Reusability**: Services can be shared across multiple endpoints #### Frontend Components @@ -108,7 +208,9 @@ graph TB | **App** | `src/App.tsx` | Main layout, connection status, theme management | | **ChatWindow** | `src/components/chat/ChatWindow.tsx` | Message display and input interface | | **DagPreview** | `src/components/constellation/DagPreview.tsx` | Interactive constellation graph visualization | -| **DeviceGrid** | `src/components/devices/DeviceGrid.tsx` | Device status cards and monitoring | +| **DevicePanel** | `src/components/devices/DevicePanel.tsx` | Device status cards, search, and add button | +| **DeviceCard** | `src/components/devices/DeviceCard.tsx` | Individual device status display | +| **AddDeviceModal** | `src/components/devices/AddDeviceModal.tsx` | Modal dialog for adding new devices | | **RightPanel** | `src/components/layout/RightPanel.tsx` | Tabbed panel for constellation, tasks, details | | **EventLog** | `src/components/EventLog.tsx` | Real-time event stream display | | **GalaxyStore** | `src/store/galaxyStore.ts` | Zustand state management | @@ -118,6 +220,89 @@ graph TB ## 🔌 Communication Protocol +### HTTP API Endpoints + +#### Health Check + +```http +GET /health +``` + +**Response:** +```json +{ + "status": "healthy", + "connections": 3, + "events_sent": 1247 +} +``` + +#### Add Device + +```http +POST /api/devices +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "device_id": "windows-laptop-1", + "server_url": "ws://192.168.1.100:8080", + "os": "Windows", + "capabilities": ["excel", "outlook", "browser"], + "metadata": { + "region": "us-west-2", + "owner": "data-team" + }, + "auto_connect": true, + "max_retries": 5 +} +``` + +**Success Response (200):** +```json +{ + "status": "success", + "message": "Device 'windows-laptop-1' added successfully", + "device": { + "device_id": "windows-laptop-1", + "server_url": "ws://192.168.1.100:8080", + "os": "Windows", + "capabilities": ["excel", "outlook", "browser"], + "auto_connect": true, + "max_retries": 5, + "metadata": { + "region": "us-west-2", + "owner": "data-team" + } + } +} +``` + +**Error Responses:** + +- **404 Not Found**: `devices.yaml` configuration file not found + ```json + { + "detail": "devices.yaml not found" + } + ``` + +- **409 Conflict**: Device ID already exists + ```json + { + "detail": "Device ID 'windows-laptop-1' already exists" + } + ``` + +- **500 Internal Server Error**: Failed to add device + ```json + { + "detail": "Failed to add device: " + } + ``` + ### WebSocket Connection The WebUI maintains a persistent WebSocket connection to the Galaxy backend for bidirectional real-time communication. @@ -370,6 +555,7 @@ graph LR - Last heartbeat timestamp - Connection metrics - Click to view device details +- **➕ Add Device Button**: Manually add new devices through UI **Device Information:** - OS type and version @@ -378,6 +564,85 @@ graph LR - Performance tier - Custom metadata +**Adding a New Device:** + +Click the **"+"** button in the Device Panel header to open the Add Device Modal: + +
+ Add Device Modal +

Add Device Modal - Register new device agents through the UI

+
+ +1. **Basic Information:** + - **Device ID**: Unique identifier for the device (required) + - **Server URL**: WebSocket endpoint URL (must start with `ws://` or `wss://`) + - **Operating System**: Select from Windows, Linux, macOS, or enter custom OS + +2. **Capabilities:** + - Add capabilities one by one (e.g., `excel`, `outlook`, `browser`) + - Remove capabilities by clicking the ✕ icon + - At least one capability is required + +3. **Advanced Options:** + - **Auto-connect**: Automatically connect to device after registration (default: enabled) + - **Max Retries**: Maximum connection retry attempts (default: 5) + +4. **Metadata (Optional):** + - Add custom key-value pairs for additional device information + - Examples: `region: us-east-1`, `tier: premium`, `owner: team-a` + +**API Endpoint:** + +```http +POST /api/devices +Content-Type: application/json + +{ + "device_id": "my-device-1", + "server_url": "ws://192.168.1.100:8080", + "os": "Windows", + "capabilities": ["excel", "outlook", "powerpoint"], + "metadata": { + "region": "us-east-1", + "tier": "standard" + }, + "auto_connect": true, + "max_retries": 5 +} +``` + +**Response:** + +```json +{ + "status": "success", + "message": "Device 'my-device-1' added successfully", + "device": { + "device_id": "my-device-1", + "server_url": "ws://192.168.1.100:8080", + "os": "Windows", + "capabilities": ["excel", "outlook", "powerpoint"], + "auto_connect": true, + "max_retries": 5, + "metadata": { + "region": "us-east-1", + "tier": "standard" + } + } +} +``` + +**Device Registration Process:** + +When a device is added through the UI: + +1. **Validation**: Form data is validated (required fields, URL format, duplicate device_id) +2. **Configuration**: Device is saved to `config/galaxy/devices.yaml` +3. **Registration**: Device is registered with the Galaxy Device Manager +4. **Connection**: If `auto_connect` is enabled, connection is initiated automatically +5. **Event Broadcast**: Device status updates are broadcast to all WebSocket clients +6. **UI Update**: Device card appears in the Device Panel with real-time status + #### 📋 Task Details **Location:** Right panel → Tasks tab / Details tab @@ -504,20 +769,45 @@ Builds production-ready frontend to `galaxy/webui/frontend/dist/` ```mermaid flowchart TD A[Galaxy Core Event] --> B[Event Bus publish] - B --> C[WebSocket Observer
on_event] - C --> D[Event Serialization
Python → JSON] - D --> E[WebSocket Broadcast
to all clients] + B --> C[WebSocketObserver
on_event] + C --> D[EventSerializer
serialize_event] + D --> D1[Type-specific
field extraction] + D --> D2[Recursive value
serialization] + D2 --> D3[Python → JSON] + D3 --> E[WebSocket Broadcast
to all clients] E --> F[Frontend Clients
receive message] F --> G[Store Update
Zustand] G --> H[UI Re-render
React Components] style A fill:#0a0e27,stroke:#ff006e,stroke-width:2px,color:#fff style C fill:#16213e,stroke:#7b2cbf,stroke-width:2px,color:#fff + style D fill:#1a1a2e,stroke:#f59e0b,stroke-width:2px,color:#fff style E fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#fff style G fill:#0f1419,stroke:#10b981,stroke-width:2px,color:#fff style H fill:#1a1a2e,stroke:#f59e0b,stroke-width:2px,color:#fff ``` +### Event Serialization + +The `EventSerializer` class handles conversion of complex Python objects to JSON-compatible format: + +**Features:** +- **Type Handler Registry**: Pre-registered handlers for Galaxy-specific types (TaskStarLine, TaskConstellation) +- **Type Caching**: Cached imports to avoid repeated import attempts +- **Recursive Serialization**: Handles nested structures (dicts, lists, dataclasses, Pydantic models) +- **Polymorphic Event Handling**: Different serialization logic for TaskEvent, ConstellationEvent, AgentEvent, DeviceEvent +- **Fallback Strategies**: Multiple serialization attempts with graceful fallback to string representation + +**Serialization Chain:** +1. Handle primitives (str, int, float, bool, None) +2. Handle datetime objects → ISO format +3. Handle collections (dict, list, tuple) → recursive serialization +4. Check registered type handlers (TaskStarLine, TaskConstellation) +5. Try dataclass serialization (`asdict()`) +6. Try Pydantic model serialization (`model_dump()`) +7. Try generic `to_dict()` method +8. Fallback to `str()` representation + ### Event Types The WebUI subscribes to all Galaxy event types: @@ -693,6 +983,67 @@ The WebUI is designed to work on various screen sizes: - Use Chrome/Edge for best performance - Disable browser extensions +### Device Addition Issues + +**Problem:** Cannot add device through UI + +**Solutions:** + +1. **Check `devices.yaml` exists:** + ```powershell + # Verify configuration file + Test-Path config/galaxy/devices.yaml + ``` + +2. **Verify device ID uniqueness:** + - Device ID must be unique across all devices + - Check existing devices in the Device Panel + +3. **Validate server URL format:** + - Must start with `ws://` or `wss://` + - Example: `ws://192.168.1.100:8080` or `wss://device.example.com` + - Ensure device server is actually running at that URL + +4. **Check backend logs:** + ```powershell + # Look for error messages + python -m galaxy --webui --log-level DEBUG + ``` + +**Problem:** Device added but not connecting + +**Solutions:** + +1. **Verify device server is running:** + - Check that the device agent is running at the specified URL + - Test connection: `curl ws://your-device-url/` + +2. **Check firewall/network:** + - Ensure WebSocket port is open + - Verify no proxy/firewall blocking connection + +3. **Check device logs:** + - Look at the device agent logs for connection errors + - Verify device can reach the Galaxy server + +4. **Manual connection:** + - If `auto_connect` failed, devices will retry automatically + - Check `connection_attempts` in device details + - Increase `max_retries` if needed + +**Problem:** Validation errors when adding device + +**Common Validation Issues:** + +| Error | Cause | Solution | +|-------|-------|----------| +| "Device ID is required" | Empty device_id field | Provide a unique identifier | +| "Device ID already exists" | Duplicate device_id | Choose a different ID | +| "Server URL is required" | Empty server_url | Provide WebSocket URL | +| "Invalid WebSocket URL" | Wrong URL format | Use `ws://` or `wss://` prefix | +| "OS is required" | No OS selected | Select or enter OS type | +| "At least one capability required" | No capabilities added | Add at least one capability | + --- ## 🧪 Development @@ -725,10 +1076,31 @@ python -m galaxy --webui ``` galaxy/webui/ -├── server.py # FastAPI backend -├── websocket_observer.py # Event broadcaster +├── server.py # FastAPI application entry point +├── dependencies.py # AppState and dependency injection +├── websocket_observer.py # EventSerializer + WebSocketObserver ├── __init__.py -└── frontend/ +├── models/ # Data models and validation +│ ├── __init__.py # Export all models +│ ├── enums.py # WebSocketMessageType, RequestStatus +│ ├── requests.py # WebSocketMessage, DeviceAddRequest, etc. +│ └── responses.py # WelcomeMessage, DeviceSnapshot, etc. +├── services/ # Business logic layer +│ ├── __init__.py +│ ├── config_service.py # Configuration management +│ ├── device_service.py # Device operations and snapshots +│ └── galaxy_service.py # Galaxy client interaction +├── handlers/ # Request/message processing +│ ├── __init__.py +│ └── websocket_handlers.py # WebSocketMessageHandler class +├── routers/ # API endpoint definitions +│ ├── __init__.py +│ ├── health.py # GET /health +│ ├── devices.py # POST /api/devices +│ └── websocket.py # WebSocket /ws +├── templates/ # HTML templates +│ └── index.html # Fallback page when frontend not built +└── frontend/ # React frontend application ├── src/ │ ├── main.tsx # Entry point │ ├── App.tsx # Main layout @@ -742,7 +1114,7 @@ galaxy/webui/ │ ├── services/ # WebSocket client │ └── store/ # Zustand store ├── public/ # Static assets - ├── dist/ # Build output + ├── dist/ # Build output (gitignored) ├── package.json # Dependencies ├── vite.config.ts # Vite configuration ├── tailwind.config.js # Tailwind CSS @@ -760,6 +1132,8 @@ Output: `galaxy/webui/frontend/dist/` ### Code Quality +**Frontend:** + ```bash # Lint npm run lint @@ -771,10 +1145,270 @@ npm run type-check npm run format ``` +**Backend:** + +The modular architecture improves testability. Example unit tests: + +```python +# tests/webui/test_event_serializer.py +import pytest +from galaxy.webui.websocket_observer import EventSerializer +from galaxy.core.events import TaskEvent + +def test_serialize_task_event(): + """Test serialization of TaskEvent.""" + serializer = EventSerializer() + + event = TaskEvent( + event_type=EventType.TASK_STARTED, + source_id="test", + timestamp=1234567890, + task_id="task_1", + status="running", + result=None, + error=None + ) + + result = serializer.serialize_event(event) + + assert result["event_type"] == "task_started" + assert result["task_id"] == "task_1" + assert result["status"] == "running" + +def test_serialize_nested_dict(): + """Test recursive serialization of nested structures.""" + serializer = EventSerializer() + + data = { + "level1": { + "level2": { + "value": 42 + } + } + } + + result = serializer.serialize_value(data) + assert result["level1"]["level2"]["value"] == 42 +``` + +```python +# tests/webui/test_services.py +import pytest +from galaxy.webui.services.device_service import DeviceService +from galaxy.webui.dependencies import AppState + +def test_build_device_snapshot(): + """Test device snapshot building.""" + app_state = AppState() + # Setup mock galaxy_client with devices + + service = DeviceService(app_state) + snapshot = service.build_device_snapshot() + + assert "device_count" in snapshot + assert "all_devices" in snapshot +``` + +```python +# tests/webui/test_handlers.py +import pytest +from unittest.mock import AsyncMock, MagicMock +from galaxy.webui.handlers.websocket_handlers import WebSocketMessageHandler +from galaxy.webui.models.enums import WebSocketMessageType + +@pytest.mark.asyncio +async def test_handle_ping(): + """Test ping message handling.""" + websocket = AsyncMock() + app_state = MagicMock() + + handler = WebSocketMessageHandler(websocket, app_state) + + response = await handler.handle_message({ + "type": WebSocketMessageType.PING, + "timestamp": 1234567890 + }) + + assert response["type"] == "pong" +``` + --- ## 🚀 Advanced Usage +### Extending the Backend + +The modular architecture makes it easy to extend the Galaxy WebUI backend: + +#### Adding a New API Endpoint + +**1. Define Pydantic models:** + +```python +# galaxy/webui/models/requests.py +from pydantic import BaseModel, Field + +class TaskQueryRequest(BaseModel): + """Request to query task status.""" + task_id: str = Field(..., description="The task ID to query") + include_history: bool = Field(default=False) +``` + +```python +# galaxy/webui/models/responses.py +from pydantic import BaseModel + +class TaskQueryResponse(BaseModel): + """Response with task details.""" + task_id: str + status: str + result: dict | None = None +``` + +**2. Create a service method:** + +```python +# galaxy/webui/services/task_service.py +from typing import Dict, Any +from galaxy.webui.dependencies import AppState + +class TaskService: + """Service for task-related operations.""" + + def __init__(self, app_state: AppState): + self.app_state = app_state + + def get_task_details(self, task_id: str, include_history: bool) -> Dict[str, Any]: + """Get details for a specific task.""" + galaxy_session = self.app_state.galaxy_session + if not galaxy_session: + raise ValueError("No active Galaxy session") + + # Your business logic here + task = galaxy_session.get_task(task_id) + return { + "task_id": task.task_id, + "status": task.status.value, + "result": task.result if include_history else None + } +``` + +**3. Add a router endpoint:** + +```python +# galaxy/webui/routers/tasks.py +from fastapi import APIRouter, Depends +from galaxy.webui.dependencies import get_app_state +from galaxy.webui.models.requests import TaskQueryRequest +from galaxy.webui.models.responses import TaskQueryResponse +from galaxy.webui.services.task_service import TaskService + +router = APIRouter(prefix="/api/tasks", tags=["tasks"]) + +@router.post("/query", response_model=TaskQueryResponse) +async def query_task( + request: TaskQueryRequest, + app_state = Depends(get_app_state) +): + """Query task status and details.""" + service = TaskService(app_state) + result = service.get_task_details(request.task_id, request.include_history) + return TaskQueryResponse(**result) +``` + +**4. Register the router:** + +```python +# galaxy/webui/server.py +from galaxy.webui.routers import tasks_router + +app.include_router(tasks_router) +``` + +#### Adding a New WebSocket Message Type + +**1. Add enum value:** + +```python +# galaxy/webui/models/enums.py +class WebSocketMessageType(str, Enum): + """Types of messages exchanged via WebSocket.""" + # ... existing types ... + CUSTOM_ACTION = "custom_action" +``` + +**2. Add request model:** + +```python +# galaxy/webui/models/requests.py +class CustomActionMessage(BaseModel): + """Custom action message.""" + action_name: str + parameters: Dict[str, Any] = Field(default_factory=dict) +``` + +**3. Add handler method:** + +```python +# galaxy/webui/handlers/websocket_handlers.py +async def _handle_custom_action(self, data: Dict[str, Any]) -> Dict[str, Any]: + """Handle custom action messages.""" + message = CustomActionMessage(**data) + + # Your logic here + result = await self.service.perform_custom_action( + message.action_name, + message.parameters + ) + + return { + "type": "custom_action_completed", + "result": result + } +``` + +**4. Register handler:** + +```python +# galaxy/webui/handlers/websocket_handlers.py +def __init__(self, websocket: WebSocket, app_state: AppState): + # ... existing code ... + self._handlers[WebSocketMessageType.CUSTOM_ACTION] = self._handle_custom_action +``` + +#### Customizing Event Serialization + +Add custom serialization for new types: + +```python +# galaxy/webui/websocket_observer.py + +class EventSerializer: + def _register_handlers(self) -> None: + """Register type-specific serialization handlers.""" + # ... existing handlers ... + + # Add custom type handler + try: + from your_module import CustomType + self._cached_types["CustomType"] = CustomType + self._type_handlers[CustomType] = self._serialize_custom_type + except ImportError: + self._cached_types["CustomType"] = None + + def _serialize_custom_type(self, value: Any) -> Dict[str, Any]: + """Serialize a CustomType object.""" + try: + return { + "id": value.id, + "data": self.serialize_value(value.data), + "metadata": value.get_metadata() + } + except Exception as e: + self.logger.warning(f"Failed to serialize CustomType: {e}") + return str(value) +``` + ### Custom Event Handlers You can extend the WebUI with custom event handlers: @@ -791,6 +1425,123 @@ export function handleCustomEvent(event: GalaxyEvent) { } ``` +### Programmatic Device Management + +Add devices programmatically using the API: + +```typescript +// Add a device via API +async function addDevice(deviceConfig: { + device_id: string; + server_url: string; + os: string; + capabilities: string[]; + metadata?: Record; + auto_connect?: boolean; + max_retries?: number; +}) { + const response = await fetch('http://localhost:8000/api/devices', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(deviceConfig), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Failed to add device'); + } + + return await response.json(); +} + +// Usage example +try { + const result = await addDevice({ + device_id: 'production-server-1', + server_url: 'wss://prod-device.company.com', + os: 'Linux', + capabilities: ['docker', 'kubernetes', 'python'], + metadata: { + region: 'us-east-1', + environment: 'production', + tier: 'premium', + }, + auto_connect: true, + max_retries: 10, + }); + + console.log('Device added:', result.device); +} catch (error) { + console.error('Failed to add device:', error); +} +``` + +**Batch Device Addition:** + +```python +# Python script to add multiple devices +import requests +import json + +devices = [ + { + "device_id": "win-desktop-1", + "server_url": "ws://192.168.1.10:8080", + "os": "Windows", + "capabilities": ["office", "excel", "outlook"], + }, + { + "device_id": "linux-server-1", + "server_url": "ws://192.168.1.20:8080", + "os": "Linux", + "capabilities": ["python", "docker", "git"], + }, + { + "device_id": "mac-laptop-1", + "server_url": "ws://192.168.1.30:8080", + "os": "macOS", + "capabilities": ["safari", "xcode", "python"], + } +] + +for device in devices: + response = requests.post( + "http://localhost:8000/api/devices", + json=device, + headers={"Content-Type": "application/json"} + ) + + if response.status_code == 200: + result = response.json() + print(f"✅ Added: {result['device']['device_id']}") + else: + error = response.json() + print(f"❌ Failed: {device['device_id']} - {error.get('detail')}") +``` + +**Checking Device Status:** + +After adding devices, monitor their connection status through WebSocket events: + +```typescript +// Listen for device connection events +websocket.onmessage = (event) => { + const data = JSON.parse(event.data); + + if (data.event_type === 'device_status_changed') { + console.log(`Device ${data.device_id} status: ${data.device_status}`); + + if (data.device_status === 'connected') { + console.log('✅ Device connected successfully'); + } else if (data.device_status === 'failed') { + console.log('❌ Device connection failed'); + } + } +}; +``` + ### Custom Components Add custom visualization components: diff --git a/documents/docs/getting_started/faq.md b/documents/docs/getting_started/faq.md deleted file mode 100644 index 5e7b75d09..000000000 --- a/documents/docs/getting_started/faq.md +++ /dev/null @@ -1,24 +0,0 @@ -# Faq - -!!!warning "Documentation In Progress" - This page is a placeholder. Content will be added soon. - -## Overview - -TODO: Add overview content for Faq. - -## Key Concepts - -TODO: Add key concepts. - -## Usage - -TODO: Add usage instructions. - -## Examples - -TODO: Add examples. - -## Related Documentation - -TODO: Add links to related pages. diff --git a/documents/docs/getting_started/migration_ufo2_to_galaxy.md b/documents/docs/getting_started/migration_ufo2_to_galaxy.md index 3e6700d7c..d218e9141 100644 --- a/documents/docs/getting_started/migration_ufo2_to_galaxy.md +++ b/documents/docs/getting_started/migration_ufo2_to_galaxy.md @@ -74,16 +74,10 @@ graph LR ### Architecture Evolution - - - - - - - - - - - -
UFO v1UFO²UFO³ Galaxy
+#### UFO v1 Architecture **Multi-Agent (GUI-Only)** + ``` User Request ↓ @@ -95,15 +89,16 @@ Windows Apps (GUI) ``` **Capabilities:** + - Multi-app workflows - Pure screenshot + click/type - No API integration - Single device - +#### UFO² Architecture **Two-Tier Hierarchy (Hybrid)** + ``` User Request ↓ @@ -115,16 +110,17 @@ Windows Apps (GUI + API) ``` **Capabilities:** + - Multi-app workflows - Desktop orchestration - Hybrid GUI–API execution - Deep OS integration - Single device - +#### UFO³ Galaxy Architecture **Constellation Model (Distributed)** + ``` User Request ↓ @@ -138,15 +134,12 @@ Cross-Platform Apps ``` **Capabilities:** + - Multi-device workflows - Parallel execution - Dynamic adaptation - Heterogeneous platforms -
- --- ## 🎯 When to Use Which? @@ -210,7 +203,7 @@ Understanding how UFO² concepts map to Galaxy: | **Action** | **TaskStar** | Executable unit (but on specific device) | | **Blackboard** | **Task Results** | Inter-task communication | | **Config File** | `config/ufo/` → `config/galaxy/` | Configuration location | -| **Execution Mode** | `--mode agent-server` | Device runs as server | +| **Execution Mode** | `python -m ufo.server.app --port ` | Device runs as WebSocket server | ### Architecture Translation @@ -395,17 +388,17 @@ LOG_TO_MARKDOWN: true # Generate trajectory reports #### Step 1: Start UFO² as Agent Server -**On each device** (Windows, Linux, etc.), run UFO² in server mode: +**On each device** (Windows, Linux, etc.), run UFO² server: ```bash # Windows Desktop -python -m ufo --mode agent-server --port 5005 +python -m ufo.server.app --port 5005 # Linux Workstation -python -m ufo --mode agent-server --port 5001 +python -m ufo.server.app --port 5001 # GPU Server -python -m ufo --mode agent-server --port 5002 +python -m ufo.server.app --port 5002 ``` **What this does:** @@ -469,7 +462,7 @@ asyncio.run(main()) #### Galaxy API (After): ```python -from galaxy.galaxy_client import GalaxyClient +from galaxy import GalaxyClient import asyncio async def main(): @@ -733,7 +726,7 @@ Use this checklist to track your migration progress: - [ ] **Decide migration strategy** (hybrid vs full Galaxy) - [ ] **Preserve UFO² config** (`config/ufo/` untouched) - [ ] **Create Galaxy config** (`config/galaxy/agent.yaml`, `devices.yaml`) -- [ ] **Start devices as agent-servers** (each device runs `--mode agent-server`) +- [ ] **Start devices as servers** (each device runs `python -m ufo.server.app --port `) - [ ] **Test single-device workflow** (verify connectivity) - [ ] **Test multi-device workflow** (cross-platform task) - [ ] **Review trajectory reports** (`logs/galaxy/*/output.md`) diff --git a/documents/docs/getting_started/more_guidance.md b/documents/docs/getting_started/more_guidance.md index 7b66648f2..ca5db3f34 100644 --- a/documents/docs/getting_started/more_guidance.md +++ b/documents/docs/getting_started/more_guidance.md @@ -1,13 +1,391 @@ # More Guidance -## For Users -If you are a user of UFO, and want to use it to automate your tasks on Windows, you can refer to the [Configuration Overview](../configuration/system/overview.md) and specific configuration guides ([Agents Config](../configuration/system/agents_config.md), [System Config](../configuration/system/system_config.md), [RAG Config](../configuration/system/rag_config.md)) to set up your environment and start using UFO. -For instance, except for configuring the `HOST_AGENT` and `APP_AGENT`, you can also configure the LLM parameters and RAG parameters in the configuration files to enhance the UFO agent with additional knowledge sources. +This page provides additional guidance and resources for different user types and use cases. +--- -## For Developers -If you are a developer who wants to contribute to UFO, you can take a look at the [Configuration Overview](../configuration/system/overview.md) and specific configuration guides ([Agents](../configuration/system/agents_config.md), [System](../configuration/system/system_config.md), [RAG](../configuration/system/rag_config.md)) to explore the development environment setup and the development workflow. +## 🎯 For End Users -You can also refer to the [Project Structure](../project_directory_structure.md) to understand the project structure and the role of each component in UFO, and use the rest of the documentation to understand the architecture and design of UFO. Taking a look at the [Session](../infrastructure/modules/session.md) and [Round](../infrastructure/modules/round.md) can help you understand the core logic of UFO. +If you want to use UFO³ to automate your tasks on Windows, Linux, or across multiple devices, here's your learning path: -For debugging and testing, it is recommended to check the log files in the `ufo/logs` directory to track the execution of UFO and identify any issues that may arise. \ No newline at end of file +### 1. Getting Started (5-10 minutes) + +Choose your path based on your needs: + +| Your Goal | Start Here | Time | +|-----------|-----------|------| +| **Automate Windows desktop tasks** | [UFO² Quick Start](quick_start_ufo2.md) | 5 min | +| **Manage Linux servers** | [Linux Quick Start](quick_start_linux.md) | 10 min | +| **Orchestrate multiple devices** | [Galaxy Quick Start](quick_start_galaxy.md) | 10 min | + +### 2. Configure Your Environment (10-20 minutes) + +After installation, customize UFO³ to your needs: + +**Essential Configuration:** + +- **[Agent Configuration](../configuration/system/agents_config.md)** - Set up LLM API keys (OpenAI, Azure, Gemini, Claude, etc.) +- **[System Configuration](../configuration/system/system_config.md)** - Adjust runtime settings (step limits, timeouts, logging) + +**Optional Enhancements:** + +- **[RAG Configuration](../configuration/system/rag_config.md)** - Add external knowledge sources: + - Offline help documents + - Bing search integration + - Experience learning from past tasks + - User demonstrations +- **[MCP Configuration](../configuration/system/mcp_reference.md)** - Enable tool servers for: + - Better Office automation + - Linux command execution + - Custom tool integration + +> **💡 Configuration Tip:** Start with default settings and adjust only what you need. See [Configuration Overview](../configuration/system/overview.md) for the big picture. + +### 3. Learn Core Features (20-30 minutes) + +**For UFO² Users (Windows Desktop Automation):** + +| Feature | Documentation | What It Does | +|---------|---------------|--------------| +| **Hybrid GUI-API Execution** | [Hybrid Actions](../ufo2/core_features/hybrid_actions.md) | Combines UI automation with native API calls for faster, more reliable execution | +| **Knowledge Substrate** | [Knowledge Overview](../ufo2/core_features/knowledge_substrate/overview.md) | Augments agents with external knowledge (docs, search, experience) | +| **MCP Integration** | [MCP Overview](../mcp/overview.md) | Extends capabilities with custom tools and Office APIs | + +**For Galaxy Users (Multi-Device Orchestration):** + +| Feature | Documentation | What It Does | +|---------|---------------|--------------| +| **Task Constellation** | [Constellation Overview](../galaxy/constellation_orchestrator/overview.md) | Decomposes tasks into parallel DAGs across devices | +| **Device Capabilities** | [Galaxy Devices Config](../configuration/system/galaxy_devices.md) | Routes tasks based on device capabilities and metadata | +| **Asynchronous Execution** | [Constellation Overview](../galaxy/constellation/overview.md) | Executes subtasks in parallel for faster completion | +| **Agent Interaction Protocol** | [AIP Overview](../aip/overview.md) | Enables persistent WebSocket communication between devices | + +### 4. Troubleshooting & Support + +**When Things Go Wrong:** + +1. **Check the [FAQ](../faq.md)** - Common issues and solutions +2. **Review logs** - Located in `logs//`: + ``` + logs/my-task-2025-11-11/ + ├── request.log # Request logs + ├── response.log # Response logs + ├── action_step*.png # Screenshots at each step + └── action_step*_annotated.png # Annotated screenshots + ``` +3. **Validate configuration:** + ```bash + python -m ufo.tools.validate_config ufo --show-config + ``` +4. **Enable debug logging:** + ```yaml + # config/ufo/system.yaml + LOG_LEVEL: "DEBUG" + ``` + +**Get Help:** + +- **[GitHub Discussions](https://github.com/microsoft/UFO/discussions)** - Ask questions, share tips +- **[GitHub Issues](https://github.com/microsoft/UFO/issues)** - Report bugs, request features +- **Email:** ufo-agent@microsoft.com + +--- + +## 👨‍💻 For Developers + +If you want to contribute to UFO³ or build extensions, here's your development guide: + +### 1. Understand the Architecture (30-60 minutes) + +**Start with the big picture:** + +- **[Project Structure](../project_directory_structure.md)** - Codebase organization and component roles +- **[Configuration Architecture](../configuration/system/overview.md)** - New modular config system design + +**Deep dive into core components:** + +| Component | Documentation | What to Learn | +|-----------|---------------|---------------| +| **Session** | [Session Module](../infrastructure/modules/session.md) | Task lifecycle management, state tracking | +| **Round** | [Round Module](../infrastructure/modules/round.md) | Single agent reasoning cycle | +| **HostAgent** | [HostAgent](../ufo2/host_agent/overview.md) | High-level task planning and app selection | +| **AppAgent** | [AppAgent](../ufo2/app_agent/overview.md) | Low-level action execution | +| **ConstellationAgent** | [ConstellationAgent](../galaxy/constellation_agent/overview.md) | Multi-device task orchestration | + +### 2. Set Up Development Environment (15-30 minutes) + +**Installation:** + +```bash +# Clone the repository +git clone https://github.com/microsoft/UFO.git +cd UFO + +# Create development environment +conda create -n ufo-dev python=3.10 +conda activate ufo-dev + +# Install dependencies (including dev tools) +pip install -r requirements.txt +pip install pytest pytest-cov black flake8 # Testing & linting +``` + +**Configuration:** + +```bash +# Create config files from templates +cp config/ufo/agents.yaml.template config/ufo/agents.yaml +cp config/galaxy/agent.yaml.template config/galaxy/agent.yaml + +# Edit with your development API keys +# (Consider using lower-cost models for testing) +``` + +### 3. Explore the Codebase (1-2 hours) + +**Key Directories:** + +``` +UFO/ +├── ufo/ # Core UFO² implementation +│ ├── agents/ # HostAgent, AppAgent +│ ├── automator/ # UI automation engines +│ ├── prompter/ # Prompt management +│ └── module/ # Core modules (Session, Round) +├── galaxy/ # Galaxy orchestration framework +│ ├── agents/ # ConstellationAgent +│ ├── constellation/ # DAG orchestration +│ └── core/ # Core Galaxy infrastructure +├── aip/ # Agent Interaction Protocol +│ ├── protocol/ # Message definitions +│ └── transport/ # WebSocket transport +├── ufo/client/ # Device agents (Windows, Linux) +│ ├── client.py # Generic client +│ └── mcp/ # MCP integration +├── ufo/server/ # Device agent server +│ └── app.py # FastAPI server +└── config/ # Configuration system + ├── ufo/ # UFO² configs + └── galaxy/ # Galaxy configs +``` + +**Entry Points:** + +- **UFO² Main:** `ufo/__main__.py` +- **Galaxy Main:** `galaxy/__main__.py` +- **Server:** `ufo/server/app.py` +- **Client:** `ufo/client/client.py` + +### 4. Development Workflows + +#### Adding a New Feature + +1. **Identify the component** to modify (Agent, Module, Automator, etc.) +2. **Read existing code** in that component +3. **Check related tests** in `tests/` directory +4. **Implement your feature** following existing patterns +5. **Add tests** for your feature +6. **Update documentation** if needed + +#### Extending Configuration + +See **[Extending Configuration](../configuration/system/extending.md)** for: +- Adding custom fields +- Creating new config modules +- Environment-specific overrides +- Plugin configuration patterns + +#### Creating Custom MCP Servers + +See **[Creating MCP Servers Tutorial](../tutorials/creating_mcp_servers.md)** for: +- MCP server architecture +- Tool definition and registration +- HTTP vs. local vs. stdio servers +- Integration with UFO³ + +### 5. Testing & Debugging + +**Run Tests:** + +```bash +# Run all tests +pytest + +# Run specific test file +pytest tests/config/test_config_system.py + +# Run with coverage +pytest --cov=ufo --cov-report=html +``` + +**Debug Logging:** + +```python +# Add debug logs to your code +import logging +logger = logging.getLogger(__name__) + +logger.debug("Debug message with context: %s", variable) +logger.info("Informational message") +logger.warning("Warning message") +logger.error("Error message") +``` + +**Interactive Debugging:** + +```python +# Add breakpoint in code +import pdb; pdb.set_trace() + +# Or use VS Code debugger with launch.json +``` + +### 6. Code Style & Best Practices + +**Formatting:** + +```bash +# Auto-format with black +black ufo/ galaxy/ + +# Check style with flake8 +flake8 ufo/ galaxy/ +``` + +**Best Practices:** + +- ✅ Use type hints: `def process(data: Dict[str, Any]) -> Optional[str]:` +- ✅ Write docstrings for public functions +- ✅ Follow existing code patterns +- ✅ Add comments for complex logic +- ✅ Keep functions focused and modular +- ✅ Handle errors gracefully +- ✅ Write tests for new features + +**Configuration Best Practices:** + +- ✅ Use typed config access: `config.system.max_step` +- ✅ Provide `.template` files for sensitive configs +- ✅ Document custom fields in YAML comments +- ✅ Use environment variables for secrets: `${OPENAI_API_KEY}` +- ✅ Validate configurations early: `ConfigValidator.validate()` + +### 7. Contributing Guidelines + +**Before Submitting a PR:** + +1. **Test your changes** thoroughly +2. **Update documentation** if needed +3. **Follow code style** (black + flake8) +4. **Write clear commit messages** +5. **Reference related issues** in PR description + +**PR Template:** + +```markdown +## Description +Brief description of changes + +## Type of Change +- [ ] Bug fix +- [ ] New feature +- [ ] Documentation update +- [ ] Refactoring + +## Testing +- [ ] Added tests for new functionality +- [ ] All tests pass locally +- [ ] Manual testing completed + +## Checklist +- [ ] Code follows project style +- [ ] Documentation updated +- [ ] No breaking changes (or documented) +``` + +### 8. Advanced Topics + +**For Deep Customization:** + +- **[Prompt Engineering](../ufo2/prompts/overview.md)** - Customize agent prompts +- **[State Management](../galaxy/constellation/overview.md)** - Constellation state machine internals +- **[Protocol Extensions](../aip/messages.md)** - Extend AIP message types +- **[Custom Automators](../ufo2/core_features/control_detection/overview.md)** - Implement new automation backends + +--- + +## 🎓 Learning Paths + +### Path 1: Basic User → Power User + +1. ✅ Complete quick start for your platform +2. ✅ Run 5-10 simple automation tasks +3. ✅ Configure RAG for your organization's docs +4. ✅ Enable MCP for better Office automation +5. ✅ Set up experience learning for common tasks +6. ✅ Create custom device configurations (Galaxy) + +**Time Investment:** 2-4 hours +**Outcome:** Efficient automation of daily tasks + +### Path 2: Power User → Developer + +1. ✅ Understand project structure and architecture +2. ✅ Read Session and Round module code +3. ✅ Create a custom MCP server +4. ✅ Add custom metadata to device configs +5. ✅ Contribute documentation improvements +6. ✅ Submit your first bug fix PR + +**Time Investment:** 10-20 hours +**Outcome:** Ability to extend and customize UFO³ + +### Path 3: Developer → Core Contributor + +1. ✅ Deep dive into agent implementations +2. ✅ Understand Galaxy orchestration internals +3. ✅ Study AIP protocol and transport layer +4. ✅ Implement a new agent capability +5. ✅ Add support for a new LLM provider +6. ✅ Contribute major features or refactorings + +**Time Investment:** 40+ hours +**Outcome:** Core contributor to UFO³ project + +--- + +## 📚 Additional Resources + +### Documentation Hubs + +| Topic | Link | Description | +|-------|------|-------------| +| **Getting Started** | [Getting Started Index](../index.md#getting-started) | All quick start guides | +| **Configuration** | [Configuration Overview](../configuration/system/overview.md) | Complete config system documentation | +| **Architecture** | [Galaxy Overview](../galaxy/overview.md), [UFO² Overview](../ufo2/overview.md) | System architecture and design | +| **API Reference** | [Agent APIs](../infrastructure/agents/overview.md) | Agent interfaces and APIs | +| **Tutorials** | [Creating Device Agents](../tutorials/creating_device_agent/index.md) | Step-by-step guides | + +### Community Resources + +- **[GitHub Repository](https://github.com/microsoft/UFO)** - Source code and releases +- **[GitHub Discussions](https://github.com/microsoft/UFO/discussions)** - Q&A and community +- **[GitHub Issues](https://github.com/microsoft/UFO/issues)** - Bug reports and features +- **[Project Website](https://microsoft.github.io/UFO/)** - Official website + +### Research Papers + +- **UFO v1** (Feb 2024): [A UI-Focused Agent for Windows OS Interaction](https://arxiv.org/abs/2402.07939) +- **UFO² v2** (Apr 2025): [A Windows Agent for Seamless OS Interaction](https://arxiv.org/abs/2504.14603) +- **UFO³ Galaxy** (Nov 2025): UFO³: Weaving the Digital Agent Galaxy *(Coming Soon)* + +--- + +## 🆘 Need More Help? + +- **Can't find what you're looking for?** Check the [FAQ](../faq.md) +- **Still stuck?** Ask on [GitHub Discussions](https://github.com/microsoft/UFO/discussions) +- **Found a bug?** Open an issue on [GitHub Issues](https://github.com/microsoft/UFO/issues) +- **Want to contribute?** Read the [Contributing Guidelines](https://github.com/microsoft/UFO/blob/main/CONTRIBUTING.md) + +**Happy automating!** 🚀 diff --git a/documents/docs/getting_started/quick_start_galaxy.md b/documents/docs/getting_started/quick_start_galaxy.md index e5cfd45f8..3b8693699 100644 --- a/documents/docs/getting_started/quick_start_galaxy.md +++ b/documents/docs/getting_started/quick_start_galaxy.md @@ -2,8 +2,9 @@ Welcome to **UFO³ Galaxy** – the Multi-Device AgentOS! This guide will help you orchestrate complex cross-platform workflows across multiple devices in just a few steps. -!!!abstract "What is UFO³ Galaxy?" - UFO³ Galaxy is a **multi-tier orchestration framework** that coordinates distributed agents across Windows and Linux devices. It enables complex workflows that span multiple machines, combining desktop automation, server operations, and heterogeneous device capabilities into unified task execution. +**What is UFO³ Galaxy?** + +UFO³ Galaxy is a multi-tier orchestration framework that coordinates distributed agents across Windows and Linux devices. It enables complex workflows that span multiple machines, combining desktop automation, server operations, and heterogeneous device capabilities into unified task execution. --- @@ -32,8 +33,7 @@ cd UFO pip install -r requirements.txt ``` -!!!tip "Using Qwen Models" - If you want to use Qwen as your LLM, uncomment the related libraries in `requirements.txt` before installing. +> **💡 Tip:** If you want to use Qwen as your LLM, uncomment the related libraries in `requirements.txt` before installing. --- @@ -72,6 +72,11 @@ CONSTELLATION_AGENT: API_VERSION: "2024-02-15-preview" API_MODEL: "gpt-4o" API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" +``` + +> **ℹ️ More LLM Options:** Galaxy supports various LLM providers including Qwen, Gemini, Claude, DeepSeek, and more. See the [Model Configuration Guide](../configuration/models/overview.md) for complete details. + +--- # Prompt configurations (use defaults) CONSTELLATION_CREATION_PROMPT: "galaxy/prompts/constellation/share/constellation_creation.yaml" @@ -114,8 +119,7 @@ Galaxy orchestrates **device agents** that execute tasks on individual machines. | **WindowsAgent (UFO²)** | Windows 10/11 | [UFO² as Galaxy Device](../ufo2/as_galaxy_device.md) | Desktop automation, Office apps, GUI operations | | **LinuxAgent** | Linux | [Linux as Galaxy Device](../linux/as_galaxy_device.md) | Server management, CLI operations, log analysis | -!!!tip "Choose Your Devices" - You can use **any combination** of Windows and Linux agents. Galaxy will intelligently route tasks based on device capabilities. +> **💡 Choose Your Devices:** You can use any combination of Windows and Linux agents. Galaxy will intelligently route tasks based on device capabilities. ### Quick Setup Overview @@ -125,10 +129,10 @@ For each device agent you want to use, you need to: 2. **Start the Device Agent Client** (executes commands) 3. **Start MCP Services** (provides automation tools, if needed) -!!!info "Detailed Setup Instructions" - **For Windows devices (UFO²):** See **[UFO² as Galaxy Device](../ufo2/as_galaxy_device.md)** for complete step-by-step instructions. - - **For Linux devices:** See **[Linux as Galaxy Device](../linux/as_galaxy_device.md)** for complete step-by-step instructions. +**Detailed Setup Instructions:** + +- **For Windows devices (UFO²):** See [UFO² as Galaxy Device](../ufo2/as_galaxy_device.md) for complete step-by-step instructions. +- **For Linux devices:** See [Linux as Galaxy Device](../linux/as_galaxy_device.md) for complete step-by-step instructions. ### Example: Quick Windows Device Setup @@ -146,8 +150,7 @@ python -m ufo.client.client ` --platform windows ``` -!!!warning "Important: Platform Flag Required" - Always include `--platform windows` for Windows devices and `--platform linux` for Linux devices! +> **💡 Important:** Always include `--platform windows` for Windows devices and `--platform linux` for Linux devices! ### Example: Quick Linux Device Setup @@ -174,6 +177,8 @@ python -m ufo.client.mcp.http_servers.linux_mcp_server After starting your device agents, register them in Galaxy's device pool configuration. +### Option 1: Add Devices via Configuration File + ### Edit Device Configuration ```powershell @@ -233,13 +238,60 @@ devices: max_retries: 5 ``` -!!!danger "Critical: IDs and URLs Must Match" - - `device_id` **must exactly match** the `--client-id` flag - - `server_url` **must exactly match** the server WebSocket URL - - Otherwise, Galaxy cannot control the device! +> **⚠️ Critical:** IDs and URLs must match exactly: +> +> - `device_id` must exactly match the `--client-id` flag +> - `server_url` must exactly match the server WebSocket URL +> - Otherwise, Galaxy cannot control the device! + +**Complete Configuration Guide:** For detailed information about all configuration options, capabilities, and metadata, see [Galaxy Devices Configuration](../configuration/system/galaxy_devices.md). + +### Option 2: Add Devices via WebUI (When Using --webui Mode) + +If you start Galaxy with the `--webui` flag (see Step 5), you can add new device agents directly through the web interface without editing configuration files. + +**Steps to Add Device via WebUI:** + +1. **Launch Galaxy with WebUI** (as shown in Step 5): + ```powershell + python -m galaxy --webui + ``` + +2. **Click the "+" button** in the top-right corner of the Device Agent panel (left sidebar) -!!!info "Complete Configuration Guide" - For detailed information about all configuration options, capabilities, and metadata, see **[Galaxy Devices Configuration](../configuration/system/galaxy_devices.md)**. +3. **Fill in the device information** in the Add Device Modal: + +
+ Add Device Modal +

➕ Add Device Modal - Register new device agents through the WebUI

+
+ +**Required Fields:** +- **Device ID**: Unique identifier (must match `--client-id` in device agent) +- **Server URL**: WebSocket endpoint (e.g., `ws://localhost:5000/ws`) +- **Operating System**: Select Windows, Linux, macOS, or enter custom OS +- **Capabilities**: Add at least one capability (e.g., `excel`, `outlook`, `log_analysis`) + +**Optional Fields:** +- **Auto-connect**: Enable to automatically connect after registration (default: enabled) +- **Max Retries**: Maximum connection attempts (default: 5) +- **Metadata**: Add custom key-value pairs (e.g., `region: us-east-1`) + +**Benefits of WebUI Device Management:** +- ✅ No need to manually edit YAML files +- ✅ Real-time validation of device ID uniqueness +- ✅ Automatic connection after registration +- ✅ Immediate visual feedback on device status +- ✅ Form validation prevents configuration errors + +**After Adding:** +The device will be: +1. Saved to `config/galaxy/devices.yaml` automatically +2. Registered with Galaxy's Device Manager +3. Connected automatically (if auto-connect is enabled) +4. Displayed in the Device Agent panel with real-time status + +> **💡 Tip:** You can add devices while Galaxy is running! No need to restart the server. --- @@ -249,15 +301,14 @@ With all device agents running and configured, you can now launch Galaxy! ### Pre-Launch Checklist -!!!warning "Verify All Components Running" - Before starting Galaxy, ensure: - - 1. ✅ **All Device Agent Servers** are running - 2. ✅ **All Device Agent Clients** are connected - 3. ✅ **MCP Services** are running (for Linux devices) - 4. ✅ **LLM configured** in `config/galaxy/agent.yaml` - 5. ✅ **Devices configured** in `config/galaxy/devices.yaml` - 6. ✅ **Network connectivity** between all components +Before starting Galaxy, ensure: + +1. ✅ All Device Agent Servers are running +2. ✅ All Device Agent Clients are connected +3. ✅ MCP Services are running (for Linux devices) +4. ✅ LLM configured in `config/galaxy/agent.yaml` +5. ✅ Devices configured in `config/galaxy/devices.yaml` +6. ✅ Network connectivity between all components ### 🎨 Launch Galaxy - WebUI Mode (Recommended) @@ -450,13 +501,7 @@ Galaxy automatically saves execution logs, task graphs, and device traces for de | `task_results/` | Task execution results | | `request_response.log` | Complete LLM request/response logs | -!!!tip "Analyzing Logs" - Use the logs to: - - - **Debug**: Understand task routing and device selection - - **Optimize**: Identify bottlenecks in multi-device workflows - - **Replay**: Reconstruct the execution flow - - **Analyze**: Study orchestration decisions +> **Analyzing Logs:** Use the logs to debug task routing, identify bottlenecks, replay execution flow, and analyze orchestration decisions. --- @@ -492,91 +537,88 @@ python -m galaxy --interactive --max-rounds 20 ### Issue 1: Device Not Appearing in Galaxy -!!!bug "Error: Device not found in configuration" - **Symptoms:** - ```log - ERROR - Device 'windows_device_1' not found in configuration - ``` - - **Solutions:** - - 1. **Verify `devices.yaml` configuration:** - ```powershell - notepad config\galaxy\devices.yaml - ``` - - 2. **Check device ID matches:** - - In `devices.yaml`: `device_id: "windows_device_1"` - - In client command: `--client-id windows_device_1` - - 3. **Check server URL matches:** - - In `devices.yaml`: `server_url: "ws://localhost:5000/ws"` - - In client command: `--ws-server ws://localhost:5000/ws` +**Error:** Device not found in configuration + +```log +ERROR - Device 'windows_device_1' not found in configuration +``` + +**Solutions:** + +1. Verify `devices.yaml` configuration: + ```powershell + notepad config\galaxy\devices.yaml + ``` + +2. Check device ID matches: + - In `devices.yaml`: `device_id: "windows_device_1"` + - In client command: `--client-id windows_device_1` + +3. Check server URL matches: + - In `devices.yaml`: `server_url: "ws://localhost:5000/ws"` + - In client command: `--ws-server ws://localhost:5000/ws` ### Issue 2: Device Agent Not Connecting -!!!bug "Error: Connection refused" - **Symptoms:** - ```log - ERROR - [WS] Failed to connect to ws://localhost:5000/ws - Connection refused - ``` - - **Solutions:** - - 1. **Verify server is running:** - ```powershell - curl http://localhost:5000/api/health - ``` - - 2. **Check port number is correct:** - - Server: `--port 5000` - - Client: `ws://localhost:5000/ws` - - 3. **Ensure platform flag is set:** - ```powershell - # For Windows devices - --platform windows - - # For Linux devices - --platform linux - ``` +**Error:** Connection refused + +```log +ERROR - [WS] Failed to connect to ws://localhost:5000/ws +Connection refused +``` + +**Solutions:** + +1. Verify server is running: + ```powershell + curl http://localhost:5000/api/health + ``` + +2. Check port number is correct: + - Server: `--port 5000` + - Client: `ws://localhost:5000/ws` + +3. Ensure platform flag is set: + ```powershell + # For Windows devices + --platform windows + + # For Linux devices + --platform linux + ``` ### Issue 3: Galaxy Cannot Find Constellation Agent Config -!!!bug "Error: Configuration file not found" - **Symptoms:** - ```log - ERROR - Cannot find config/galaxy/agent.yaml - ``` - - **Solution:** - ```powershell - # Copy template to create configuration file - copy config\galaxy\agent.yaml.template config\galaxy\agent.yaml - - # Edit with your LLM credentials - notepad config\galaxy\agent.yaml - ``` +**Error:** Configuration file not found + +```log +ERROR - Cannot find config/galaxy/agent.yaml +``` + +**Solution:** +```powershell +# Copy template to create configuration file +copy config\galaxy\agent.yaml.template config\galaxy\agent.yaml + +# Edit with your LLM credentials +notepad config\galaxy\agent.yaml +``` ### Issue 4: Task Not Routed to Expected Device -!!!bug "Issue: Wrong device selected for task" - **Diagnosis:** - - Check device capabilities in `devices.yaml`: - - ```yaml - capabilities: - - "desktop_automation" - - "office_applications" - - "excel" # Required for Excel tasks - - "outlook" # Required for email tasks - ``` - - **Solution:** - - Add appropriate capabilities to your device configuration. +**Issue:** Wrong device selected for task + +**Diagnosis:** Check device capabilities in `devices.yaml`: + +```yaml +capabilities: + - "desktop_automation" + - "office_applications" + - "excel" # Required for Excel tasks + - "outlook" # Required for email tasks +``` + +**Solution:** Add appropriate capabilities to your device configuration. --- @@ -584,34 +626,38 @@ python -m galaxy --interactive --max-rounds 20 ### Core Documentation -!!!info "Architecture & Concepts" - - **[Galaxy Overview](../galaxy/overview.md)** - System architecture and design principles - - **[Constellation Orchestrator](../galaxy/constellation_orchestrator/overview.md)** - Task orchestration and DAG management - - **[Agent Interaction Protocol (AIP)](../aip/overview.md)** - Communication substrate +**Architecture & Concepts:** + +- [Galaxy Overview](../galaxy/overview.md) - System architecture and design principles +- [Constellation Orchestrator](../galaxy/constellation_orchestrator/overview.md) - Task orchestration and DAG management +- [Agent Interaction Protocol (AIP)](../aip/overview.md) - Communication substrate ### Device Agent Setup -!!!info "Device Agent Guides" - - **[UFO² as Galaxy Device](../ufo2/as_galaxy_device.md)** - Complete Windows device setup - - **[Linux as Galaxy Device](../linux/as_galaxy_device.md)** - Complete Linux device setup - - **[UFO² Overview](../ufo2/overview.md)** - Windows desktop automation capabilities - - **[Linux Agent Overview](../linux/overview.md)** - Linux server automation capabilities +**Device Agent Guides:** + +- [UFO² as Galaxy Device](../ufo2/as_galaxy_device.md) - Complete Windows device setup +- [Linux as Galaxy Device](../linux/as_galaxy_device.md) - Complete Linux device setup +- [UFO² Overview](../ufo2/overview.md) - Windows desktop automation capabilities +- [Linux Agent Overview](../linux/overview.md) - Linux server automation capabilities ### Configuration -!!!info "Configuration Guides" - - **[Galaxy Devices Configuration](../configuration/system/galaxy_devices.md)** - Complete device pool configuration - - **[Galaxy Constellation Configuration](../configuration/system/galaxy_constellation.md)** - Runtime settings - - **[Agents Configuration](../configuration/system/agents_config.md)** - LLM settings for all agents - - **[Model Configuration](../configuration/models/overview.md)** - Supported LLM providers +**Configuration Guides:** + +- [Galaxy Devices Configuration](../configuration/system/galaxy_devices.md) - Complete device pool configuration +- [Galaxy Constellation Configuration](../configuration/system/galaxy_constellation.md) - Runtime settings +- [Agents Configuration](../configuration/system/agents_config.md) - LLM settings for all agents +- [Model Configuration](../configuration/models/overview.md) - Supported LLM providers ### Advanced Features -!!!info "Advanced Topics" - - **[Task Constellation](../galaxy/constellation/task_constellation.md)** - DAG-based task planning - - **[Constellation Orchestrator](../galaxy/constellation_orchestrator/overview.md)** - Multi-device orchestration - - **[Device Registry](../galaxy/agent_registration/device_registry.md)** - Device management - - **[Agent Profiles](../galaxy/agent_registration/agent_profile.md)** - Multi-source profiling +**Advanced Topics:** + +- [Task Constellation](../galaxy/constellation/task_constellation.md) - DAG-based task planning +- [Constellation Orchestrator](../galaxy/constellation_orchestrator/overview.md) - Multi-device orchestration +- [Device Registry](../galaxy/agent_registration/device_registry.md) - Device management +- [Agent Profiles](../galaxy/agent_registration/agent_profile.md) - Multi-source profiling --- diff --git a/documents/docs/getting_started/quick_start_linux.md b/documents/docs/getting_started/quick_start_linux.md index da444d170..7b48ef55f 100644 --- a/documents/docs/getting_started/quick_start_linux.md +++ b/documents/docs/getting_started/quick_start_linux.md @@ -1,21 +1,19 @@ # ⚡ Quick Start: Linux Agent -!!!quote "Get Linux Device Running in 5 Minutes" - Connect your Linux machine as a UFO³ device agent and start executing CLI tasks. This guide walks you through server/client configuration and MCP service initialization. +Get your Linux device running as a UFO³ device agent in 5 minutes. This guide walks you through server/client configuration and MCP service initialization. --- ## 📋 Prerequisites -!!!info "Requirements Checklist" - Before you begin, ensure you have: - - - **Python 3.10+** installed on both server and client machines - - **UFO repository** cloned - - **Network connectivity** between server and client machines - - **Linux machine** for task execution (client) - - **Terminal access** (bash, ssh, etc.) - - **LLM configured** in `config/ufo/agents.yaml` (same as AppAgent) +Before you begin, ensure you have: + +- **Python 3.10+** installed on both server and client machines +- **UFO repository** cloned +- **Network connectivity** between server and client machines +- **Linux machine** for task execution (client) +- **Terminal access** (bash, ssh, etc.) +- **LLM configured** in `config/ufo/agents.yaml` (same as AppAgent) | Component | Minimum Version | Verification Command | |-----------|----------------|---------------------| @@ -24,21 +22,13 @@ | Network | N/A | `ping ` | | LLM API Key | N/A | Check `config/ufo/agents.yaml` | -!!!tip "LLM Configuration Required" - The Linux Agent shares the **same LLM configuration** with the AppAgent. Before starting, ensure you have: - - 1. Configured your LLM provider (OpenAI, Azure OpenAI, Gemini, Claude, etc.) - 2. Added your API keys to `config/ufo/agents.yaml` - 3. Tested the configuration works - - 👉 See [Model Setup Guide](../configuration/models/overview.md) for detailed instructions. +> **⚠️ LLM Configuration Required:** The Linux Agent shares the same LLM configuration with the AppAgent. Before starting, ensure you have configured your LLM provider (OpenAI, Azure OpenAI, Gemini, Claude, etc.) and added your API keys to `config/ufo/agents.yaml`. See [Model Setup Guide](../configuration/models/overview.md) for detailed instructions. --- ## 📦 Step 1: Install Dependencies -!!!example "Install Required Packages" - Install all dependencies from the requirements file: +Install all dependencies from the requirements file: ```bash pip install -r requirements.txt @@ -50,38 +40,35 @@ pip install -r requirements.txt python3 -c "import ufo; print('✅ UFO² installed successfully')" ``` -!!!tip "Virtual Environment Recommended" - For production deployments, use a virtual environment to isolate dependencies: - - ```bash - python3 -m venv venv - source venv/bin/activate # Linux/macOS - pip install -r requirements.txt - ``` +> **Tip:** For production deployments, use a virtual environment to isolate dependencies: +> +> ```bash +> python3 -m venv venv +> source venv/bin/activate # Linux/macOS +> pip install -r requirements.txt +> ``` --- ## 🖥️ Step 2: Start Device Agent Server -!!!info "Server Component" - The **Device Agent Server** is the central hub that manages connections from client devices and dispatches tasks. It can run on **any machine** (Linux, Windows, or remote server). +**Server Component:** The Device Agent Server is the central hub that manages connections from client devices and dispatches tasks. It can run on any machine (Linux, Windows, or remote server). ### Server Machine Setup You can run the server on: -- ✅ **Same machine** as the client (localhost setup for testing) -- ✅ **Different machine** on the same network -- ✅ **Remote server** (requires proper network routing/SSH tunneling) +- ✅ Same machine as the client (localhost setup for testing) +- ✅ Different machine on the same network +- ✅ Remote server (requires proper network routing/SSH tunneling) ### Basic Server Startup -!!!example "Start Server with Default Settings" - On the server machine, run: - - ```bash - python -m ufo.server.app --port 5001 - ``` +On the server machine, run: + +```bash +python -m ufo.server.app --port 5001 +``` **Expected Output:** @@ -93,8 +80,7 @@ INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:5001 (Press CTRL+C to quit) ``` -!!!success "Server Running" - Once you see "Uvicorn running", the server is ready at `ws://0.0.0.0:5001/ws` +Once you see "Uvicorn running", the server is ready at `ws://0.0.0.0:5001/ws`. ### Server Configuration Options @@ -104,22 +90,22 @@ INFO: Uvicorn running on http://0.0.0.0:5001 (Press CTRL+C to quit) | `--host` | `0.0.0.0` | Bind address (0.0.0.0 = all interfaces) | `--host 127.0.0.1` | | `--log-level` | `INFO` | Logging verbosity | `--log-level DEBUG` | -!!!example "Custom Server Configuration" - - **Custom Port:** - ```bash - python -m ufo.server.app --port 8080 - ``` - - **Specific IP Binding:** - ```bash - python -m ufo.server.app --host 192.168.1.100 --port 5001 - ``` - - **Debug Mode:** - ```bash - python -m ufo.server.app --port 5001 --log-level DEBUG - ``` +**Custom Server Configuration:** + +**Custom Port:** +```bash +python -m ufo.server.app --port 8080 +``` + +**Specific IP Binding:** +```bash +python -m ufo.server.app --host 192.168.1.100 --port 5001 +``` + +**Debug Mode:** +```bash +python -m ufo.server.app --port 5001 --log-level DEBUG +``` ### Verify Server is Running @@ -137,28 +123,25 @@ curl http://localhost:5001/api/health } ``` -!!!tip "Documentation Reference" - For detailed server configuration and advanced features, see [Server Quick Start Guide](../server/quick_start.md). +> **Documentation Reference:** For detailed server configuration and advanced features, see [Server Quick Start Guide](../server/quick_start.md). --- ## 🐧 Step 3: Start Device Agent Client (Linux Machine) -!!!info "Client Component" - The **Device Agent Client** runs on the Linux machine where you want to execute tasks. It connects to the server via WebSocket and receives task commands. +**Client Component:** The Device Agent Client runs on the Linux machine where you want to execute tasks. It connects to the server via WebSocket and receives task commands. ### Basic Client Startup -!!!example "Connect to Server" - On the **Linux machine** where you want to execute tasks: - - ```bash - python -m ufo.client.client \ - --ws \ - --ws-server ws://172.23.48.1:5001/ws \ - --client-id linux_agent_1 \ - --platform linux - ``` +On the Linux machine where you want to execute tasks: + +```bash +python -m ufo.client.client \ + --ws \ + --ws-server ws://172.23.48.1:5001/ws \ + --client-id linux_agent_1 \ + --platform linux +``` ### Client Parameters Explained @@ -169,10 +152,11 @@ curl http://localhost:5001/api/health | `--client-id` | ✅ Yes | **Unique** device identifier | `linux_agent_1` | | `--platform` | ✅ Yes (Linux) | Platform type (must be `linux` for Linux Agent) | `--platform linux` | -!!!warning "Critical Requirements" - 1. **`--client-id` must be globally unique** - No two devices can share the same ID - 2. **`--platform linux` is mandatory** - Without this flag, the Linux Agent won't work correctly - 3. **Server address must be correct** - Replace `172.23.48.1:5001` with your actual server IP and port +> **⚠️ Critical Requirements:** +> +> 1. `--client-id` must be globally unique - No two devices can share the same ID +> 2. `--platform linux` is mandatory - Without this flag, the Linux Agent won't work correctly +> 3. Server address must be correct - Replace `172.23.48.1:5001` with your actual server IP and port ### Understanding the WebSocket URL @@ -210,8 +194,7 @@ INFO - [WS] ✅ Registered device client: linux_agent_1 INFO - [WS] Device linux_agent_1 platform: linux ``` -!!!success "Client Connected" - When you see "Successfully registered", the Linux client is connected and ready to receive tasks! +Client is connected and ready to receive tasks when you see "Successfully registered"! ### Verify Connection @@ -236,24 +219,21 @@ curl http://172.23.48.1:5001/api/clients } ``` -!!!tip "Documentation Reference" - For detailed client configuration, see [Client Quick Start Guide](../client/quick_start.md). +> **Documentation Reference:** For detailed client configuration, see [Client Quick Start Guide](../client/quick_start.md). --- ## 🔌 Step 4: Start MCP Service (Linux Machine) -!!!info "MCP Service Component" - The **MCP (Model Context Protocol) Service** provides the execution layer for CLI commands. It must be running on the **same Linux machine** as the client to handle command execution requests. +**MCP Service Component:** The MCP (Model Context Protocol) Service provides the execution layer for CLI commands. It must be running on the same Linux machine as the client to handle command execution requests. ### Start the MCP Server -!!!example "Launch Linux MCP Service" - On the **Linux machine** (same machine as the client): - - ```bash - python -m ufo.client.mcp.http_servers.linux_mcp_server - ``` +On the Linux machine (same machine as the client): + +```bash +python -m ufo.client.mcp.http_servers.linux_mcp_server +``` **Expected Output:** @@ -264,8 +244,7 @@ INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8010 (Press CTRL+C to quit) ``` -!!!success "MCP Service Running" - The MCP service is now ready to execute CLI commands at `http://127.0.0.1:8010` +The MCP service is now ready to execute CLI commands at `http://127.0.0.1:8010`. ### What is the MCP Service? @@ -294,25 +273,18 @@ sequenceDiagram The MCP server typically runs on `localhost:8010` by default. The client automatically connects to it when configured properly. -!!!warning "MCP Service Must Be Running" - If the MCP service is not running, the Linux Agent cannot execute commands and will fail with: - ``` - ERROR: Cannot connect to MCP server at http://127.0.0.1:8010 - ``` +> **⚠️ MCP Service Must Be Running:** If the MCP service is not running, the Linux Agent cannot execute commands and will fail with: +> ``` +> ERROR: Cannot connect to MCP server at http://127.0.0.1:8010 +> ``` -!!!tip "Documentation Reference" - For detailed MCP command specifications, see: - - - [MCP Overview](../mcp/overview.md) - - [Linux MCP Commands](../linux/commands.md) - - [BashExecutor Server](../mcp/servers/bash_executor.md) +**Documentation Reference:** For detailed MCP command specifications, see [MCP Overview](../mcp/overview.md), [Linux MCP Commands](../linux/commands.md), and [BashExecutor Server](../mcp/servers/bash_executor.md). --- ## 🎯 Step 5: Dispatch Tasks via HTTP API -!!!info "Task Dispatch" - Once the server, client, and MCP service are all running, you can dispatch tasks to the Linux agent through the server's HTTP API. +Once the server, client, and MCP service are all running, you can dispatch tasks to the Linux agent through the server's HTTP API. ### API Endpoint @@ -332,41 +304,39 @@ POST http://:/api/dispatch ### Example: Simple File Listing -!!!example "List Files in Directory" - - **Using cURL:** - ```bash - curl -X POST http://172.23.48.1:5001/api/dispatch \ - -H "Content-Type: application/json" \ - -d '{ +**Using cURL:** +```bash +curl -X POST http://172.23.48.1:5001/api/dispatch \ + -H "Content-Type: application/json" \ + -d '{ + "client_id": "linux_agent_1", + "request": "List all files in the /tmp directory", + "task_name": "list_tmp_files" + }' +``` + +**Using Python:** +```python +import requests + +response = requests.post( + "http://172.23.48.1:5001/api/dispatch", + json={ "client_id": "linux_agent_1", "request": "List all files in the /tmp directory", "task_name": "list_tmp_files" - }' - ``` - - **Using Python:** - ```python - import requests - - response = requests.post( - "http://172.23.48.1:5001/api/dispatch", - json={ - "client_id": "linux_agent_1", - "request": "List all files in the /tmp directory", - "task_name": "list_tmp_files" - } - ) - print(response.json()) - ``` - - **Using HTTPie:** - ```bash - http POST http://172.23.48.1:5001/api/dispatch \ - client_id=linux_agent_1 \ - request="List all files in the /tmp directory" \ - task_name=list_tmp_files - ``` + } +) +print(response.json()) +``` + +**Using HTTPie:** +```bash +http POST http://172.23.48.1:5001/api/dispatch \ + client_id=linux_agent_1 \ + request="List all files in the /tmp directory" \ + task_name=list_tmp_files +``` **Successful Response:** @@ -381,29 +351,27 @@ POST http://:/api/dispatch ### Example: System Information Query -!!!example "Check Disk Usage" - ```bash - curl -X POST http://172.23.48.1:5001/api/dispatch \ - -H "Content-Type: application/json" \ - -d '{ - "client_id": "linux_agent_1", - "request": "Show disk usage for all mounted filesystems", - "task_name": "check_disk_usage" - }' - ``` +```bash +curl -X POST http://172.23.48.1:5001/api/dispatch \ + -H "Content-Type: application/json" \ + -d '{ + "client_id": "linux_agent_1", + "request": "Show disk usage for all mounted filesystems", + "task_name": "check_disk_usage" + }' +``` ### Example: Log File Analysis -!!!example "Find Error Logs" - ```bash - curl -X POST http://172.23.48.1:5001/api/dispatch \ - -H "Content-Type: application/json" \ - -d '{ - "client_id": "linux_agent_1", - "request": "Find all ERROR or FATAL entries in /var/log/app.log from the last hour", - "task_name": "analyze_error_logs" - }' - ``` +```bash +curl -X POST http://172.23.48.1:5001/api/dispatch \ + -H "Content-Type: application/json" \ + -d '{ + "client_id": "linux_agent_1", + "request": "Find all ERROR or FATAL entries in /var/log/app.log from the last hour", + "task_name": "analyze_error_logs" + }' +``` ### Task Execution Flow @@ -442,25 +410,23 @@ sequenceDiagram | `request` | ✅ Yes | string | Natural language task description | `"List files in /var/log"` | | `task_name` | ❌ Optional | string | Unique task identifier (auto-generated if omitted) | `"task_001"` | -!!!warning "Client Must Be Online" - If the `client_id` is not connected, you'll receive: - ```json - { - "detail": "Client not online" - } - ``` - - Verify the client is connected: - ```bash - curl http://172.23.48.1:5001/api/clients - ``` +> **⚠️ Client Must Be Online:** If the `client_id` is not connected, you'll receive: +> ```json +> { +> "detail": "Client not online" +> } +> ``` +> +> Verify the client is connected: +> ```bash +> curl http://172.23.48.1:5001/api/clients +> ``` --- ## 🌉 Network Connectivity & SSH Tunneling -!!!info "Remote Access Scenarios" - When the server and client are on different networks or behind firewalls, you may need SSH tunneling to establish connectivity. +When the server and client are on different networks or behind firewalls, you may need SSH tunneling to establish connectivity. ### Scenario 1: Same Network (No Tunnel Needed) @@ -488,17 +454,16 @@ python -m ufo.client.client \ **Solution: SSH Reverse Tunnel** -!!!example "Establish Reverse Tunnel" - On the **client machine**, create an SSH reverse tunnel: - - ```bash - ssh -N -R 5001:localhost:5001 user@203.0.113.50 - ``` - - **Parameters:** - - `-N`: No remote command execution (tunnel only) - - `-R 5001:localhost:5001`: Forward remote port 5001 to local port 5001 - - `user@203.0.113.50`: SSH server address (where the UFO server runs) +On the **client machine**, create an SSH reverse tunnel: + +```bash +ssh -N -R 5001:localhost:5001 user@203.0.113.50 +``` + +**Parameters:** +- `-N`: No remote command execution (tunnel only) +- `-R 5001:localhost:5001`: Forward remote port 5001 to local port 5001 +- `user@203.0.113.50`: SSH server address (where the UFO server runs) **What This Does:** @@ -536,17 +501,16 @@ python -m ufo.client.client \ **Solution: SSH Forward Tunnel** -!!!example "Establish Forward Tunnel" - On the **client machine**, create an SSH forward tunnel to the server's network: - - ```bash - ssh -N -L 5001:192.168.1.100:5001 gateway-user@vpn.company.com - ``` - - **Parameters:** - - `-N`: No remote command execution - - `-L 5001:192.168.1.100:5001`: Forward local port 5001 to remote 192.168.1.100:5001 - - `gateway-user@vpn.company.com`: SSH gateway that can access the server +On the **client machine**, create an SSH forward tunnel to the server's network: + +```bash +ssh -N -L 5001:192.168.1.100:5001 gateway-user@vpn.company.com +``` + +**Parameters:** +- `-N`: No remote command execution +- `-L 5001:192.168.1.100:5001`: Forward local port 5001 to remote 192.168.1.100:5001 +- `gateway-user@vpn.company.com`: SSH gateway that can access the server **After tunnel is established:** @@ -561,73 +525,68 @@ python -m ufo.client.client \ ### Example: Complex Tunnel Setup -!!!example "Real-World Scenario" - **Situation:** - - Server IP: `10.0.0.50:5001` (corporate network) - - Client IP: `192.168.1.75` (home network) - - SSH Gateway: `vpn.company.com` (accessible from internet) - - **Step 1: Create SSH Tunnel** - ```bash - # On client machine - ssh -N -L 5001:10.0.0.50:5001 myuser@vpn.company.com - ``` - - **Step 2: Start Client (in another terminal)** - ```bash - python -m ufo.client.client \ - --ws \ - --ws-server ws://localhost:5001/ws \ - --client-id linux_agent_home_1 \ - --platform linux - ``` +**Situation:** +- Server IP: `10.0.0.50:5001` (corporate network) +- Client IP: `192.168.1.75` (home network) +- SSH Gateway: `vpn.company.com` (accessible from internet) + +**Step 1: Create SSH Tunnel** +```bash +# On client machine +ssh -N -L 5001:10.0.0.50:5001 myuser@vpn.company.com +``` + +**Step 2: Start Client (in another terminal)** +```bash +python -m ufo.client.client \ + --ws \ + --ws-server ws://localhost:5001/ws \ + --client-id linux_agent_home_1 \ + --platform linux +``` ### SSH Tunnel Best Practices -!!!tip "Production Tunnel Configuration" - For production use, add these flags to your SSH tunnel: - - ```bash - ssh -N \ - -L 5001:server:5001 \ - -o ServerAliveInterval=60 \ - -o ServerAliveCountMax=3 \ - -o ExitOnForwardFailure=yes \ - user@gateway - ``` - - **Flags explained:** - - `ServerAliveInterval=60`: Send keep-alive every 60 seconds - - `ServerAliveCountMax=3`: Disconnect after 3 failed keep-alives - - `ExitOnForwardFailure=yes`: Exit if port forwarding fails +For production use, add these flags to your SSH tunnel: + +```bash +ssh -N \ + -L 5001:server:5001 \ + -o ServerAliveInterval=60 \ + -o ServerAliveCountMax=3 \ + -o ExitOnForwardFailure=yes \ + user@gateway +``` + +**Flags explained:** +- `ServerAliveInterval=60`: Send keep-alive every 60 seconds +- `ServerAliveCountMax=3`: Disconnect after 3 failed keep-alives +- `ExitOnForwardFailure=yes`: Exit if port forwarding fails ### Persistent SSH Tunnel with Autossh -!!!example "Auto-Restart Tunnel" - For production, use `autossh` to automatically restart the tunnel if it fails: - - ```bash - # Install autossh - sudo apt-get install autossh # Debian/Ubuntu - - # Start persistent tunnel - autossh -M 0 \ - -N \ - -L 5001:server:5001 \ - -o ServerAliveInterval=60 \ - -o ServerAliveCountMax=3 \ - user@gateway - ``` +For production, use `autossh` to automatically restart the tunnel if it fails: -!!!tip "Documentation Reference" - For more network configuration details, see [Server Quick Start - Troubleshooting](../server/quick_start.md#common-issues-troubleshooting). +```bash +# Install autossh +sudo apt-get install autossh # Debian/Ubuntu + +# Start persistent tunnel +autossh -M 0 \ + -N \ + -L 5001:server:5001 \ + -o ServerAliveInterval=60 \ + -o ServerAliveCountMax=3 \ + user@gateway +``` + +> **ℹ️ Network Configuration:** For more network configuration details, see [Server Quick Start - Troubleshooting](../server/quick_start.md#common-issues-troubleshooting). --- ## 🌌 Step 6: Configure as UFO³ Galaxy Device -!!!info "Galaxy Integration" - To use the Linux Agent as a managed device within the **UFO³ Galaxy** multi-tier framework, you need to register it in the `devices.yaml` configuration file. +To use the Linux Agent as a managed device within the **UFO³ Galaxy** multi-tier framework, you need to register it in the `devices.yaml` configuration file. ### Device Configuration File @@ -639,28 +598,27 @@ config/galaxy/devices.yaml ### Add Linux Agent Configuration -!!!example "Register Linux Agent" - Edit `config/galaxy/devices.yaml` and add your Linux agent under the `devices` section: - - ```yaml - devices: - - device_id: "linux_agent_1" - server_url: "ws://172.23.48.1:5001/ws" - os: "linux" - capabilities: - - "server" - - "log_analysis" - - "file_operations" - metadata: - os: "linux" - performance: "medium" - logs_file_path: "/var/log/myapp/app.log" - dev_path: "/home/user/development/" - warning_log_pattern: "WARN" - error_log_pattern: "ERROR|FATAL" - auto_connect: true - max_retries: 5 - ``` +Edit `config/galaxy/devices.yaml` and add your Linux agent under the `devices` section: + +```yaml +devices: + - device_id: "linux_agent_1" + server_url: "ws://172.23.48.1:5001/ws" + os: "linux" + capabilities: + - "server" + - "log_analysis" + - "file_operations" + metadata: + os: "linux" + performance: "medium" + logs_file_path: "/var/log/myapp/app.log" + dev_path: "/home/user/development/" + warning_log_pattern: "WARN" + error_log_pattern: "ERROR|FATAL" + auto_connect: true + max_retries: 5 +``` ### Configuration Fields Explained @@ -689,92 +647,90 @@ The `metadata` section can contain any custom fields relevant to your Linux agen ### Multiple Linux Agents Example -!!!example "Multi-Agent Configuration" - ```yaml - devices: - - device_id: "linux_agent_1" - server_url: "ws://172.23.48.1:5001/ws" - os: "linux" - capabilities: - - "web_server" - metadata: - logs_file_path: "/var/log/nginx/access.log" - dev_path: "/var/www/html/" - warning_log_pattern: "WARN" - error_log_pattern: "ERROR|FATAL" - auto_connect: true - max_retries: 5 - - - device_id: "linux_agent_2" - server_url: "ws://172.23.48.2:5002/ws" - os: "linux" - capabilities: - - "database_server" - metadata: - logs_file_path: "/var/log/postgresql/postgresql.log" - dev_path: "/var/lib/postgresql/" - warning_log_pattern: "WARNING" - error_log_pattern: "ERROR|FATAL|PANIC" - auto_connect: true - max_retries: 5 - - - device_id: "linux_agent_3" - server_url: "ws://172.23.48.3:5003/ws" - os: "linux" - capabilities: - - "monitoring" - metadata: - logs_file_path: "/var/log/prometheus/prometheus.log" - dev_path: "/opt/prometheus/" - warning_log_pattern: "level=warn" - error_log_pattern: "level=error" - auto_connect: true - max_retries: 5 - ``` +```yaml +devices: + - device_id: "linux_agent_1" + server_url: "ws://172.23.48.1:5001/ws" + os: "linux" + capabilities: + - "web_server" + metadata: + logs_file_path: "/var/log/nginx/access.log" + dev_path: "/var/www/html/" + warning_log_pattern: "WARN" + error_log_pattern: "ERROR|FATAL" + auto_connect: true + max_retries: 5 + + - device_id: "linux_agent_2" + server_url: "ws://172.23.48.2:5002/ws" + os: "linux" + capabilities: + - "database_server" + metadata: + logs_file_path: "/var/log/postgresql/postgresql.log" + dev_path: "/var/lib/postgresql/" + warning_log_pattern: "WARNING" + error_log_pattern: "ERROR|FATAL|PANIC" + auto_connect: true + max_retries: 5 + + - device_id: "linux_agent_3" + server_url: "ws://172.23.48.3:5003/ws" + os: "linux" + capabilities: + - "monitoring" + metadata: + logs_file_path: "/var/log/prometheus/prometheus.log" + dev_path: "/opt/prometheus/" + warning_log_pattern: "level=warn" + error_log_pattern: "level=error" + auto_connect: true + max_retries: 5 +``` ### Critical Requirements -!!!danger "Configuration Validation" - ⚠️ **These fields MUST match exactly:** - - 1. **`device_id` in YAML** ↔ **`--client-id` in client command** - ```yaml - device_id: "linux_agent_1" # In devices.yaml - ``` - ```bash - --client-id linux_agent_1 # In client command - ``` - - 2. **`server_url` in YAML** ↔ **`--ws-server` in client command** - ```yaml - server_url: "ws://172.23.48.1:5001/ws" # In devices.yaml - ``` - ```bash - --ws-server ws://172.23.48.1:5001/ws # In client command - ``` - - **If these don't match, Galaxy cannot control the device!** +> **⚠️ Configuration Validation - These fields MUST match exactly:** +> +> 1. **`device_id` in YAML** ↔ **`--client-id` in client command** +> ```yaml +> device_id: "linux_agent_1" # In devices.yaml +> ``` +> ```bash +> --client-id linux_agent_1 # In client command +> ``` +> +> 2. **`server_url` in YAML** ↔ **`--ws-server` in client command** +> ```yaml +> server_url: "ws://172.23.48.1:5001/ws" # In devices.yaml +> ``` +> ```bash +> --ws-server ws://172.23.48.1:5001/ws # In client command +> ``` +> +> **If these don't match, Galaxy cannot control the device!** ### Using Galaxy to Control Linux Agents Once configured, you can launch Galaxy and it will automatically manage the Linux agents: ```bash -python -m galaxy --config config/galaxy/devices.yaml +python -m galaxy --interactive ``` **Galaxy will:** -1. ✅ Connect to all configured devices -2. ✅ Orchestrate multi-device tasks -3. ✅ Route tasks based on capabilities -4. ✅ Monitor device health - -!!!tip "Documentation Reference" - For detailed Galaxy configuration and usage, see: - - - [Galaxy Overview](../galaxy/overview.md) - - [Galaxy Quick Start](quick_start_galaxy.md) - - [Constellation Orchestrator](../galaxy/constellation_orchestrator/overview.md) +1. ✅ Automatically load device configuration from `config/galaxy/devices.yaml` +2. ✅ Connect to all configured devices +3. ✅ Orchestrate multi-device tasks +4. ✅ Route tasks based on capabilities +5. ✅ Monitor device health + +> **ℹ️ Galaxy Documentation:** For detailed Galaxy configuration and usage, see: +> +> - [Galaxy Overview](../galaxy/overview.md) +> - [Galaxy Quick Start](quick_start_galaxy.md) +> - [Constellation Orchestrator](../galaxy/constellation_orchestrator/overview.md) --- @@ -782,226 +738,226 @@ python -m galaxy --config config/galaxy/devices.yaml ### Issue 1: Client Cannot Connect to Server -!!!bug "Error: Connection Refused" - **Symptoms:** - ```log - ERROR - [WS] Failed to connect to ws://172.23.48.1:5001/ws - Connection refused - ``` - - **Diagnosis Checklist:** - - - [ ] Is the server running? (`curl http://172.23.48.1:5001/api/health`) - - [ ] Is the port correct? (Check server startup logs) - - [ ] Can client reach server IP? (`ping 172.23.48.1`) - - [ ] Is firewall blocking port 5001? - - [ ] Is SSH tunnel established (if needed)? - - **Solutions:** - - **Verify Server:** - ```bash - # On server machine - curl http://localhost:5001/api/health - - # From client machine - curl http://172.23.48.1:5001/api/health - ``` - - **Check Network:** - ```bash - # Test connectivity - ping 172.23.48.1 - - # Test port accessibility - nc -zv 172.23.48.1 5001 - telnet 172.23.48.1 5001 - ``` - - **Check Firewall:** - ```bash - # On server machine (Ubuntu/Debian) - sudo ufw status - sudo ufw allow 5001/tcp - - # On server machine (RHEL/CentOS) - sudo firewall-cmd --list-ports - sudo firewall-cmd --add-port=5001/tcp --permanent - sudo firewall-cmd --reload - ``` +**Error: Connection Refused** + +Symptoms: +```log +ERROR - [WS] Failed to connect to ws://172.23.48.1:5001/ws +Connection refused +``` + +**Diagnosis Checklist:** + +- [ ] Is the server running? (`curl http://172.23.48.1:5001/api/health`) +- [ ] Is the port correct? (Check server startup logs) +- [ ] Can client reach server IP? (`ping 172.23.48.1`) +- [ ] Is firewall blocking port 5001? +- [ ] Is SSH tunnel established (if needed)? + +**Solutions:** + +Verify Server: +```bash +# On server machine +curl http://localhost:5001/api/health + +# From client machine +curl http://172.23.48.1:5001/api/health +``` + +Check Network: +```bash +# Test connectivity +ping 172.23.48.1 + +# Test port accessibility +nc -zv 172.23.48.1 5001 +telnet 172.23.48.1 5001 +``` + +Check Firewall: +```bash +# On server machine (Ubuntu/Debian) +sudo ufw status +sudo ufw allow 5001/tcp + +# On server machine (RHEL/CentOS) +sudo firewall-cmd --list-ports +sudo firewall-cmd --add-port=5001/tcp --permanent +sudo firewall-cmd --reload +``` ### Issue 2: MCP Service Not Responding -!!!bug "Error: Cannot Execute Commands" - **Symptoms:** - ```log - ERROR - Cannot connect to MCP server at http://127.0.0.1:8010 - ERROR - Command execution failed - ``` - - **Diagnosis:** - - - [ ] Is the MCP service running? - - [ ] Is it running on the correct port? - - [ ] Are there any startup errors in MCP logs? - - **Solutions:** - - **Verify MCP Service:** - ```bash - # Check if MCP service is running - curl http://localhost:8010/health - - # Or check process - ps aux | grep linux_mcp_server - ``` - - **Restart MCP Service:** - ```bash - # Kill existing process (if hung) - pkill -f linux_mcp_server - - # Start fresh - python -m ufo.client.mcp.http_servers.linux_mcp_server - ``` - - **Check Port Conflict:** - ```bash - # See if something else is using port 8010 - lsof -i :8010 - netstat -tuln | grep 8010 - - # If port is taken, start MCP on different port - python -m ufo.client.mcp.http_servers.linux_mcp_server --port 8011 - ``` +**Error: Cannot Execute Commands** + +Symptoms: +```log +ERROR - Cannot connect to MCP server at http://127.0.0.1:8010 +ERROR - Command execution failed +``` + +**Diagnosis:** + +- [ ] Is the MCP service running? +- [ ] Is it running on the correct port? +- [ ] Are there any startup errors in MCP logs? + +**Solutions:** + +Verify MCP Service: +```bash +# Check if MCP service is running +curl http://localhost:8010/health + +# Or check process +ps aux | grep linux_mcp_server +``` + +Restart MCP Service: +```bash +# Kill existing process (if hung) +pkill -f linux_mcp_server + +# Start fresh +python -m ufo.client.mcp.http_servers.linux_mcp_server +``` + +Check Port Conflict: +```bash +# See if something else is using port 8010 +lsof -i :8010 +netstat -tuln | grep 8010 + +# If port is taken, start MCP on different port +python -m ufo.client.mcp.http_servers.linux_mcp_server --port 8011 +``` ### Issue 3: Missing `--platform linux` Flag -!!!bug "Error: Incorrect Agent Type" - **Symptoms:** - - Client connects but cannot execute Linux commands - - Server logs show wrong platform type - - Tasks fail with "unsupported operation" errors - - **Cause:** - Forgot to add `--platform linux` flag when starting the client. - - **Solution:** - ```bash - # Wrong (missing platform) - python -m ufo.client.client --ws --client-id linux_agent_1 - - # Correct - python -m ufo.client.client \ - --ws \ - --client-id linux_agent_1 \ - --platform linux - ``` +**Error: Incorrect Agent Type** + +Symptoms: +- Client connects but cannot execute Linux commands +- Server logs show wrong platform type +- Tasks fail with "unsupported operation" errors + +**Cause:** Forgot to add `--platform linux` flag when starting the client. + +**Solution:** +```bash +# Wrong (missing platform) +python -m ufo.client.client --ws --client-id linux_agent_1 + +# Correct +python -m ufo.client.client \ + --ws \ + --client-id linux_agent_1 \ + --platform linux +``` ### Issue 4: Duplicate Client ID -!!!bug "Error: Registration Failed" - **Symptoms:** - ```log - ERROR - [WS] Registration failed: client_id already exists - ERROR - Another device is using ID 'linux_agent_1' - ``` - - **Cause:** - Multiple clients trying to use the same `client_id`. - - **Solutions:** - - 1. **Use unique client IDs:** - ```bash - # Device 1 - --client-id linux_agent_1 - - # Device 2 - --client-id linux_agent_2 - - # Device 3 - --client-id linux_agent_3 - ``` - - 2. **Check currently connected clients:** - ```bash - curl http://172.23.48.1:5001/api/clients - ``` +**Error: Registration Failed** -### Issue 5: Galaxy Cannot Find Device +Symptoms: +```log +ERROR - [WS] Registration failed: client_id already exists +ERROR - Another device is using ID 'linux_agent_1' +``` -!!!bug "Error: Device Not Configured" - **Symptoms:** - ```log - ERROR - Device 'linux_agent_1' not found in configuration - WARNING - Cannot dispatch task to unknown device - ``` - - **Cause:** - Mismatch between `devices.yaml` configuration and actual client setup. - - **Diagnosis:** - - Check that these match **exactly**: - - | Location | Field | Example | - |----------|-------|---------| - | `devices.yaml` | `device_id` | `"linux_agent_1"` | - | Client command | `--client-id` | `linux_agent_1` | - | `devices.yaml` | `server_url` | `"ws://172.23.48.1:5001/ws"` | - | Client command | `--ws-server` | `ws://172.23.48.1:5001/ws` | - - **Solution:** - - Update `devices.yaml` to match your client configuration, or vice versa. +**Cause:** Multiple clients trying to use the same `client_id`. -### Issue 6: SSH Tunnel Keeps Disconnecting +**Solutions:** -!!!bug "Error: Tunnel Connection Lost" - **Symptoms:** - - Client disconnects after a few minutes - - SSH tunnel closes unexpectedly - - "Connection reset by peer" errors - - **Solutions:** - - **Use ServerAliveInterval:** +1. **Use unique client IDs:** ```bash - ssh -N \ - -L 5001:server:5001 \ - -o ServerAliveInterval=60 \ - -o ServerAliveCountMax=3 \ - user@gateway - ``` + # Device 1 + --client-id linux_agent_1 - **Use Autossh:** - ```bash - autossh -M 0 \ - -N \ - -L 5001:server:5001 \ - -o ServerAliveInterval=60 \ - user@gateway - ``` + # Device 2 + --client-id linux_agent_2 - **Run in Screen/Tmux:** + # Device 3 + --client-id linux_agent_3 + ``` + +2. **Check currently connected clients:** ```bash - # Start screen session - screen -S ssh-tunnel - - # Run SSH tunnel - ssh -N -L 5001:server:5001 user@gateway - - # Detach: Ctrl+A, then D - # Reattach: screen -r ssh-tunnel + curl http://172.23.48.1:5001/api/clients ``` +### Issue 5: Galaxy Cannot Find Device + +**Error: Device Not Configured** + +Symptoms: +```log +ERROR - Device 'linux_agent_1' not found in configuration +WARNING - Cannot dispatch task to unknown device +``` + +**Cause:** Mismatch between `devices.yaml` configuration and actual client setup. + +**Diagnosis:** + +Check that these match **exactly**: + +| Location | Field | Example | +|----------|-------|---------| +| `devices.yaml` | `device_id` | `"linux_agent_1"` | +| Client command | `--client-id` | `linux_agent_1` | +| `devices.yaml` | `server_url` | `"ws://172.23.48.1:5001/ws"` | +| Client command | `--ws-server` | `ws://172.23.48.1:5001/ws` | + +**Solution:** Update `devices.yaml` to match your client configuration, or vice versa. + +### Issue 6: SSH Tunnel Keeps Disconnecting + +**Error: Tunnel Connection Lost** + +Symptoms: +- Client disconnects after a few minutes +- SSH tunnel closes unexpectedly +- "Connection reset by peer" errors + +**Solutions:** + +Use ServerAliveInterval: +```bash +ssh -N \ + -L 5001:server:5001 \ + -o ServerAliveInterval=60 \ + -o ServerAliveCountMax=3 \ + user@gateway +``` + +Use Autossh: +```bash +autossh -M 0 \ + -N \ + -L 5001:server:5001 \ + -o ServerAliveInterval=60 \ + user@gateway +``` + +Run in Screen/Tmux: +```bash +# Start screen session +screen -S ssh-tunnel + +# Run SSH tunnel +ssh -N -L 5001:server:5001 user@gateway + +# Detach: Ctrl+A, then D +# Reattach: screen -r ssh-tunnel +``` + --- ## 📚 Next Steps -!!!quote "Continue Learning" - You've successfully set up a Linux Agent! Explore these topics to deepen your understanding: +You've successfully set up a Linux Agent! Explore these topics to deepen your understanding: ### Immediate Next Steps @@ -1032,24 +988,26 @@ python -m galaxy --config config/galaxy/devices.yaml ## ✅ Summary -!!!success "What You've Accomplished" - Congratulations! You've successfully: - - ✅ Switched to the `linux-client` branch - ✅ Installed all dependencies - ✅ Started the Device Agent Server - ✅ Connected a Linux Device Agent Client - ✅ Launched the MCP service for command execution - ✅ Dispatched tasks via HTTP API - ✅ (Optional) Configured SSH tunneling for remote access - ✅ (Optional) Registered the device in Galaxy configuration - -!!!info "Your Linux Agent is Ready" - You can now: - - - 🎯 Execute CLI commands on Linux machines remotely - - 📊 Analyze log files across multiple servers - - 🔧 Manage development environments - - 🌌 Integrate with UFO³ Galaxy for multi-device workflows - - **Start exploring and automating your Linux infrastructure!** 🚀 +## ✅ What You've Accomplished + +Congratulations! You've successfully: + +✅ Switched to the `linux-client` branch +✅ Installed all dependencies +✅ Started the Device Agent Server +✅ Connected a Linux Device Agent Client +✅ Launched the MCP service for command execution +✅ Dispatched tasks via HTTP API +✅ (Optional) Configured SSH tunneling for remote access +✅ (Optional) Registered the device in Galaxy configuration + +**Your Linux Agent is Ready** + +You can now: + +- 🎯 Execute CLI commands on Linux machines remotely +- 📊 Analyze log files across multiple servers +- 🔧 Manage development environments +- 🌌 Integrate with UFO³ Galaxy for multi-device workflows + +**Start exploring and automating your Linux infrastructure!** 🚀 diff --git a/documents/docs/getting_started/quick_start_ufo2.md b/documents/docs/getting_started/quick_start_ufo2.md index 67f673f0e..f91412bed 100644 --- a/documents/docs/getting_started/quick_start_ufo2.md +++ b/documents/docs/getting_started/quick_start_ufo2.md @@ -2,8 +2,9 @@ Welcome to **UFO²** – the Desktop AgentOS! This guide will help you get started with UFO² in just a few minutes. -!!!abstract "What is UFO²?" - UFO² is a **Desktop AgentOS** that turns natural-language requests into automatic, reliable, multi-application workflows on Windows. It goes beyond UI-focused automation by combining GUI actions with native API calls for faster and more robust execution. +**What is UFO²?** + +UFO² is a Desktop AgentOS that turns natural-language requests into automatic, reliable, multi-application workflows on Windows. It goes beyond UI-focused automation by combining GUI actions with native API calls for faster and more robust execution. --- @@ -30,8 +31,7 @@ cd UFO pip install -r requirements.txt ``` -!!!tip "Using Qwen Models" - If you want to use Qwen as your LLM, uncomment the related libraries in `requirements.txt` before installing. +> **💡 Tip:** If you want to use Qwen as your LLM, uncomment the related libraries in `requirements.txt` before installing. --- @@ -63,9 +63,7 @@ config/ufo/ └── ... # Other modular configs with defaults ``` -!!!note "Configuration Files" - - **`agents.yaml`**: Contains sensitive information (API keys) - **MUST be configured** - - Other config files have default values and only need editing for customization +> **Configuration Files:** `agents.yaml` contains sensitive information (API keys) and must be configured. Other config files have default values and only need editing for customization. **Migration Benefits:** @@ -83,8 +81,7 @@ copy ufo\config\config.yaml.template ufo\config\config.yaml notepad ufo\config\config.yaml # Paste your key & endpoint ``` -!!!warning "Config Precedence" - If both old and new configs exist, the new config in `config/ufo/` takes precedence. A warning will be displayed during startup. +> **Config Precedence:** If both old and new configs exist, the new config in `config/ufo/` takes precedence. A warning will be displayed during startup. --- @@ -145,8 +142,7 @@ APP_AGENT: API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" ``` -!!!info "More LLM Options" - UFO² supports various LLM providers including **Qwen**, **Gemini**, **Claude**, **DeepSeek**, and more. See the **[Model Configuration Guide](../configuration/models/overview.md)** for complete details. +> **ℹ️ More LLM Options:** UFO² supports various LLM providers including Qwen, Gemini, Claude, DeepSeek, and more. See the [Model Configuration Guide](../configuration/models/overview.md) for complete details. --- @@ -180,8 +176,7 @@ BING_API_KEY: "YOUR_BING_API_KEY" # Get from https://www.microsoft.com/en-us/bi RAG_EXPERIENCE: true ``` -!!!tip "RAG Resources" - See **[Knowledge Substrate Overview](../ufo2/core_features/knowledge_substrate/overview.md)** for complete RAG configuration and best practices. +> **ℹ️ RAG Resources:** See [Knowledge Substrate Overview](../ufo2/core_features/knowledge_substrate/overview.md) for complete RAG configuration and best practices. --- @@ -245,15 +240,9 @@ UFO² automatically saves execution logs, screenshots, and traces for debugging | `ui_trees/` | UI control tree snapshots (if enabled) | | `request_response.log` | Complete LLM request/response logs | -!!!tip "Analyzing Logs" - Use the logs to: - - - **Debug**: Understand agent behavior and identify errors - - **Replay**: Reconstruct the execution flow - - **Analyze**: Study agent decision-making patterns +> **Analyzing Logs:** Use the logs to debug agent behavior, replay execution flow, and analyze agent decision-making patterns. -!!!warning "Privacy Notice" - Screenshots may contain sensitive or confidential information. Ensure no private data is visible during execution. See **[DISCLAIMER.md](https://github.com/microsoft/UFO/blob/main/DISCLAIMER.md)** for details. +> **Privacy Notice:** Screenshots may contain sensitive or confidential information. Ensure no private data is visible during execution. See [DISCLAIMER.md](https://github.com/microsoft/UFO/blob/main/DISCLAIMER.md) for details. --- @@ -291,8 +280,7 @@ python -m ufo.tools.convert_config --force | `agent_mcp.yaml` | → | `mcp.yaml` | Rename + format conversion | | `config_prices.yaml` | → | `prices.yaml` | Rename + format conversion | -!!!info "Migration Guide" - For detailed migration instructions, rollback procedures, and troubleshooting, see the **[Configuration Migration Guide](../configuration/system/migration.md)**. +> **Migration Guide:** For detailed migration instructions, rollback procedures, and troubleshooting, see the [Configuration Migration Guide](../configuration/system/migration.md). --- @@ -300,34 +288,38 @@ python -m ufo.tools.convert_config --force ### Core Documentation -!!!info "Architecture & Concepts" - - **[UFO² Overview](../ufo2/overview.md)** - System architecture and design principles - - **[HostAgent](../ufo2/host_agent/overview.md)** - Desktop-level coordination agent - - **[AppAgent](../ufo2/app_agent/overview.md)** - Application-level execution agent +**Architecture & Concepts:** + +- [UFO² Overview](../ufo2/overview.md) - System architecture and design principles +- [HostAgent](../ufo2/host_agent/overview.md) - Desktop-level coordination agent +- [AppAgent](../ufo2/app_agent/overview.md) - Application-level execution agent ### Configuration -!!!info "Configuration Guides" - - **[Configuration Overview](../configuration/system/overview.md)** - Configuration system architecture - - **[Agents Configuration](../configuration/system/agents_config.md)** - LLM and agent settings - - **[System Configuration](../configuration/system/system_config.md)** - Runtime and execution settings - - **[MCP Configuration](../configuration/system/mcp_reference.md)** - MCP server settings - - **[Model Configuration](../configuration/models/overview.md)** - Supported LLM providers +**Configuration Guides:** + +- [Configuration Overview](../configuration/system/overview.md) - Configuration system architecture +- [Agents Configuration](../configuration/system/agents_config.md) - LLM and agent settings +- [System Configuration](../configuration/system/system_config.md) - Runtime and execution settings +- [MCP Configuration](../configuration/system/mcp_reference.md) - MCP server settings +- [Model Configuration](../configuration/models/overview.md) - Supported LLM providers ### Advanced Features -!!!info "Advanced Topics" - - **[Hybrid Actions](../ufo2/core_features/hybrid_actions.md)** - GUI + API automation - - **[Control Detection](../ufo2/core_features/control_detection/overview.md)** - UIA + Vision detection - - **[Knowledge Substrate](../ufo2/core_features/knowledge_substrate/overview.md)** - RAG and learning - - **[Multi-Action Execution](../ufo2/core_features/multi_action.md)** - Speculative action batching +**Advanced Topics:** + +- [Hybrid Actions](../ufo2/core_features/hybrid_actions.md) - GUI + API automation +- [Control Detection](../ufo2/core_features/control_detection/overview.md) - UIA + Vision detection +- [Knowledge Substrate](../ufo2/core_features/knowledge_substrate/overview.md) - RAG and learning +- [Multi-Action Execution](../ufo2/core_features/multi_action.md) - Speculative action batching ### Evaluation & Benchmarks -!!!info "Benchmarking" - - **[Benchmark Overview](../ufo2/evaluation/benchmark/overview.md)** - Evaluation framework and datasets - - **[Windows Agent Arena](../ufo2/evaluation/benchmark/windows_agent_arena.md)** - 154 real Windows tasks - - **[OSWorld](../ufo2/evaluation/benchmark/osworld.md)** - Cross-application benchmarks +**Benchmarking:** + +- [Benchmark Overview](../ufo2/evaluation/benchmark/overview.md) - Evaluation framework and datasets +- [Windows Agent Arena](../ufo2/evaluation/benchmark/windows_agent_arena.md) - 154 real Windows tasks +- [OSWorld](../ufo2/evaluation/benchmark/osworld.md) - Cross-application benchmarks --- diff --git a/documents/docs/img/add_device.png b/documents/docs/img/add_device.png new file mode 100644 index 000000000..ab4beec34 Binary files /dev/null and b/documents/docs/img/add_device.png differ diff --git a/documents/docs/index.md b/documents/docs/index.md index f8e32f629..08ecba4f5 100644 --- a/documents/docs/index.md +++ b/documents/docs/index.md @@ -356,7 +356,6 @@ Get help when you need it: | Resource | What You'll Find | |----------|------------------| | [FAQ](faq.md) | Common questions and answers | -| [Getting Started FAQ](getting_started/faq.md) | Setup and installation questions | | [GitHub Discussions](https://github.com/microsoft/UFO/discussions) | Community Q&A | | [GitHub Issues](https://github.com/microsoft/UFO/issues) | Bug reports and feature requests | diff --git a/documents/docs/mcp/action.md b/documents/docs/mcp/action.md index 5fadb0f77..6d0b1adc1 100644 --- a/documents/docs/mcp/action.md +++ b/documents/docs/mcp/action.md @@ -4,118 +4,66 @@ **Action Servers** provide tools that modify system state by executing actions. These servers enable agents to interact with the environment, automate tasks, and implement decisions. -!!!success "LLM-Selectable Tools" - **Action servers are the only servers whose tools can be selected by the LLM agent.** At each step, the agent chooses which action tool to execute based on the task and current context. - - - **LLM Decision**: Agent actively selects from available action tools - - **Dynamic Selection**: Different action chosen at each step based on needs - - **Tool Visibility**: All action tools are presented to the LLM in the prompt - - **[Data Collection Servers](./data_collection.md) are NOT LLM-selectable** - they are automatically invoked by the framework. +**Action servers are the only servers whose tools can be selected by the LLM agent.** At each step, the agent chooses which action tool to execute based on the task and current context. -!!!warning "How Tool Metadata Becomes LLM Instructions" - **Every action tool's implementation directly affects what the LLM sees and understands.** The UFO² framework automatically extracts: - - - **`Annotated` type hints**: Parameter types, constraints, and descriptions - - **Docstrings**: Tool purpose, parameter explanations, return value descriptions - - **Function signatures**: Parameter names, defaults, required vs. optional - - These are **automatically assembled into structured tool instructions** that appear in the LLM's prompt. The LLM uses these instructions to: - - 1. **Understand** what each tool does - 2. **Select** the appropriate tool for each step - 3. **Call** the tool with correct parameters - - **Therefore, developers MUST write clear, comprehensive metadata:** - - ```python - # ✅ GOOD: Clear metadata helps LLM understand and use the tool correctly - @mcp.tool() - def click_input( - control_id: Annotated[str, "The unique ID of the control to click"], - button: Annotated[Literal["left", "right"], "Mouse button to use"] = "left", - ) -> Annotated[str, "Success message or error description"]: - """ - Click on a UI control by its ID. - - Use this tool when you need to interact with buttons, links, or other - clickable elements. The control_id must be obtained from observation. - - Args: - control_id: The numeric ID from the annotated screenshot - button: Which mouse button to click (left for normal clicks, right for context menus) - - Returns: - A success message if the click succeeded, or an error description if it failed. - - Example: - click_input(control_id="5", button="left") # Clicks button with ID 5 - """ - # Implementation... +- **LLM Decision**: Agent actively selects from available action tools +- **Dynamic Selection**: Different action chosen at each step based on needs +- **Tool Visibility**: All action tools are presented to the LLM in the prompt + +**[Data Collection Servers](./data_collection.md) are NOT LLM-selectable** - they are automatically invoked by the framework. + +### How Tool Metadata Becomes LLM Instructions + +**Every action tool's implementation directly affects what the LLM sees and understands.** The UFO² framework automatically extracts: + +- **`Annotated` type hints**: Parameter types, constraints, and descriptions +- **Docstrings**: Tool purpose, parameter explanations, return value descriptions +- **Function signatures**: Parameter names, defaults, required vs. optional + +These are automatically assembled into structured tool instructions that appear in the LLM's prompt. The LLM uses these instructions to understand what each tool does, select the appropriate tool for each step, and call the tool with correct parameters. + +**Therefore, developers MUST write clear, comprehensive metadata.** For examples: + +- See [AppUIExecutor documentation](servers/app_ui_executor.md) for well-documented UI automation tools +- See [WordCOMExecutor documentation](servers/word_com_executor.md) for COM API tool examples +- See [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md) for step-by-step guide on writing tool metadata + +```mermaid +graph TB + LLM["LLM Agent Decision
(Selects Action Tool)"] - # ❌ BAD: Poor metadata confuses LLM, leads to incorrect tool usage - @mcp.tool() - def click_input(control_id, button="left"): - """Click something.""" - # Implementation... - ``` + Agent["Agent Decision
'Click OK Button'"] - **Impact on LLM Performance:** + MCP["MCP Server
Action Server"] - - **Good metadata** → LLM selects correct tool, provides valid parameters → High success rate - - **Poor metadata** → LLM guesses tool usage, provides invalid parameters → High error rate, wasted API calls + subgraph Tools["Available Action Tools"] + Click["click()"] + Type["type_text()"] + Insert["insert_table()"] + Shell["run_shell()"] + end - **Best Practices:** + System["System Modified
✅ Side Effects"] - 1. ✅ Use `Annotated[type, "description"]` for all parameters - 2. ✅ Write detailed docstrings explaining when and how to use the tool - 3. ✅ Include examples in docstrings showing typical usage - 4. ✅ Describe return values clearly - 5. ✅ Specify constraints (e.g., valid ranges, formats, dependencies) - 6. ❌ Don't leave parameters undocumented - 7. ❌ Don't write vague descriptions like "some value" or "the thing" + LLM --> Agent + Agent --> MCP + MCP --> Tools + Tools --> System - **See individual server documentation for examples of well-documented tools.** - -``` -┌─────────────────────────────────────────────────────┐ -│ Action Execution Flow (LLM-Driven) │ -└─────────────────────────────────────────────────────┘ - │ - ┌─────────────┴─────────────┐ - │ LLM Agent Decision │ - │ (Selects Action Tool) │ - └─────────────┬─────────────┘ - │ - ┌─────────────┴─────────────┐ - │ │ - ▼ ▼ -┌───────────────┐ ┌───────────────┐ -│ Agent │ │ MCP Server │ -│ Decision │──────────│ Action Server│ -│ "Click OK" │ Choose └───────────────┘ -└───────────────┘ Tool │ - ▼ - ┌───────────────────┐ - │ click() │ - │ type_text() │ - │ insert_table() │ - │ run_shell() │ - └───────────────────┘ - │ - ▼ - ┌───────────────────┐ - │ System Modified │ - │ ✅ Side Effects │ - └───────────────────┘ + style LLM fill:#e3f2fd,stroke:#1976d2,stroke-width:2px + style Agent fill:#fff3e0,stroke:#f57c00,stroke-width:2px + style MCP fill:#e8f5e9,stroke:#388e3c,stroke-width:2px + style Tools fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px + style System fill:#ffebee,stroke:#c62828,stroke-width:2px ``` -!!!warning "Side Effects" - - **✅ Modifies State**: Can change system, files, UI - - **⚠️ Not Idempotent**: Same action may have different results - - **🔒 Use with Caution**: Always verify before executing - - **📝 Audit Trail**: Log all actions for debugging - - **🤖 LLM-Controlled**: Agent decides when and which action to execute +**Side Effects:** + +- **✅ Modifies State**: Can change system, files, UI +- **⚠️ Not Idempotent**: Same action may have different results +- **🔒 Use with Caution**: Always verify before executing +- **📝 Audit Trail**: Log all actions for debugging +- **🤖 LLM-Controlled**: Agent decides when and which action to execute ## Tool Type Identifier @@ -170,14 +118,13 @@ UFO² provides several built-in action servers for different automation scenario | **[ConstellationEditor](servers/constellation_editor.md)** | Multi-Device | Create and manage multi-device task workflows | [Full Details →](servers/constellation_editor.md) | | **[HardwareExecutor](servers/hardware_executor.md)** | Hardware Control | Control Arduino, robot arms, test fixtures, mobile devices | [Full Details →](servers/hardware_executor.md) | -!!!tip "Quick Reference" - Each server documentation page includes: - - - 📋 **Complete tool reference** with all parameters and return values - - 💡 **Code examples** showing actual usage patterns - - ⚙️ **Configuration examples** for different scenarios - - ✅ **Best practices** with do's and don'ts - - 🎯 **Use cases** with complete workflows +**Quick Reference:** Each server documentation page includes: + +- 📋 **Complete tool reference** with all parameters and return values +- 💡 **Code examples** showing actual usage patterns +- ⚙️ **Configuration examples** for different scenarios +- ✅ **Best practices** with do's and don'ts +- 🎯 **Use cases** with complete workflows ## Configuration Examples @@ -396,7 +343,13 @@ after_screenshot = await computer.run_actions([ ]) ``` -For more details, see: +For more details on agent execution patterns: + +- [HostAgent Commands](../ufo2/host_agent/commands.md) - HostAgent command patterns +- [AppAgent Commands](../ufo2/app_agent/commands.md) - AppAgent action patterns +- [Agent Overview](../ufo2/overview.md) - UFO² agent architecture + +For more details on data collection: - [Data Collection Servers](data_collection.md) - Observation tools - [UICollector Documentation](servers/ui_collector.md) - Complete data collection reference @@ -410,11 +363,10 @@ For more details, see: - [Computer](../client/computer.md) - Action execution layer - [MCP Overview](overview.md) - High-level MCP architecture -!!!danger "Safety Reminder" - Action servers can **modify system state**. Always: - - 1. ✅ **Validate inputs** before execution - 2. ✅ **Verify targets** exist and are accessible - 3. ✅ **Log all actions** for audit trail - 4. ✅ **Handle failures** gracefully with retries - 5. ✅ **Test in safe environment** before production use +**Safety Reminder:** Action servers can **modify system state**. Always: + +1. ✅ **Validate inputs** before execution +2. ✅ **Verify targets** exist and are accessible +3. ✅ **Log all actions** for audit trail +4. ✅ **Handle failures** gracefully with retries +5. ✅ **Test in safe environment** before production use diff --git a/documents/docs/mcp/configuration.md b/documents/docs/mcp/configuration.md index 28e91745b..5ed6000d7 100644 --- a/documents/docs/mcp/configuration.md +++ b/documents/docs/mcp/configuration.md @@ -8,8 +8,7 @@ MCP configuration in UFO² uses a **hierarchical YAML structure** that maps agen config/ufo/mcp.yaml ``` -!!!info "Configuration Reference" - For complete field documentation, see [MCP Reference](../configuration/system/mcp_reference.md). +For complete field documentation, see [MCP Reference](../configuration/system/mcp_reference.md). ## Configuration Structure @@ -46,8 +45,7 @@ AgentName └─ ... ``` -!!!tip "Default Sub-Type" - Always define a `default` sub-type as a fallback configuration. If a specific sub-type is not found, the agent will use `default`. +**Default Sub-Type:** Always define a `default` sub-type as a fallback configuration. If a specific sub-type is not found, the agent will use `default`. ## Server Configuration Fields @@ -202,8 +200,7 @@ AppAgent: - **Data Collection**: Same as default - **Actions**: App UI automation + Word COM API (insert_table, select_text, etc.) -!!!warning "Reset Flag" - Set `reset: true` for stateful tools (like COM executors) to prevent state leakage between contexts (e.g., different documents). +**Reset Flag:** Set `reset: true` for stateful tools (like COM executors) to prevent state leakage between contexts (e.g., different documents). #### Excel-Specific Configuration @@ -303,8 +300,7 @@ HardwareAgent: - **Data Collection**: CPU info, memory info, disk info - **Actions**: Hardware control commands -!!!tip "Remote Deployment" - For remote servers, ensure the HTTP MCP server is running on the target machine. See [Remote Servers](remote_servers.md) for deployment guide. +**Remote Deployment:** For remote servers, ensure the HTTP MCP server is running on the target machine. See [Remote Servers](remote_servers.md) for deployment guide. ### LinuxAgent @@ -624,8 +620,7 @@ HostAgent: type: local ``` -!!!info "Configuration Migration" - See [Configuration Migration Guide](../configuration/system/migration.md) for detailed migration instructions. +For detailed migration instructions, see [Configuration Migration Guide](../configuration/system/migration.md). ## Related Documentation @@ -634,14 +629,17 @@ HostAgent: - [Action Servers](action.md) - Action server configuration - [Local Servers](local_servers.md) - Built-in local MCP servers - [Remote Servers](remote_servers.md) - HTTP and Stdio deployment -- **[Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md)** - Build your own servers +- [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md) - Build your own servers - [MCP Reference](../configuration/system/mcp_reference.md) - Complete field reference - [Configuration Guide](../configuration/system/overview.md) - General configuration guide +- [HostAgent Overview](../ufo2/host_agent/overview.md) - HostAgent configuration examples +- [AppAgent Overview](../ufo2/app_agent/overview.md) - AppAgent configuration examples -!!!quote "Configuration Philosophy" - MCP configuration follows the **convention over configuration** principle: - - - **Sensible defaults** - Minimal configuration required - - **Explicit when needed** - Full control when customization is necessary - - **Type-safe** - Validated on load to catch errors early - - **Hierarchical** - Inherit from defaults, override as needed +**Configuration Philosophy:** + +MCP configuration follows the **convention over configuration** principle: + +- **Sensible defaults** - Minimal configuration required +- **Explicit when needed** - Full control when customization is necessary +- **Type-safe** - Validated on load to catch errors early +- **Hierarchical** - Inherit from defaults, override as needed diff --git a/documents/docs/mcp/data_collection.md b/documents/docs/mcp/data_collection.md index 7e404e06d..ed5e4512a 100644 --- a/documents/docs/mcp/data_collection.md +++ b/documents/docs/mcp/data_collection.md @@ -4,53 +4,50 @@ **Data Collection Servers** provide read-only tools that observe and retrieve system state without modifying it. These servers are essential for agents to understand the current environment before taking actions. -!!!warning "Not LLM-Selectable" - **Data Collection servers are automatically invoked by the UFO² framework** to gather context and build observation prompts for the LLM. The LLM agent **does not select these tools** - they run in the background to provide system state information. +**Data Collection servers are automatically invoked by the UFO² framework** to gather context and build observation prompts for the LLM. The LLM agent **does not select these tools** - they run in the background to provide system state information. + +- **Framework-Driven**: Automatically called to collect screenshots, UI controls, system info +- **Observation Purpose**: Build the prompt that the LLM uses for decision-making +- **Not in Tool List**: These tools are NOT presented to the LLM as selectable actions + +**Only [Action Servers](./action.md) are LLM-selectable.** + +```mermaid +graph TB + Framework["UFO² Framework
(Automatic Invocation)"] - - **Framework-Driven**: Automatically called to collect screenshots, UI controls, system info - - **Observation Purpose**: Build the prompt that the LLM uses for decision-making - - **Not in Tool List**: These tools are NOT presented to the LLM as selectable actions + AgentStep["Agent Step
Observation & Prompt Build"] - **Only [Action Servers](./action.md) are LLM-selectable.** - -``` -┌─────────────────────────────────────────────────────┐ -│ Data Collection Flow (Automatic) │ -└─────────────────────────────────────────────────────┘ - │ - ┌─────────────┴─────────────┐ - │ UFO² Framework │ - │ (Automatic Invocation) │ - └─────────────┬─────────────┘ - │ - ┌─────────────┴─────────────┐ - │ │ - ▼ ▼ -┌───────────────┐ ┌───────────────┐ -│ Agent Step │ │ MCP Server │ -│ Observation │◄─────────│ UICollector │ -│ Prompt Build │ └───────────────┘ -└───────────────┘ │ - ▼ - ┌───────────────────┐ - │ take_screenshot() │ - │ get_window_list() │ - │ get_control_info()│ - └───────────────────┘ - │ - ▼ - ┌───────────────────┐ - │ System State │ - │ → LLM Context │ - └───────────────────┘ + MCP["MCP Server
UICollector"] + + subgraph Tools["Data Collection Tools"] + Screenshot["take_screenshot()"] + WindowList["get_window_list()"] + ControlInfo["get_control_info()"] + end + + SystemState["System State
→ LLM Context"] + + Framework --> AgentStep + Framework --> MCP + MCP --> Tools + Tools --> SystemState + SystemState --> AgentStep + + style Framework fill:#e3f2fd,stroke:#1976d2,stroke-width:2px + style AgentStep fill:#fff3e0,stroke:#f57c00,stroke-width:2px + style MCP fill:#e8f5e9,stroke:#388e3c,stroke-width:2px + style Tools fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px + style SystemState fill:#fce4ec,stroke:#c2185b,stroke-width:2px ``` -!!!info "Characteristics" - - **❌ No Side Effects**: Cannot modify system state - - **✅ Safe to Retry**: Can be called multiple times without risk - - **✅ Idempotent**: Same input always produces same output - - **📊 Observation Only**: Provides information for decision-making - - **🤖 Framework-Invoked**: Not selectable by LLM agent +**Characteristics:** + +- **❌ No Side Effects**: Cannot modify system state +- **✅ Safe to Retry**: Can be called multiple times without risk +- **✅ Idempotent**: Same input always produces same output +- **📊 Observation Only**: Provides information for decision-making +- **🤖 Framework-Invoked**: Not selectable by LLM agent ## Tool Type Identifier @@ -233,6 +230,12 @@ See the [UICollector documentation](servers/ui_collector.md) for complete exampl Data collection servers are typically used in the **observation phase** of agent execution. See the [UICollector documentation](servers/ui_collector.md) for complete integration patterns. +For more details on agent architecture and execution flow: + +- [HostAgent Overview](../ufo2/host_agent/overview.md) - HostAgent architecture and workflow +- [AppAgent Overview](../ufo2/app_agent/overview.md) - AppAgent architecture and workflow +- [Agent Overview](../ufo2/overview.md) - UFO² agent system architecture + ```python # Agent execution loop while not task_complete: @@ -259,10 +262,11 @@ while not task_complete: - [Computer](../client/computer.md) - Tool execution layer - [MCP Overview](overview.md) - High-level MCP architecture -!!!success "Key Takeaways" - - Data collection servers are **read-only** and **safe to retry** - - Always **observe before acting** to make informed decisions - - **Cache results** when state hasn't changed to improve performance - - Handle **errors gracefully** with retries and fallback logic - - Use **appropriate regions** and **parallel collection** for performance - - See the **[UICollector documentation](servers/ui_collector.md)** for complete details +**Key Takeaways:** + +- Data collection servers are **read-only** and **safe to retry** +- Always **observe before acting** to make informed decisions +- **Cache results** when state hasn't changed to improve performance +- Handle **errors gracefully** with retries and fallback logic +- Use **appropriate regions** and **parallel collection** for performance +- See the **[UICollector documentation](servers/ui_collector.md)** for complete details diff --git a/documents/docs/mcp/local_servers.md b/documents/docs/mcp/local_servers.md index b3d4d87b6..f38c58d9f 100644 --- a/documents/docs/mcp/local_servers.md +++ b/documents/docs/mcp/local_servers.md @@ -155,4 +155,7 @@ AppAgent: - [Action Servers](./action.md) - Action server overview - [MCP Configuration](./configuration.md) - Configuration guide - [Remote Servers](./remote_servers.md) - HTTP/Stdio deployment -- **[Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md)** - Learn to build your own servers +- [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md) - Learn to build your own servers +- [HostAgent Overview](../ufo2/host_agent/overview.md) - HostAgent architecture +- [AppAgent Overview](../ufo2/app_agent/overview.md) - AppAgent architecture +- [Hybrid Actions](../ufo2/core_features/hybrid_actions.md) - GUI + API dual-mode automation diff --git a/documents/docs/mcp/overview.md b/documents/docs/mcp/overview.md index 12bcdb292..34ed61499 100644 --- a/documents/docs/mcp/overview.md +++ b/documents/docs/mcp/overview.md @@ -8,37 +8,38 @@ - **Execute actions** through action servers - **Extend capabilities** through custom MCP servers -``` -┌─────────────────────────────────────────────────────────┐ -│ UFO² Agent │ -│ ┌──────────────┐ ┌──────────────┐ │ -│ │ HostAgent │ │ AppAgent │ │ -│ └──────┬───────┘ └──────┬───────┘ │ -│ │ │ │ -│ ▼ ▼ │ -│ ┌──────────────────────────────────────────────┐ │ -│ │ Computer (MCP Tool Manager) │ │ -│ └──────────────────────────────────────────────┘ │ -└─────────────────┬───────────────────────────────────────┘ - │ - ┌─────────┴─────────┐ - │ │ - ▼ ▼ -┌───────────────┐ ┌──────────────┐ -│ Data Collection│ │ Action │ -│ Servers │ │ Servers │ -└───────────────┘ └──────────────┘ - │ │ - ▼ ▼ -┌───────────────┐ ┌──────────────┐ -│ UICollector │ │ UIExecutor │ -│ Hardware Info │ │ CLI Executor │ -│ Screen Capture│ │ COM Executor │ -└───────────────┘ └──────────────┘ +```mermaid +graph TB + subgraph Agent["UFO² Agent"] + HostAgent[HostAgent] + AppAgent[AppAgent] + end + + Computer["Computer
(MCP Tool Manager)"] + + subgraph DataServers["Data Collection Servers"] + UICollector["UICollector
• Screenshots
• Window Info"] + HWInfo["Hardware Info
• CPU/Memory
• System State"] + end + + subgraph ActionServers["Action Servers"] + UIExecutor["UIExecutor
• Click/Type
• UI Automation"] + CLIExecutor["CLI Executor
• Shell Commands"] + COMExecutor["COM Executor
• API Calls"] + end + + HostAgent --> Computer + AppAgent --> Computer + Computer --> DataServers + Computer --> ActionServers + + style Agent fill:#e3f2fd,stroke:#1976d2,stroke-width:2px + style Computer fill:#fff3e0,stroke:#f57c00,stroke-width:2px + style DataServers fill:#e8f5e9,stroke:#388e3c,stroke-width:2px + style ActionServers fill:#fce4ec,stroke:#c2185b,stroke-width:2px ``` -!!!info "MCP in UFO²" - MCP serves as the **execution layer** in UFO²'s architecture. While agents make decisions about *what* to do, MCP servers handle *how* to do it by providing concrete tool implementations. +MCP serves as the **execution layer** in UFO²'s architecture. While agents make decisions about *what* to do, MCP servers handle *how* to do it by providing concrete tool implementations. ## Key Concepts @@ -51,51 +52,22 @@ MCP servers in UFO² are categorized into two types based on their purpose: | **Data Collection** | Retrieve system state
Read-only operations | UI detection, Screenshot, System info | ❌ None | ❌ **No** - Auto-invoked | | **Action** | Modify system state
State-changing operations | Click, Type text, Run command | ✅ Yes | ✅ **Yes** - LLM chooses | -!!!tip "Server Selection Model" - - **[Data Collection Servers](./data_collection.md)**: Automatically invoked by the framework to gather context and build observation prompts. **NOT selectable by LLM.** - - **[Action Servers](./action.md)**: LLM agent actively selects which action tool to execute at each step based on the task. **Only action tools are LLM-selectable.** - - **How Action Tools Reach the LLM**: Each action tool's `Annotated` type hints and docstring are automatically extracted and converted into structured instructions that appear in the LLM's prompt. The LLM uses these instructions to understand what each tool does, what parameters it requires, and when to use it. **Therefore, developers should write clear, comprehensive docstrings and type annotations** - they directly impact the LLM's ability to use the tool correctly. +**Server Selection Model:** + +- **[Data Collection Servers](./data_collection.md)**: Automatically invoked by the framework to gather context and build observation prompts. Not selectable by LLM. +- **[Action Servers](./action.md)**: LLM agent actively selects which action tool to execute at each step based on the task. Only action tools are LLM-selectable. + +**How Action Tools Reach the LLM**: Each action tool's `Annotated` type hints and docstring are automatically extracted and converted into structured instructions that appear in the LLM's prompt. The LLM uses these instructions to understand what each tool does, what parameters it requires, and when to use it. Therefore, developers should write clear, comprehensive docstrings and type annotations - they directly impact the LLM's ability to use the tool correctly. ### 2. Server Deployment Models UFO² supports three deployment models for MCP servers: -``` -┌──────────────────────────────────────────────────────────┐ -│ Deployment Models │ -├──────────────────────────────────────────────────────────┤ -│ │ -│ 1. Local (In-Process) │ -│ ┌─────────────────────────────────────┐ │ -│ │ Agent Process │ │ -│ │ ├─ Agent Logic │ │ -│ │ └─ MCP Server (FastMCP) │ │ -│ └─────────────────────────────────────┘ │ -│ ✅ Fast (no IPC overhead) │ -│ ✅ Simple setup │ -│ ❌ Shares process resources │ -│ │ -│ 2. HTTP (Remote) │ -│ ┌──────────────┐ ┌──────────────┐ │ -│ │ Agent Process│────────>│ HTTP Server │ │ -│ │ │<────────│ (Port 8006) │ │ -│ └──────────────┘ REST └──────────────┘ │ -│ ✅ Process isolation │ -│ ✅ Language-agnostic │ -│ ❌ Network overhead │ -│ │ -│ 3. Stdio (Process) │ -│ ┌──────────────┐ ┌──────────────┐ │ -│ │ Agent Process│────────>│ Child Process│ │ -│ │ │<────────│ (stdin/out) │ │ -│ └──────────────┘ └──────────────┘ │ -│ ✅ Process isolation │ -│ ✅ Bidirectional streaming │ -│ ❌ Platform-specific │ -│ │ -└──────────────────────────────────────────────────────────┘ -``` +| Model | Description | Benefits | Trade-offs | +|-------|-------------|----------|------------| +| **Local (In-Process)** | Server runs in same process as agent | Fast (no IPC overhead), Simple setup | Shares process resources | +| **HTTP (Remote)** | Server runs as HTTP service (e.g., Port 8006) | Process isolation, Language-agnostic | Network overhead | +| **Stdio (Process)** | Server runs as child process using stdin/stdout | Process isolation, Bidirectional streaming | Platform-specific | ### 3. Namespace Isolation @@ -117,28 +89,20 @@ HostAgent: **Tool Key Format**: `{tool_type}::{tool_name}` -```python -# Examples: -"data_collection::screenshot" # Screenshot tool in data_collection -"action::click" # Click tool in action -"action::run_shell" # Shell command in action -``` +- Example: `data_collection::screenshot` - Screenshot tool in data_collection +- Example: `action::click` - Click tool in action +- Example: `action::run_shell` - Shell command in action ## Key Features ### 1. GUI + API Dual-Mode Automation -!!!success "Hybrid Automation - UFO²'s Unique Strength" - **UFO² supports both GUI automation and API-based automation simultaneously.** Each agent can register multiple action servers, combining: - - - **GUI Automation**: Windows UI Automation (UIA) - clicking, typing, scrolling when visual interaction is needed - - **API Automation**: Direct COM API calls, shell commands, REST APIs for efficient, reliable operations - - **The LLM agent dynamically chooses the best action at each step** based on: - - Task requirements (GUI needed for visual elements vs. API for batch operations) - - Reliability (APIs are more stable than GUI clicks) - - Speed (APIs are faster than GUI navigation) - - Availability (fallback to GUI when API is unavailable) +**UFO² supports both GUI automation and API-based automation simultaneously.** Each agent can register multiple action servers, combining: + +- **GUI Automation**: Windows UI Automation (UIA) - clicking, typing, scrolling when visual interaction is needed +- **API Automation**: Direct COM API calls, shell commands, REST APIs for efficient, reliable operations + +**The LLM agent dynamically chooses the best action at each step** based on task requirements, reliability, speed, and availability. **Example: Word Document Automation** @@ -178,10 +142,11 @@ Step 5: Save as PDF → Reason: One API call vs. multiple GUI clicks (File → Save As → Format → PDF) ``` -!!!tip "Why Hybrid Automation Matters" - - **APIs**: ~10x faster, deterministic, no visual dependency - - **GUI**: Handles visual elements, fallback when API unavailable - - **LLM Decision**: Chooses optimal approach per step, not locked into one mode +**Why Hybrid Automation Matters:** + +- **APIs**: ~10x faster, deterministic, no visual dependency +- **GUI**: Handles visual elements, fallback when API unavailable +- **LLM Decision**: Chooses optimal approach per step, not locked into one mode ### 2. Multi-Server Per Agent @@ -232,87 +197,69 @@ MCP servers can run: ### 4. Namespace Isolation +Each MCP server has a unique namespace that groups related tools together, preventing naming conflicts and enabling modular organization. See [Namespace Isolation](#3-namespace-isolation) section above for details. + ## Architecture ### MCP Server Lifecycle -``` -┌─────────────────────────────────────────────────────────┐ -│ MCP Server Lifecycle │ -└─────────────────────────────────────────────────────────┘ - │ - ┌──────────────┴──────────────┐ - │ 1. Configuration Loading │ - │ (mcp.yaml) │ - └──────────────┬──────────────┘ - │ - ┌──────────────┴──────────────┐ - │ 2. MCPServerManager │ - │ Creates BaseMCPServer │ - └──────────────┬──────────────┘ - │ - ┌──────────────┴──────────────┐ - │ 3. Server.start() │ - │ - Local: Get from registry│ - │ - HTTP: Build URL │ - │ - Stdio: Spawn process │ - └──────────────┬──────────────┘ - │ - ┌──────────────┴──────────────┐ - │ 4. Computer Registration │ - │ - List tools from server │ - │ - Register in tool registry│ - └──────────────┬──────────────┘ - │ - ┌──────────────┴──────────────┐ - │ 5. Tool Execution │ - │ - Agent sends Command │ - │ - Computer routes to tool │ - │ - MCP server executes │ - └──────────────┬──────────────┘ - │ - ┌──────────────┴──────────────┐ - │ 6. Server.reset() (optional) │ - │ - Reset server state │ - └──────────────────────────────┘ +```mermaid +graph TB + Start([MCP Server Lifecycle]) + + Config["1. Configuration Loading
(mcp.yaml)"] + Manager["2. MCPServerManager
Creates BaseMCPServer"] + ServerStart["3. Server.start()
• Local: Get from registry
• HTTP: Build URL
• Stdio: Spawn process"] + Register["4. Computer Registration
• List tools from server
• Register in tool registry"] + Execute["5. Tool Execution
• Agent sends Command
• Computer routes to tool
• MCP server executes"] + Reset["6. Server.reset() (optional)
Reset server state"] + + Start --> Config + Config --> Manager + Manager --> ServerStart + ServerStart --> Register + Register --> Execute + Execute --> Reset + + style Start fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px + style Config fill:#e1f5fe,stroke:#0277bd,stroke-width:2px + style Manager fill:#fff9c4,stroke:#f57f17,stroke-width:2px + style ServerStart fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px + style Register fill:#fce4ec,stroke:#c2185b,stroke-width:2px + style Execute fill:#e0f2f1,stroke:#00695c,stroke-width:2px + style Reset fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px ``` ### Component Relationships -``` -┌────────────────────────────────────────────────────────────┐ -│ MCP Architecture │ -├────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────────────────────────────────────┐ │ -│ │ MCPRegistry (Singleton) │ │ -│ │ - Stores server factories │ │ -│ │ - Lazy initialization │ │ -│ └───────────────────┬──────────────────────────┘ │ -│ │ │ -│ ┌──────────────────┴──────────────────────────┐ │ -│ │ MCPServerManager (Singleton) │ │ -│ │ - Creates server instances │ │ -│ │ - Maps server types to classes │ │ -│ │ - Manages server lifecycle │ │ -│ └───────────────────┬──────────────────────────┘ │ -│ │ │ -│ ┌──────────────┼──────────────┐ │ -│ │ │ │ │ -│ ┌────▼────┐ ┌──────▼─────┐ ┌────▼────┐ │ -│ │ Local │ │ HTTP │ │ Stdio │ │ -│ │ MCP │ │ MCP │ │ MCP │ │ -│ │ Server │ │ Server │ │ Server │ │ -│ └─────────┘ └────────────┘ └─────────┘ │ -│ │ -│ ┌──────────────────────────────────────────────┐ │ -│ │ Computer (Per Agent) │ │ -│ │ - Manages multiple MCP servers │ │ -│ │ - Routes commands to tools │ │ -│ │ - Maintains tool registry │ │ -│ └──────────────────────────────────────────────┘ │ -│ │ -└────────────────────────────────────────────────────────────┘ +```mermaid +graph TB + subgraph Architecture["MCP Architecture"] + Registry["MCPRegistry (Singleton)
• Stores server factories
• Lazy initialization"] + + Manager["MCPServerManager (Singleton)
• Creates server instances
• Maps server types to classes
• Manages server lifecycle"] + + subgraph ServerTypes["MCP Server Types"] + Local["Local MCP Server
(In-Process)"] + HTTP["HTTP MCP Server
(Remote)"] + Stdio["Stdio MCP Server
(Child Process)"] + end + + Computer["Computer (Per Agent)
• Manages multiple MCP servers
• Routes commands to tools
• Maintains tool registry"] + + Registry --> Manager + Manager --> ServerTypes + Manager --> Computer + end + + style Architecture fill:#fafafa,stroke:#424242,stroke-width:2px + style Registry fill:#e1f5fe,stroke:#01579b,stroke-width:2px + style Manager fill:#fff9c4,stroke:#f57f17,stroke-width:2px + style ServerTypes fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px + style Local fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px + style HTTP fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px + style Stdio fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px + style Computer fill:#fce4ec,stroke:#880e4f,stroke-width:2px ``` ## Built-in MCP Servers @@ -404,13 +351,14 @@ HardwareAgent: path: "/mcp" ``` -!!!tip "Configuration Hierarchy" - Agent configurations follow this hierarchy: - - 1. **Agent Name** (e.g., `HostAgent`, `AppAgent`) - 2. **Sub-type** (e.g., `default`, `WINWORD.EXE`) - 3. **Tool Type** (e.g., `data_collection`, `action`) - 4. **Server List** (array of server configurations) +**Configuration Hierarchy:** + +Agent configurations follow this hierarchy: + +1. **Agent Name** (e.g., `HostAgent`, `AppAgent`) +2. **Sub-type** (e.g., `default`, `WINWORD.EXE`) +3. **Tool Type** (e.g., `data_collection`, `action`) +4. **Server List** (array of server configurations) ## Key Features @@ -424,10 +372,10 @@ AppAgent: action: - namespace: WordCOMExecutor type: local - reset: true # ✅ Reset COM state when switching documents -``` + reset: true + +**When to use reset:** -When `reset: true`: - Server state is cleared when switching contexts - Prevents state leakage between tasks - Useful for stateful tools (e.g., COM APIs) @@ -447,8 +395,7 @@ self._tool_timeout = 6000 # 100 minutes - Protects WebSocket connections from timeouts - Enables concurrent tool execution -!!!warning "Timeout Protection" - If a tool takes longer than 6000 seconds, it will be **cancelled** and return a timeout error. Adjust `_tool_timeout` for long-running operations. +**Timeout Protection:** If a tool takes longer than 6000 seconds, it will be cancelled and return a timeout error. Adjust `_tool_timeout` for long-running operations. ### 3. Dynamic Server Management @@ -485,6 +432,8 @@ result = await computer.run_actions([tool_call]) # Returns: List of all available action tools ``` +For more details on introspection capabilities, see [Computer - Meta Tools](../client/computer.md#meta-tools). + ## Configuration Files MCP configuration is located at: @@ -493,12 +442,11 @@ MCP configuration is located at: config/ufo/mcp.yaml ``` -!!!info "Configuration Reference" - For detailed configuration options, see: - - - [MCP Configuration Guide](configuration.md) - Complete configuration reference - - [System Configuration](../configuration/system/system_config.md) - MCP-related system settings - - [MCP Reference](../configuration/system/mcp_reference.md) - MCP-specific settings +For detailed configuration options, see: + +- [MCP Configuration Guide](configuration.md) - Complete configuration reference +- [System Configuration](../configuration/system/system_config.md) - MCP-related system settings +- [MCP Reference](../configuration/system/mcp_reference.md) - MCP-specific settings ## Use Cases @@ -553,11 +501,11 @@ HardwareAgent: To start using MCP in UFO²: -1. **Understand the two server types** - [Data Collection](data_collection.md) and [Action](action.md) -2. **Configure your agents** - See [Configuration Guide](configuration.md) -3. **Use built-in servers** - Explore [Local Servers](local_servers.md) -4. **Create custom servers** - Follow [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md) -5. **Deploy remotely** - Set up [Remote Servers](remote_servers.md) +1. **Understand the two server types** - Read about [Data Collection](data_collection.md) and [Action](action.md) servers +2. **Configure your agents** - See [Configuration Guide](configuration.md) for setup details +3. **Use built-in servers** - Explore available [Local Servers](local_servers.md) +4. **Create custom servers** - Follow the [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md) +5. **Deploy remotely** - Learn about [Remote Servers](remote_servers.md) deployment ## Related Documentation @@ -566,15 +514,17 @@ To start using MCP in UFO²: - [Configuration Guide](configuration.md) - How to configure MCP for agents - [Local Servers](local_servers.md) - Built-in MCP servers - [Remote Servers](remote_servers.md) - HTTP and Stdio deployment -- **[Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md)** - Step-by-step guide to building custom servers +- [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md) - Step-by-step guide to building custom servers - [Computer](../client/computer.md) - MCP tool execution layer - [Agent Client](../client/overview.md) - Client architecture overview +- [Agent Overview](../ufo2/overview.md) - UFO² agent system architecture -!!!quote "Design Philosophy" - MCP in UFO² follows the **separation of concerns** principle: - - - **Agents** decide *what* to do (high-level planning) - - **MCP servers** implement *how* to do it (low-level execution) - - **Computer** manages the routing between them (middleware) - - This architecture enables flexibility, extensibility, and maintainability. +**Design Philosophy:** + +MCP in UFO² follows the **separation of concerns** principle: + +- **Agents** decide *what* to do (high-level planning) +- **MCP servers** implement *how* to do it (low-level execution) +- **Computer** manages the routing between them (middleware) + +This architecture enables flexibility, extensibility, and maintainability. diff --git a/documents/docs/mcp/remote_servers.md b/documents/docs/mcp/remote_servers.md index 87f364bef..ccb7d0c8a 100644 --- a/documents/docs/mcp/remote_servers.md +++ b/documents/docs/mcp/remote_servers.md @@ -2,8 +2,7 @@ Remote MCP servers run as separate processes or on different machines, communicating with UFO² over HTTP or stdio. This enables **cross-platform automation**, process isolation, and distributed workflows. -!!!tip "Cross-Platform Automation" - Remote servers enable **Windows UFO² agents to control Linux systems, mobile devices, and hardware** through HTTP MCP servers running on those platforms. +**Cross-Platform Automation:** Remote servers enable **Windows UFO² agents to control Linux systems, mobile devices, and hardware** through HTTP MCP servers running on those platforms. ## Deployment Models @@ -154,17 +153,19 @@ CustomAgent: ## Best Practices -!!!tip "DO - Recommended Practices" - - ✅ **Use HTTP for cross-platform automation** - - ✅ **Use stdio for process isolation** - - ✅ **Validate remote server connectivity** before deployment - - ✅ **Set appropriate timeouts** for long-running commands - - ✅ **Use environment variables** for sensitive credentials - -!!!danger "DON'T - Anti-Patterns" - - ❌ **Don't expose HTTP servers to public internet** without authentication - - ❌ **Don't hardcode credentials** in configuration files - - ❌ **Don't forget to start remote servers** before client connection +**Recommended Practices:** + +- ✅ **Use HTTP for cross-platform automation** +- ✅ **Use stdio for process isolation** +- ✅ **Validate remote server connectivity** before deployment +- ✅ **Set appropriate timeouts** for long-running commands +- ✅ **Use environment variables** for sensitive credentials + +**Anti-Patterns to Avoid:** + +- ❌ **Don't expose HTTP servers to public internet** without authentication +- ❌ **Don't hardcode credentials** in configuration files +- ❌ **Don't forget to start remote servers** before client connection --- diff --git a/documents/docs/mcp/servers/app_ui_executor.md b/documents/docs/mcp/servers/app_ui_executor.md index cb79f69a5..af4f69bc3 100644 --- a/documents/docs/mcp/servers/app_ui_executor.md +++ b/documents/docs/mcp/servers/app_ui_executor.md @@ -4,11 +4,10 @@ **AppUIExecutor** is an action server that provides application-level UI automation for the AppAgent. It enables precise interaction with UI controls within the currently selected application window. -!!!info "Server Type" - **Type**: Action - **Deployment**: Local (in-process) - **Agent**: AppAgent - **LLM-Selectable**: ✅ Yes +**Server Type:** Action +**Deployment:** Local (in-process) +**Agent:** AppAgent +**LLM-Selectable:** ✅ Yes ## Server Information @@ -412,3 +411,4 @@ screenshot = await computer.run_actions([ - [HostUIExecutor](./host_ui_executor.md) - Window selection - [UICollector](./ui_collector.md) - Control discovery - [Action Servers](../action.md) - Action concepts +- [AppAgent Overview](../../ufo2/app_agent/overview.md) - AppAgent architecture diff --git a/documents/docs/mcp/servers/bash_executor.md b/documents/docs/mcp/servers/bash_executor.md index b0a14810f..aec03574a 100644 --- a/documents/docs/mcp/servers/bash_executor.md +++ b/documents/docs/mcp/servers/bash_executor.md @@ -4,11 +4,10 @@ **BashExecutor** provides Linux shell command execution with output capture and system information retrieval via HTTP MCP server. -!!!info "Server Type" - **Type**: Action - **Deployment**: HTTP (remote Linux server) - **Default Port**: 8010 - **LLM-Selectable**: ✅ Yes +**Server Type:** Action +**Deployment:** HTTP (remote Linux server) +**Default Port:** 8010 +**LLM-Selectable:** ✅ Yes ## Server Information diff --git a/documents/docs/mcp/servers/command_line_executor.md b/documents/docs/mcp/servers/command_line_executor.md index 86c1def60..f31e2ff04 100644 --- a/documents/docs/mcp/servers/command_line_executor.md +++ b/documents/docs/mcp/servers/command_line_executor.md @@ -4,11 +4,10 @@ **CommandLineExecutor** provides shell command execution capabilities for launching applications and running system commands. -!!!info "Server Type" - **Type**: Action - **Deployment**: Local (in-process) - **Agent**: HostAgent, AppAgent - **LLM-Selectable**: ✅ Yes +**Server Type:** Action +**Deployment:** Local (in-process) +**Agent:** HostAgent, AppAgent +**LLM-Selectable:** ✅ Yes ## Server Information @@ -292,8 +291,7 @@ await computer.run_actions([ - **Async execution**: No way to know when command completes - **Security risk**: Arbitrary command execution -!!!tip "Alternative for Linux" - For Linux systems with output capture and better control, use **BashExecutor** server instead. +**Tip:** For Linux systems with output capture and better control, use **BashExecutor** server instead. ## Related Documentation diff --git a/documents/docs/mcp/servers/constellation_editor.md b/documents/docs/mcp/servers/constellation_editor.md index 6d1a067c1..183ffcda2 100644 --- a/documents/docs/mcp/servers/constellation_editor.md +++ b/documents/docs/mcp/servers/constellation_editor.md @@ -4,11 +4,10 @@ **ConstellationEditor** provides multi-device task coordination and dependency management for distributed workflows in UFO². -!!!info "Server Type" - **Type**: Action - **Deployment**: Local (in-process) - **Agent**: GalaxyAgent - **LLM-Selectable**: ✅ Yes +**Server Type:** Action +**Deployment:** Local (in-process) +**Agent:** GalaxyAgent +**LLM-Selectable:** ✅ Yes ## Server Information @@ -117,8 +116,7 @@ Update specific fields of an existing task. | `target_device_id` | `str` | No | `None` | New target device | | `tips` | `List[str]` | No | `None` | New tips list | -!!!note "Partial Updates" - Only provided fields are updated; others remain unchanged. +**Note:** Only provided fields are updated; others remain unchanged. #### Returns @@ -445,3 +443,5 @@ await computer.run_actions([ - [Action Servers](../action.md) - Action server concepts - [MCP Overview](../overview.md) - MCP architecture +- [Configuration Guide](../configuration.md) - Constellation setup +- [Local Servers](../local_servers.md) - Local server deployment diff --git a/documents/docs/mcp/servers/excel_com_executor.md b/documents/docs/mcp/servers/excel_com_executor.md index 6df42a582..214a8dcde 100644 --- a/documents/docs/mcp/servers/excel_com_executor.md +++ b/documents/docs/mcp/servers/excel_com_executor.md @@ -4,12 +4,11 @@ **ExcelCOMExecutor** provides Microsoft Excel automation via COM API for efficient spreadsheet manipulation. -!!!info "Server Type" - **Type**: Action - **Deployment**: Local (in-process) - **Agent**: AppAgent - **Target Application**: Microsoft Excel (`EXCEL.EXE`) - **LLM-Selectable**: ✅ Yes +**Server Type:** Action +**Deployment:** Local (in-process) +**Agent:** AppAgent +**Target Application:** Microsoft Excel (`EXCEL.EXE`) +**LLM-Selectable:** ✅ Yes ## Server Information diff --git a/documents/docs/mcp/servers/hardware_executor.md b/documents/docs/mcp/servers/hardware_executor.md index b9360ea9e..39a2acf53 100644 --- a/documents/docs/mcp/servers/hardware_executor.md +++ b/documents/docs/mcp/servers/hardware_executor.md @@ -4,11 +4,10 @@ **HardwareExecutor** provides hardware control capabilities including Arduino HID, BB-8 test fixture, robot arm, mouse control, and screenshot capture. -!!!info "Server Type" - **Type**: Action - **Deployment**: HTTP (remote server) - **Default Port**: 8006 - **LLM-Selectable**: ✅ Yes +**Server Type:** Action +**Deployment:** HTTP (remote server) +**Default Port:** 8006 +**LLM-Selectable:** ✅ Yes ## Server Information diff --git a/documents/docs/mcp/servers/host_ui_executor.md b/documents/docs/mcp/servers/host_ui_executor.md index 93375ff53..9db8c1b74 100644 --- a/documents/docs/mcp/servers/host_ui_executor.md +++ b/documents/docs/mcp/servers/host_ui_executor.md @@ -4,11 +4,10 @@ **HostUIExecutor** is an action server that provides system-level UI automation capabilities for the HostAgent. It enables window management, window switching, and cross-application interactions at the desktop level. -!!!info "Server Type" - **Type**: Action - **Deployment**: Local (in-process) - **Agent**: HostAgent - **LLM-Selectable**: ✅ Yes (LLM chooses when to execute) +**Server Type:** Action +**Deployment:** Local (in-process) +**Agent:** HostAgent +**LLM-Selectable:** ✅ Yes (LLM chooses when to execute) ## Server Information diff --git a/documents/docs/mcp/servers/pdf_reader_executor.md b/documents/docs/mcp/servers/pdf_reader_executor.md index c2b2168ad..40b7da94e 100644 --- a/documents/docs/mcp/servers/pdf_reader_executor.md +++ b/documents/docs/mcp/servers/pdf_reader_executor.md @@ -4,11 +4,10 @@ **PDFReaderExecutor** provides PDF text extraction with optional human simulation capabilities. -!!!info "Server Type" - **Type**: Action - **Deployment**: Local (in-process) - **Agent**: AppAgent, HostAgent - **LLM-Selectable**: ✅ Yes +**Server Type:** Action +**Deployment:** Local (in-process) +**Agent:** AppAgent, HostAgent +**LLM-Selectable:** ✅ Yes ## Server Information diff --git a/documents/docs/mcp/servers/ppt_com_executor.md b/documents/docs/mcp/servers/ppt_com_executor.md index c7831331c..910428a6c 100644 --- a/documents/docs/mcp/servers/ppt_com_executor.md +++ b/documents/docs/mcp/servers/ppt_com_executor.md @@ -4,12 +4,11 @@ **PowerPointCOMExecutor** provides Microsoft PowerPoint automation via COM API for efficient presentation manipulation. -!!!info "Server Type" - **Type**: Action - **Deployment**: Local (in-process) - **Agent**: AppAgent - **Target Application**: Microsoft PowerPoint (`POWERPNT.EXE`) - **LLM-Selectable**: ✅ Yes +**Server Type:** Action +**Deployment:** Local (in-process) +**Agent:** AppAgent +**Target Application:** Microsoft PowerPoint (`POWERPNT.EXE`) +**LLM-Selectable:** ✅ Yes ## Server Information @@ -314,10 +313,9 @@ await computer.run_actions([ - **No content creation**: Cannot add text, shapes, or images via COM (use UI automation) - **No slide management**: Cannot add/delete/reorder slides (use UI automation) -!!!tip "Complementary Usage" - Combine with **AppUIExecutor** for full PowerPoint automation: - - **PowerPointCOMExecutor**: Background colors, export - - **AppUIExecutor**: Add slides, insert text, shapes, animations +**Tip:** Combine with **AppUIExecutor** for full PowerPoint automation: +- **PowerPointCOMExecutor**: Background colors, export +- **AppUIExecutor**: Add slides, insert text, shapes, animations ## Related Documentation diff --git a/documents/docs/mcp/servers/ui_collector.md b/documents/docs/mcp/servers/ui_collector.md index d1c71dc35..7f2ba8bd4 100644 --- a/documents/docs/mcp/servers/ui_collector.md +++ b/documents/docs/mcp/servers/ui_collector.md @@ -4,11 +4,10 @@ **UICollector** is a data collection MCP server that provides comprehensive UI observation and information retrieval capabilities for the UFO² framework. It automatically gathers screenshots, window lists, control information, and UI trees to build the observation context for LLM decision-making. -!!!info "Server Type" - **Type**: Data Collection - **Deployment**: Local (in-process) - **Agent**: HostAgent, AppAgent - **LLM-Selectable**: ❌ No (automatically invoked by framework) +**Server Type:** Data Collection +**Deployment:** Local (in-process) +**Agent:** HostAgent, AppAgent +**LLM-Selectable:** ❌ No (automatically invoked by framework) ## Server Information diff --git a/documents/docs/mcp/servers/word_com_executor.md b/documents/docs/mcp/servers/word_com_executor.md index d26142a08..1743d04be 100644 --- a/documents/docs/mcp/servers/word_com_executor.md +++ b/documents/docs/mcp/servers/word_com_executor.md @@ -4,12 +4,11 @@ **WordCOMExecutor** provides Microsoft Word automation via COM API for efficient document manipulation beyond UI automation. -!!!info "Server Type" - **Type**: Action - **Deployment**: Local (in-process) - **Agent**: AppAgent - **Target Application**: Microsoft Word (`WINWORD.EXE`) - **LLM-Selectable**: ✅ Yes +**Server Type:** Action +**Deployment:** Local (in-process) +**Agent:** AppAgent +**Target Application:** Microsoft Word (`WINWORD.EXE`) +**LLM-Selectable:** ✅ Yes ## Server Information diff --git a/documents/docs/project_directory_structure.md b/documents/docs/project_directory_structure.md index e1a3934ad..2b44bef6d 100644 --- a/documents/docs/project_directory_structure.md +++ b/documents/docs/project_directory_structure.md @@ -2,11 +2,14 @@ This repository implements **UFO³**, a multi-tier AgentOS architecture spanning from single-device automation (UFO²) to cross-device orchestration (Galaxy). This document provides an overview of the directory structure to help you understand the codebase organization. -!!!tip "Architecture Overview" - - **🌌 Galaxy**: Multi-device DAG-based orchestration framework that coordinates agents across different platforms - - **🎯 UFO²**: Single-device Windows desktop agent system that can serve as Galaxy's sub-agent - - **🔌 AIP**: Agent Integration Protocol for cross-device communication - - **⚙️ Modular Configuration**: Type-safe configs in `config/galaxy/` and `config/ufo/` +> **New to UFO³?** Start with the [Documentation Home](index.md) for an introduction and [Quick Start Guide](getting_started/quick_start_galaxy.md) to get up and running. + +**Architecture Overview:** + +- **🌌 Galaxy**: Multi-device DAG-based orchestration framework that coordinates agents across different platforms +- **🎯 UFO²**: Single-device Windows desktop agent system that can serve as Galaxy's sub-agent +- **🔌 AIP**: Agent Integration Protocol for cross-device communication +- **⚙️ Modular Configuration**: Type-safe configs in `config/galaxy/` and `config/ufo/` --- @@ -47,12 +50,12 @@ galaxy/ │ └── presenters/ # Response formatting │ ├── constellation/ # 🌟 Core DAG management system -│ ├── constellation.py # TaskConstellation - DAG container +│ ├── task_constellation.py # TaskConstellation - DAG container │ ├── task_star.py # TaskStar - Task nodes │ ├── task_star_line.py # TaskStarLine - Dependency edges +│ ├── enums.py # Enums for constellation components │ ├── editor/ # Interactive DAG editing with undo/redo -│ ├── orchestrator/ # Event-driven execution coordination -│ └── types/ # Type definitions (priority, dependency, device) +│ └── orchestrator/ # Event-driven execution coordination │ ├── session/ # 📊 Session lifecycle management │ ├── galaxy_session.py # GalaxySession implementation @@ -60,16 +63,16 @@ galaxy/ │ ├── client/ # 📡 Device management │ ├── constellation_client.py # Device registration interface -│ ├── constellation_device_manager.py # Device management coordinator -│ ├── constellation_config.py # Configuration loading +│ ├── device_manager.py # Device management coordinator +│ ├── config_loader.py # Configuration loading │ ├── components/ # Device registry, connection manager, etc. -│ └── orchestration/ # Client orchestration +│ └── support/ # Client support utilities │ ├── core/ # ⚡ Foundational components -│ ├── types/ # Type system (protocols, dataclasses, enums) -│ ├── interfaces/ # Interface definitions -│ ├── di/ # Dependency injection container -│ └── events/ # Event system +│ ├── types.py # Type system (protocols, dataclasses, enums) +│ ├── interfaces.py # Interface definitions +│ ├── di_container.py # Dependency injection container +│ └── events.py # Event system │ ├── visualization/ # 🎨 Rich console visualization │ ├── dag_visualizer.py # DAG topology visualization @@ -86,7 +89,7 @@ galaxy/ ├── galaxy.py # Main Galaxy orchestrator ├── galaxy_client.py # Galaxy client interface ├── README.md # Galaxy overview -└── README_UFO3.md # UFO³ detailed documentation +└── README_ZH.md # Galaxy overview (Chinese) ``` ### Key Components @@ -99,12 +102,13 @@ galaxy/ | **DeviceManager** | Multi-device coordination and assignment | [Device Manager](galaxy/client/device_manager.md) | | **Visualization** | Rich console DAG monitoring | [Galaxy Overview](galaxy/overview.md) | -!!!info "Galaxy Documentation" - - **[Galaxy Overview](galaxy/overview.md)** - Architecture and concepts - - **[Quick Start](getting_started/quick_start_galaxy.md)** - Get started with Galaxy - - **[Constellation Agent](galaxy/constellation_agent/overview.md)** - AI-powered task planning - - **[Constellation Orchestrator](galaxy/constellation_orchestrator/overview.md)** - Event-driven coordination - - **[Device Manager](galaxy/client/device_manager.md)** - Multi-device management +**Galaxy Documentation:** + +- [Galaxy Overview](galaxy/overview.md) - Architecture and concepts +- [Quick Start](getting_started/quick_start_galaxy.md) - Get started with Galaxy +- [Constellation Agent](galaxy/constellation_agent/overview.md) - AI-powered task planning +- [Constellation Orchestrator](galaxy/constellation_orchestrator/overview.md) - Event-driven coordination +- [Device Manager](galaxy/client/device_manager.md) - Multi-device management --- @@ -169,12 +173,14 @@ ufo/ | **Automator** | Hybrid GUI-API automation with fallback | [Core Features](ufo2/core_features/hybrid_actions.md) | | **RAG** | Knowledge retrieval from multiple sources | [Knowledge Substrate](ufo2/core_features/knowledge_substrate/overview.md) | -!!!info "UFO² Documentation" - - **[UFO² Overview](ufo2/overview.md)** - Architecture and concepts - - **[Quick Start](getting_started/quick_start_ufo2.md)** - Get started with UFO² - - **[HostAgent States](ufo2/host_agent/state.md)** - Desktop orchestration states - - **[AppAgent States](ufo2/app_agent/state.md)** - Application execution states - - **[As Galaxy Device](ufo2/as_galaxy_device.md)** - Using UFO² as Galaxy sub-agent +**UFO² Documentation:** + +- [UFO² Overview](ufo2/overview.md) - Architecture and concepts +- [Quick Start](getting_started/quick_start_ufo2.md) - Get started with UFO² +- [HostAgent States](ufo2/host_agent/state.md) - Desktop orchestration states +- [AppAgent States](ufo2/app_agent/state.md) - Application execution states +- [As Galaxy Device](ufo2/as_galaxy_device.md) - Using UFO² as Galaxy sub-agent +- [Creating Custom Agents](tutorials/creating_app_agent/overview.md) - Build your own application agents --- @@ -194,6 +200,8 @@ aip/ **Purpose**: Enables Galaxy to coordinate UFO² agents running on different devices and platforms through standardized messaging over HTTP/WebSocket. +**Documentation**: See [AIP Overview](aip/overview.md) for protocol details and [Message Types](aip/messages.md) for message specifications. + --- ## 🐧 Linux Agent @@ -208,10 +216,11 @@ Lightweight CLI-based agent for Linux devices that integrates with Galaxy as a t **Configuration**: Configured in `config/ufo/third_party.yaml` under `THIRD_PARTY_AGENT_CONFIG.LinuxAgent` -!!!info "Linux Agent Documentation" - - **[Linux Agent Overview](linux/overview.md)** - Architecture and capabilities - - **[Quick Start](getting_started/quick_start_linux.md)** - Setup and deployment - - **[As Galaxy Device](linux/as_galaxy_device.md)** - Integration with Galaxy +**Linux Agent Documentation:** + +- [Linux Agent Overview](linux/overview.md) - Architecture and capabilities +- [Quick Start](getting_started/quick_start_linux.md) - Setup and deployment +- [As Galaxy Device](linux/as_galaxy_device.md) - Integration with Galaxy --- @@ -241,22 +250,24 @@ config/ └── config_schemas.py # Pydantic validation schemas ``` -!!!warning "Configuration Files" - - Template files (`.yaml.template`) should be copied to `.yaml` and edited - - Active config files (`.yaml`) contain API keys and should NOT be committed - - **Galaxy**: Uses `config/galaxy/agent.yaml` for ConstellationAgent LLM settings - - **UFO²**: Uses `config/ufo/agents.yaml` for HostAgent/AppAgent LLM settings - - **Third-Party**: Configure LinuxAgent and HardwareAgent in `config/ufo/third_party.yaml` - - Use `python -m ufo.tools.convert_config` to migrate from legacy configs - -!!!info "Configuration Documentation" - - **[Configuration Overview](configuration/system/overview.md)** - System architecture - - **[Agents Configuration](configuration/system/agents_config.md)** - LLM and agent settings - - **[System Configuration](configuration/system/system_config.md)** - Runtime and execution settings - - **[RAG Configuration](configuration/system/rag_config.md)** - Knowledge retrieval - - **[Third-Party Configuration](configuration/system/third_party_config.md)** - LinuxAgent and external agents - - **[MCP Configuration](configuration/system/mcp_reference.md)** - MCP server setup - - **[Model Configuration](configuration/models/overview.md)** - LLM provider setup +**Configuration Files:** + +- Template files (`.yaml.template`) should be copied to `.yaml` and edited +- Active config files (`.yaml`) contain API keys and should NOT be committed +- **Galaxy**: Uses `config/galaxy/agent.yaml` for ConstellationAgent LLM settings +- **UFO²**: Uses `config/ufo/agents.yaml` for HostAgent/AppAgent LLM settings +- **Third-Party**: Configure LinuxAgent and HardwareAgent in `config/ufo/third_party.yaml` +- Use `python -m ufo.tools.convert_config` to migrate from legacy configs + +**Configuration Documentation:** + +- [Configuration Overview](configuration/system/overview.md) - System architecture +- [Agents Configuration](configuration/system/agents_config.md) - LLM and agent settings +- [System Configuration](configuration/system/system_config.md) - Runtime and execution settings +- [RAG Configuration](configuration/system/rag_config.md) - Knowledge retrieval +- [Third-Party Configuration](configuration/system/third_party_config.md) - LinuxAgent and external agents +- [MCP Configuration](configuration/system/mcp_reference.md) - MCP server setup +- [Model Configuration](configuration/models/overview.md) - LLM provider setup --- @@ -304,19 +315,19 @@ documents/ ## 🗄️ Supporting Modules ### VectorDB (`vectordb/`) -Vector database storage for RAG knowledge sources (help documents, execution traces, user demonstrations). +Vector database storage for RAG knowledge sources (help documents, execution traces, user demonstrations). See [RAG Configuration](configuration/system/rag_config.md) for setup details. ### Learner (`learner/`) -Tools for indexing help documents into vector database for RAG retrieval. +Tools for indexing help documents into vector database for RAG retrieval. Integrates with the [Knowledge Substrate](ufo2/core_features/knowledge_substrate/overview.md) feature. ### Record Processor (`record_processor/`) Parses human demonstrations from Windows Step Recorder for learning from user actions. ### Dataflow (`dataflow/`) -Data collection pipeline for Large Action Model (LAM) training. +Data collection pipeline for Large Action Model (LAM) training. See the [Dataflow](ufo2/dataflow/overview.md) documentation for workflow details. ### Model Worker (`model_worker/`) -Custom LLM deployment tools for running local models. +Custom LLM deployment tools for running local models. See [Model Configuration](configuration/models/overview.md) for supported providers. ### Logs (`logs/`) Auto-generated execution logs organized by task and timestamp, including screenshots, UI trees, and agent actions. @@ -335,11 +346,12 @@ Auto-generated execution logs organized by task and timestamp, including screens | **Best For** | Cross-device collaboration | Windows desktop tasks | Linux server operations | | **Integration** | Orchestrates all agents | Can be Galaxy device | Can be Galaxy device | -!!!tip "Choosing the Right Framework" - - **Use Galaxy** when: Tasks span multiple devices/platforms, complex workflows with dependencies - - **Use UFO² Standalone** when: Single-device Windows automation, rapid prototyping - - **Use Linux Agent** when: Linux server/CLI operations needed in Galaxy workflows - - **Best Practice**: Galaxy orchestrates UFO² (Windows) + Linux Agent (Linux) for cross-platform tasks +**Choosing the Right Framework:** + +- **Use Galaxy** when: Tasks span multiple devices/platforms, complex workflows with dependencies +- **Use UFO² Standalone** when: Single-device Windows automation, rapid prototyping +- **Use Linux Agent** when: Linux server/CLI operations needed in Galaxy workflows +- **Best Practice**: Galaxy orchestrates UFO² (Windows) + Linux Agent (Linux) for cross-platform tasks --- @@ -440,9 +452,9 @@ UFO³ follows **SOLID principles** and established software engineering patterns --- -!!!success "Next Steps" - 1. Start with **[Galaxy Quick Start](getting_started/quick_start_galaxy.md)** for multi-device orchestration - 2. Or explore **[UFO² Quick Start](getting_started/quick_start_ufo2.md)** for single-device automation - 3. Check **[FAQ](getting_started/faq.md)** for common questions - 4. Join our community and contribute! +**Next Steps:** +1. Start with [Galaxy Quick Start](getting_started/quick_start_galaxy.md) for multi-device orchestration +2. Or explore [UFO² Quick Start](getting_started/quick_start_ufo2.md) for single-device automation +3. Check [FAQ](faq.md) for common questions +4. Join our community and contribute! diff --git a/documents/docs/server/api.md b/documents/docs/server/api.md index 43b634983..5a443d611 100644 --- a/documents/docs/server/api.md +++ b/documents/docs/server/api.md @@ -1,7 +1,6 @@ # HTTP API Reference -!!!quote "REST API for External Integration" - The UFO Server provides a RESTful HTTP API for external systems to dispatch tasks, monitor client connections, retrieve results, and perform health checks. All endpoints are prefixed with `/api`. +The UFO Server provides a RESTful HTTP API for external systems to dispatch tasks, monitor client connections, retrieve results, and perform health checks. All endpoints are prefixed with `/api`. ## 🎯 Overview @@ -56,11 +55,12 @@ graph LR | **Result Retrieval** | `GET /api/task_result/{task_name}` | Fetch task execution results | | **Health Checks** | `GET /api/health` | Monitor server status and uptime | -!!!info "Why Use the HTTP API?" - - **External Integration**: Trigger UFO tasks from web apps, scripts, or CI/CD pipelines - - **Stateless**: No WebSocket connection required - - **RESTful**: Standard HTTP methods and JSON payloads - - **Monitoring**: Health checks for load balancers and monitoring systems +**Why Use the HTTP API?** + +- **External Integration**: Trigger UFO tasks from web apps, scripts, or CI/CD pipelines +- **Stateless**: No WebSocket connection required +- **RESTful**: Standard HTTP methods and JSON payloads +- **Monitoring**: Health checks for load balancers and monitoring systems --- @@ -68,8 +68,7 @@ graph LR ### POST /api/dispatch -!!!success "Dispatch Tasks via HTTP" - Send a task to a connected device without establishing a WebSocket connection. Ideal for external systems, web apps, and automation scripts. +Send a task to a connected device without establishing a WebSocket connection. Ideal for external systems, web apps, and automation scripts. #### Request Format @@ -87,17 +86,14 @@ graph LR | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `client_id` | `string` | ✅ **Yes** | - | Target client identifier (device or constellation) | -| `request` | `string` | ✅ **Yes** | - | Natural language task description (user request) | +| `client_id` | `string` | �?**Yes** | - | Target client identifier (device or constellation) | +| `request` | `string` | �?**Yes** | - | Natural language task description (user request) | | `task_name` | `string` | ⚠️ No | Auto-generated UUID | Human-readable task identifier | -!!!warning "Parameter Names Changed" - **Old documentation** used `device_id` and `task` - these are **incorrect**. - - **Actual parameters** (verified from source code): - - `client_id` (not `device_id`) - - `request` (not `task`) - - `task_name` (optional identifier) +**Important:** The correct parameter names (verified from source code) are: +- `client_id` (not `device_id`) +- `request` (not `task`) +- `task_name` (optional identifier) #### Success Response (200) @@ -156,10 +152,11 @@ graph LR #### Implementation Details -!!!example "Actual Source Code" - ```python - @router.post("/api/dispatch") - async def dispatch_task_api(data: Dict[str, Any]): +**Source Code** (verified from `ufo/server/services/api.py`): + +```python +@router.post("/api/dispatch") +async def dispatch_task_api(data: Dict[str, Any]): # Extract parameters client_id = data.get("client_id") user_request = data.get("request", "") @@ -217,8 +214,7 @@ graph LR } ``` -!!!tip "Use `session_id` to Track Results" - Save the returned `session_id` to retrieve task results later via `GET /api/task_result/{task_name}`. +**Tip:** Use the returned `session_id` to track results via `GET /api/task_result/{task_name}`. #### Sequence Diagram @@ -256,8 +252,7 @@ sequenceDiagram ### GET /api/clients -!!!info "List Connected Clients" - Query all currently connected clients (devices and constellations) to determine which targets are available for task dispatch. +Query all currently connected clients (devices and constellations) to determine which targets are available for task dispatch. #### Request @@ -285,12 +280,23 @@ GET /api/clients |-------|------|-------------| | `online_clients` | `array` | List of all connected client IDs | -!!!example "Actual Source Code" - ```python - @router.get("/api/clients") - async def list_clients(): - return {"online_clients": client_manager.list_clients()} - ``` +**Source Code:** + +```python +@router.get("/api/clients") +async def list_clients(): + return {"online_clients": client_manager.list_clients()} +``` + +#### Usage Patterns + +**Source Code:** + +```python +@router.get("/api/clients") +async def list_clients(): + return {"online_clients": client_manager.list_clients()} +``` #### Usage Patterns @@ -304,10 +310,10 @@ clients = response.json()["online_clients"] target_device = "device_windows_001" if target_device in clients: - print(f"✅ {target_device} is online") + print(f"�?{target_device} is online") # Dispatch task else: - print(f"❌ {target_device} is offline") + print(f"�?{target_device} is offline") ``` **Filter by Client Type:** @@ -342,8 +348,7 @@ while True: ### GET /api/task_result/{task_name} -!!!success "Retrieve Task Results" - Poll this endpoint to get the result of a dispatched task. Use the `task_name` returned from `/api/dispatch`. +Poll this endpoint to get the result of a dispatched task. Use the `task_name` returned from `/api/dispatch`. #### Request @@ -400,32 +405,35 @@ If `task_name` doesn't exist in session manager: #### Implementation Details -!!!example "Actual Source Code" - ```python - @router.get("/api/task_result/{task_name}") - async def get_task_result(task_name: str): - # Query session manager for result - result = session_manager.get_result_by_task(task_name) - - if not result: - return {"status": "pending"} - - return {"status": "done", "result": result} - ``` +**Source Code:** -!!!warning "Result Retention" - Results are stored in memory and may be cleared after: +```python +@router.get("/api/task_result/{task_name}") +async def get_task_result(task_name: str): + # Query session manager for result + result = session_manager.get_result_by_task(task_name) - - Server restart - - Session cleanup (if implemented) - - Memory limits reached + if not result: + return {"status": "pending"} - **Recommendation:** Poll frequently and persist results on the client side. + return {"status": "done", "result": result} +``` + +**Note on Result Retention:** + +Results are stored in memory and may be cleared after: + +- Server restart +- Session cleanup (if implemented) +- Memory limits reached + +**Recommendation:** Poll frequently and persist results on the client side. #### Polling Pattern -!!!example "Recommended Polling Implementation" - ```python +**Recommended Polling Implementation:** + +```python import requests import time @@ -460,10 +468,10 @@ If `task_name` doesn't exist in session manager: data = response.json() if data["status"] == "done": - print(f"✅ Task completed in {elapsed:.1f}s") + print(f"�?Task completed in {elapsed:.1f}s") return data["result"] - print(f"⏳ Waiting for task... ({elapsed:.0f}s)") + print(f"�?Waiting for task... ({elapsed:.0f}s)") time.sleep(interval) # Usage @@ -471,15 +479,14 @@ If `task_name` doesn't exist in session manager: result = wait_for_result("github_navigation_task", timeout=60) print("Result:", result) except TimeoutError as e: - print(f"❌ {e}") + print(f"�?{e}") ``` --- ### GET /api/health -!!!tip "Health Monitoring" - Use this endpoint for monitoring systems, load balancers, and Kubernetes liveness/readiness probes. +Use this endpoint for monitoring systems, load balancers, and Kubernetes liveness/readiness probes. #### Request @@ -511,15 +518,16 @@ GET /api/health #### Implementation Details -!!!example "Actual Source Code" - ```python - @router.get("/api/health") - async def health_check(): - return { - "status": "healthy", - "online_clients": client_manager.list_clients() - } - ``` +**Source Code:** + +```python +@router.get("/api/health") +async def health_check(): + return { + "status": "healthy", + "online_clients": client_manager.list_clients() + } +``` #### Integration Examples @@ -569,7 +577,7 @@ def monitor_server_health(url="http://localhost:5000/api/health"): client_count = len(data.get("online_clients", [])) print( - f"✅ Server healthy - {client_count} clients connected" + f"�?Server healthy - {client_count} clients connected" ) consecutive_failures = 0 else: @@ -581,7 +589,7 @@ def monitor_server_health(url="http://localhost:5000/api/health"): except requests.RequestException as e: consecutive_failures += 1 print( - f"❌ Server unreachable: {e} " + f"�?Server unreachable: {e} " f"(failures: {consecutive_failures})" ) @@ -610,8 +618,9 @@ upstream ufo_backend { ### Python (requests) -!!!example "Complete Task Dispatch Workflow" - ```python +**Complete Task Dispatch Workflow:** + +```python import requests import time @@ -624,10 +633,10 @@ upstream ufo_backend { target_client = "device_windows_001" if target_client not in clients: - print(f"❌ {target_client} is not online") + print(f"�?{target_client} is not online") exit(1) - print(f"✅ {target_client} is online") + print(f"�?{target_client} is online") # Step 2: Dispatch task dispatch_response = requests.post( @@ -640,7 +649,7 @@ upstream ufo_backend { ) if dispatch_response.status_code != 200: - print(f"❌ Dispatch failed: {dispatch_response.json()}") + print(f"�?Dispatch failed: {dispatch_response.json()}") exit(1) dispatch_data = dispatch_response.json() @@ -650,7 +659,7 @@ upstream ufo_backend { print(f"Task dispatched: {task_name} (session: {session_id})") # Step 3: Poll for result - print("⏳ Waiting for result...") + print("�?Waiting for result...") max_wait = 120 # 2 minutes poll_interval = 2 @@ -663,22 +672,23 @@ upstream ufo_backend { result_data = result_response.json() if result_data["status"] == "done": - print(f"✅ Task completed!") + print(f"�?Task completed!") print(f"Result: {result_data['result']}") break time.sleep(poll_interval) waited += poll_interval - print(f"⏳ Still waiting... ({waited}s)") + print(f"�?Still waiting... ({waited}s)") else: - print(f"❌ Timeout: Task did not complete in {max_wait}s") + print(f"�?Timeout: Task did not complete in {max_wait}s") ``` ### cURL -!!!example "Command-Line HTTP Requests" - **Dispatch Task:** - ```bash +**Command-Line HTTP Requests:** + +**Dispatch Task:** +```bash curl -X POST http://localhost:5000/api/dispatch \ -H "Content-Type: application/json" \ -d '{ @@ -736,8 +746,9 @@ upstream ufo_backend { ### JavaScript (fetch) -!!!example "Browser/Node.js Integration" - ```javascript +**Browser/Node.js Integration:** + +```javascript // Dispatch task and wait for result async function dispatchAndWait(clientId, request, taskName) { const BASE_URL = 'http://localhost:5000'; @@ -761,10 +772,10 @@ upstream ufo_backend { } const {session_id, task_name} = await dispatchResponse.json(); - console.log(`✅ Dispatched: ${task_name} (session: ${session_id})`); + console.log(`�?Dispatched: ${task_name} (session: ${session_id})`); // Step 2: Poll for result - console.log('⏳ Waiting for result...'); + console.log('�?Waiting for result...'); const maxWait = 120000; // 2 minutes in ms const pollInterval = 2000; // 2 seconds @@ -783,11 +794,11 @@ upstream ufo_backend { const resultData = await resultResponse.json(); if (resultData.status === 'done') { - console.log('✅ Task completed!'); + console.log('�?Task completed!'); return resultData.result; } - console.log(`⏳ Still waiting... (${Math.floor(elapsed / 1000)}s)`); + console.log(`�?Still waiting... (${Math.floor(elapsed / 1000)}s)`); await new Promise(resolve => setTimeout(resolve, pollInterval)); } } @@ -831,8 +842,9 @@ All API errors follow FastAPI's standard format: ### Error Handling Patterns -!!!example "Robust Error Handling" - ```python +**Robust Error Handling:** + +```python import requests from requests.exceptions import RequestException @@ -899,19 +911,18 @@ All API errors follow FastAPI's standard format: ) if result: - print(f"✅ Dispatched successfully: {result['session_id']}") + print(f"�?Dispatched successfully: {result['session_id']}") else: - print("❌ Dispatch failed, check errors above") + print("�?Dispatch failed, check errors above") ``` --- -## Best Practices +## 💡 Best Practices ### 1. Validate Client Availability -!!!tip "Check Before Dispatch" - Always verify the target client is online before dispatching tasks. +Always verify the target client is online before dispatching tasks. ```python def is_client_online(client_id: str) -> bool: @@ -930,8 +941,7 @@ else: ### 2. Implement Exponential Backoff -!!!success "Efficient Polling" - Use exponential backoff to reduce server load when polling for results. +Use exponential backoff to reduce server load when polling for results. ```python import time @@ -962,8 +972,7 @@ def poll_with_backoff(task_name: str, max_wait: int = 300): ### 3. Use Health Checks for Monitoring -!!!info "Production Monitoring" - Integrate health checks into your monitoring infrastructure. +Integrate health checks into your monitoring infrastructure. ```python import requests @@ -999,8 +1008,7 @@ def check_server_health() -> bool: ### 4. Handle Timeouts Gracefully -!!!warning "Set Appropriate Timeouts" - Different tasks have different execution times. Set timeouts accordingly. +Set appropriate timeouts - different tasks have different execution times. ```python def dispatch_with_timeout( @@ -1042,8 +1050,9 @@ def dispatch_with_timeout( ### 5. Log All API Interactions -!!!example "Production Logging" - ```python +**Production Logging:** + +```python import logging import requests @@ -1092,8 +1101,7 @@ def dispatch_with_timeout( ### 6. Cache Client List -!!!tip "Reduce API Calls" - Cache the client list if you're dispatching multiple tasks. +Reduce API calls by caching the client list if you're dispatching multiple tasks. ```python from datetime import datetime, timedelta @@ -1243,13 +1251,14 @@ await task_protocol.send_task_assignment( | `GET` | `/api/task_result/{task_name}` | Get task result | No | | `GET` | `/api/health` | Health check | No | -!!!note "No Authentication" - The current API implementation does **not** include authentication. For production deployments, consider adding: - - - API keys - - OAuth2/JWT tokens - - Rate limiting - - IP whitelisting +**Note on Authentication:** + +The current API implementation does **not** include authentication. For production deployments, consider adding: + +- API keys +- OAuth2/JWT tokens +- Rate limiting +- IP whitelisting ### Request/Response Models @@ -1310,8 +1319,7 @@ await task_protocol.send_task_assignment( ## 🎓 Summary -!!!quote "RESTful Integration Layer" - The HTTP API provides a **stateless, RESTful interface** for external systems to interact with the UFO server without maintaining WebSocket connections. +The HTTP API provides a **stateless, RESTful interface** for external systems to interact with the UFO server without maintaining WebSocket connections. **Key Characteristics:** @@ -1380,566 +1388,3 @@ graph TD - [Session Manager](./session_manager.md) - Task execution and result tracking - [Quick Start](./quick_start.md) - Get started with UFO server -### POST /api/dispatch - -Dispatch a task to a connected device. - -**Request Body** - -```json -{ - "device_id": "device_windows_001", - "task": "Open Chrome and navigate to github.com", - "mode": "normal", - "plan": [] -} -``` - -**Request Schema** - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `device_id` | string | Yes | Target device identifier | -| `task` | string | Yes | Natural language task description | -| `mode` | string | No | Execution mode: "normal" or "follower" (default: "normal") | -| `plan` | list | No | Predefined action plan (default: []) | - -**Success Response (200)** - -```json -{ - "status": "success", - "session_id": "session_20240115_143022_abc123", - "message": "Task dispatched to device_windows_001" -} -``` - -**Error Responses** - -**Device Not Connected (400)** - -```json -{ - "status": "error", - "message": "Device device_windows_001 is not connected" -} -``` - -**Invalid Request (422)** - -```json -{ - "detail": [ - { - "loc": ["body", "device_id"], - "msg": "field required", - "type": "value_error.missing" - } - ] -} -``` - -**Implementation** - -```python -@router.post("/api/dispatch") -async def dispatch_task(request: DispatchRequest): - """Dispatch a task to a device.""" - - # Validate device connection - if not client_manager.is_device_connected(request.device_id): - raise HTTPException( - status_code=400, - detail=f"Device {request.device_id} is not connected" - ) - - # Generate session ID - session_id = generate_session_id() - - # Get device WebSocket - device_ws = client_manager.get_client(request.device_id) - - # Get platform - platform = client_manager.get_client_info(request.device_id).platform - - # Execute task asynchronously - await session_manager.execute_task_async( - session_id=session_id, - task_name=request.task, - request=request.dict(), - websocket=device_ws, - platform_override=platform - ) - - return { - "status": "success", - "session_id": session_id, - "message": f"Task dispatched to {request.device_id}" - } -``` - -!!!tip - Use the returned `session_id` to retrieve task results via `/api/task_result/{session_id}`. - -### GET /api/clients - -Get information about all connected clients. - -**Response (200)** - -```json -{ - "clients": [ - { - "client_id": "device_windows_001", - "type": "device", - "platform": "windows", - "connected_at": 1705324822.123, - "uptime_seconds": 3600 - }, - { - "client_id": "constellation_coordinator_001", - "type": "constellation", - "platform": "linux", - "connected_at": 1705325422.456, - "uptime_seconds": 3000 - } - ], - "total": 2 -} -``` - -**Response Schema** - -| Field | Type | Description | -|-------|------|-------------| -| `clients` | array | List of connected clients | -| `clients[].client_id` | string | Unique client identifier | -| `clients[].type` | string | Client type: "device" or "constellation" | -| `clients[].platform` | string | OS platform: "windows", "linux", "darwin" | -| `clients[].connected_at` | float | Unix timestamp of connection time | -| `clients[].uptime_seconds` | float | Time since connection in seconds | -| `total` | integer | Total number of connected clients | - -**Implementation** - -```python -@router.get("/api/clients") -async def get_clients(): - """Get information about all connected clients.""" - - clients_info = client_manager.get_all_clients() - current_time = time.time() - - clients = [ - { - "client_id": info.client_id, - "type": info.client_type.value, - "platform": info.platform, - "connected_at": info.connect_time, - "uptime_seconds": current_time - info.connect_time - } - for info in clients_info - ] - - return { - "clients": clients, - "total": len(clients) - } -``` - -!!!info - This endpoint is useful for monitoring which devices are available for task dispatch. - -### GET /api/task_result/{task_name} - -Retrieve the result of a completed task. - -**Path Parameters** - -| Parameter | Type | Description | -|-----------|------|-------------| -| `task_name` | string | Session ID or task identifier | - -**Success Response (200)** - -```json -{ - "status": "completed", - "result": { - "action_taken": "Opened Chrome and navigated to github.com", - "screenshot": "base64_encoded_image_data", - "control_selected": { - "label": "Address bar", - "control_text": "github.com" - } - }, - "session_id": "session_20240115_143022_abc123" -} -``` - -**Pending Response (202)** - -```json -{ - "status": "pending", - "message": "Task is still running" -} -``` - -**Not Found Response (404)** - -```json -{ - "detail": "Task session_invalid not found" -} -``` - -**Implementation** - -```python -@router.get("/api/task_result/{task_name}") -async def get_task_result(task_name: str): - """Get the result of a task.""" - - # Try to get session - session = session_manager.get_session(task_name) - - if not session: - raise HTTPException( - status_code=404, - detail=f"Task {task_name} not found" - ) - - # Check if task is complete - if session.is_running(): - return { - "status": "pending", - "message": "Task is still running" - } - - # Get result - result = session.get_result() - - return { - "status": "completed", - "result": result, - "session_id": task_name - } -``` - -!!!warning - Results may be cleared after a certain time period. Poll this endpoint periodically if needed. - -### GET /api/health - -Health check endpoint for monitoring and load balancers. - -**Response (200)** - -```json -{ - "status": "healthy", - "uptime_seconds": 86400, - "connected_clients": 5, - "active_sessions": 2 -} -``` - -**Implementation** - -```python -@router.get("/api/health") -async def health_check(): - """Health check endpoint.""" - - return { - "status": "healthy", - "uptime_seconds": time.time() - server_start_time, - "connected_clients": client_manager.get_online_count(), - "active_sessions": session_manager.get_active_session_count() - } -``` - -!!!success - This endpoint is useful for Kubernetes liveness/readiness probes and monitoring systems. - -## Usage Examples - -### Python - -**Dispatch Task** - -```python -import requests - -response = requests.post( - "http://localhost:8000/api/dispatch", - json={ - "device_id": "device_windows_001", - "task": "Open Notepad and type Hello World" - } -) - -result = response.json() -session_id = result["session_id"] -``` - -**Get Task Result** - -```python -import time - -while True: - response = requests.get( - f"http://localhost:8000/api/task_result/{session_id}" - ) - - result = response.json() - - if result["status"] == "completed": - print("Task completed:", result["result"]) - break - - time.sleep(1) -``` - -**List Connected Clients** - -```python -response = requests.get("http://localhost:8000/api/clients") -clients = response.json()["clients"] - -for client in clients: - print(f"{client['client_id']} ({client['platform']})") -``` - -### cURL - -**Dispatch Task** - -```bash -curl -X POST http://localhost:8000/api/dispatch \ - -H "Content-Type: application/json" \ - -d '{ - "device_id": "device_windows_001", - "task": "Open Calculator" - }' -``` - -**Get Clients** - -```bash -curl http://localhost:8000/api/clients -``` - -**Get Task Result** - -```bash -curl http://localhost:8000/api/task_result/session_20240115_143022_abc123 -``` - -**Health Check** - -```bash -curl http://localhost:8000/api/health -``` - -### JavaScript (fetch) - -```javascript -// Dispatch task -const response = await fetch('http://localhost:8000/api/dispatch', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - device_id: 'device_windows_001', - task: 'Open Chrome' - }) -}); - -const {session_id} = await response.json(); - -// Poll for result -while (true) { - const result_response = await fetch( - `http://localhost:8000/api/task_result/${session_id}` - ); - - const result = await result_response.json(); - - if (result.status === 'completed') { - console.log('Task completed:', result.result); - break; - } - - await new Promise(resolve => setTimeout(resolve, 1000)); -} -``` - -## Error Handling - -**Standard Error Response** - -All endpoints return errors in a consistent format: - -```json -{ - "detail": "Error message description" -} -``` - -**Common Status Codes** - -| Code | Meaning | Common Causes | -|------|---------|---------------| -| 200 | OK | Request succeeded | -| 202 | Accepted | Task pending (for /api/task_result) | -| 400 | Bad Request | Device not connected, invalid parameters | -| 404 | Not Found | Task/resource not found | -| 422 | Unprocessable Entity | Invalid request body schema | -| 500 | Internal Server Error | Unexpected server error | - -## Best Practices - -**Check Device Availability** - -Always verify device connection before dispatching: - -```python -# Get available devices -clients_response = requests.get("http://localhost:8000/api/clients") -available_devices = [ - c["client_id"] for c in clients_response.json()["clients"] - if c["type"] == "device" -] - -if target_device in available_devices: - # Dispatch task - ... -``` - -**Handle Async Results** - -Implement proper polling with backoff: - -```python -import time - -max_wait = 300 # 5 minutes -poll_interval = 2 -waited = 0 - -while waited < max_wait: - response = requests.get(f"/api/task_result/{session_id}") - result = response.json() - - if result["status"] == "completed": - return result["result"] - - time.sleep(poll_interval) - waited += poll_interval - -raise TimeoutError("Task did not complete in time") -``` - -**Use Health Checks** - -Implement health monitoring: - -```python -def is_server_healthy(): - try: - response = requests.get( - "http://localhost:8000/api/health", - timeout=5 - ) - return response.status_code == 200 - except: - return False -``` - -**Handle Errors Gracefully** - -```python -try: - response = requests.post("/api/dispatch", json=request_data) - response.raise_for_status() - return response.json() -except requests.HTTPError as e: - if e.response.status_code == 400: - print("Device not available") - elif e.response.status_code == 422: - print("Invalid request:", e.response.json()) - else: - print("Server error") -``` - -## Integration Points - -### WebSocket Handler - -API endpoints coordinate with WebSocket handler for task dispatch: - -```python -# API dispatches via WebSocket handler -device_ws = client_manager.get_client(device_id) -await session_manager.execute_task_async( - session_id=session_id, - task_name=task, - request=request_data, - websocket=device_ws, - platform_override=platform -) -``` - -### Session Manager - -API retrieves task results from session manager: - -```python -# Get session result -session = session_manager.get_session(session_id) -result = session.get_result() -``` - -### Client Connection Manager - -API queries client status from Client Connection Manager: - -```python -# Check device availability -is_connected = client_manager.is_device_connected(device_id) - -# Get all clients -clients = client_manager.get_all_clients() -``` - -## API Reference - -```python -from ufo.server.services.api import router - -# Include in FastAPI app -app.include_router(router) -``` - -**Request Models** - -```python -from pydantic import BaseModel - -class DispatchRequest(BaseModel): - device_id: str - task: str - mode: str = "normal" - plan: list = [] -``` - -For more information: - -- [Overview](./overview.md) - Server architecture -- [WebSocket Handler](./websocket_handler.md) - WebSocket communication -- [Session Manager](./session_manager.md) - Session lifecycle -- [Client Connection Manager](./client_connection_manager.md) - Connection management - diff --git a/documents/docs/server/client_connection_manager.md b/documents/docs/server/client_connection_manager.md index af621126d..ae77f6c50 100644 --- a/documents/docs/server/client_connection_manager.md +++ b/documents/docs/server/client_connection_manager.md @@ -1,14 +1,14 @@ # Client Connection Manager -!!!quote "The Client Registry" - The **ClientConnectionManager** is the central registry for all connected clients, maintaining connection state, session mappings, device information, and providing efficient lookup mechanisms for client routing and management. +The **ClientConnectionManager** is the central registry for all connected clients, maintaining connection state, session mappings, device information, and providing efficient lookup mechanisms for client routing and management. + +For more context on how this component fits into the server architecture, see the [Server Overview](overview.md). --- ## 🎯 Overview -!!!info "Core Responsibilities" - The Client Connection Manager serves as the "address book" and "session tracker" for the entire server: +The Client Connection Manager serves as the "address book" and "session tracker" for the entire server: | Responsibility | Description | Benefit | |----------------|-------------|---------| @@ -72,19 +72,22 @@ graph TB ### ClientInfo Dataclass -!!!info "Complete Client Metadata" - Each connected client is represented by a `ClientInfo` dataclass that stores all relevant connection details: +Each connected client is represented by a `ClientInfo` dataclass that stores all relevant connection details: ```python @dataclass class ClientInfo: """Information about a connected client.""" - websocket: WebSocket # Active WebSocket connection - client_type: ClientType # DEVICE or CONSTELLATION - connected_at: datetime # Connection timestamp - metadata: Dict = None # Additional client metadata - platform: str = "windows" # OS platform (windows/linux) - system_info: Dict = None # Device system information (for devices only) + websocket: WebSocket # Active WebSocket connection + client_type: ClientType # DEVICE or CONSTELLATION + connected_at: datetime # Connection timestamp + metadata: Dict = None # Additional client metadata + platform: str = "windows" # OS platform (windows/linux) + system_info: Dict = None # Device system information (for devices only) + + # AIP protocol instances for this client + transport: Optional[WebSocketTransport] = None # AIP WebSocket transport + task_protocol: Optional[TaskExecutionProtocol] = None # AIP task protocol ``` **Field Descriptions:** @@ -97,6 +100,8 @@ class ClientInfo: | `metadata` | `Dict` | Custom metadata from registration message | `{"hostname": "WIN-001"}` | | `platform` | `str` | Operating system | `"windows"`, `"linux"` | | `system_info` | `Dict` | Device capabilities and system specs | See System Info Structure below | +| `transport` | `Optional[WebSocketTransport]` | AIP WebSocket transport layer | `` | +| `task_protocol` | `Optional[TaskExecutionProtocol]` | AIP task execution protocol handler | `` | **System Info Structure Example:** @@ -121,8 +126,7 @@ class ClientInfo: ## 👥 Client Registry Management -!!!success "Central Client Database" - The client registry (`online_clients`) is the authoritative source of truth for all connected clients. +The client registry (`online_clients`) is the authoritative source of truth for all connected clients. ### Adding Clients @@ -133,7 +137,9 @@ def add_client( platform: str, ws: WebSocket, client_type: ClientType = ClientType.DEVICE, - metadata: Dict = None + metadata: Dict = None, + transport: Optional[WebSocketTransport] = None, + task_protocol: Optional[TaskExecutionProtocol] = None ): """Register a new client connection.""" @@ -156,41 +162,49 @@ def add_client( client_type=client_type, connected_at=datetime.now(), metadata=metadata or {}, - system_info=system_info + system_info=system_info, + transport=transport, + task_protocol=task_protocol ) ``` -!!!example "Adding a Device Client" - ```python - client_manager.add_client( - client_id="device_windows_001", - platform="windows", - ws=websocket, - client_type=ClientType.DEVICE, - metadata={ - "hostname": "WIN-OFFICE-01", - "system_info": { - "os": "Windows", - "screen_resolution": "1920x1080", - "installed_applications": ["Chrome", "Excel"] - } - } - ) - ``` +**Example - Adding a Device Client:** -!!!example "Adding a Constellation Client" - ```python - client_manager.add_client( - client_id="constellation_orchestrator_001", - platform="linux", # Platform of the constellation server - ws=websocket, - client_type=ClientType.CONSTELLATION, - metadata={ - "orchestrator_version": "2.0.0", - "max_concurrent_tasks": 10 +```python +client_manager.add_client( + client_id="device_windows_001", + platform="windows", + ws=websocket, + client_type=ClientType.DEVICE, + metadata={ + "hostname": "WIN-OFFICE-01", + "system_info": { + "os": "Windows", + "screen_resolution": "1920x1080", + "installed_applications": ["Chrome", "Excel"] } - ) - ``` + }, + transport=websocket_transport, + task_protocol=task_execution_protocol +) +``` + +**Example - Adding a Constellation Client:** + +```python +client_manager.add_client( + client_id="constellation_orchestrator_001", + platform="linux", # Platform of the constellation server + ws=websocket, + client_type=ClientType.CONSTELLATION, + metadata={ + "orchestrator_version": "2.0.0", + "max_concurrent_tasks": 10 + }, + transport=websocket_transport, + task_protocol=task_execution_protocol +) +``` **Thread Safety:** @@ -199,13 +213,12 @@ with self.lock: # threading.Lock ensures atomic operations self.online_clients[client_id] = client_info ``` -!!!warning "Client ID Uniqueness" +!!! warning "Client ID Uniqueness" If a client reconnects with the same `client_id`, the new connection **overwrites** the old entry. This effectively disconnects the old WebSocket. Use unique IDs to prevent collisions. ### Retrieving Clients -!!!info "Multiple Retrieval Methods" - The ClientConnectionManager provides several methods to lookup clients based on different criteria: +The ClientConnectionManager provides several methods to lookup clients based on different criteria: **Get WebSocket Connection:** ```python @@ -317,8 +330,7 @@ def remove_client(self, client_id: str): ## 🔍 Connection State Checking -!!!tip "Validate Before Dispatch" - Always check if the target device is connected before attempting to dispatch tasks. This prevents errors and improves user experience. +Always check if the target device is connected before attempting to dispatch tasks. This prevents errors and improves user experience. ### Device Connection Validation @@ -336,22 +348,23 @@ def is_device_connected(self, device_id: str) -> bool: return client_info.client_type == ClientType.DEVICE ``` -!!!example "Usage: Validate Before Task Dispatch" - ```python - # In WebSocket Handler - constellation requesting task on device - target_device_id = data.target_id - - if not client_manager.is_device_connected(target_device_id): - error_msg = f"Target device '{target_device_id}' is not connected" - await send_error(error_msg) - raise ValueError(error_msg) - - # Safe to dispatch - target_ws = client_manager.get_client(target_device_id) - await dispatch_task(target_ws, task_request) - ``` +**Example - Validate Before Task Dispatch:** + +```python +# In WebSocket Handler - constellation requesting task on device +target_device_id = data.target_id -!!!warning "Type Check is Critical" +if not client_manager.is_device_connected(target_device_id): + error_msg = f"Target device '{target_device_id}' is not connected" + await send_error(error_msg) + raise ValueError(error_msg) + +# Safe to dispatch +target_ws = client_manager.get_client(target_device_id) +await dispatch_task(target_ws, task_request) +``` + +!!! warning "Type Check is Critical" The method returns `False` if the client exists but is **not a device** (e.g., it's a constellation). This prevents accidentally dispatching device tasks to constellation clients. ### Generic Online Status Check @@ -375,13 +388,12 @@ def is_online(self, client_id: str) -> bool: ## 📋 Session Mapping -!!!info "Two-Way Session Tracking" - The ClientConnectionManager tracks sessions from **two perspectives**: - - 1. **Constellation → Sessions**: Which sessions did a constellation initiate? - 2. **Device → Sessions**: Which sessions is a device currently executing? - - This dual tracking enables proper cleanup when either constellation or device disconnects. +The ClientConnectionManager tracks sessions from **two perspectives**: + +1. **Constellation → Sessions**: Which sessions did a constellation initiate? +2. **Device → Sessions**: Which sessions is a device currently executing? + +This dual tracking enables proper cleanup when either constellation or device disconnects. ```mermaid graph TB @@ -419,8 +431,7 @@ graph TB ### Constellation Session Mapping -!!!success "Track Orchestrator Sessions" - Constellation clients initiate tasks on remote devices. Track these sessions to enable cleanup when the orchestrator disconnects. +Constellation clients initiate tasks on remote devices. Track these sessions to enable cleanup when the orchestrator disconnects. **Add Constellation Session:** @@ -456,34 +467,34 @@ def remove_constellation_sessions(self, client_id: str) -> List[str]: # Returns removed sessions for cleanup ``` -!!!example "Constellation Disconnect Cleanup" - ```python - # In WebSocket Handler - when constellation disconnects - constellation_id = "constellation_001" - - # Get all sessions this constellation initiated - session_ids = client_manager.get_constellation_sessions(constellation_id) - - logger.info( - f"Constellation {constellation_id} disconnected, " - f"cancelling {len(session_ids)} sessions" +**Example - Constellation Disconnect Cleanup:** + +```python +# In WebSocket Handler - when constellation disconnects +constellation_id = "constellation_001" + +# Get all sessions this constellation initiated +session_ids = client_manager.get_constellation_sessions(constellation_id) + +logger.info( + f"Constellation {constellation_id} disconnected, " + f"cancelling {len(session_ids)} sessions" +) + +# Cancel each session +for session_id in session_ids: + await session_manager.cancel_task( + session_id, + reason="constellation_disconnected" # Don't send callback ) - - # Cancel each session - for session_id in session_ids: - await session_manager.cancel_task( - session_id, - reason="constellation_disconnected" # Don't send callback - ) - - # Remove mappings - client_manager.remove_constellation_sessions(constellation_id) - ``` + +# Remove mappings +client_manager.remove_constellation_sessions(constellation_id) +``` ### Device Session Mapping -!!!success "Track Execution Location" - Device clients execute tasks sent by constellations (or themselves). Track these sessions to enable cleanup when the device disconnects. +Device clients execute tasks sent by constellations (or themselves). Track these sessions to enable cleanup when the device disconnects. **Add Device Session:** @@ -614,8 +625,7 @@ sequenceDiagram ## 💻 System Information Management -!!!info "Device Capabilities Caching" - The ClientConnectionManager caches device system information to enable intelligent task routing by constellations without repeatedly querying devices. +The ClientConnectionManager caches device system information to enable intelligent task routing by constellations without repeatedly querying devices. ### System Info Storage @@ -697,8 +707,7 @@ for device_id, info in all_devices.items(): ### Server Configuration Merging -!!!success "Enhanced Device Info" - The ClientConnectionManager supports loading device-specific configuration from YAML/JSON files and **merging** them with auto-detected system info. +The ClientConnectionManager supports loading device-specific configuration from YAML/JSON files and **merging** them with auto-detected system info. **Device Configuration File (`device_config.yaml`):** @@ -778,89 +787,59 @@ def _merge_device_info( } ``` -!!!tip "Why Merge?" - - **Auto-detected info**: Always accurate (OS, memory, screen resolution) - - **Server config**: Administrative metadata (tags, tier, priorities) - - **Combined**: Rich device profile for intelligent task routing +**Why Merge Configuration?** + +- **Auto-detected info**: Always accurate (OS, memory, screen resolution) +- **Server config**: Administrative metadata (tags, tier, priorities) +- **Combined**: Rich device profile for intelligent task routing --- ## 📊 Client Statistics and Monitoring -!!!info "Real-Time Metrics" - The `get_stats()` method provides comprehensive metrics for monitoring connected clients and sessions. +The `get_stats()` method provides basic metrics for monitoring connected clients. ### Get Statistics ```python -def get_stats(self) -> Dict[str, Any]: - """Get comprehensive statistics about connected clients.""" +def get_stats(self) -> Dict[str, int]: + """Get statistics about connected clients.""" with self.lock: - devices = [ - c for c in self.online_clients.values() - if c.client_type == ClientType.DEVICE - ] - - constellations = [ - c for c in self.online_clients.values() - if c.client_type == ClientType.CONSTELLATION - ] - + device_count = sum( + 1 + for info in self.online_clients.values() + if info.client_type == ClientType.DEVICE + ) + constellation_count = sum( + 1 + for info in self.online_clients.values() + if info.client_type == ClientType.CONSTELLATION + ) return { - "total_clients": len(self.online_clients), - "devices": { - "count": len(devices), - "ids": [id for id, c in self.online_clients.items() if c.client_type == ClientType.DEVICE], - "platforms": self._count_platforms(devices) - }, - "constellations": { - "count": len(constellations), - "ids": list(self._constellation_sessions.keys()) - }, - "sessions": { - "constellation_count": sum(len(s) for s in self._constellation_sessions.values()), - "device_count": sum(len(s) for s in self._device_sessions.values()) - } + "total": len(self.online_clients), + "device_clients": device_count, + "constellation_clients": constellation_count } - -def _count_platforms(self, clients: List[ClientInfo]) -> Dict[str, int]: - """Count clients by platform.""" - platforms = {} - for client in clients: - platform = client.platform or "unknown" - platforms[platform] = platforms.get(platform, 0) + 1 - return platforms ``` -!!!example "Monitoring Dashboard Usage" - ```python - # Get current statistics - stats = client_manager.get_stats() - - print(f"📊 Server Statistics:") - print(f" Total Clients: {stats['total_clients']}") - print(f" Devices: {stats['devices']['count']}") - print(f" - Windows: {stats['devices']['platforms'].get('Windows', 0)}") - print(f" - Linux: {stats['devices']['platforms'].get('Linux', 0)}") - print(f" - Darwin: {stats['devices']['platforms'].get('Darwin', 0)}") - print(f" Constellations: {stats['constellations']['count']}") - print(f" Active Sessions:") - print(f" - Constellation Sessions: {stats['sessions']['constellation_count']}") - print(f" - Device Sessions: {stats['sessions']['device_count']}") - - # Output: - # 📊 Server Statistics: - # Total Clients: 5 - # Devices: 3 - # - Windows: 2 - # - Linux: 1 - # - Darwin: 0 - # Constellations: 2 - # Active Sessions: - # - Constellation Sessions: 7 - # - Device Sessions: 4 - ``` +**Example Usage:** + +```python +# Get current statistics +stats = client_manager.get_stats() + +print(f"📊 Server Statistics:") +print(f" Total Clients: {stats['total']}") +print(f" Devices: {stats['device_clients']}") +print(f" Constellations: {stats['constellation_clients']}") + +# Output: +# 📊 Server Statistics: +# Total Clients: 5 +# Devices: 3 +# Constellations: 2 +``` ### Filtering and Querying @@ -931,65 +910,63 @@ docker_devices = client_manager.find_devices_with_capability("docker_support") ## 🎯 Usage Patterns -### Pattern 1: Task Dispatch Validation +### Safe Task Dispatch -!!!example "Safe Task Dispatch" - ```python - async def dispatch_task_to_device( - client_manager: ClientConnectionManager, - constellation_id: str, - target_device_id: str, - task_request: dict, - session_id: str - ): - """Dispatch task with comprehensive validation.""" - - # Step 1: Validate constellation is connected - if not client_manager.is_online(constellation_id): - raise ValueError(f"Constellation {constellation_id} not connected") - - # Step 2: Validate target device is connected - if not client_manager.is_device_connected(target_device_id): - raise ValueError(f"Device {target_device_id} not connected") - - # Step 3: Get device WebSocket - device_ws = client_manager.get_client(target_device_id) - if not device_ws: - raise ValueError(f"Could not get WebSocket for device {target_device_id}") - - # Step 4: Track session mappings - client_manager.add_constellation_session(constellation_id, session_id) - client_manager.add_device_session(target_device_id, session_id) - - # Step 5: Send task - await device_ws.send_json({ - "type": "TASK_ASSIGNMENT", - "session_id": session_id, - "request": task_request - }) - - logger.info( - f"Task {session_id} dispatched: " - f"{constellation_id} → {target_device_id}" - ) - ``` +```python +async def dispatch_task_to_device( + client_manager: ClientConnectionManager, + constellation_id: str, + target_device_id: str, + task_request: dict, + session_id: str +): + """Dispatch task with comprehensive validation.""" + + # Step 1: Validate constellation is connected + if not client_manager.is_online(constellation_id): + raise ValueError(f"Constellation {constellation_id} not connected") + + # Step 2: Validate target device is connected + if not client_manager.is_device_connected(target_device_id): + raise ValueError(f"Device {target_device_id} not connected") + + # Step 3: Get device WebSocket + device_ws = client_manager.get_client(target_device_id) + if not device_ws: + raise ValueError(f"Could not get WebSocket for device {target_device_id}") + + # Step 4: Track session mappings + client_manager.add_constellation_session(constellation_id, session_id) + client_manager.add_device_session(target_device_id, session_id) + + # Step 5: Send task + await device_ws.send_json({ + "type": "TASK_ASSIGNMENT", + "session_id": session_id, + "request": task_request + }) + + logger.info( + f"Task {session_id} dispatched: " + f"{constellation_id} → {target_device_id}" + ) +``` -### Pattern 2: Client Disconnect Cleanup +### Graceful Client Disconnect Handling -!!!example "Graceful Disconnection Handling" - ```python - async def handle_client_disconnect( - client_manager: ClientConnectionManager, - session_manager: SessionManager, - client_id: str, - client_type: ClientType - ): - """Handle client disconnect with full cleanup.""" - - logger.info(f"Client disconnected: {client_id} ({client_type})") - - # Step 1: Get all related sessions - if client_type == ClientType.CONSTELLATION: +```python +async def handle_client_disconnect( + client_manager: ClientConnectionManager, + session_manager: SessionManager, + client_id: str, + client_type: ClientType +): + """Handle client disconnect with full cleanup.""" + + logger.info(f"Client disconnected: {client_id} ({client_type})") + + # Step 1: Get all related sessions + if client_type == ClientType.CONSTELLATION: session_ids = client_manager.get_constellation_sessions(client_id) cancel_reason = "constellation_disconnected" else: # DEVICE @@ -1017,71 +994,69 @@ docker_devices = client_manager.find_devices_with_capability("docker_support") f"Cleanup complete: {client_id}, " f"cancelled {len(session_ids)} sessions" ) - ``` +``` -### Pattern 3: Device Selection and Routing +### Intelligent Device Selection -!!!example "Intelligent Device Selection" - ```python - def select_optimal_device( - client_manager: ClientConnectionManager, - required_platform: str = None, - required_capabilities: List[str] = None, - preferred_tags: List[str] = None - ) -> Optional[str]: - """Select the best available device for a task.""" +```python +def select_optimal_device( + client_manager: ClientConnectionManager, + required_platform: str = None, + required_capabilities: List[str] = None, + preferred_tags: List[str] = None +) -> Optional[str]: + """Select the best available device for a task.""" + + with client_manager.lock: + candidates = [] - with client_manager.lock: - candidates = [] + for device_id, client_info in client_manager.online_clients.items(): + # Filter by type + if client_info.client_type != ClientType.DEVICE: + continue - for device_id, client_info in client_manager.online_clients.items(): - # Filter by type - if client_info.client_type != ClientType.DEVICE: - continue - - # Filter by platform - if required_platform and client_info.platform != required_platform: + # Filter by platform + if required_platform and client_info.platform != required_platform: + continue + + # Filter by capabilities + if required_capabilities and client_info.system_info: + features = client_info.system_info.get("supported_features", []) + if not all(cap in features for cap in required_capabilities): continue - - # Filter by capabilities - if required_capabilities and client_info.system_info: - features = client_info.system_info.get("supported_features", []) - if not all(cap in features for cap in required_capabilities): - continue - - # Calculate score based on preferred tags - score = 0 - if preferred_tags and client_info.system_info: - tags = client_info.system_info.get("tags", []) - score = len(set(tags) & set(preferred_tags)) - - candidates.append((device_id, score)) - if not candidates: - return None + # Calculate score based on preferred tags + score = 0 + if preferred_tags and client_info.system_info: + tags = client_info.system_info.get("tags", []) + score = len(set(tags) & set(preferred_tags)) - # Return device with highest score (or first if all score 0) - candidates.sort(key=lambda x: x[1], reverse=True) - return candidates[0][0] - - # Usage - device_id = select_optimal_device( - client_manager, - required_platform="Windows", - required_capabilities=["excel_automation"], - preferred_tags=["production", "high_priority"] - ) - - if device_id: - print(f"Selected device: {device_id}") - else: - print("No suitable device available") - ``` + candidates.append((device_id, score)) + + if not candidates: + return None + + # Return device with highest score (or first if all score 0) + candidates.sort(key=lambda x: x[1], reverse=True) + return candidates[0][0] + +# Usage +device_id = select_optimal_device( + client_manager, + required_platform="Windows", + required_capabilities=["excel_automation"], + preferred_tags=["production", "high_priority"] +) + +if device_id: + print(f"Selected device: {device_id}") +else: + print("No suitable device available") +``` -### Pattern 4: Session Cleanup (After Task Completion) +### Session Cleanup After Task Completion -!!!warning "Session Mappings Persist!" - Current implementation does **not automatically remove** session mappings when tasks complete. Consider implementing this pattern: +**Note:** Current implementation does **not automatically remove** session mappings when tasks complete. Consider implementing this pattern: ```python async def handle_task_completion( @@ -1106,12 +1081,11 @@ async def handle_task_completion( --- -## Best Practices +## 💡 Best Practices -### 1. Always Use Thread Safety +### Thread Safety -!!!danger "Concurrent Access Protection" - The ClientConnectionManager is accessed by multiple WebSocket handlers concurrently. **Always** acquire the lock before modifying shared state. +The ClientConnectionManager is accessed by multiple WebSocket handlers concurrently. **Always** acquire the lock before modifying shared state. ```python # WRONG - No thread safety @@ -1130,10 +1104,9 @@ def good_example(self): return None ``` -### 2. Validate Before Dispatch +### Validate Before Dispatch -!!!tip "Prevent Runtime Errors" - Always check if the target device is connected before attempting to send messages. +Always check if the target device is connected before attempting to send messages. ```python # CORRECT - Validation first @@ -1145,10 +1118,9 @@ else: # Handle error appropriately ``` -### 3. Cleanup on Disconnect +### Cleanup on Disconnect -!!!success "Prevent Resource Leaks" - When a client disconnects, clean up **all** related resources: +When a client disconnects, clean up **all** related resources: **Checklist:** @@ -1158,12 +1130,13 @@ else: - [x] Remove device info cache (if applicable) - [x] Notify affected parties -### 4. Cache Device Info Appropriately +### Cache Device Information + +Balance freshness and performance: -!!!info "Balance Freshness and Performance" - - **Cache during registration**: Fast lookups for task routing - - **Update on REQUEST_DEVICE_LIST**: Keep cache fresh - - **Don't cache sensitive data**: Only cache non-sensitive system info +- **Cache during registration**: Fast lookups for task routing +- **Update on REQUEST_DEVICE_LIST**: Keep cache fresh +- **Don't cache sensitive data**: Only cache non-sensitive system info ```python # During registration - cache system info @@ -1179,11 +1152,9 @@ client_manager.add_client( device_info = client_manager.get_device_system_info(device_id) ``` -### 5. Handle Edge Cases +### Handle Edge Cases -!!!warning "Common Edge Cases" - - **Case 1: Client re-connects with same ID** +**Case 1: Client re-connects with same ID** ```python # Old connection still in registry if client_manager.is_online(client_id): @@ -1192,19 +1163,21 @@ device_info = client_manager.get_device_system_info(device_id) # Now add new connection client_manager.add_client(client_id, platform, ws, client_type, metadata) - ``` - - **Case 2: Session mapped to disconnected clients** - ```python +``` + +**Case 2: Session mapped to disconnected clients** + +```python # Before dispatching if not client_manager.is_device_connected(device_id): # Device disconnected, session mapping might still exist # This is expected - cleanup happens on disconnect raise ValueError(f"Device {device_id} no longer connected") - ``` - - **Case 3: Constellation and device both disconnect** - ```python +``` + +**Case 3: Constellation and device both disconnect** + +```python # Session will be cancelled twice (once for each disconnect) # Ensure cancel_task is idempotent: async def cancel_task(self, session_id, reason): @@ -1213,12 +1186,11 @@ device_info = client_manager.get_device_system_info(device_id) return # Idempotent # Proceed with cancellation - ``` +``` -### 6. Monitor Session Accumulation +### Monitor Session Accumulation -!!!bug "Potential Memory Leak" - Session mappings are **not automatically removed** after task completion. Over time, this can cause memory growth. +**Note:** Session mappings are **not automatically removed** after task completion. Over time, this can cause memory growth. **Mitigation strategies:** @@ -1419,8 +1391,7 @@ class ClientType(Enum): ## 🎓 Summary -!!!quote "The Client Registry - Core Capabilities" - The Client Connection Manager is the **central registry** for all client connections and session mappings in the UFO server. It provides thread-safe operations for: +The ClientConnectionManager is the **central registry** for all client connections and session mappings in the UFO server. It provides thread-safe operations for tracking clients, validating connectivity, mapping sessions, and caching device information. **Core Capabilities:** diff --git a/documents/docs/server/monitoring.md b/documents/docs/server/monitoring.md index 71746d805..7dffb96e7 100644 --- a/documents/docs/server/monitoring.md +++ b/documents/docs/server/monitoring.md @@ -1,7 +1,9 @@ # Monitoring and Observability -!!!quote "Production-Ready Monitoring" - Monitor the health, performance, and reliability of your UFO Server deployment with comprehensive observability tools, metrics, and alerting strategies. +Monitor the health, performance, and reliability of your UFO Server deployment with comprehensive observability tools, metrics, and alerting strategies. + +!!! tip "Before You Begin" + Make sure you have the UFO Server running. See the [Quick Start Guide](./quick_start.md) for setup instructions. ## 🎯 Overview @@ -10,33 +12,27 @@ graph TB subgraph "Monitoring Layers" Health[Health Checks] Metrics[Performance Metrics] - Logs[Logging & Analysis] - Alerts[Alerting Systems] + Logs[Logs & Analysis] + Alerts[Alerting] end subgraph "UFO Server" API[HTTP API] - WS[WebSocket Handler] - SM[Session Manager] - WSM[Client Connection Manager] + WS[WebSocket] end - subgraph "Monitoring Tools" - Prom[Prometheus] - Graf[Grafana] + subgraph "Tools" K8s[Kubernetes] - Slack[Slack/Email] + Prom[Prometheus] + Slack[Notifications] end Health --> API - Metrics --> SM - Metrics --> WSM + Metrics --> WS Logs --> WS - Logs --> SM Health --> K8s Metrics --> Prom - Metrics --> Graf Alerts --> Slack style Health fill:#bbdefb @@ -54,12 +50,13 @@ graph TB | **Logging** | Event tracking, debugging, auditing | Python logging, log aggregation | | **Alerting** | Proactive issue detection | Slack, Email, PagerDuty | -!!!info "Why Monitor?" - - **Detect Issues Early**: Catch problems before users notice - - **Performance Optimization**: Identify bottlenecks and inefficiencies - - **Capacity Planning**: Track growth and resource utilization - - **Debugging**: Trace errors and understand system behavior - - **SLA Compliance**: Ensure service level objectives are met +**Why Monitor?** + +- **Detect Issues Early**: Catch problems before users notice +- **Performance Optimization**: Identify bottlenecks and inefficiencies +- **Capacity Planning**: Track growth and resource utilization +- **Debugging**: Trace errors and understand system behavior +- **SLA Compliance**: Ensure service level objectives are met --- @@ -67,8 +64,7 @@ graph TB ### HTTP Health Endpoint -!!!success "Built-In Health Check" - The `/api/health` endpoint provides real-time server status without authentication. +The `/api/health` endpoint provides real-time server status without authentication. For detailed API specifications, see the [HTTP API Reference](./api.md). #### Endpoint Details @@ -96,69 +92,71 @@ GET /api/health | `status` | `string` | Always `"healthy"` if server is responding | | `online_clients` | `array` | List of connected client IDs | -!!!tip "Quick Test" - ```bash - # Test health endpoint - curl http://localhost:5000/api/health - - # With jq for formatted output - curl -s http://localhost:5000/api/health | jq . - ``` +**Quick Test:** + +```bash +# Test health endpoint +curl http://localhost:5000/api/health + +# With jq for formatted output +curl -s http://localhost:5000/api/health | jq . +``` ### Automated Health Monitoring #### Kubernetes Liveness and Readiness Probes -!!!example "Production Kubernetes Configuration" - ```yaml - apiVersion: v1 - kind: Pod - metadata: - name: ufo-server - labels: - app: ufo-server - spec: - containers: - - name: ufo-server - image: ufo-server:latest - ports: - - containerPort: 5000 - name: http - protocol: TCP - - # Liveness probe - restart container if failing - livenessProbe: - httpGet: - path: /api/health - port: 5000 - scheme: HTTP - initialDelaySeconds: 30 # Wait 30s after startup - periodSeconds: 10 # Check every 10s - timeoutSeconds: 5 # 5s timeout per check - successThreshold: 1 # 1 success = healthy - failureThreshold: 3 # 3 failures = restart - - # Readiness probe - remove from service if failing - readinessProbe: - httpGet: - path: /api/health - port: 5000 - scheme: HTTP - initialDelaySeconds: 10 # Wait 10s after startup - periodSeconds: 5 # Check every 5s - timeoutSeconds: 3 # 3s timeout - successThreshold: 1 # 1 success = ready - failureThreshold: 2 # 2 failures = not ready - - # Resource limits - resources: - requests: - memory: "256Mi" - cpu: "250m" - limits: - memory: "512Mi" - cpu: "500m" - ``` +Example production Kubernetes configuration: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: ufo-server + labels: + app: ufo-server +spec: + containers: + - name: ufo-server + image: ufo-server:latest + ports: + - containerPort: 5000 + name: http + protocol: TCP + + # Liveness probe - restart container if failing + livenessProbe: + httpGet: + path: /api/health + port: 5000 + scheme: HTTP + initialDelaySeconds: 30 # Wait 30s after startup + periodSeconds: 10 # Check every 10s + timeoutSeconds: 5 # 5s timeout per check + successThreshold: 1 # 1 success = healthy + failureThreshold: 3 # 3 failures = restart + + # Readiness probe - remove from service if failing + readinessProbe: + httpGet: + path: /api/health + port: 5000 + scheme: HTTP + initialDelaySeconds: 10 # Wait 10s after startup + periodSeconds: 5 # Check every 5s + timeoutSeconds: 3 # 3s timeout + successThreshold: 1 # 1 success = ready + failureThreshold: 2 # 2 failures = not ready + + # Resource limits + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" +``` **Probe Configuration Guide:** @@ -171,122 +169,128 @@ GET /api/health #### Uptime Monitoring Script -!!!example "Continuous Health Monitoring" - ```python - import requests - import time - from datetime import datetime - import logging - - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s' - ) +Example continuous health monitoring script: + +```python +import requests +import time +from datetime import datetime +import logging + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) + +class HealthMonitor: + def __init__(self, url="http://localhost:5000/api/health", interval=30): + self.url = url + self.interval = interval + self.consecutive_failures = 0 + self.uptime_start = None + self.total_checks = 0 + self.failed_checks = 0 - class HealthMonitor: - def __init__(self, url="http://localhost:5000/api/health", interval=30): - self.url = url - self.interval = interval - self.consecutive_failures = 0 - self.uptime_start = None - self.total_checks = 0 - self.failed_checks = 0 + def check_health(self): + """Perform single health check.""" + self.total_checks += 1 - def check_health(self): - """Perform single health check.""" - self.total_checks += 1 + try: + response = requests.get(self.url, timeout=5) - try: - response = requests.get(self.url, timeout=5) + if response.status_code == 200: + data = response.json() + client_count = len(data.get("online_clients", [])) - if response.status_code == 200: - data = response.json() - client_count = len(data.get("online_clients", [])) - - if self.uptime_start is None: - self.uptime_start = datetime.now() - - uptime = datetime.now() - self.uptime_start - availability = ((self.total_checks - self.failed_checks) / - self.total_checks * 100) - - logging.info( - f"✅ Server healthy - {client_count} clients connected | " - f"Uptime: {uptime} | Availability: {availability:.2f}%" - ) - - self.consecutive_failures = 0 - return True - else: - raise Exception(f"HTTP {response.status_code}") - - except Exception as e: - self.consecutive_failures += 1 - self.failed_checks += 1 - self.uptime_start = None # Reset uptime on failure + if self.uptime_start is None: + self.uptime_start = datetime.now() + + uptime = datetime.now() - self.uptime_start + availability = ((self.total_checks - self.failed_checks) / + self.total_checks * 100) - logging.error( - f"❌ Health check failed: {e} " - f"(consecutive failures: {self.consecutive_failures})" + logging.info( + f"✅ Server healthy - {client_count} clients connected | " + f"Uptime: {uptime} | Availability: {availability:.2f}%" ) - # Alert after 3 consecutive failures - if self.consecutive_failures == 3: - self.send_alert( - f"Server down for {self.consecutive_failures} checks! " - f"Last error: {e}" - ) + self.consecutive_failures = 0 + return True + else: + raise Exception(f"HTTP {response.status_code}") - return False - - def send_alert(self, message): - """Send alert (implement your alerting mechanism).""" - logging.critical(f"🚨 ALERT: {message}") - # TODO: Implement Slack/Email/PagerDuty notification - - def run(self): - """Run continuous monitoring.""" - logging.info(f"Starting health monitor (interval: {self.interval}s)") + except Exception as e: + self.consecutive_failures += 1 + self.failed_checks += 1 + self.uptime_start = None # Reset uptime on failure + + logging.error( + f"❌ Health check failed: {e} " + f"(consecutive failures: {self.consecutive_failures})" + ) + + # Alert after 3 consecutive failures + if self.consecutive_failures == 3: + self.send_alert( + f"Server down for {self.consecutive_failures} checks! " + f"Last error: {e}" + ) - while True: - self.check_health() - time.sleep(self.interval) + return False - # Run monitor - if __name__ == "__main__": - monitor = HealthMonitor(interval=30) - monitor.run() - ``` + def send_alert(self, message): + """Send alert (implement your alerting mechanism).""" + logging.critical(f"🚨 ALERT: {message}") + # TODO: Implement Slack/Email/PagerDuty notification + + def run(self): + """Run continuous monitoring.""" + logging.info(f"Starting health monitor (interval: {self.interval}s)") + + while True: + self.check_health() + time.sleep(self.interval) + +# Run monitor +if __name__ == "__main__": + monitor = HealthMonitor(interval=30) + monitor.run() +``` #### Docker Healthcheck -!!!example "Docker Compose Health Configuration" - ```yaml - version: '3.8' - - services: - ufo-server: - image: ufo-server:latest - ports: - - "5000:5000" - - # Docker health check - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:5000/api/health"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 40s - - restart: unless-stopped - - environment: - - LOG_LEVEL=INFO - - volumes: - - ./logs:/app/logs - - ./config:/app/config - ``` +Docker Compose health configuration: + +```yaml +version: '3.8' + +services: + ufo-server: + image: ufo-server:latest + ports: + - "5000:5000" + + # Docker health check + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5000/api/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 40s + + restart: unless-stopped + + environment: + - LOG_LEVEL=INFO + + volumes: + - ./logs:/app/logs + - ./config:/app/config +``` --- @@ -294,8 +298,7 @@ GET /api/health ### Request Latency Monitoring -!!!info "Track API Response Times" - Monitor endpoint latency to detect performance degradation. +Track API response times to detect performance degradation. #### Latency Measurement @@ -400,8 +403,7 @@ clients: ### Task Throughput Monitoring -!!!success "Track Task Completion Rate" - Monitor how many tasks are completed over time to detect bottlenecks. +Track task completion rate to detect bottlenecks. ```python from collections import deque @@ -457,8 +459,8 @@ print(f"Tasks in last {stats['window_seconds']}s: {stats['completions_in_window' ### Connection Stability Metrics -!!!warning "Track Client Connection Reliability" - Monitor disconnection rates to identify network or client issues. +!!! warning "Monitor Client Connection Reliability" + Track disconnection rates to identify network or client issues. For more on client management, see the [Client Connection Manager](./client_connection_manager.md) documentation. ```python from datetime import datetime, timedelta @@ -554,9 +556,10 @@ print(f"Flaky clients: {stats['flaky_clients']}") ### Log Configuration -!!!example "Production Logging Setup" - ```python - import logging +Production logging setup: + +```python +import logging import sys from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler import json @@ -645,49 +648,54 @@ print(f"Flaky clients: {stats['flaky_clients']}") # Usage setup_logging(log_level=logging.INFO, log_dir="./logs") - ``` +``` ### Log Event Categories -!!!info "Key Events to Log" - - **Connection Events:** - - ```python - logger.info(f"[WS] ✅ Registered {client_type} client: {client_id}", - extra={"client_id": client_id, "client_type": client_type}) - - logger.info(f"[WS] 🔌 Client disconnected: {client_id}", - extra={"client_id": client_id}) - ``` - - **Task Events:** - - ```python - logger.info(f"[Session] Created session {session_id} for task: {task_name}", +**Key Events to Log:** + +**Connection Events:** + +```python +# These log messages are generated by the WebSocket Handler +# See: WebSocket Handler documentation for connection lifecycle details +logger.info(f"[WS] ✅ Registered {client_type} client: {client_id}", + extra={"client_id": client_id, "client_type": client_type}) + +logger.info(f"[WS] 🔌 Client disconnected: {client_id}", + extra={"client_id": client_id}) +``` + +**Task Events:** + +```python +# These log messages are generated by the Session Manager +# See: Session Manager documentation for task lifecycle details +logger.info(f"[Session] Created session {session_id} for task: {task_name}", extra={"session_id": session_id, "task_name": task_name}) logger.info(f"[Session] Task completed: {session_id}", extra={"session_id": session_id, "duration_seconds": duration}) - logger.warning(f"[Session] Task cancelled: {session_id} (reason: {reason})", - extra={"session_id": session_id, "cancel_reason": reason}) - ``` - - **Error Events:** - - ```python - logger.error(f"[WS] ❌ Failed to send result for session {session_id}: {error}", - extra={"session_id": session_id}, exc_info=True) - - logger.error(f"[Session] Task execution failed: {session_id}", - extra={"session_id": session_id}, exc_info=True) - ``` +logger.warning(f"[Session] Task cancelled: {session_id} (reason: {reason})", + extra={"session_id": session_id, "cancel_reason": reason}) +``` + +**Error Events:** + +```python +logger.error(f"[WS] ❌ Failed to send result for session {session_id}: {error}", + extra={"session_id": session_id}, exc_info=True) + +logger.error(f"[Session] Task execution failed: {session_id}", + extra={"session_id": session_id}, exc_info=True) +``` ### Log Analysis Scripts -!!!example "Parse and Analyze JSON Logs" - ```python +Parse and analyze JSON logs: + +```python import json from collections import Counter, defaultdict from datetime import datetime @@ -757,7 +765,7 @@ print(f"Flaky clients: {stats['flaky_clients']}") # Run analysis analyze_logs("logs/ufo_server.log") - ``` +``` --- @@ -765,7 +773,9 @@ print(f"Flaky clients: {stats['flaky_clients']}") ### Alert Conditions -!!!danger "Critical Conditions to Monitor" +!!! danger "Critical Conditions to Monitor" + + Track these critical conditions to maintain server reliability. **1. No Connected Devices** @@ -1080,274 +1090,22 @@ send_alert( --- -## 📈 Monitoring Dashboard - -### Simple Web Dashboard - -!!!example "Real-Time Monitoring Dashboard" - ```html - - - - - - UFO Server Dashboard - - - -
-

🚀 UFO Server Dashboard

- -
- -
-

Server Status

-
-
Healthy
-
- - -
-

Total Clients

-
0
-
Connected
-
- - -
-

Devices

-
0
-
Online
-
- - -
-

Constellations

-
0
-
Online
-
-
- -
-

Connected Clients

-
-
- Loading... -
-
-
- -
- Last updated: Never -
-
- - - - - ``` - ---- - ## Best Practices ### 1. Monitoring Strategy -!!!tip "Layered Monitoring Approach" - - | Layer | Purpose | Frequency | - |-------|---------|-----------| - | **Health Checks** | Service availability | Every 30-60 seconds | - | **Performance Metrics** | Response times, throughput | Continuous | - | **Error Logs** | Debugging and diagnostics | Real-time | - | **Alerts** | Critical issue notification | Event-driven | +**Layered Monitoring Approach:** + +| Layer | Purpose | Frequency | +|-------|---------|-----------| +| **Health Checks** | Service availability | Every 30-60 seconds | +| **Performance Metrics** | Response times, throughput | Continuous | +| **Error Logs** | Debugging and diagnostics | Real-time | +| **Alerts** | Critical issue notification | Event-driven | ### 2. Alert Thresholds -!!!warning "Avoid Alert Fatigue" +!!! warning "Avoid Alert Fatigue" Set reasonable thresholds to prevent excessive alerting: - **No devices for > 5 minutes**: Critical @@ -1358,19 +1116,20 @@ send_alert( ### 3. Log Retention -!!!info "Log Retention Policy" - - | Log Type | Retention | Storage | - |----------|-----------|---------| - | **Detailed logs** | 7 days | Local SSD | - | **Summary logs** | 30 days | Local disk | - | **Monthly summaries** | 1 year | Archive storage | - | **Error logs** | 90 days | Separate file | +**Log Retention Policy:** + +| Log Type | Retention | Storage | +|----------|-----------|---------| +| **Detailed logs** | 7 days | Local SSD | +| **Summary logs** | 30 days | Local disk | +| **Monthly summaries** | 1 year | Archive storage | +| **Error logs** | 90 days | Separate file | ### 4. Performance Baselines -!!!success "Establish Baselines" - Track normal operating metrics to detect anomalies: +**Establish Baselines:** + +Track normal operating metrics to detect anomalies: ```python BASELINE_METRICS = { @@ -1401,48 +1160,41 @@ send_alert( def send_alert(title, message, severity="info"): channels = ALERT_ROUTING.get(severity, ["slack"]) # Send to appropriate channels... - ``` +``` --- ## 🎓 Summary -!!!quote "Comprehensive Observability" - Production monitoring requires a **layered approach** combining health checks, performance metrics, structured logging, and proactive alerting. +Production monitoring requires a **layered approach** combining health checks, performance metrics, structured logging, and proactive alerting. **Monitoring Stack:** ```mermaid -graph TB - subgraph "Data Collection" - HC[Health Endpoint] - PM[Performance Metrics] - LOG[Structured Logs] +graph LR + subgraph "Collect" + HC[Health Checks] + PM[Metrics] + LOG[Logs] end - subgraph "Storage & Analysis" - JSON[JSON Log Files] - Metrics[In-Memory Metrics] - Dashboards[Real-Time Dashboard] + subgraph "Store & Analyze" + Files[Log Files] + Dash[Dashboard] end - subgraph "Alerting" + subgraph "Alert" Rules[Alert Rules] - Slack[Slack] - Email[Email] - PD[PagerDuty] + Notify[Notifications] end - HC --> Dashboards - PM --> Metrics - LOG --> JSON - - Metrics --> Rules - JSON --> Rules + HC --> Dash + PM --> Dash + LOG --> Files - Rules --> Slack - Rules --> Email - Rules --> PD + Files --> Rules + Dash --> Rules + Rules --> Notify style HC fill:#bbdefb style PM fill:#c8e6c9 @@ -1465,501 +1217,6 @@ graph TB - [Session Manager](./session_manager.md) - Task tracking - [Quick Start](./quick_start.md) - Get started with UFO server -### Health Endpoint - -The `/api/health` endpoint provides server status: - -```bash -curl http://localhost:8000/api/health -``` - -Response: - -```json -{ - "status": "healthy", - "uptime_seconds": 86400, - "connected_clients": 5, - "active_sessions": 2 -} -``` - -### Automated Monitoring - -**Kubernetes Liveness Probe** - -```yaml -livenessProbe: - httpGet: - path: /api/health - port: 8000 - initialDelaySeconds: 30 - periodSeconds: 10 -``` - -**Prometheus Scraping** - -```yaml -scrape_configs: - - job_name: 'ufo-server' - static_configs: - - targets: ['localhost:8000'] - metrics_path: '/api/health' -``` - -**Uptime Monitoring Script** - -```python -import requests -import time - -def check_health(): - try: - response = requests.get("http://localhost:8000/api/health", timeout=5) - if response.status_code == 200: - data = response.json() - print(f"Healthy - {data['connected_clients']} clients, {data['active_sessions']} sessions") - return True - else: - print(f"Unhealthy - HTTP {response.status_code}") - return False - except Exception as e: - print(f"Error: {e}") - return False - -# Check every 30 seconds -while True: - check_health() - time.sleep(30) -``` - -## Client Monitoring - -### Connected Clients - -Query all connected clients: - -```bash -curl http://localhost:8000/api/clients -``` - -Response: - -```json -{ - "clients": [ - { - "client_id": "device_windows_001", - "type": "device", - "platform": "windows", - "connected_at": 1730736000.0, - "uptime_seconds": 3600 - }, - { - "client_id": "constellation_orchestrator", - "type": "constellation", - "platform": "linux", - "connected_at": 1730737000.0, - "uptime_seconds": 2600 - } - ], - "total": 2 -} -``` - -### Client Statistics - -**Count by Type** - -```python -import requests - -response = requests.get("http://localhost:8000/api/clients") -clients = response.json()["clients"] - -devices = [c for c in clients if c["type"] == "device"] -constellations = [c for c in clients if c["type"] == "constellation"] - -print(f"Devices: {len(devices)}") -print(f"Constellations: {len(constellations)}") -``` - -**Count by Platform** - -```python -from collections import Counter - -platforms = Counter(c["platform"] for c in clients) - -print("Clients by platform:") -for platform, count in platforms.items(): - print(f" {platform}: {count}") -``` - -**Average Uptime** - -```python -if clients: - avg_uptime = sum(c["uptime_seconds"] for c in clients) / len(clients) - print(f"Average uptime: {avg_uptime / 60:.1f} minutes") -``` - -## Server Logs - -### Log Levels - -Configure logging verbosity: - -```python -import logging - -# Set log level -logging.basicConfig(level=logging.INFO) - -# For debugging -logging.basicConfig(level=logging.DEBUG) -``` - -### Log Events - -**Connection Events** - -``` -INFO: [WS] Registered device client: device_windows_001 -INFO: [WS] 🌟 Constellation constellation_orchestrator requesting task on device_windows_001 -INFO: [WS] 🔌 Removed client: device_windows_001 -``` - -**Task Events** - -``` -INFO: [Session] Created session session_20251104_143022_abc123 -INFO: [Session] Executing task in background: Open Notepad -INFO: [Session] Task completed: session_20251104_143022_abc123 -``` - -**Error Events** - -``` -ERROR: [WS] Failed to send result for session_20251104_143022_abc123: Connection closed -WARNING: [Session] Task cancelled: session_20251104_143022_abc123 (reason: device_disconnected) -``` - -### Log Aggregation - -**File Logging** - -```bash -python -m ufo.server.app --port 8000 > server.log 2>&1 -``` - -**Structured Logging** - -```python -import json -import logging - -class JsonFormatter(logging.Formatter): - def format(self, record): - log_data = { - "timestamp": record.created, - "level": record.levelname, - "message": record.getMessage(), - "module": record.module - } - return json.dumps(log_data) - -handler = logging.StreamHandler() -handler.setFormatter(JsonFormatter()) -logging.root.addHandler(handler) -``` - -**Log Rotation** - -```python -from logging.handlers import RotatingFileHandler - -handler = RotatingFileHandler( - "server.log", - maxBytes=10*1024*1024, # 10 MB - backupCount=5 -) -logging.root.addHandler(handler) -``` - -## Performance Metrics - -### Request Latency - -Track API response times: - -```python -import time -import requests - -def measure_latency(endpoint): - start = time.time() - response = requests.get(f"http://localhost:8000{endpoint}") - latency = (time.time() - start) * 1000 # ms - - print(f"{endpoint}: {latency:.2f} ms") - return latency - -# Measure endpoints -measure_latency("/api/health") -measure_latency("/api/clients") -``` - -### Task Throughput - -Monitor task completion rate: - -```python -from collections import deque -import time - -class ThroughputMonitor: - def __init__(self, window_seconds=60): - self.window = window_seconds - self.completions = deque() - - def record_completion(self): - now = time.time() - self.completions.append(now) - - # Remove old completions outside window - cutoff = now - self.window - while self.completions and self.completions[0] < cutoff: - self.completions.popleft() - - def get_rate(self): - """Tasks per minute.""" - return len(self.completions) * (60 / self.window) - -monitor = ThroughputMonitor() - -# Record each completion -monitor.record_completion() - -# Get current rate -print(f"Throughput: {monitor.get_rate():.1f} tasks/min") -``` - -### Connection Stability - -Track disconnection rate: - -```python -class ConnectionMonitor: - def __init__(self): - self.total_connections = 0 - self.total_disconnections = 0 - - def on_connect(self): - self.total_connections += 1 - - def on_disconnect(self): - self.total_disconnections += 1 - - def get_stability(self): - if self.total_connections == 0: - return 1.0 - return 1 - (self.total_disconnections / self.total_connections) - -monitor = ConnectionMonitor() -print(f"Connection stability: {monitor.get_stability() * 100:.1f}%") -``` - -## Alerting - -### Alert Conditions - -**No Connected Devices** - -```python -def check_devices(): - response = requests.get("http://localhost:8000/api/clients") - clients = response.json()["clients"] - devices = [c for c in clients if c["type"] == "device"] - - if not devices: - send_alert("No devices connected!") -``` - -**High Error Rate** - -```python -import re - -def analyze_logs(log_file): - error_count = 0 - total_count = 0 - - with open(log_file) as f: - for line in f: - total_count += 1 - if "ERROR" in line or "WARNING" in line: - error_count += 1 - - error_rate = error_count / total_count if total_count > 0 else 0 - - if error_rate > 0.1: # 10% error rate - send_alert(f"High error rate: {error_rate * 100:.1f}%") -``` - -**Slow Response Times** - -```python -def check_latency(): - latency = measure_latency("/api/health") - - if latency > 1000: # 1 second - send_alert(f"Slow response: {latency:.0f} ms") -``` - -### Alert Delivery - -**Email Alerts** - -```python -import smtplib -from email.message import EmailMessage - -def send_alert(message): - msg = EmailMessage() - msg['Subject'] = 'UFO Server Alert' - msg['From'] = 'alerts@example.com' - msg['To'] = 'admin@example.com' - msg.set_content(message) - - with smtplib.SMTP('localhost') as s: - s.send_message(msg) -``` - -**Slack Alerts** - -```python -import requests - -def send_alert(message): - webhook_url = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" - - payload = { - "text": f"🚨 UFO Server Alert: {message}" - } - - requests.post(webhook_url, json=payload) -``` - -**PagerDuty Integration** - -```python -import requests - -def send_alert(message, severity="error"): - api_key = "YOUR_PAGERDUTY_API_KEY" - - payload = { - "routing_key": api_key, - "event_action": "trigger", - "payload": { - "summary": message, - "severity": severity, - "source": "ufo-server" - } - } - - requests.post( - "https://events.pagerduty.com/v2/enqueue", - json=payload - ) -``` - -## Dashboard - -### Simple Web Dashboard - -```html - - - - UFO Server Dashboard - - - -

UFO Server Dashboard

- -
-

Health Status

-

Loading...

-
- -
-

Connected Clients

-
    -
    - - - - -``` - -## Best Practices - -**Regular Health Checks** - -Poll `/api/health` every 30-60 seconds. - -**Client Monitoring** - -Track client connection/disconnection events to detect instability. - -**Log Analysis** - -Regularly analyze logs for errors and warnings. - -**Performance Baselines** - -Establish baseline metrics for latency and throughput to detect anomalies. - -**Alerting Thresholds** - -Set reasonable thresholds to avoid alert fatigue: -- No devices connected for > 5 minutes -- Error rate > 10% -- Response time > 2 seconds -- Session failure rate > 20% - -**Retention Policies** - -Rotate and archive logs regularly: -- Keep detailed logs for 7 days -- Keep summary logs for 30 days -- Archive monthly summaries - ## Next Steps - [Quick Start](./quick_start.md) - Get the server running diff --git a/documents/docs/server/overview.md b/documents/docs/server/overview.md index aa28107ed..9f7a66b76 100644 --- a/documents/docs/server/overview.md +++ b/documents/docs/server/overview.md @@ -1,14 +1,12 @@ # Agent Server Overview +The **Agent Server** is the central orchestration engine that transforms UFO into a distributed multi-agent system, enabling seamless task coordination across heterogeneous devices through persistent WebSocket connections and robust state management. -The **Agent Server** is the central orchestration engine that transforms UFO² into a distributed multi-agent system, enabling seamless task coordination across heterogeneous devices through persistent WebSocket connections and robust state management. - -!!!tip "Quick Start" - New to the Agent Server? Start with the [Quick Start Guide](./quick_start.md) to get up and running in minutes. +New to the Agent Server? Start with the [Quick Start Guide](./quick_start.md) to get up and running in minutes. ## What is the Agent Server? -The Agent Server is a **FastAPI-based asynchronous WebSocket server** that serves as the communication hub for UFO²'s distributed architecture. It bridges constellation orchestrators, device agents, and external systems through a unified protocol interface. +The Agent Server is a **FastAPI-based asynchronous WebSocket server** that serves as the communication hub for UFO's distributed architecture. It bridges constellation orchestrators, device agents, and external systems through a unified protocol interface. ### Core Responsibilities @@ -20,46 +18,42 @@ The Agent Server is a **FastAPI-based asynchronous WebSocket server** that serve | **🌐 Dual API Interface** | WebSocket (AIP) + HTTP (REST) endpoints | Flexible integration options | | **🛡️ Resilience** | Handles disconnections, timeouts, failures gracefully | Production-grade reliability | -!!!success "Why Use the Agent Server?" - - **Centralized Control**: Single point of orchestration for multi-device workflows - - **Protocol Abstraction**: Clients communicate via [AIP](../aip/overview.md), hiding network complexity - - **Async by Design**: Non-blocking execution enables high concurrency - - **Platform Agnostic**: Supports Windows, Linux, macOS (in development) +**Why Use the Agent Server?** + +- **Centralized Control**: Single point of orchestration for multi-device workflows +- **Protocol Abstraction**: Clients communicate via [AIP](../aip/overview.md), hiding network complexity +- **Async by Design**: Non-blocking execution enables high concurrency +- **Platform Agnostic**: Supports Windows, Linux, macOS (in development) -!!!info "Server-Client Architecture" - The Agent Server is part of UFO's distributed **server-client architecture**, where it handles orchestration and state management while [Agent Clients](../client/overview.md) handle command execution. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md) for the complete design rationale and communication patterns. +The Agent Server is part of UFO's distributed **server-client architecture**, where it handles orchestration and state management while [Agent Clients](../client/overview.md) handle command execution. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md) for the complete design rationale and communication patterns. --- ## Architecture -!!!info "Three-Tier Service Architecture" - The server follows a clean separation of concerns with distinct layers for web service, connection management, and protocol handling. +The server follows a clean separation of concerns with distinct layers for web service, connection management, and protocol handling. ### Architectural Overview **Component Interaction Diagram:** -The following diagram shows how the FastAPI application layer delegates to specialized managers and handlers: - ```mermaid graph TB - subgraph "Web Service Layer" - FastAPI[FastAPI Application] - HTTP[HTTP REST API
    /api/*] - WS[WebSocket Endpoint
    /ws] + subgraph "Web Layer" + FastAPI[FastAPI App] + HTTP[HTTP API] + WS[WebSocket /ws] end subgraph "Service Layer" - WSM[Client Connection Manager
    Connection Registry] - SM[Session Manager
    Execution Lifecycle] - WSH[WebSocket Handler
    AIP Protocol] + WSM[Client Manager] + SM[Session Manager] + WSH[WebSocket Handler] end - subgraph "External Interfaces" + subgraph "Clients" DC[Device Clients] CC[Constellation Clients] - EXT[External Systems] end FastAPI --> HTTP @@ -71,11 +65,9 @@ graph TB WSH --> WSM WSH --> SM - WSM --> SM DC -->|WebSocket| WS CC -->|WebSocket| WS - EXT -->|HTTP| HTTP style FastAPI fill:#e1f5ff style WSM fill:#fff4e1 @@ -94,11 +86,11 @@ This layered design ensures each component has a single, well-defined responsibi | **Session Manager** | Execution lifecycle | ✅ Platform-specific session creation
    ✅ Background async task execution
    ✅ Result callback delivery
    ✅ Session cancellation | | **WebSocket Handler** | Protocol implementation | ✅ AIP message parsing/routing
    ✅ Client registration
    ✅ Heartbeat monitoring
    ✅ Task/command dispatch | -!!!note "Component Documentation" - - [Session Manager](./session_manager.md) - Session lifecycle and background execution - - [Client Connection Manager](./client_connection_manager.md) - Connection registry and client tracking - - [WebSocket Handler](./websocket_handler.md) - AIP protocol message handling - - [HTTP API](./api.md) - REST endpoint specifications +**Component Documentation:** +- [Session Manager](./session_manager.md) - Session lifecycle and background execution +- [Client Connection Manager](./client_connection_manager.md) - Connection registry and client tracking +- [WebSocket Handler](./websocket_handler.md) - AIP protocol message handling +- [HTTP API](./api.md) - REST endpoint specifications --- @@ -106,8 +98,7 @@ This layered design ensures each component has a single, well-defined responsibi ### 1. Multi-Client Coordination -!!!info "Dual Client Model" - The server supports two distinct client types with different roles in the distributed architecture. +The server supports two distinct client types with different roles in the distributed architecture. **Client Type Comparison:** @@ -134,10 +125,7 @@ See [Agent Client Overview](../client/overview.md) for detailed client architect - Coordinate complex cross-device DAG execution - Aggregate results from multiple devices -!!!tip "Connection Flow" - Both client types connect to `/ws` and register using the `REGISTER` message. The server differentiates behavior based on `client_type` field. - - For the complete server-client architecture and design rationale, see [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md). +Both client types connect to `/ws` and register using the `REGISTER` message. The server differentiates behavior based on `client_type` field. For the complete server-client architecture and design rationale, see [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md). See [Quick Start](./quick_start.md) for registration examples. @@ -145,26 +133,23 @@ See [Quick Start](./quick_start.md) for registration examples. ### 2. Session Lifecycle Management -!!!success "Stateful Execution Model" - Unlike stateless HTTP servers, the Agent Server maintains **session state** throughout task execution, enabling multi-turn interactions and result callbacks. +Unlike stateless HTTP servers, the Agent Server maintains **session state** throughout task execution, enabling multi-turn interactions and result callbacks. **Session Lifecycle State Machine:** ```mermaid stateDiagram-v2 [*] --> Created: create_session() - Created --> Running: Start background execution - Running --> Running: Multi-turn commands - Running --> Completed: Task succeeds - Running --> Failed: Task fails - Running --> Cancelled: Client disconnect - Completed --> [*]: Cleanup resources - Failed --> [*]: Cleanup resources - Cancelled --> [*]: Cleanup resources + Created --> Running: Start execution + Running --> Completed: Success + Running --> Failed: Error + Running --> Cancelled: Disconnect + Completed --> [*] + Failed --> [*] + Cancelled --> [*] note right of Running - Async execution in background - Results via callback + Async background execution Non-blocking server end note ``` @@ -179,7 +164,7 @@ stateDiagram-v2 | **Failed** | `TASK_END` (error) | Error callback delivery | Error logged | | **Cancelled** | Client disconnect | Cancel async task, cleanup | Session removed | -!!!warning "Platform-Specific Implementations" +!!!warning "Platform-Specific Sessions" The SessionManager creates different session types based on the target platform: - **Windows**: `WindowsSession` with UI automation support - **Linux**: `LinuxSession` with bash automation @@ -190,15 +175,14 @@ stateDiagram-v2 - ✅ **Platform abstraction**: Hides Windows/Linux differences - ✅ **Background execution**: Non-blocking async task execution - ✅ **Callback routing**: Delivers results via WebSocket -- **Resource cleanup**: Cancels tasks on disconnect -- **Result caching**: Stores results for HTTP retrieval +- ✅ **Resource cleanup**: Cancels tasks on disconnect +- ✅ **Result caching**: Stores results for HTTP retrieval --- ### 3. Resilient Communication -!!!info "Built on AIP" - The server implements the [Agent Interaction Protocol (AIP)](../aip/overview.md), providing structured, type-safe communication with automatic failure handling. +The server implements the [Agent Interaction Protocol (AIP)](../aip/overview.md), providing structured, type-safe communication with automatic failure handling. **Protocol Features:** @@ -214,33 +198,26 @@ stateDiagram-v2 ```mermaid sequenceDiagram - participant Client as Device/Constellation - participant Server as Agent Server + participant Client + participant Server participant SM as Session Manager Client-xServer: Connection lost - Server->>Server: Detect disconnection - Server->>SM: Cancel sessions for client - SM->>SM: Cancel background tasks + Server->>SM: Cancel sessions SM->>SM: Cleanup resources Server->>Server: Remove from registry - alt Client is Device - Server->>CC: Notify constellation of failure - end - Note over Server: Client can reconnect
    with same client_id ``` -!!!danger "Session Cancellation on Disconnect" +!!!danger "Important: Session Cancellation on Disconnect" When a client disconnects (device or constellation), **all associated sessions are immediately cancelled** to prevent orphaned tasks and resource leaks. --- ### 4. Dual API Interface -!!!tip "Flexible Integration Options" - The server provides two API styles to support different integration patterns: real-time WebSocket for agents and simple HTTP for external systems. +The server provides two API styles to support different integration patterns: real-time WebSocket for agents and simple HTTP for external systems. **WebSocket API (AIP-based)** @@ -278,7 +255,7 @@ Purpose: Task dispatch and monitoring from external systems (HTTP clients, CI/CD | Endpoint | Method | Purpose | Authentication | |----------|--------|---------|----------------| | `/api/dispatch` | POST | Dispatch task to device | Optional (if configured) | -| `/api/task_result/{id}` | GET | Retrieve task results | Optional | +| `/api/task_result/{task_name}` | GET | Retrieve task results | Optional | | `/api/clients` | GET | List connected clients | Optional | | `/api/health` | GET | Server health check | None | @@ -288,14 +265,15 @@ Purpose: Task dispatch and monitoring from external systems (HTTP clients, CI/CD curl -X POST http://localhost:5000/api/dispatch \ -H "Content-Type: application/json" \ -d '{ + "client_id": "my_windows_device", "request": "Open Notepad and type Hello World", - "target_id": "windows_agent_001" + "task_name": "test_task_001" }' - # Response: {"session_id": "session_abc123"} + # Response: {"status": "dispatched", "session_id": "session_abc123", "task_name": "test_task_001"} # Retrieve results - curl http://localhost:5000/api/task_result/session_abc123 + curl http://localhost:5000/api/task_result/test_task_001 ``` See [HTTP API Reference](./api.md) for complete endpoint documentation. @@ -316,11 +294,11 @@ sequenceDiagram participant WSH as WebSocket Handler participant DC as Device Client - EXT->>HTTP: POST /api/dispatch
    {request, target_id} + EXT->>HTTP: POST /api/dispatch
    {client_id, request, task_name} HTTP->>SM: create_session() SM->>SM: Create platform session SM-->>HTTP: session_id - HTTP-->>EXT: 200 {session_id} + HTTP-->>EXT: 200 {session_id, task_name} SM->>WSH: send_task(session_id, task) WSH->>DC: TASK message (AIP) @@ -387,14 +365,13 @@ sequenceDiagram Server->>CC: TASK_END (both tasks) ``` -The server acts as a message router, forwarding tasks to target devices and routing results back to the constellation orchestrator. +The server acts as a message router, forwarding tasks to target devices and routing results back to the constellation orchestrator. See [Constellation Documentation](../galaxy/overview.md) for more details on multi-device orchestration. --- ## Platform Support -!!!info "Cross-Platform Compatibility" - The server automatically detects client platforms and creates appropriate session implementations. +The server automatically detects client platforms and creates appropriate session implementations. **Supported Platforms:** @@ -406,27 +383,27 @@ The server acts as a message router, forwarding tasks to target devices and rout **Platform Auto-Detection:** -```python -# Server auto-detects platform from client registration -# Or override globally with --platform flag +The server automatically detects the client's platform during registration. You can override this globally with the `--platform` flag when needed for testing or specific deployment scenarios. +```bash python -m ufo.server.app --platform windows # Force Windows sessions python -m ufo.server.app --platform linux # Force Linux sessions python -m ufo.server.app # Auto-detect (default) ``` -!!!warning "Platform Override Use Cases" +!!!warning "When to Use Platform Override" Use `--platform` override when: - Testing cross-platform sessions without actual devices - Running server in container different from target platform - Debugging platform-specific session behavior +For more details on platform-specific implementations, see [Windows Agent](../linux/overview.md) and [Linux Agent](../linux/overview.md). + --- ## Configuration -!!!tip "Minimal Configuration Required" - The server runs out-of-the-box with sensible defaults. Advanced configuration inherits from UFO's central config system. +The server runs out-of-the-box with sensible defaults. Advanced configuration inherits from UFO's central config system. ### Command-Line Arguments @@ -476,8 +453,7 @@ See [Configuration Guide](../configuration/system/overview.md) for comprehensive ### Health Monitoring -!!!success "Built-in Health Checks" - Monitor server status and performance using HTTP endpoints. +Monitor server status and performance using HTTP endpoints. **Health Check Endpoints:** @@ -488,8 +464,7 @@ curl http://localhost:5000/api/health # Response: # { # "status": "healthy", -# "uptime_seconds": 3600, -# "connected_clients": 5 +# "online_clients": [...] # } # Connected clients list @@ -497,23 +472,15 @@ curl http://localhost:5000/api/clients # Response: # { -# "clients": [ -# {"client_id": "windows_001", "type": "device", "connected_at": "2025-01-01T10:00:00Z"}, -# {"client_id": "constellation_main", "type": "constellation", "connected_at": "2025-01-01T10:05:00Z"} -# ] +# "online_clients": ["windows_001", "linux_002", ...] # } ``` -See [Monitoring Guide](./monitoring.md) for comprehensive monitoring strategies including: -- Performance metrics collection -- Log aggregation patterns -- Alert configuration -- Dashboard setup +For comprehensive monitoring strategies including performance metrics collection, log aggregation patterns, alert configuration, and dashboard setup, see [Monitoring Guide](./monitoring.md). ### Error Handling -!!!danger "Failure Scenarios" - The server handles common failure scenarios gracefully to maintain system stability. +The server handles common failure scenarios gracefully to maintain system stability. **Disconnection Handling Matrix:** @@ -525,15 +492,8 @@ See [Monitoring Guide](./monitoring.md) for comprehensive monitoring strategies | **Network Partition** | Heartbeat timeout | Mark disconnected, enable reconnection | Client reconnects with same ID | | **Server Crash** | N/A | Clients detect via heartbeat | Clients reconnect to new instance | -!!!note "Reconnection Behavior" - Clients can reconnect with the same `client_id`. The server will: - - Re-register the client - - Restore heartbeat monitoring - - **Not restore previous sessions** (sessions are ephemeral) - ---- - -## Best Practices +!!!note "Reconnection Support" + Clients can reconnect with the same `client_id`. The server will re-register the client and restore heartbeat monitoring, but **will not restore previous sessions** (sessions are ephemeral). --- @@ -541,8 +501,7 @@ See [Monitoring Guide](./monitoring.md) for comprehensive monitoring strategies ### Development Environment -!!!tip "Local Development Setup" - Optimize your development workflow with these recommended practices. +Optimize your development workflow with these recommended practices. **Recommended Development Configuration:** @@ -575,13 +534,12 @@ python -m ufo.server.app \ # Terminal 3: Dispatch test task curl -X POST http://127.0.0.1:5000/api/dispatch \ -H "Content-Type: application/json" \ - -d '{"request": "Open Notepad", "target_id": "windowsagent"}' + -d '{"client_id": "windowsagent", "request": "Open Notepad", "task_name": "test_001"}' ``` ### Production Deployment -!!!warning "Production Hardening Required" - The default configuration is **not production-ready**. Implement these security and reliability measures. +The default configuration is **not production-ready**. Implement these security and reliability measures. **Production Architecture:** @@ -631,63 +589,43 @@ graph LR | **Logging** | Structured logging, log aggregation (ELK) | Centralized debugging and audit trails | | **Resource Limits** | Set max connections, memory limits | Prevent resource exhaustion | -!!!example "Nginx Reverse Proxy Configuration" - ```nginx - upstream ufo_server { - server localhost:5000; - server localhost:5001; - server localhost:5002; +**Example Nginx Configuration:** + +```nginx +upstream ufo_server { + server localhost:5000; +} + +server { + listen 443 ssl; + server_name ufo-server.example.com; + + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + # WebSocket endpoint + location /ws { + proxy_pass http://ufo_server; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 3600s; } - server { - listen 443 ssl; - server_name ufo-server.example.com; - - ssl_certificate /path/to/cert.pem; - ssl_certificate_key /path/to/key.pem; - - # WebSocket endpoint - location /ws { - proxy_pass http://ufo_server; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_read_timeout 3600s; # Long timeout for persistent connections - } - - # HTTP API - location /api/ { - proxy_pass http://ufo_server; - proxy_set_header Host $host; - } + # HTTP API + location /api/ { + proxy_pass http://ufo_server; + proxy_set_header Host $host; } - ``` +} +``` -!!!example "Systemd Service File" - ```ini - [Unit] - Description=UFO Agent Server - After=network.target - - [Service] - Type=simple - User=ufo - WorkingDirectory=/opt/ufo - ExecStart=/opt/ufo/venv/bin/python -m ufo.server.app --port 5000 --log-level INFO - Restart=always - RestartSec=10 - StandardOutput=append:/var/log/ufo/server.log - StandardError=append:/var/log/ufo/server-error.log - - [Install] - WantedBy=multi-user.target - ``` + ### Scaling Strategies -!!!info "Horizontal Scaling Considerations" - The server can scale horizontally for high-load deployments, but requires careful session management. +The server can scale horizontally for high-load deployments, but requires careful session management. **Scaling Patterns:** @@ -697,10 +635,10 @@ graph LR | **Horizontal (Sticky Sessions)** | Multiple instances with session affinity | 100-1000 clients | Load balancer routes same client to same instance | | **Horizontal (Shared State)** | Multiple instances with Redis | > 1000 clients | Requires session state externalization | -!!!warning "Current Limitation: Sticky Sessions Required" - The current implementation stores session state in-memory. For horizontal scaling, use **sticky sessions** (client affinity) in your load balancer to route clients to consistent server instances. - - **Future**: Shared state backend (Redis) for true stateless horizontal scaling. +!!!warning "Current Limitation" + The current implementation stores session state in-memory. For horizontal scaling, use **sticky sessions** (client affinity) in your load balancer to route clients to consistent server instances. **Future**: Shared state backend (Redis) for true stateless horizontal scaling. + + --- @@ -708,8 +646,6 @@ graph LR ### Common Issues -!!!bug "Common Problems and Solutions" - **Issue: Clients Can't Connect** ```bash @@ -739,7 +675,7 @@ python -m ufo.server.app --host 0.0.0.0 --port 5000 # Solution: # Ensure client_id in request matches registered client curl -X POST http://localhost:5000/api/dispatch \ - -d '{"request": "test", "target_id": "correct_client_id"}' + -d '{"client_id": "correct_client_id", "request": "test", "task_name": "test_001"}' ``` **Issue: Memory Leak / High Memory Usage** @@ -776,6 +712,8 @@ curl -X POST http://localhost:5000/api/dispatch \ ## Documentation Map +Explore related documentation to deepen your understanding of the Agent Server ecosystem. + ### Getting Started | Document | Purpose | @@ -803,16 +741,16 @@ curl -X POST http://localhost:5000/api/dispatch \ | Document | Purpose | |----------|---------| | [AIP Protocol](../aip/overview.md) | Communication protocol specification | -| [Configuration](../configuration/system/overview.md) | UFO configuration system | -| [Agents](../infrastructure/agents/overview.md) | Agent architecture and design | +| [Agent Architecture](../infrastructure/agents/overview.md) | Agent design and FSM framework | +| [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md) | Distributed architecture rationale | | [Client Overview](../client/overview.md) | Device client architecture | +| [MCP Integration](../mcp/overview.md) | Model Context Protocol tool servers | --- ## Next Steps -!!!quote "Learning Path" - Follow this recommended sequence to master the Agent Server: +Follow this recommended sequence to master the Agent Server: **1. Run the Server** (5 minutes) - Follow the [Quick Start Guide](./quick_start.md) @@ -839,3 +777,4 @@ curl -X POST http://localhost:5000/api/dispatch \ - Implement monitoring - Test failover scenarios + \ No newline at end of file diff --git a/documents/docs/server/quick_start.md b/documents/docs/server/quick_start.md index 2ce78de46..19392c6de 100644 --- a/documents/docs/server/quick_start.md +++ b/documents/docs/server/quick_start.md @@ -1,26 +1,24 @@ # Quick Start -!!!quote "Get Up and Running in 5 Minutes" - This hands-on guide walks you through starting the UFO Agent Server, connecting clients, and dispatching your first task. Perfect for first-time users. +This hands-on guide walks you through starting the UFO Agent Server, connecting clients, and dispatching your first task. Perfect for first-time users. --- ## 📋 Prerequisites -!!!info "Requirements Checklist" - Before you begin, ensure you have: - - - **Python 3.10+** installed - - **UFO² dependencies** installed (`pip install -r requirements.txt`) - - **Network connectivity** for WebSocket connections - - **Terminal access** (PowerShell, bash, or equivalent) +Before you begin, ensure you have: + +- **Python 3.10+** installed +- **UFO dependencies** installed (`pip install -r requirements.txt`) +- **Network connectivity** for WebSocket connections +- **Terminal access** (PowerShell, bash, or equivalent) | Component | Minimum Version | Recommended | |-----------|----------------|-------------| | Python | 3.10 | 3.11+ | | FastAPI | 0.104+ | Latest | | Uvicorn | 0.24+ | Latest | -| UFO² | - | Latest commit | +| UFO | - | Latest commit | --- @@ -28,79 +26,73 @@ ### Basic Startup -!!!example "Minimal Command" - Start the server with default settings (port **5000**): - - ```bash - python -m ufo.server.app - ``` +Start the server with default settings (port **5000**): + +```bash +python -m ufo.server.app +``` **Expected Output:** ```console 2024-11-04 14:30:22 - ufo.server.app - INFO - Starting UFO Server on 0.0.0.0:5000 2024-11-04 14:30:22 - ufo.server.app - INFO - Platform: auto-detected -2024-11-04 14:30:22 - ufo.server.app - INFO - Log level: INFO +2024-11-04 14:30:22 - ufo.server.app - INFO - Log level: WARNING INFO: Started server process [12345] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:5000 (Press CTRL+C to quit) ``` -!!!success "Server Running" - Once you see "Uvicorn running", the server is ready to accept WebSocket connections at `ws://0.0.0.0:5000/ws` +Once you see "Uvicorn running", the server is ready to accept WebSocket connections at `ws://0.0.0.0:5000/ws`. ### Configuration Options -!!!tip "CLI Arguments Reference" - The server supports several command-line arguments for customization. All arguments are optional. - | Argument | Type | Default | Description | Example | |----------|------|---------|-------------|---------| | `--port` | int | `5000` | Server listening port | `--port 8080` | | `--host` | str | `0.0.0.0` | Bind address (0.0.0.0 = all interfaces) | `--host 127.0.0.1` | | `--platform` | str | `auto` | Platform override (`windows`, `linux`) | `--platform windows` | -| `--log-level` | str | `INFO` | Logging verbosity | `--log-level DEBUG` | +| `--log-level` | str | `WARNING` | Logging verbosity | `--log-level DEBUG` | | `--local` | flag | `False` | Restrict to localhost connections only | `--local` | -!!!example "Common Startup Configurations" +**Common Startup Configurations:** - **Development (Local Only):** - ```bash - python -m ufo.server.app --local --log-level DEBUG - ``` - - Accepts connections only from `localhost` - - Verbose debug logging - - Default port 5000 - - **Custom Port:** - ```bash - python -m ufo.server.app --port 8080 - ``` - - Useful if port 5000 is already in use - - Accessible from network - - **Production (Linux):** - ```bash - python -m ufo.server.app --port 5000 --platform linux --log-level WARNING - ``` - - Explicit platform specification - - Reduced logging for performance - - Production-ready configuration - - **Multi-Interface Binding:** - ```bash - python -m ufo.server.app --host 192.168.1.100 --port 5000 - ``` - - Binds to specific network interface - - Useful for multi-homed servers +**Development (Local Only):** +```bash +python -m ufo.server.app --local --log-level DEBUG +``` +- Accepts connections only from `localhost` +- Verbose debug logging +- Default port 5000 + +**Custom Port:** +```bash +python -m ufo.server.app --port 8080 +``` +- Useful if port 5000 is already in use +- Accessible from network + +**Production (Linux):** +```bash +python -m ufo.server.app --port 5000 --platform linux --log-level WARNING +``` +- Explicit platform specification +- Reduced logging for performance +- Production-ready configuration + +**Multi-Interface Binding:** +```bash +python -m ufo.server.app --host 192.168.1.100 --port 5000 +``` +- Binds to specific network interface +- Useful for multi-homed servers --- -## 🖥Connecting Device Clients +## 🖥️ Connecting Device Clients -!!!info "What is a Device Client?" - A Device Client is an agent running on a physical or virtual machine that can execute tasks. Each device connects via WebSocket and registers with a unique `client_id`. +A Device Client is an agent running on a physical or virtual machine that can execute tasks. Each device connects via WebSocket and registers with a unique `client_id`. Once the server is running, connect device agents using the command line: @@ -116,12 +108,10 @@ python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id my_linux_device --platform linux ``` - -!!!success "Registration Success Indicator" - When a client connects successfully, the server logs will display: - ```console - INFO: [WS] Registered device client: my_windows_device - ``` +When a client connects successfully, the server logs will display: +```console +INFO: [WS] 📱 Device client my_windows_device connected +``` ### Client Connection Parameters @@ -132,11 +122,11 @@ python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id | `--client-id` | Yes | string | Unique device identifier (must be unique across all clients) | `device_win_001` | | `--platform` | ⚠️ Optional | string | Platform type: `windows`, `linux` | `--platform windows` | -!!!warning "Client ID Uniqueness" +!!!warning "Important: Client ID Uniqueness" Each `client_id` must be globally unique. If a client connects with an existing ID, the old connection will be terminated. !!!tip "Platform Auto-Detection" - If you don't specify `--platform`, the client will auto-detect the operating system using Python's `platform.system()`. However, **explicit specification is recommended** for clarity and to avoid edge cases. + If you don't specify `--platform`, the client will auto-detect the operating system. However, **explicit specification is recommended** for clarity. ### Registration Protocol Flow @@ -144,33 +134,24 @@ python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id sequenceDiagram participant C as Device Client participant S as Agent Server - participant M as ClientConnectionManager - Note over C,S: 1️⃣ WebSocket Connection - C->>+S: WebSocket CONNECT to /ws + C->>S: WebSocket CONNECT /ws S-->>C: Connection accepted - Note over C,S: 2️⃣ AIP Registration Protocol - C->>S: REGISTER message
    {client_id, client_type, platform} - S->>S: Validate client_id uniqueness - S->>M: Register client in ClientConnectionManager - - Note over C,S: 3️⃣ Confirmation - S-->>-C: REGISTER_CONFIRM
    {status: "success"} + C->>S: REGISTER
    {client_id, platform} + S->>S: Validate & register + S-->>C: REGISTER_CONFIRM Note over C: Client Ready - C->>C: Start task listening loop ``` -!!!note "AIP Protocol Integration" - The registration process uses the **Agent Interaction Protocol (AIP)** for structured, reliable communication. See [AIP Documentation](../aip/overview.md) for protocol details. +The registration process uses the **Agent Interaction Protocol (AIP)** for structured communication. See [AIP Documentation](../aip/overview.md) for details. --- ## 🌌 Connecting Constellation Clients -!!!info "What is a Constellation Client?" - A Constellation Client is an orchestrator that coordinates multi-device tasks. It connects to the server and can dispatch work across multiple registered device clients. +A Constellation Client is an orchestrator that coordinates multi-device tasks. It connects to the server and can dispatch work across multiple registered device clients. ### Basic Constellation Connection @@ -186,14 +167,10 @@ python -m galaxy.constellation.constellation --ws --ws-server ws://127.0.0.1:500 | `--ws-server` | Yes | Server WebSocket URL | `ws://127.0.0.1:5000/ws` | | `--target-id` | ⚠️ Optional | Initial target device ID for tasks | `my_windows_device` | -!!!danger "Target Device Must Be Online" - If you specify `--target-id`, that device **must already be connected** to the server. Otherwise, the constellation registration will fail with: - ``` - Target device 'my_windows_device' is not connected - ``` +!!!danger "Important: Target Device Must Be Online" + If you specify `--target-id`, that device **must already be connected** to the server. Otherwise, registration will fail with: `Target device 'my_windows_device' is not connected` -!!!tip "Multi-Device Orchestration" - A constellation can dynamically dispatch tasks to different devices, not just the `target-id`. The `target-id` is primarily used for initial routing. +A constellation can dynamically dispatch tasks to different devices, not just the `target-id`. For more on multi-device orchestration, see [Constellation Documentation](../galaxy/overview.md). --- @@ -201,78 +178,58 @@ python -m galaxy.constellation.constellation --ws --ws-server ws://127.0.0.1:500 ### Method 1: Check Connected Clients -!!!example "List All Connected Clients" - Use the HTTP API to verify connections: - - ```bash - curl http://localhost:5000/api/clients - ``` +Use the HTTP API to verify connections: + +```bash +curl http://localhost:5000/api/clients +``` **Expected Response:** ```json { - "online_clients": [ - { - "client_id": "my_windows_device", - "client_type": "device", - "platform": "windows", - "connected_at": "2024-11-04T14:30:45.123456", - "uptime_seconds": 120 - } - ] + "online_clients": ["my_windows_device", "my_linux_device"] } ``` -!!!success "Client Connected" - If you see your `client_id` in the `online_clients` list, the device is successfully connected and ready to receive tasks. +If you see your `client_id` in the list, the device is successfully connected and ready to receive tasks. ### Method 2: Health Check -!!!example "Server Health Endpoint" - ```bash - curl http://localhost:5000/api/health - ``` +```bash +curl http://localhost:5000/api/health +``` **Expected Response:** ```json { "status": "healthy", - "online_clients": [ - { - "client_id": "my_windows_device", - "client_type": "device", - "platform": "windows" - } - ] + "online_clients": ["my_windows_device"] } ``` -!!!tip "Monitoring Tip" - The `/api/health` endpoint is useful for health checks in production monitoring systems (e.g., Prometheus, Nagios). +The `/api/health` endpoint is useful for health checks in production monitoring systems. --- ## 🎯 Dispatching Your First Task -!!!info "Task Dispatch via HTTP API" - The easiest way to send a task to a connected device is through the HTTP `/api/dispatch` endpoint. This is ideal for external integrations, scripts, or manual testing. +The easiest way to send a task to a connected device is through the HTTP `/api/dispatch` endpoint. ### Basic Task Dispatch -!!!example "Send a Simple Task" - Use the HTTP API to dispatch a task to a connected device: - - ```bash - curl -X POST http://localhost:5000/api/dispatch \ - -H "Content-Type: application/json" \ - -d '{ - "client_id": "my_windows_device", - "request": "Open Notepad and type Hello World", - "task_name": "test_task_001" - }' - ``` +Use the HTTP API to dispatch a task to a connected device: + +```bash +curl -X POST http://localhost:5000/api/dispatch \ + -H "Content-Type: application/json" \ + -d '{ + "client_id": "my_windows_device", + "request": "Open Notepad and type Hello World", + "task_name": "test_task_001" + }' +``` **Request Body Parameters:** @@ -293,56 +250,36 @@ python -m galaxy.constellation.constellation --ws --ws-server ws://127.0.0.1:500 } ``` -!!!success "Task Dispatched" - The `status: "dispatched"` indicates the task was successfully sent to the device via AIP protocol. The device will begin executing immediately. +The `status: "dispatched"` indicates the task was successfully sent to the device. The device will begin executing immediately. !!!warning "Client Must Be Online" - If the target `client_id` is not connected, you'll receive: - ```json - { - "detail": "Client not online" - } - ``` - Use `/api/clients` to verify the device is connected first. + If the target `client_id` is not connected, you'll receive `{"detail": "Client not online"}`. Use `/api/clients` to verify the device is connected first. ### Task Execution Flow ```mermaid sequenceDiagram participant API as HTTP Client - participant S as Agent Server - participant D as Device Client - participant U as UFO Agent - - Note over API,S: 1️⃣ Task Submission - API->>S: POST /api/dispatch
    {client_id, request, task_name} - S->>S: Validate client_id is online - S->>S: Generate session_id & response_id - - Note over S,D: 2️⃣ AIP Task Assignment - S->>D: TASK_ASSIGNMENT
    {user_request, task_name, session_id} - D->>U: Start UFO round execution - - Note over D,U: 3️⃣ Task Execution - U->>U: Parse request Select actions Execute - U-->>D: Execution result - - Note over D,S: 4️⃣ Result Reporting - D->>S: TASK_RESULT
    {status, result, session_id} - - Note over API,S: 5️⃣ Result Retrieval - API->>S: GET /api/task_result/{task_name} - S-->>API: {status: "done", result} + participant S as Server + participant D as Device + + API->>S: POST /api/dispatch + S->>D: TASK (AIP) + D->>D: Execute task + D->>S: TASK_RESULT + API->>S: GET /task_result + S->>API: Results ``` +For detailed API specifications, see [HTTP API Reference](./api.md). + ### Checking Task Results -!!!example "Poll for Task Completion" - Use the task name to retrieve results: - - ```bash - curl http://localhost:5000/api/task_result/test_task_001 - ``` +Use the task name to retrieve results: + +```bash +curl http://localhost:5000/api/task_result/test_task_001 +``` **While Task is Running:** @@ -409,136 +346,106 @@ curl -X POST http://localhost:5000/api/dispatch \ ### Issue 1: Port Already in Use -!!!bug "Error: `Address already in use`" - **Symptoms:** - ```console - ERROR: [Errno 98] Address already in use - ``` - - **Cause:** Another process is already using port 5000. - - **Solutions:** - - **Use Different Port:** - ```bash - python -m ufo.server.app --port 8080 - ``` +**Symptoms:** +```console +ERROR: [Errno 98] Address already in use +``` + +**Cause:** Another process is already using port 5000. + +**Solutions:** + +**Use Different Port:** +```bash +python -m ufo.server.app --port 8080 +``` + +**Find & Kill Process (Linux/Mac):** +```bash +# Find process using port 5000 +lsof -i :5000 - **Find & Kill Process (Linux/Mac):** - ```bash - # Find process using port 5000 - lsof -i :5000 - - # Kill the process - kill -9 - ``` +# Kill the process +kill -9 +``` + +**Find & Kill Process (Windows):** +```powershell +# Find process using port 5000 +netstat -ano | findstr :5000 - **Find & Kill Process (Windows):** - ```powershell - # Find process using port 5000 - netstat -ano | findstr :5000 - - # Kill the process - taskkill /PID /F - ``` +# Kill the process +taskkill /PID /F +``` ### Issue 2: Connection Refused -!!!bug "Error: Device client cannot connect" - **Symptoms:** - ```console - [WS] Failed to connect to ws://127.0.0.1:5000/ws - Connection refused - ``` - - **Diagnosis Checklist:** - - - [ ] Is the server actually running? Check for "Uvicorn running" message - - [ ] Does the port match in both server and client commands? - - [ ] Are you using `--local` mode? If yes, clients must connect from `localhost` - - [ ] Is there a firewall blocking the connection? - - **Solutions:** - - 1. **Verify server is running:** - ```bash - curl http://localhost:5000/api/health - ``` - - 2. **Check server logs** for startup errors - - 3. **If using `--local` mode**, ensure client uses `127.0.0.1`: - ```bash - # Server - python -m ufo.server.app --local - - # Client (must use 127.0.0.1, not external IP) - python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id test - ``` - - 4. **If connecting from another machine**, remove `--local` flag: - ```bash - python -m ufo.server.app # No --local flag - ``` +**Symptoms:** +```console +[WS] Failed to connect to ws://127.0.0.1:5000/ws +Connection refused +``` + +**Diagnosis Checklist:** + +- Is the server actually running? Check for "Uvicorn running" message +- Does the port match in both server and client commands? +- Are you using `--local` mode? If yes, clients must connect from `localhost` +- Is there a firewall blocking the connection? + +**Solutions:** + +1. Verify server is running: + ```bash + curl http://localhost:5000/api/health + ``` + +2. Check server logs for startup errors + +3. If using `--local` mode, ensure client uses `127.0.0.1` + +4. If connecting from another machine, remove `--local` flag ### Issue 3: Device Not Connected Error -!!!bug "Error: `Client not online`" - **Symptoms:** - When dispatching a task: - ```json - { - "detail": "Client not online" - } - ``` - - **Diagnosis:** - - 1. **List all connected clients:** - ```bash - curl http://localhost:5000/api/clients - ``` - - 2. **Check the `client_id` matches exactly** (case-sensitive!) - - **Solutions:** - - - Verify the device client is running and successfully registered - - Check server logs for `Registered device client: ` - - Ensure no typos in `client_id` when dispatching - - If the device disconnected, restart the client connection +**Symptoms:** +When dispatching a task: +```json +{ + "detail": "Client not online" +} +``` + +**Diagnosis:** + +1. List all connected clients: + ```bash + curl http://localhost:5000/api/clients + ``` + +2. Check the `client_id` matches exactly (case-sensitive!) + +**Solutions:** + +- Verify the device client is running and successfully registered +- Check server logs for `📱 Device client connected` +- Ensure no typos in `client_id` when dispatching +- If the device disconnected, restart the client connection ### Issue 4: Empty Task Content Error -!!!bug "Error: `Empty task content`" - **Symptoms:** - ```json - { - "detail": "Empty task content" - } - ``` - - **Cause:** The `request` field in `/api/dispatch` is missing or empty. - - **Solution:** - ```bash - # Wrong (missing request) - curl -X POST http://localhost:5000/api/dispatch \ - -H "Content-Type: application/json" \ - -d '{"client_id": "my_device"}' - - # Correct - curl -X POST http://localhost:5000/api/dispatch \ - -H "Content-Type: application/json" \ - -d '{ - "client_id": "my_device", - "request": "Open Notepad" - }' - ``` +**Symptoms:** +```json +{ + "detail": "Empty task content" +} +``` -### Issue 5: Firewall Blocking Connections +**Cause:** The `request` field in `/api/dispatch` is missing or empty. -!!!bug "Remote clients cannot connect" +**Solution:** Always include the `request` field with a task description. + +### Issue 5: Firewall Blocking Connections **Symptoms:** Clients on other machines cannot connect, but `curl localhost:5000/api/health` works on server machine. **Diagnosis:** @@ -580,39 +487,25 @@ curl -X POST http://localhost:5000/api/dispatch \ ### Issue 6: Target Device Not Connected (Constellation) -!!!bug "Constellation registration fails" - **Symptoms:** - ```console - Target device 'my_windows_device' is not connected - ``` - - **Solution:** - - 1. **Connect the device client first:** - ```bash - python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id my_windows_device - ``` - - 2. **Wait for registration confirmation** (check server logs) - - 3. **Then connect constellation:** - ```bash - python -m galaxy.constellation.constellation --ws --ws-server ws://127.0.0.1:5000/ws --target-id my_windows_device - ``` +**Symptoms:** +```console +Target device 'my_windows_device' is not connected +``` + +**Solution:** + +1. Connect the device client first +2. Wait for registration confirmation (check server logs) +3. Then connect constellation !!!tip "Debug Mode" - For maximum verbosity during troubleshooting, start the server with: - ```bash - python -m ufo.server.app --log-level DEBUG - ``` - This will show detailed WebSocket handshake logs, AIP protocol messages, and session state transitions. + For maximum verbosity, start the server with: `python -m ufo.server.app --log-level DEBUG` --- ## 📚 Next Steps -!!!quote "Learning Path" - Now that you have the server running and can dispatch tasks, explore these topics to deepen your understanding: +Now that you have the server running and can dispatch tasks, explore these topics: ### Immediate Next Steps @@ -672,147 +565,42 @@ curl -X POST http://localhost:5000/api/dispatch \ sudo systemctl status ufo-server ``` -!!!example "PM2 Process Manager (Cross-Platform)" - ```bash - # Install PM2 - npm install -g pm2 - - # Start server with PM2 - pm2 start "python -m ufo.server.app --port 5000" --name ufo-server - - # Setup auto-restart on system boot - pm2 startup - pm2 save - - # Monitor - pm2 logs ufo-server - pm2 monit - ``` - -### 2. Reverse Proxy with SSL/TLS - -!!!example "Nginx Configuration" - Create `/etc/nginx/sites-available/ufo-server`: - - ```nginx - upstream ufo_backend { - server 127.0.0.1:5000; - } - - server { - listen 80; - server_name ufo.yourdomain.com; - - # Redirect HTTP to HTTPS - return 301 https://$server_name$request_uri; - } - - server { - listen 443 ssl http2; - server_name ufo.yourdomain.com; - - # SSL Configuration - ssl_certificate /etc/letsencrypt/live/ufo.yourdomain.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/ufo.yourdomain.com/privkey.pem; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!aNULL:!MD5; - - # HTTP API endpoints - location /api/ { - proxy_pass http://ufo_backend; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - # WebSocket endpoint (critical settings) - location /ws { - proxy_pass http://ufo_backend; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # Disable buffering for WebSocket - proxy_buffering off; - - # Timeouts for long-lived connections - proxy_read_timeout 3600s; - proxy_send_timeout 3600s; - } - } - ``` - - **Enable and reload:** - ```bash - sudo ln -s /etc/nginx/sites-available/ufo-server /etc/nginx/sites-enabled/ - sudo nginx -t - sudo systemctl reload nginx - ``` - -### 3. Logging & Monitoring - -!!!tip "Structured Logging" - Redirect logs to files for persistence: - - ```bash - python -m ufo.server.app --port 5000 --log-level INFO \ - 2>&1 | tee -a /var/log/ufo/server.log - ``` - -!!!example "Health Check Monitoring" - Set up periodic health checks (e.g., with cron): - - ```bash - # /etc/cron.d/ufo-health-check - */5 * * * * curl -f http://localhost:5000/api/health || systemctl restart ufo-server - ``` - -### 4. Security Hardening +**PM2 Process Manager (Cross-Platform):** +```bash +# Install PM2 +npm install -g pm2 -!!!danger "Security Checklist" - - [ ] **Never use `--host 0.0.0.0` without a firewall** - Restrict to `127.0.0.1` if behind reverse proxy - - [ ] **Enable SSL/TLS** - Use Let's Encrypt or valid certificates - - [ ] **Implement authentication** - Add API keys or OAuth to `/api/dispatch` - - [ ] **Rate limiting** - Use nginx `limit_req` or application-level rate limiting - - [ ] **Input validation** - Sanitize all task requests to prevent injection attacks - - [ ] **Network isolation** - Use VPNs or private networks for device-server communication +# Start server with PM2 +pm2 start "python -m ufo.server.app --port 5000" --name ufo-server -### 5. Scaling Considerations +# Setup auto-restart on system boot +pm2 startup +pm2 save -| Scaling Pattern | When to Use | Implementation | -|-----------------|-------------|----------------| -| **Vertical Scaling** | < 50 concurrent clients | Increase server CPU/RAM | -| **Load Balancing** | 50-200 concurrent clients | Multiple server instances + nginx upstream | -| **Sticky Sessions** | Load balanced setup | Nginx `ip_hash` or session affinity | -| **Database Backend** | > 200 clients or persistence required | Replace in-memory session storage with Redis/PostgreSQL | +# Monitor +pm2 logs ufo-server +pm2 monit +``` -!!!warning "WebSocket Sticky Sessions Required" - If using multiple server instances behind a load balancer, you **must** enable sticky sessions (session affinity) to ensure WebSocket connections remain on the same server instance. +For complete production deployment guidance including SSL/TLS, security hardening, and scaling strategies, see [Server Overview - Production Deployment](./overview.md#production-deployment). --- ## 🎓 What You Learned -!!!success "Congratulations!" - You've successfully: - - - Started the UFO Agent Server with custom configurations - - Connected device and constellation clients via WebSocket - - Dispatched tasks using the HTTP API - - Verified connections and monitored health - - Troubleshot common issues - - Learned production deployment best practices - -!!!info "Ready for More?" - Continue your journey with: - - - **Architecture Deep Dive**: [Server Overview](./overview.md) - - **API Exploration**: [HTTP API Reference](./api.md) - - **Client Development**: [Client Documentation](../client/overview.md) - - **Multi-Device Coordination**: Coming Soon +You've successfully: + +- Started the UFO Agent Server with custom configurations +- Connected device and constellation clients via WebSocket +- Dispatched tasks using the HTTP API +- Verified connections and monitored health +- Troubleshot common issues +- Learned production deployment best practices + +Continue your journey with: +- **Architecture Deep Dive**: [Server Overview](./overview.md) +- **API Exploration**: [HTTP API Reference](./api.md) +- **Client Development**: [Client Documentation](../client/overview.md) +- **Multi-Device Coordination**: [Constellation Overview](../galaxy/overview.md) + \ No newline at end of file diff --git a/documents/docs/server/session_manager.md b/documents/docs/server/session_manager.md index c9310fa6a..4d8035282 100644 --- a/documents/docs/server/session_manager.md +++ b/documents/docs/server/session_manager.md @@ -1,20 +1,20 @@ # Session Manager -!!!quote "The Task Execution Engine" - The **SessionManager** orchestrates agent session lifecycles, coordinates background task execution, and maintains execution state across the server. Think of it as the "execution engine" that powers UFO's autonomous task capabilities. +The **SessionManager** orchestrates agent session lifecycles, coordinates background task execution, and maintains execution state across the server. It serves as the "execution engine" that powers UFO's autonomous task capabilities. + +For context on how this component fits into the server architecture, see the [Server Overview](overview.md). --- ## 🎯 Overview -!!!info "Core Responsibilities" - The SessionManager is a critical server component that bridges task dispatch and actual execution: +The SessionManager is a critical server component that bridges task dispatch and actual execution: | Capability | Description | Benefit | |------------|-------------|---------| | **Platform-Agnostic Creation** | Automatically creates Windows/Linux sessions | No manual platform handling needed | | **Background Execution** | Tasks run without blocking WebSocket event loop | Maintains connection health during long tasks | -| **State Tracking** | Monitors session lifecycle (created running completed/failed) | Enables task monitoring & result retrieval | +| **State Tracking** | Monitors session lifecycle (created → running → completed/failed) | Enables task monitoring & result retrieval | | **Graceful Cancellation** | Handles disconnections with context-aware cleanup | Prevents orphaned tasks & resource leaks | | **Concurrent Management** | Multiple sessions can run simultaneously | Supports multi-device orchestration | @@ -52,49 +52,51 @@ graph TB style WH fill:#bbdefb ``` -!!!success "Why Background Execution Matters" - Without background execution, a long-running task (e.g., 5-minute workflow) would **block the WebSocket event loop**, preventing: - - - Heartbeat messages from being sent/received - - Ping/pong frames from maintaining the connection - - Other clients' tasks from being dispatched - - Background execution solves this by using Python's `asyncio.create_task()` to run sessions concurrently. +**Why Background Execution Matters:** + +Without background execution, a long-running task (e.g., 5-minute workflow) would **block the WebSocket event loop**, preventing: + +- Heartbeat messages from being sent/received +- Ping/pong frames from maintaining the connection +- Other clients' tasks from being dispatched + +Background execution solves this by using Python's `asyncio.create_task()` to run sessions concurrently. --- -## 🏗Core Functionality +## 🏗 Core Functionality ### Session Creation -!!!info "SessionFactory Integration" - The SessionManager uses the **SessionFactory** pattern to create platform-specific session implementations. This abstraction layer automatically selects the correct session type based on platform and mode. +The SessionManager uses the **SessionFactory** pattern to create platform-specific session implementations. This abstraction layer automatically selects the correct session type based on platform and mode. -!!!example "Creating a Session" - ```python - session = session_manager.get_or_create_session( - session_id="session_abc123", - task_name="create_file", - request="Open Notepad and create a file", - websocket=ws, - platform_override="windows" # or "linux" or None (auto-detect) - ) - ``` +**Creating a Session:** + +```python +session = session_manager.get_or_create_session( + session_id="session_abc123", + task_name="create_file", + request="Open Notepad and create a file", + task_protocol=task_protocol, # AIP TaskExecutionProtocol instance + platform_override="windows" # or "linux" or None (auto-detect) +) +``` **Session Types:** | Session Type | Use Case | Platform | Dispatcher | MCP Tools | |--------------|----------|----------|------------|-----------| -| **WindowsServiceSession** | Remote Windows device | Windows | WebSocket-based | Windows MCP servers | -| **LinuxServiceSession** | Remote Linux device | Linux | WebSocket-based | Linux MCP servers | +| **ServiceSession (Windows)** | Remote Windows device | Windows | AIP protocol-based | Windows MCP servers | +| **LinuxServiceSession** | Remote Linux device | Linux | AIP protocol-based | Linux MCP servers | | **Local Session** | Local testing/debugging | Any | Direct execution | Local MCP servers | -!!!tip "Automatic Platform Detection" - If `platform_override=None`, the SessionManager uses Python's `platform.system()` to auto-detect: - - - `"Windows"` WindowsServiceSession - - `"Linux"` LinuxServiceSession - - `"Darwin"` (macOS) Currently treated as Linux session +**Platform Detection:** + +If `platform_override=None`, the SessionManager uses Python's `platform.system()` to auto-detect: + +- `"Windows"` → ServiceSession (Windows) +- `"Linux"` → LinuxServiceSession +- `"Darwin"` (macOS) → Currently uses LinuxServiceSession **Session Factory Logic Flow:** @@ -105,7 +107,7 @@ graph LR B -->|No| D{local mode?} D -->|Yes| E[Create Local Session] D -->|No| F{Platform?} - F -->|windows| G[WindowsServiceSession] + F -->|windows| G[ServiceSession] F -->|linux| H[LinuxServiceSession] E --> I[Store in sessions dict] G --> I @@ -119,20 +121,20 @@ graph LR ### Background Execution -!!!success "Non-Blocking Async Execution" - The **critical innovation** of the SessionManager is background task execution using `asyncio.create_task()`. This prevents long-running sessions from blocking the WebSocket event loop. +The **critical innovation** of the SessionManager is background task execution using `asyncio.create_task()`. This prevents long-running sessions from blocking the WebSocket event loop. -!!!example "Execute Task Asynchronously" - ```python - await session_manager.execute_task_async( - session_id=session_id, - task_name=task_name, - request=user_request, - websocket=websocket, - platform_override="windows", - callback=result_callback # Called when task completes - ) - ``` +**Execute Task Asynchronously:** + +```python +await session_manager.execute_task_async( + session_id=session_id, + task_name=task_name, + request=user_request, + task_protocol=task_protocol, # AIP TaskExecutionProtocol instance + platform_override="windows", + callback=result_callback # Called when task completes +) +``` **Benefits of Background Execution:** @@ -179,36 +181,37 @@ sequenceDiagram BT->>SM: Remove from _running_tasks dict ``` -!!!warning "Thread Safety" - The SessionManager uses `threading.Lock` for thread-safe access to shared dictionaries: - - ```python - with self.lock: - self.sessions[session_id] = session - ``` - - This prevents race conditions in multi-threaded environments (though FastAPI primarily uses async/await). +**Thread Safety:** + +The SessionManager uses `threading.Lock` for thread-safe access to shared dictionaries: + +```python +with self.lock: + self.sessions[session_id] = session +``` + +This prevents race conditions in multi-threaded environments (though FastAPI primarily uses async/await). ### Callback Mechanism -!!!info "Result Notification Pattern" - When a task completes (successfully, with errors, or via cancellation), the SessionManager invokes a registered callback function. This decouples task execution from result delivery. +When a task completes (successfully, with errors, or via cancellation), the SessionManager invokes a registered callback function. This decouples task execution from result delivery. -!!!example "Registering a Callback" - ```python - async def send_result_to_client(session_id: str, result_msg: ServerMessage): - """Called when task completes.""" - await websocket.send_text(result_msg.model_dump_json()) - logger.info(f"Sent TASK_END for {session_id}") - - await session_manager.execute_task_async( - session_id="abc123", - task_name="open_notepad", - request="Open Notepad", - websocket=ws, - callback=send_result_to_client # Register callback - ) - ``` +**Registering a Callback:** + +```python +async def send_result_to_client(session_id: str, result_msg: ServerMessage): + """Called when task completes.""" + await websocket.send_text(result_msg.model_dump_json()) + logger.info(f"Sent TASK_END for {session_id}") + +await session_manager.execute_task_async( + session_id="abc123", + task_name="open_notepad", + request="Open Notepad", + task_protocol=task_protocol, + callback=send_result_to_client # Register callback +) +``` **Callback Execution Flow:** @@ -255,31 +258,32 @@ result_message = ServerMessage( | `timestamp` | str | ISO 8601 timestamp (UTC) | `"2024-11-04T14:30:22Z"` | | `response_id` | str | Unique response UUID | `"3f4a2b1c-9d8e-4f3a-b2c1-..."` | -!!!warning "Callback Error Handling" - If the callback raises an exception, the SessionManager **logs the error but doesn't fail the session**: - - ```python - try: - await callback(session_id, result_message) - except Exception as e: - self.logger.error(f"Callback error: {e}") - # Session results are still persisted! - ``` - - This prevents callback bugs from breaking task execution. +**Callback Error Handling:** + +If the callback raises an exception, the SessionManager **logs the error but doesn't fail the session**: + +```python +try: + await callback(session_id, result_message) +except Exception as e: + self.logger.error(f"Callback error: {e}") + # Session results are still persisted! +``` + +This prevents callback bugs from breaking task execution. ### Task Cancellation -!!!danger "Context-Aware Cancellation" - The SessionManager supports **graceful task cancellation** with different behaviors based on **why** the cancellation occurred. This is critical for handling client disconnections properly. +The SessionManager supports **graceful task cancellation** with different behaviors based on **why** the cancellation occurred. This is critical for handling client disconnections properly. -!!!example "Cancel a Running Task" - ```python - await session_manager.cancel_task( - session_id="session_abc123", - reason="device_disconnected" # or "constellation_disconnected" - ) - ``` +**Cancel a Running Task:** + +```python +await session_manager.cancel_task( + session_id="session_abc123", + reason="device_disconnected" # or "constellation_disconnected" +) +``` **Cancellation Reasons:** @@ -348,37 +352,35 @@ async def cancel_task(self, session_id: str, reason: str) -> bool: except (asyncio.CancelledError, asyncio.TimeoutError): pass # Expected - # Cleanup - self._running_tasks.pop(session_id, None) - self._cancellation_reasons.pop(session_id, None) - self.remove_session(session_id) - - return True - return False + # Cleanup + self._running_tasks.pop(session_id, None) + self._cancellation_reasons.pop(session_id, None) + self.remove_session(session_id) + + return True +return False ``` -!!!warning "Cancellation is Asynchronous" - Cancellation is **not immediate**. The background task receives an `asyncio.CancelledError` at the next `await` point. If the session is executing synchronous code (e.g., LLM inference), cancellation won't take effect until that operation completes. - - **Grace Period:** The SessionManager waits up to **2 seconds** for graceful cancellation before giving up. +**Important Notes:** -!!!tip "Best Practice: Handle Disconnections" - When a client disconnects, the WebSocket Handler should: - - 1. Identify all active sessions for that client - 2. Call `cancel_task()` with the appropriate `reason` - 3. Clean up client registration in ClientConnectionManager - - This prevents orphaned sessions from consuming resources. +- **Cancellation is asynchronous**: The background task receives an `asyncio.CancelledError` at the next `await` point. If the session is executing synchronous code (e.g., LLM inference), cancellation won't take effect until that operation completes. +- **Grace Period**: The SessionManager waits up to **2 seconds** for graceful cancellation before giving up. + +**Best Practice:** + +When a client disconnects, the WebSocket Handler should: + +1. Identify all active sessions for that client +2. Call `cancel_task()` with the appropriate `reason` +3. Clean up client registration in ClientConnectionManager + +This prevents orphaned sessions from consuming resources. --- ## 🔄 Session Lifecycle -!!!info "From Creation to Cleanup" - Sessions follow a predictable lifecycle from initial dispatch through execution to final cleanup. Understanding this flow is essential for debugging and monitoring. - -```mermaid +Sessions follow a predictable lifecycle from initial dispatch through execution to final cleanup. Understanding this flow is essential for debugging and monitoring.```mermaid stateDiagram-v2 [*] --> Created: get_or_create_session() Created --> Stored: Add to sessions dict @@ -414,60 +416,61 @@ stateDiagram-v2 | **7. Callback** | Notify registered callback | `await callback(session_id, msg)` | 50-500ms | | **8. Cleanup** | Remove from active sessions | `remove_session(session_id)` | < 10ms | -!!!example "Complete Lifecycle Code" - ```python - # Stage 1-2: Creation - session = session_manager.get_or_create_session( - session_id="abc123", - task_name="demo_task", - request="Open Notepad", - websocket=ws, - platform_override="windows" - ) - - # Stage 3: Background Dispatch - await session_manager.execute_task_async( - session_id="abc123", - task_name="demo_task", - request="Open Notepad", - websocket=ws, - platform_override="windows", - callback=send_result_callback - ) - # Returns immediately! Task runs in background - - # Stage 4: Execution (happens in background) - # session.run() executes: - # - LLM reasoning - # - Action selection - # - Command execution via device - # - Result observation - - # Stage 5-6: Results (automatic) - # Session completes, results collected and persisted - - # Stage 7: Callback (automatic) - # await callback("abc123", ServerMessage(...)) - - # Stage 8: Cleanup (manual or automatic) - session_manager.remove_session("abc123") - ``` +**Complete Lifecycle Example:** -!!!warning "Session Persistence" - Sessions remain in the `sessions` dict until explicitly removed via `remove_session()`. This allows: - - - **Result retrieval** via `/api/task_result/{task_name}` - - **Session inspection** for debugging - - **Reconnection scenarios** (future feature) - - However, this means **sessions consume memory** until cleaned up. Implement periodic cleanup for production deployments. +```python +# Stage 1-2: Creation +session = session_manager.get_or_create_session( + session_id="abc123", + task_name="demo_task", + request="Open Notepad", + task_protocol=task_protocol, + platform_override="windows" +) + +# Stage 3: Background Dispatch +await session_manager.execute_task_async( + session_id="abc123", + task_name="demo_task", + request="Open Notepad", + task_protocol=task_protocol, + platform_override="windows", + callback=send_result_callback +) +# Returns immediately! Task runs in background + +# Stage 4: Execution (happens in background) +# session.run() executes: +# - LLM reasoning +# - Action selection +# - Command execution via device +# - Result observation + +# Stage 5-6: Results (automatic) +# Session completes, results collected and persisted + +# Stage 7: Callback (automatic) +# await callback("abc123", ServerMessage(...)) + +# Stage 8: Cleanup (manual or automatic) +session_manager.remove_session("abc123") +``` + +**Session Persistence:** + +Sessions remain in the `sessions` dict until explicitly removed via `remove_session()`. This allows: + +- **Result retrieval** via `/api/task_result/{task_name}` +- **Session inspection** for debugging +- **Reconnection scenarios** (future feature) + +However, this means **sessions consume memory** until cleaned up. Implement periodic cleanup for production deployments. --- ## 💾 State Management -!!!info "Three-Tier Storage Architecture" - The SessionManager maintains three separate dictionaries for different aspects of session state: +The SessionManager maintains three separate dictionaries for different aspects of session state: ### 1. Active Sessions Storage @@ -479,20 +482,21 @@ self.sessions: Dict[str, BaseSession] = {} |---------|-----------|-----------|---------------| | Store active session objects | `{session_id: BaseSession}` | Until `remove_session()` called | `threading.Lock` | -!!!example "Session Storage Operations" - ```python - # Store session - with self.lock: - self.sessions[session_id] = session - - # Retrieve session - with self.lock: - session = self.sessions.get(session_id) - - # Remove session - with self.lock: - self.sessions.pop(session_id, None) - ``` +**Session Storage Operations:** + +```python +# Store session +with self.lock: + self.sessions[session_id] = session + +# Retrieve session +with self.lock: + session = self.sessions.get(session_id) + +# Remove session +with self.lock: + self.sessions.pop(session_id, None) +``` **Benefits:** @@ -516,21 +520,22 @@ self.results: Dict[str, Dict[str, Any]] = {} |---------|-----------|----------------|-------------------| | Cache completed task results | `{session_id: results_dict}` | After task completion via `set_results()` | `get_result()`, `get_result_by_task()` | -!!!example "Result Storage & Retrieval" - ```python - # Persist results after completion - def set_results(self, session_id: str): - with self.lock: - if session_id in self.sessions: - self.results[session_id] = self.sessions[session_id].results - - # Retrieve by session ID - result = session_manager.get_result("abc123") - # Returns: {"action": "opened notepad", "screenshot": "base64..."} - - # Retrieve by task name - result = session_manager.get_result_by_task("demo_task") - ``` +**Result Storage & Retrieval:** + +```python +# Persist results after completion +def set_results(self, session_id: str): + with self.lock: + if session_id in self.sessions: + self.results[session_id] = self.sessions[session_id].results + +# Retrieve by session ID +result = session_manager.get_result("abc123") +# Returns: {"action": "opened notepad", "screenshot": "base64..."} + +# Retrieve by task name +result = session_manager.get_result_by_task("demo_task") +``` **Result Structure Example:** @@ -557,18 +562,19 @@ self.session_id_dict: Dict[str, str] = {} |---------|-----------|----------| | Map task names to session IDs | `{task_name: session_id}` | Allow result retrieval by task name instead of session ID | -!!!example "Task Name Mapping" - ```python - # Created during session creation - self.session_id_dict[task_name] = session_id - - # Usage: Get result by task name - def get_result_by_task(self, task_name: str): - with self.lock: - session_id = self.session_id_dict.get(task_name) - if session_id: - return self.get_result(session_id) - ``` +**Task Name Mapping:** + +```python +# Created during session creation +self.session_id_dict[task_name] = session_id + +# Usage: Get result by task name +def get_result_by_task(self, task_name: str): + with self.lock: + session_id = self.session_id_dict.get(task_name) + if session_id: + return self.get_result(session_id) +``` **Why This Matters:** @@ -592,21 +598,22 @@ self._running_tasks: Dict[str, asyncio.Task] = {} |---------|-----------|----------| | Track active background tasks for cancellation | `{session_id: asyncio.Task}` | Enable graceful task cancellation when clients disconnect | -!!!example "Running Task Management" - ```python - # Register background task - task = asyncio.create_task(self._run_session_background(...)) - self._running_tasks[session_id] = task - - # Cancel running task - task = self._running_tasks.get(session_id) - if task and not task.done(): - task.cancel() - await asyncio.wait_for(task, timeout=2.0) - - # Cleanup after completion - self._running_tasks.pop(session_id, None) - ``` +**Running Task Management:** + +```python +# Register background task +task = asyncio.create_task(self._run_session_background(...)) +self._running_tasks[session_id] = task + +# Cancel running task +task = self._running_tasks.get(session_id) +if task and not task.done(): + task.cancel() + await asyncio.wait_for(task, timeout=2.0) + +# Cleanup after completion +self._running_tasks.pop(session_id, None) +``` ### 5. Cancellation Reasons Tracking @@ -618,62 +625,62 @@ self._cancellation_reasons: Dict[str, str] = {} |---------|-----------|-----------| | Store why each task was cancelled | `{session_id: reason}` | From `cancel_task()` to `_run_session_background()` cleanup | -!!!example "Cancellation Reason Flow" - ```python - # Store reason when cancelling - async def cancel_task(self, session_id: str, reason: str): - self._cancellation_reasons[session_id] = reason - task.cancel() - - # Retrieve reason during cancellation handling - async def _run_session_background(...): - try: - await session.run() - except asyncio.CancelledError: - reason = self._cancellation_reasons.get(session_id, "unknown") - if reason == "device_disconnected": - # Send callback to constellation - elif reason == "constellation_disconnected": - # Skip callback - ``` +**Cancellation Reason Flow:** + +```python +# Store reason when cancelling +async def cancel_task(self, session_id: str, reason: str): + self._cancellation_reasons[session_id] = reason + task.cancel() + +# Retrieve reason during cancellation handling +async def _run_session_background(...): + try: + await session.run() + except asyncio.CancelledError: + reason = self._cancellation_reasons.get(session_id, "unknown") + if reason == "device_disconnected": + # Send callback to constellation + elif reason == "constellation_disconnected": + # Skip callback +``` --- ### Thread Safety -!!!warning "Lock Protection Required" - The SessionManager uses `threading.Lock` for thread-safe access to shared dictionaries: - - ```python - def __init__(self): - self.lock = threading.Lock() - - def get_or_create_session(self, ...): - with self.lock: - if session_id not in self.sessions: - self.sessions[session_id] = session - return self.sessions[session_id] - ``` - - **Why this matters:** Although FastAPI primarily uses async/await (single-threaded event loop), the lock protects against: - - - **Thread pool executors** for sync operations - - **Background tasks** accessing shared state - - **Future multi-threading** in FastAPI/Uvicorn +The SessionManager uses `threading.Lock` for thread-safe access to shared dictionaries: -!!!tip "Performance Consideration" - Lock contention is minimal because: - - - Lock is held only for **dictionary operations** (O(1) operations) - - Session execution happens **outside the lock** (async background tasks) - - Most operations are **read-heavy** (get_result) which are fast +```python +def __init__(self): + self.lock = threading.Lock() + +def get_or_create_session(self, ...): + with self.lock: + if session_id not in self.sessions: + self.sessions[session_id] = session + return self.sessions[session_id] +``` + +**Why this matters:** Although FastAPI primarily uses async/await (single-threaded event loop), the lock protects against: + +- **Thread pool executors** for sync operations +- **Background tasks** accessing shared state +- **Future multi-threading** in FastAPI/Uvicorn + +**Performance Consideration:** + +Lock contention is minimal because: + +- Lock is held only for **dictionary operations** (O(1) operations) +- Session execution happens **outside the lock** (async background tasks) +- Most operations are **read-heavy** (get_result) which are fast --- -## 🖥Platform Support +## 🖥 Platform Support -!!!info "Cross-Platform Session Factory" - The SessionManager supports both Windows and Linux platforms through the **SessionFactory** abstraction layer. Platform-specific implementations handle OS-specific UI automation and tool execution. +The SessionManager supports both Windows and Linux platforms through the **SessionFactory** abstraction layer. Platform-specific implementations handle OS-specific UI automation and tool execution. ### Platform Detection @@ -693,7 +700,7 @@ graph TD H --> I I --> J{Platform?} - J -->|windows| K[WindowsServiceSession] + J -->|windows| K[ServiceSession] J -->|linux| L[LinuxServiceSession] style H fill:#ffe0b2 @@ -714,64 +721,67 @@ def __init__(self, platform_override: Optional[str] = None): | Platform | Session Class | UI Automation | MCP Tools | Status | |----------|---------------|---------------|-----------|--------| -| **Windows** | `WindowsServiceSession` | Win32 API, UI Automation | Windows MCP servers (filesystem, browser, etc.) | Fully Supported | +| **Windows** | `ServiceSession` | Win32 API, UI Automation | Windows MCP servers (filesystem, browser, etc.) | Fully Supported | | **Linux** | `LinuxServiceSession` | X11/Wayland, AT-SPI | Linux MCP servers | Fully Supported | | **macOS (Darwin)** | `LinuxServiceSession` | Currently treated as Linux | Linux MCP servers | ⚠️ Experimental | -!!!example "Windows Session Creation" - ```python - # Explicit Windows platform - session = session_manager.get_or_create_session( - session_id="win_session_001", - task_name="windows_task", - request="Open File Explorer and navigate to Downloads", - websocket=ws, - platform_override="windows" - ) - # Creates WindowsServiceSession - ``` +**Windows Session Creation:** -!!!example "Linux Session Creation" - ```python - # Explicit Linux platform - session = session_manager.get_or_create_session( - session_id="linux_session_001", - task_name="linux_task", - request="Open Nautilus and create a new folder", - websocket=ws, - platform_override="linux" - ) - # Creates LinuxServiceSession - ``` +```python +# Explicit Windows platform +session = session_manager.get_or_create_session( + session_id="win_session_001", + task_name="windows_task", + request="Open File Explorer and navigate to Downloads", + task_protocol=task_protocol, + platform_override="windows" +) +# Creates ServiceSession +``` -!!!example "Auto-Detection" - ```python - # Let SessionManager detect platform - session = session_manager.get_or_create_session( - session_id="auto_session_001", - task_name="auto_task", - request="Open text editor", - websocket=ws, - platform_override=None # Auto-detect - ) - # Uses platform.system() to determine session type - ``` +**Linux Session Creation:** -!!!warning "macOS Limitations" - macOS (Darwin) is currently treated as Linux, which may result in: - - - Incorrect UI automation commands - - Missing macOS-specific tool integrations - - ⚠️ Limited functionality - - **Recommendation:** Use explicit `platform_override="linux"` for Linux-like behavior, or wait for dedicated macOS session implementation. +```python +# Explicit Linux platform +session = session_manager.get_or_create_session( + session_id="linux_session_001", + task_name="linux_task", + request="Open Nautilus and create a new folder", + task_protocol=task_protocol, + platform_override="linux" +) +# Creates LinuxServiceSession +``` + +**Auto-Detection:** + +```python +# Let SessionManager detect platform +session = session_manager.get_or_create_session( + session_id="auto_session_001", + task_name="auto_task", + request="Open text editor", + task_protocol=task_protocol, + platform_override=None # Auto-detect +) +# Uses platform.system() to determine session type +``` + +**macOS Limitations:** + +macOS (Darwin) is currently treated as Linux, which may result in: + +- Incorrect UI automation commands +- Missing macOS-specific tool integrations +- ⚠️ Limited functionality + +**Recommendation:** Use explicit `platform_override="linux"` for Linux-like behavior, or wait for dedicated macOS session implementation. --- ## 🐛 Error Handling -!!!danger "Robust Error Recovery" - The SessionManager implements comprehensive error handling to prevent task failures from breaking the server. +The SessionManager implements comprehensive error handling to prevent task failures from breaking the server. ### Error Categories @@ -812,21 +822,22 @@ async def _run_session_background(...): error = str(e) ``` -!!!example "Error Result Structure" - When a session fails, the result includes error details: - - ```json - { - "status": "FAILED", - "error": "LLM API timeout after 60 seconds", - "session_id": "abc123", - "result": { - "failure": "session ended with an error", - "last_action": "open_notepad", - "traceback": "Traceback (most recent call last)..." - } - } - ``` +**Error Result Structure:** + +When a session fails, the result includes error details: + +```json +{ + "status": "FAILED", + "error": "LLM API timeout after 60 seconds", + "session_id": "abc123", + "result": { + "failure": "session ended with an error", + "last_action": "open_notepad", + "traceback": "Traceback (most recent call last)..." + } +} +``` ### Callback Error Handling @@ -842,15 +853,16 @@ except Exception as e: # Client may not receive notification ``` -!!!warning "Callback Failures Don't Fail Sessions" - If the callback raises an exception (e.g., WebSocket already closed), the SessionManager: - - - **Logs the error** for debugging - - **Persists the results** in `self.results` - - **Completes cleanup** (removes from `_running_tasks`) - - **Does NOT re-raise** the exception - - **Implication:** Results can be retrieved via `/api/task_result/{task_name}` even if WebSocket notification failed. +**Callback Failures Don't Fail Sessions:** + +If the callback raises an exception (e.g., WebSocket already closed), the SessionManager: + +- **Logs the error** for debugging +- **Persists the results** in `self.results` +- **Completes cleanup** (removes from `_running_tasks`) +- **Does NOT re-raise** the exception + +**Implication:** Results can be retrieved via `/api/task_result/{task_name}` even if WebSocket notification failed. ### Unknown State Handling @@ -866,26 +878,25 @@ else: self.logger.warning(f"Session {session_id} ended in unknown state") ``` -!!!bug "Edge Case: Session Hangs" - If `session.run()` completes but the session is neither `is_finished()` nor `is_error()`, this indicates: - - - Possible bug in session state management - - Incomplete session implementation - - Unexpected session interruption - - The SessionManager marks this as **FAILED** to prevent silent failures. +**Edge Case - Session Hangs:** + +If `session.run()` completes but the session is neither `is_finished()` nor `is_error()`, this indicates: + +- Possible bug in session state management +- Incomplete session implementation +- Unexpected session interruption + +The SessionManager marks this as **FAILED** to prevent silent failures. --- -## Best Practices +## 💡 Best Practices -!!!tip "Production-Ready SessionManager Usage" - Follow these best practices to ensure reliable, scalable session management: +Follow these best practices to ensure reliable, scalable session management: ### 1. Configure Appropriate Timeouts -!!!example "Task Timeout Configuration" - Session timeouts should match task complexity: +Session timeouts should match task complexity: | Task Type | Timeout | Reason | |-----------|---------|--------| @@ -901,40 +912,40 @@ else: ### 2. Monitor Session Count -!!!warning "Resource Exhaustion Prevention" - Sessions consume memory. Implement limits to prevent resource exhaustion: +Sessions consume memory. Implement limits to prevent resource exhaustion: + +```python +MAX_CONCURRENT_SESSIONS = 100 # Adjust based on server resources + +async def execute_task_safe(session_manager, ...): + active_count = len(session_manager.sessions) - ```python - MAX_CONCURRENT_SESSIONS = 100 # Adjust based on server resources + if active_count >= MAX_CONCURRENT_SESSIONS: + # Option 1: Reject new sessions + raise HTTPException( + status_code=503, + detail=f"Server at capacity ({active_count} active sessions)" + ) - async def execute_task_safe(session_manager, ...): - active_count = len(session_manager.sessions) - - if active_count >= MAX_CONCURRENT_SESSIONS: - # Option 1: Reject new sessions - raise HTTPException( - status_code=503, - detail=f"Server at capacity ({active_count} active sessions)" - ) - - # Option 2: Cancel oldest sessions - oldest_session_id = min( - session_manager.sessions.keys(), - key=lambda s: session_manager.sessions[s].created_at - ) - await session_manager.cancel_task( - oldest_session_id, - reason="capacity_limit" - ) - - # Proceed with new session - await session_manager.execute_task_async(...) - ``` + # Option 2: Cancel oldest sessions + oldest_session_id = min( + session_manager.sessions.keys(), + key=lambda s: session_manager.sessions[s].created_at + ) + await session_manager.cancel_task( + oldest_session_id, + reason="capacity_limit" + ) + + # Proceed with new session + await session_manager.execute_task_async(...) +``` ### 3. Clean Up Completed Sessions -!!!danger "Memory Leak Prevention" - Sessions persist in `sessions` dict until explicitly removed. Implement cleanup: +⚠️ **Memory Leak Prevention:** + +Sessions persist in `sessions` dict until explicitly removed. Implement cleanup: ```python # Option 1: Cleanup immediately after result retrieval @@ -963,46 +974,42 @@ else: logger.info(f"Cleaned up old session: {session_id}") # Start cleanup task on server startup - asyncio.create_task(cleanup_old_sessions(session_manager)) - ``` +asyncio.create_task(cleanup_old_sessions(session_manager)) +``` ### 4. Handle Cancellation Gracefully -!!!example "Context-Aware Cancellation Handling" - Different cancellation reasons require different responses: +Different cancellation reasons require different responses: + +```python +async def handle_client_disconnect(client_id, client_type, session_manager, client_manager): + """Handle disconnection based on client type.""" - ```python - async def handle_client_disconnect(client_id, client_type, session_manager, client_manager): - """Handle disconnection based on client type.""" - - if client_type == ClientType.CONSTELLATION: - # Constellation disconnected - cancel all its tasks - session_ids = client_manager.get_constellation_sessions(client_id) - for session_id in session_ids: - await session_manager.cancel_task( - session_id, - reason="constellation_disconnected" # Don't send callback - ) - - elif client_type == ClientType.DEVICE: - # Device disconnected - notify constellations to reassign - session_ids = client_manager.get_device_sessions(client_id) - for session_id in session_ids: - await session_manager.cancel_task( - session_id, - reason="device_disconnected" # Send callback to constellation - ) - - # Clean up client registration - client_manager.remove_client(client_id) - ``` + if client_type == ClientType.CONSTELLATION: + # Constellation disconnected - cancel all its tasks + session_ids = client_manager.get_constellation_sessions(client_id) + for session_id in session_ids: + await session_manager.cancel_task( + session_id, + reason="constellation_disconnected" # Don't send callback + ) + + elif client_type == ClientType.DEVICE: + # Device disconnected - notify constellations to reassign + session_ids = client_manager.get_device_sessions(client_id) + for session_id in session_ids: + await session_manager.cancel_task( + session_id, + reason="device_disconnected" # Send callback to constellation + ) + + # Clean up client registration + client_manager.remove_client(client_id) +``` ### 5. Log Session Lifecycle Events -!!!tip "Comprehensive Logging" - Log key lifecycle events for debugging and monitoring: - - ```python +Log key lifecycle events for debugging and monitoring: ```python # Session creation self.logger.info(f"Created {platform} session: {session_id} (type: {session_type})") @@ -1022,48 +1029,46 @@ else: # Cleanup self.logger.info(f"Session {session_id} completed with status {status}") - ``` +``` ### 6. Implement Result Expiration -!!!example "Automatic Result Cleanup" - Prevent `results` dict from growing indefinitely: +Prevent `results` dict from growing indefinitely: + +```python +from collections import OrderedDict +import time + +class SessionManagerWithExpiration(SessionManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Store (result, timestamp) tuples + self.results: Dict[str, Tuple[Dict, float]] = {} + self.result_ttl = 3600 # 1 hour - ```python - from collections import OrderedDict - import time - - class SessionManagerWithExpiration(SessionManager): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Store (result, timestamp) tuples - self.results: Dict[str, Tuple[Dict, float]] = {} - self.result_ttl = 3600 # 1 hour - - def set_results(self, session_id: str): - with self.lock: - if session_id in self.sessions: - self.results[session_id] = ( - self.sessions[session_id].results, - time.time() - ) - - def get_result(self, session_id: str): - with self.lock: - if session_id in self.results: - result, timestamp = self.results[session_id] - # Check expiration - if time.time() - timestamp > self.result_ttl: - self.results.pop(session_id) - return None - return result - return None - ``` + def set_results(self, session_id: str): + with self.lock: + if session_id in self.sessions: + self.results[session_id] = ( + self.sessions[session_id].results, + time.time() + ) + + def get_result(self, session_id: str): + with self.lock: + if session_id in self.results: + result, timestamp = self.results[session_id] + # Check expiration + if time.time() - timestamp > self.result_ttl: + self.results.pop(session_id) + return None + return result + return None +``` ### 7. Monitor Background Tasks -!!!warning "Detect Hung Tasks" - Monitor background tasks for unexpectedly long execution: +Monitor background tasks for unexpectedly long execution: ```python import asyncio @@ -1090,8 +1095,7 @@ else: ## 🔗 Integration with Server Components -!!!info "SessionManager in the Server Ecosystem" - The SessionManager doesn't operate in isolation—it's deeply integrated with other server components. +The SessionManager doesn't operate in isolation—it's deeply integrated with other server components. ### Integration Architecture @@ -1138,32 +1142,34 @@ graph TB ### 1. WebSocket Handler Integration -!!!example "Handler Creates Sessions with Callbacks" - ```python - # In WebSocket Handler - async def handle_task_dispatch(self, session_id, request, client_id): - """Handle incoming task from constellation.""" - - # Define callback to send results back - async def send_result(sid: str, msg: ServerMessage): - await self.websocket.send_text(msg.model_dump_json()) - logger.info(f"Sent TASK_END for {sid}") - - # Execute task with callback - await self.session_manager.execute_task_async( - session_id=session_id, - task_name=f"task_{session_id[:8]}", - request=request, - websocket=self.websocket, # For device command dispatcher - platform_override=None, # Auto-detect - callback=send_result # Register callback - ) - ``` +The WebSocket Handler creates sessions with callbacks to send results back to clients: + +```python +# In WebSocket Handler +async def handle_task_dispatch(self, session_id, request, client_id): + """Handle incoming task from constellation.""" + + # Define callback to send results back + async def send_result(sid: str, msg: ServerMessage): + await self.websocket.send_text(msg.model_dump_json()) + logger.info(f"Sent TASK_END for {sid}") + + # Execute task with callback + await self.session_manager.execute_task_async( + session_id=session_id, + task_name=f"task_{session_id[:8]}", + request=request, + task_protocol=self.task_protocol, # AIP protocol instance + platform_override=None, # Auto-detect + callback=send_result # Register callback + ) +``` + +For more details, see the [WebSocket Handler Documentation](websocket_handler.md). ### 2. Client Connection Manager Integration -!!!info "Session-to-Client Mapping" - The Client Connection Manager tracks which clients own which sessions: +The Client Connection Manager tracks which clients own which sessions: ```python # Track constellation sessions @@ -1181,56 +1187,56 @@ graph TB # Retrieve all sessions for a client session_ids = client_manager.get_constellation_sessions("constellation_001") - # On disconnect, cancel all client sessions - for session_id in session_ids: - await session_manager.cancel_task(session_id, reason="client_disconnected") - ``` +# On disconnect, cancel all client sessions +for session_id in session_ids: + await session_manager.cancel_task(session_id, reason="client_disconnected") +``` + +For more details, see the [Client Connection Manager Documentation](client_connection_manager.md). ### 3. HTTP API Integration -!!!example "API Router Uses SessionManager" - ```python - # In API router (ufo/server/services/api.py) - @router.post("/api/dispatch") - async def dispatch_task_api(data: Dict[str, Any]): - client_id = data.get("client_id") - user_request = data.get("request") - task_name = data.get("task_name", str(uuid4())) - - # Get client WebSocket - ws = client_manager.get_client(client_id) - if not ws: - raise HTTPException(status_code=404, detail="Client not online") - - session_id = str(uuid4()) - - # Use AIP protocol to send task (simplified here) - # ... send TASK_ASSIGNMENT via WebSocket ... - - return { - "status": "dispatched", - "task_name": task_name, - "client_id": client_id, - "session_id": session_id - } - - @router.get("/api/task_result/{task_name}") - async def get_task_result(task_name: str): - # Use SessionManager to retrieve results - result = session_manager.get_result_by_task(task_name) - if not result: - return {"status": "pending"} - return {"status": "done", "result": result} - ``` +The API router uses SessionManager to retrieve results: + +```python +# In API router (ufo/server/services/api.py) +@router.post("/api/dispatch") +async def dispatch_task_api(data: Dict[str, Any]): + client_id = data.get("client_id") + user_request = data.get("request") + task_name = data.get("task_name", str(uuid4())) + + # Get client protocol + task_protocol = client_manager.get_task_protocol(client_id) + if not task_protocol: + raise HTTPException(status_code=404, detail="Client not online") + + session_id = str(uuid4()) + + # Use AIP protocol to send task + # ... send TASK_ASSIGNMENT via protocol ... + + return { + "status": "dispatched", + "task_name": task_name, + "client_id": client_id, + "session_id": session_id + } + +@router.get("/api/task_result/{task_name}") +async def get_task_result(task_name: str): + # Use SessionManager to retrieve results + result = session_manager.get_result_by_task(task_name) + if not result: + return {"status": "pending"} + return {"status": "done", "result": result} +``` --- ## 📖 API Reference -!!!example "Complete SessionManager API" - Comprehensive reference for all public methods: - -### Initialization +Complete SessionManager API reference:### Initialization ```python from ufo.server.services.session_manager import SessionManager @@ -1257,7 +1263,7 @@ session = manager.get_or_create_session( session_id="abc123", task_name="demo_task", request="Open Notepad", - websocket=ws, + task_protocol=task_protocol, platform_override="windows", local=False ) @@ -1270,7 +1276,7 @@ session = manager.get_or_create_session( | `session_id` | `str` | Yes | - | Unique session identifier | | `task_name` | `Optional[str]` | No | `"test_task"` | Human-readable task name | | `request` | `Optional[str]` | No | `None` | User request text | -| `websocket` | `Optional[WebSocket]` | No | `None` | WebSocket for device communication | +| `task_protocol` | `Optional[TaskExecutionProtocol]` | No | `None` | AIP TaskExecutionProtocol instance | | `platform_override` | `Optional[str]` | No | `None` | Platform type override | | `local` | `bool` | No | `False` | Whether to create local session (for testing) | @@ -1285,7 +1291,7 @@ session_id = await manager.execute_task_async( session_id="abc123", task_name="demo_task", request="Open Notepad", - websocket=ws, + task_protocol=task_protocol, platform_override="windows", callback=my_callback ) @@ -1298,7 +1304,7 @@ session_id = await manager.execute_task_async( | `session_id` | `str` | Yes | Session identifier | | `task_name` | `str` | Yes | Task name | | `request` | `str` | Yes | User request text | -| `websocket` | `WebSocket` | Yes | WebSocket for device commands | +| `task_protocol` | `Optional[TaskExecutionProtocol]` | No | AIP TaskExecutionProtocol instance | | `platform_override` | `str` | Yes | Platform type | | `callback` | `Optional[Callable]` | No | Async function called on completion | @@ -1409,8 +1415,7 @@ manager.remove_session("abc123") ## 📚 Related Documentation -!!!info "Learn More" - Explore related components to understand the full server architecture: +Explore related components to understand the full server architecture: | Component | Purpose | Link | |-----------|---------|------| @@ -1419,26 +1424,26 @@ manager.remove_session("abc123") | **WebSocket Handler** | Message handling and protocol implementation | [WebSocket Handler](./websocket_handler.md) | | **Client Connection Manager** | Connection management and client tracking | [Client Connection Manager](./client_connection_manager.md) | | **HTTP API** | RESTful API endpoints | [API Reference](./api.md) | -| **Session Factory** | Session creation patterns | Internal Module | +| **Session Factory** | Session creation patterns | [Session Pool](../infrastructure/modules/session_pool.md) | | **AIP Protocol** | Agent Interaction Protocol details | [AIP Overview](../aip/overview.md) | --- -## 🎓 What You Learned +## 🎓 Key Takeaways -!!!success "Key Takeaways" - After reading this guide, you should understand: - - - **Background execution** prevents WebSocket blocking during long tasks - - **SessionFactory** creates platform-specific sessions (Windows/Linux) - - **Callbacks** decouple task execution from result delivery - - **Cancellation reasons** enable context-aware disconnection handling - - **Thread safety** protects shared state in concurrent environments - - **State management** uses three separate dicts (sessions, results, task_names) - - **Best practices** prevent resource exhaustion and memory leaks +After reading this guide, you should understand: + +- **Background execution** prevents WebSocket blocking during long tasks +- **SessionFactory** creates platform-specific sessions (Windows/Linux) +- **Callbacks** decouple task execution from result delivery +- **Cancellation reasons** enable context-aware disconnection handling +- **Thread safety** protects shared state in concurrent environments +- **State management** uses five separate dicts (sessions, results, task_names, running_tasks, cancellation_reasons) +- **Best practices** prevent resource exhaustion and memory leaks + +**Next Steps:** -!!!tip "Next Steps" - - Explore [WebSocket Handler](./websocket_handler.md) to see how sessions are triggered - - Learn about [AIP Protocol](../aip/overview.md) for task assignment message format - - Review [Monitoring](./monitoring.md) for production session tracking +- Explore [WebSocket Handler](./websocket_handler.md) to see how sessions are triggered +- Learn about [AIP Protocol](../aip/overview.md) for task assignment message format +- Review [Client Connection Manager](./client_connection_manager.md) for session-to-client mapping diff --git a/documents/docs/server/websocket_handler.md b/documents/docs/server/websocket_handler.md index de3bf3a5c..7b919064a 100644 --- a/documents/docs/server/websocket_handler.md +++ b/documents/docs/server/websocket_handler.md @@ -1,14 +1,14 @@ # WebSocket Handler -!!!quote "The Protocol Orchestrator" - The **UFOWebSocketHandler** is the central nervous system of the server, implementing the Agent Interaction Protocol (AIP) to manage structured, reliable communication between the server and all connected clients. +The **UFOWebSocketHandler** is the central nervous system of the server, implementing the Agent Interaction Protocol (AIP) to manage structured, reliable communication between the server and all connected clients. + +For context on how this component fits into the server architecture, see the [Server Overview](overview.md). --- ## 🎯 Overview -!!!info "Core Responsibilities" - The WebSocket Handler acts as the protocol orchestrator, managing all aspects of client communication: +The WebSocket Handler acts as the protocol orchestrator, managing all aspects of client communication: | Responsibility | Description | Protocol Used | |----------------|-------------|---------------| @@ -18,7 +18,7 @@ | **Device Info Exchange** | Query and share device capabilities | AIP Device Info Protocol | | **Command Results** | Relay execution results from devices to requesters | AIP Message Transport | | **Error Handling** | Gracefully handle communication failures | AIP Error Protocol | -| **Connection Lifecycle** | Manage registration active cleanup flow | WebSocket + AIP | +| **Connection Lifecycle** | Manage registration → active → cleanup flow | WebSocket + AIP | ### Architecture Position @@ -76,8 +76,7 @@ graph TB ## 🔌 AIP Protocol Integration -!!!success "Multi-Protocol Architecture" - The handler uses **four specialized AIP protocols**, each handling a specific aspect of communication. This separation of concerns makes the code maintainable and testable. +The handler uses **four specialized AIP protocols**, each handling a specific aspect of communication. This separation of concerns makes the code maintainable and testable. For detailed protocol specifications, see the [AIP Protocol Documentation](../aip/overview.md). ```python def __init__(self, client_manager, session_manager, local=False): @@ -112,15 +111,15 @@ async def connect(self, websocket: WebSocket) -> str: # ... registration flow ... ``` -!!!note "Per-Connection Protocol Instances" - Each WebSocket connection gets its **own set of protocol instances**, ensuring message routing and state management are isolated between clients. +**Per-Connection Protocol Instances:** + +Each WebSocket connection gets its **own set of protocol instances**, ensuring message routing and state management are isolated between clients. --- ## 📝 Client Registration -!!!info "First Contact Protocol" - Registration is the critical first step when a client connects. The handler validates client identity, checks permissions, and establishes the communication session. +Registration is the critical first step when a client connects. The handler validates client identity, checks permissions, and establishes the communication session. ### Registration Flow @@ -171,41 +170,43 @@ sequenceDiagram ### Registration Steps (Code Walkthrough) -!!!example "Step 1: WebSocket Connection Accepted" - ```python - async def connect(self, websocket: WebSocket) -> str: - # Accept WebSocket connection - await websocket.accept() - - # Initialize AIP protocols for this connection - self.transport = WebSocketTransport(websocket) - self.registration_protocol = RegistrationProtocol(self.transport) - self.heartbeat_protocol = HeartbeatProtocol(self.transport) - self.device_info_protocol = DeviceInfoProtocol(self.transport) - self.task_protocol = TaskExecutionProtocol(self.transport) - ``` +**Step 1: WebSocket Connection Accepted** -!!!example "Step 2: Receive Registration Message" - ```python - async def _parse_registration_message(self, websocket: WebSocket) -> ClientMessage: - """Parse and validate registration message using AIP Transport.""" - self.logger.info("[WS] [AIP] Waiting for registration message...") - - # Receive via AIP Transport - reg_data = await self.transport.receive() - if isinstance(reg_data, bytes): - reg_data = reg_data.decode("utf-8") - - # Parse using Pydantic model - reg_info = ClientMessage.model_validate_json(reg_data) - - self.logger.info( - f"[WS] [AIP] Received registration from {reg_info.client_id}, " - f"type={reg_info.client_type}" - ) - - return reg_info - ``` +```python +async def connect(self, websocket: WebSocket) -> str: + # Accept WebSocket connection + await websocket.accept() + + # Initialize AIP protocols for this connection + self.transport = WebSocketTransport(websocket) + self.registration_protocol = RegistrationProtocol(self.transport) + self.heartbeat_protocol = HeartbeatProtocol(self.transport) + self.device_info_protocol = DeviceInfoProtocol(self.transport) + self.task_protocol = TaskExecutionProtocol(self.transport) +``` + +**Step 2: Receive Registration Message** + +```python +async def _parse_registration_message(self) -> ClientMessage: + """Parse and validate registration message using AIP Transport.""" + self.logger.info("[WS] [AIP] Waiting for registration message...") + + # Receive via AIP Transport + reg_data = await self.transport.receive() + if isinstance(reg_data, bytes): + reg_data = reg_data.decode("utf-8") + + # Parse using Pydantic model + reg_info = ClientMessage.model_validate_json(reg_data) + + self.logger.info( + f"[WS] [AIP] Received registration from {reg_info.client_id}, " + f"type={reg_info.client_type}" + ) + + return reg_info +``` **Expected Registration Message:** @@ -223,25 +224,24 @@ sequenceDiagram } ``` -!!!example "Step 3: Validation" - ```python - # Basic validation - if not reg_info.client_id: - raise ValueError("Client ID is required for WebSocket registration") - if reg_info.type != ClientMessageType.REGISTER: - raise ValueError("First message must be a registration message") - - # Constellation-specific validation - if client_type == ClientType.CONSTELLATION: - await self._validate_constellation_client(reg_info, websocket) - ``` +**Step 3: Validation** + +```python +# Basic validation +if not reg_info.client_id: + raise ValueError("Client ID is required for WebSocket registration") +if reg_info.type != ClientMessageType.REGISTER: + raise ValueError("First message must be a registration message") + +# Constellation-specific validation +if client_type == ClientType.CONSTELLATION: + await self._validate_constellation_client(reg_info) +``` **Constellation Validation:** ```python -async def _validate_constellation_client( - self, reg_info: ClientMessage, websocket: WebSocket -) -> None: +async def _validate_constellation_client(self, reg_info: ClientMessage) -> None: """Validate constellation's claimed target_id.""" claimed_device_id = reg_info.target_id @@ -254,34 +254,38 @@ async def _validate_constellation_client( self.logger.warning(f"[WS] Constellation registration failed: {error_msg}") # Send error via AIP protocol - await self._send_error_response(websocket, error_msg) - await websocket.close() + await self._send_error_response(error_msg) + await self.transport.close() raise ValueError(error_msg) ``` -!!!example "Step 4: Register Client in ClientConnectionManager" - ```python - client_type = reg_info.client_type - platform = reg_info.metadata.get("platform", "windows") if reg_info.metadata else "windows" - - # Register in Client Connection Manager - self.client_manager.add_client( - client_id=reg_info.client_id, - platform=platform, - websocket=websocket, - client_type=client_type, - metadata=reg_info.metadata - ) - ``` +**Step 4: Register Client in [ClientConnectionManager](./client_connection_manager.md)** -!!!example "Step 5: Send Confirmation" - ```python - async def _send_registration_confirmation(self, websocket: WebSocket) -> None: - """Send successful registration confirmation using AIP RegistrationProtocol.""" - self.logger.info("[WS] [AIP] Sending registration confirmation...") - await self.registration_protocol.send_registration_confirmation() - self.logger.info("[WS] [AIP] Registration confirmation sent") - ``` +```python +client_type = reg_info.client_type +platform = reg_info.metadata.get("platform", "windows") if reg_info.metadata else "windows" + +# Register in Client Connection Manager +self.client_manager.add_client( + client_id, + platform, + websocket, + client_type, + reg_info.metadata, + transport=self.transport, + task_protocol=self.task_protocol, +) +``` + +**Step 5: Send Confirmation** + +```python +async def _send_registration_confirmation(self) -> None: + """Send successful registration confirmation using AIP RegistrationProtocol.""" + self.logger.info("[WS] [AIP] Sending registration confirmation...") + await self.registration_protocol.send_registration_confirmation() + self.logger.info("[WS] [AIP] Registration confirmation sent") +``` **Confirmation Message:** @@ -294,15 +298,16 @@ async def _validate_constellation_client( } ``` -!!!example "Step 6: Log Success" - ```python - def _log_client_connection(self, client_id: str, client_type: ClientType) -> None: - """Log successful client connection with appropriate emoji.""" - if client_type == ClientType.DEVICE: - self.logger.info(f"[WS] Registered device client: {client_id}") - elif client_type == ClientType.CONSTELLATION: - self.logger.info(f"[WS] 🌟 Registered constellation client: {client_id}") - ``` +**Step 6: Log Success** + +```python +def _log_client_connection(self, client_id: str, client_type: ClientType) -> None: + """Log successful client connection with appropriate emoji.""" + if client_type == ClientType.DEVICE: + self.logger.info(f"[WS] Registered device client: {client_id}") + elif client_type == ClientType.CONSTELLATION: + self.logger.info(f"[WS] 🌟 Registered constellation client: {client_id}") +``` ### Validation Rules @@ -313,12 +318,13 @@ async def _validate_constellation_client( | **Target Device (Constellation)** | If `target_id` specified, device must be online | `"Target device '' is not connected"` | Send error + close | | **Client ID Uniqueness** | No existing client with same ID | Handled by ClientConnectionManager | Disconnect old connection | -!!!warning "Constellation Dependency" - Constellations **must** specify a valid `target_id` that refers to an already-connected device. If the device is offline or doesn't exist, registration fails immediately. - - **Workaround:** Connect devices first, then constellations. +**Constellation Dependency:** -!!!danger "Security Consideration" +Constellations **must** specify a valid `target_id` that refers to an already-connected device. If the device is offline or doesn't exist, registration fails immediately. + +**Workaround:** Connect devices first, then constellations. + +**Security Consideration:** The current implementation does **not** authenticate clients. Any client can register with any `client_id`. For production deployments: - Implement authentication tokens in `metadata` @@ -330,8 +336,7 @@ async def _validate_constellation_client( ## 📨 Message Handling -!!!info "Central Message Router" - After registration, the handler enters a message loop, routing incoming client messages to specialized handlers based on message type. +After registration, the handler enters a message loop, routing incoming client messages to specialized handlers based on message type. ### Message Dispatcher @@ -412,8 +417,7 @@ async def handle_message(self, msg: str, websocket: WebSocket) -> None: ### Task Request Handling -!!!success "Dual Client Support" - The handler supports task requests from **both device clients** (self-execution) and **constellation clients** (orchestrated execution on target devices). +The handler supports task requests from **both device clients** (self-execution) and **constellation clients** (orchestrated execution on target devices). **Task Request Flow:** @@ -452,37 +456,39 @@ sequenceDiagram WH->>C: TASK_END
    {status, result} ``` -!!!example "Device Client Self-Execution" - When a **device** requests a task for itself: - - ```python - async def handle_task_request(self, data: ClientMessage, websocket: WebSocket): - client_id = data.client_id - client_type = data.client_type - - if client_type == ClientType.DEVICE: - # Device executing task on itself - target_ws = websocket # Use requesting client's WebSocket - platform = self.client_manager.get_client_info(client_id).platform - target_device_id = client_id - # ... - ``` +**Device Client Self-Execution:** -!!!example "Constellation Orchestrated Execution" - When a **constellation** dispatches a task to a target device: +When a **device** requests a task for itself: + +```python +async def handle_task_request(self, data: ClientMessage, websocket: WebSocket): + client_id = data.client_id + client_type = data.client_type + + if client_type == ClientType.DEVICE: + # Device executing task on itself + target_ws = websocket # Use requesting client's WebSocket + platform = self.client_manager.get_client_info(client_id).platform + target_device_id = client_id + # ... +``` + +**Constellation Orchestrated Execution:** + +When a **constellation** dispatches a task to a target device: + +```python +async def handle_task_request(self, data: ClientMessage, websocket: WebSocket): + client_id = data.client_id + client_type = data.client_type - ```python - async def handle_task_request(self, data: ClientMessage, websocket: WebSocket): - client_id = data.client_id - client_type = data.client_type + if client_type == ClientType.CONSTELLATION: + # Constellation dispatching to target device + target_device_id = data.target_id + target_ws = self.client_manager.get_client(target_device_id) + platform = self.client_manager.get_client_info(target_device_id).platform - if client_type == ClientType.CONSTELLATION: - # Constellation dispatching to target device - target_device_id = data.target_id - target_ws = self.client_manager.get_client(target_device_id) - platform = self.client_manager.get_client_info(target_device_id).platform - - # Validate target device exists + # Validate target device exists if not target_ws: raise ValueError(f"Target device '{target_device_id}' not connected") @@ -512,7 +518,7 @@ async def send_result(sid: str, result_msg: ServerMessage): if websocket.client_state == WebSocketState.CONNECTED: await websocket.send_text(result_msg.model_dump_json()) -# Execute in background +# Execute in background via SessionManager await self.session_manager.execute_task_async( session_id=session_id, task_name=task_name, @@ -526,14 +532,15 @@ await self.session_manager.execute_task_async( await self.task_protocol.send_ack(session_id=session_id) ``` -!!!info "Why Immediate ACK?" - The handler sends an **immediate ACK** after dispatching the task to the SessionManager. This confirms: - - - Task was received and validated - - Session was created successfully - - Task is now executing in background - - The actual task result is delivered later via the `send_result` callback. +**Why Immediate ACK?** + +The handler sends an **immediate ACK** after dispatching the task to the [SessionManager](./session_manager.md). This confirms: + +- Task was received and validated +- Session was created successfully +- Task is now executing in background + +The actual task result is delivered later via the `send_result` callback. **Session Tracking:** @@ -547,8 +554,7 @@ await self.task_protocol.send_ack(session_id=session_id) ### Command Result Handling -!!!info "Device Server Communication" - When a device executes a command (e.g., "click button", "type text"), it sends results back to the server for processing by the session's command dispatcher. +When a device executes a command (e.g., "click button", "type text"), it sends results back to the server for processing by the session's command dispatcher. **Command Result Flow:** @@ -606,15 +612,15 @@ async def handle_command_result(self, data: ClientMessage): ) ``` -!!!warning "Critical for Session Execution" - Without proper command result handling, sessions would **hang indefinitely** waiting for device responses. The `set_result()` call is what unblocks the `await` in the command dispatcher. +**Critical for Session Execution:** + +Without proper command result handling, sessions would **hang indefinitely** waiting for device responses. The `set_result()` call is what unblocks the `await` in the command dispatcher. --- ### Heartbeat Handling -!!!success "Connection Health Monitoring" - Heartbeats are lightweight ping/pong messages that ensure the WebSocket connection is alive and healthy. +Heartbeats are lightweight ping/pong messages that ensure the WebSocket connection is alive and healthy. ```python async def handle_heartbeat(self, data: ClientMessage, websocket: WebSocket) -> None: @@ -650,18 +656,18 @@ async def handle_heartbeat(self, data: ClientMessage, websocket: WebSocket) -> N } ``` -!!!tip "Heartbeat Best Practices" - - **Frequency:** Clients should send heartbeats every **30-60 seconds** - - **Timeout:** Server should consider connection dead after **2-3 missed heartbeats** - - **Lightweight:** Heartbeat messages are small and processed quickly - - **Non-blocking:** Heartbeat handling doesn't block task execution +**Heartbeat Best Practices:** + +- **Frequency:** Clients should send heartbeats every **30-60 seconds** +- **Timeout:** Server should consider connection dead after **2-3 missed heartbeats** +- **Lightweight:** Heartbeat messages are small and processed quickly +- **Non-blocking:** Heartbeat handling doesn't block task execution --- ### Device Info Handling -!!!info "Capability Discovery" - Constellations can query device capabilities (screen resolution, installed apps, OS version) to make intelligent task routing decisions. +Constellations can query device capabilities (screen resolution, installed apps, OS version) to make intelligent task routing decisions. **Device Info Request Flow:** @@ -736,8 +742,9 @@ async def handle_device_info_request( ## 🔌 Client Disconnection -!!!danger "Critical Cleanup Process" - When a client disconnects (gracefully or abruptly), the handler must clean up sessions, remove registry entries, and prevent resource leaks. +**Critical Cleanup Process:** + +When a client disconnects (gracefully or abruptly), the handler must clean up sessions, remove registry entries, and prevent resource leaks. ### Disconnection Detection @@ -797,61 +804,63 @@ graph TD style J fill:#c8e6c9 ``` -!!!example "Device Client Cleanup" - ```python - async def disconnect(self, client_id: str) -> None: - """Handle client disconnection and cleanup.""" - client_info = self.client_manager.get_client_info(client_id) - - if client_info and client_info.client_type == ClientType.DEVICE: - # Get all sessions running on this device - session_ids = self.client_manager.get_device_sessions(client_id) - - if session_ids: - self.logger.info( - f"[WS] 📱 Device {client_id} disconnected, " - f"cancelling {len(session_ids)} active session(s)" - ) - - # Cancel all sessions - for session_id in session_ids: - try: - await self.session_manager.cancel_task( - session_id, - reason="device_disconnected" # Send callback to constellation - ) - except Exception as e: - self.logger.error(f"Error cancelling session {session_id}: {e}") - - # Clean up mappings - self.client_manager.remove_device_sessions(client_id) - ``` +**Device Client Cleanup:** -!!!example "Constellation Client Cleanup" - ```python - if client_info and client_info.client_type == ClientType.CONSTELLATION: - # Get all sessions initiated by constellation - session_ids = self.client_manager.get_constellation_sessions(client_id) +```python +async def disconnect(self, client_id: str) -> None: + """Handle client disconnection and cleanup.""" + client_info = self.client_manager.get_client_info(client_id) + + if client_info and client_info.client_type == ClientType.DEVICE: + # Get all sessions running on this device + session_ids = self.client_manager.get_device_sessions(client_id) if session_ids: self.logger.info( - f"[WS] 🌟 Constellation {client_id} disconnected, " + f"[WS] 📱 Device {client_id} disconnected, " f"cancelling {len(session_ids)} active session(s)" ) - # Cancel all associated sessions + # Cancel all sessions for session_id in session_ids: try: await self.session_manager.cancel_task( session_id, - reason="constellation_disconnected" # Don't send callback + reason="device_disconnected" # Send callback to constellation ) except Exception as e: self.logger.error(f"Error cancelling session {session_id}: {e}") # Clean up mappings - self.client_manager.remove_constellation_sessions(client_id) - ``` + self.client_manager.remove_device_sessions(client_id) +``` + +**Constellation Client Cleanup:** + +```python +if client_info and client_info.client_type == ClientType.CONSTELLATION: + # Get all sessions initiated by constellation + session_ids = self.client_manager.get_constellation_sessions(client_id) + + if session_ids: + self.logger.info( + f"[WS] 🌟 Constellation {client_id} disconnected, " + f"cancelling {len(session_ids)} active session(s)" + ) + + # Cancel all associated sessions + for session_id in session_ids: + try: + await self.session_manager.cancel_task( + session_id, + reason="constellation_disconnected" # Don't send callback + ) + except Exception as e: + self.logger.error(f"Error cancelling session {session_id}: {e}") + + # Clean up mappings + self.client_manager.remove_constellation_sessions(client_id) +``` **Final Registry Cleanup:** @@ -868,20 +877,20 @@ self.logger.info(f"[WS] {client_id} disconnected") | **Device Disconnects** | `device_disconnected` | Yes Constellation | Notify orchestrator to reassign task | | **Constellation Disconnects** | `constellation_disconnected` | No | Requester is gone, no one to notify | -!!!warning "Proper Cleanup is Critical" - Failing to clean up disconnected clients leads to: - - - **Orphaned sessions** consuming server memory - - **Stale WebSocket references** causing errors - - **Registry pollution** with non-existent clients - - **Resource leaks** (file handles, memory) +**Proper Cleanup is Critical:** + +Failing to clean up disconnected clients leads to: + +- **Orphaned sessions** consuming server memory +- **Stale WebSocket references** causing errors +- **Registry pollution** with non-existent clients +- **Resource leaks** (file handles, memory) --- ## 🚨 Error Handling -!!!info "Graceful Degradation" - The handler implements comprehensive error handling to prevent failures from cascading and breaking the entire server. +The handler implements comprehensive error handling to prevent failures from cascading and breaking the entire server. ### Error Categories @@ -905,8 +914,9 @@ async def handle_heartbeat(self, data: ClientMessage, websocket: WebSocket): # Don't raise - connection is already closed ``` -!!!tip "Why Catch and Ignore?" - When a connection is abruptly closed, attempts to send messages will raise `ConnectionError`. Since the client is already gone, there's no point in propagating the error—just log it and continue cleanup. +**Why Catch and Ignore?** + +When a connection is abruptly closed, attempts to send messages will raise `ConnectionError`. Since the client is already gone, there's no point in propagating the error—just log it and continue cleanup. ### Message Parsing Errors @@ -961,7 +971,7 @@ async def handle_task_request(self, data: ClientMessage, websocket: WebSocket): ### Validation Errors with Connection Closure ```python -async def _validate_constellation_client(self, reg_info, websocket): +async def _validate_constellation_client(self, reg_info: ClientMessage) -> None: """Validate constellation's target device.""" claimed_device_id = reg_info.target_id @@ -970,30 +980,29 @@ async def _validate_constellation_client(self, reg_info, websocket): self.logger.warning(f"Constellation registration failed: {error_msg}") # Send error via AIP protocol - await self._send_error_response(websocket, error_msg) + await self._send_error_response(error_msg) # Close connection immediately - await websocket.close() + await self.transport.close() # Raise to prevent further processing raise ValueError(error_msg) ``` -!!!danger "When to Close Connections" - Close connections immediately for: - - - **Invalid registration** (missing client_id, wrong message type) - - **Authorization failures** (target device not connected for constellations) - - **Protocol violations** (sending TASK before REGISTER) - - For other errors, log and send error messages but **keep connection alive**. +**When to Close Connections:** + +Close connections immediately for: + +- **Invalid registration** (missing client_id, wrong message type) +- **Authorization failures** (target device not connected for constellations) +- **Protocol violations** (sending TASK before REGISTER) + +For other errors, log and send error messages but **keep connection alive**. --- ## Best Practices -!!!tip "Production-Ready WebSocket Handling" - ### 1. Validate Early and Thoroughly ```python @@ -1077,21 +1086,21 @@ async def handler(self, websocket: WebSocket): # Loop continues immediately, doesn't wait for handler to finish ``` -!!!warning "Why `asyncio.create_task`?" - Without `create_task`, the handler would process messages **sequentially**, blocking new messages while handling the current one. This is problematic for: - - - Long-running task dispatches - - Command result processing - - Device info queries - - Background tasks allow **concurrent message processing** while keeping the receive loop responsive. +**Why `asyncio.create_task`?** + +Without `create_task`, the handler would process messages **sequentially**, blocking new messages while handling the current one. This is problematic for: + +- Long-running task dispatches +- Command result processing +- Device info queries + +Background tasks allow **concurrent message processing** while keeping the receive loop responsive. --- ## 📚 Related Documentation -!!!info "Learn More" - Explore related components to understand the full server architecture: +Explore related components to understand the full server architecture: | Component | Purpose | Link | |-----------|---------|------| @@ -1106,21 +1115,21 @@ async def handler(self, websocket: WebSocket): ## 🎓 What You Learned -!!!success "Key Takeaways" - After reading this guide, you should understand: - - - **AIP Protocol Integration** - Four specialized protocols handle different communication aspects - - **Registration Flow** - Validation Registration Confirmation - - **Message Routing** - Central dispatcher routes messages to specialized handlers - - **Dual Client Support** - Devices (self-execution) vs. Constellations (orchestration) - - **Background Task Dispatch** - Immediate ACK + async execution - - **Command Result Handling** - Unblocks command dispatcher waiting for device responses - - **Heartbeat Monitoring** - Lightweight connection health checks - - **Disconnection Cleanup** - Context-aware session cancellation and registry cleanup - - **Error Handling** - Graceful degradation without cascading failures - -!!!tip "Next Steps" - - Explore [Session Manager](./session_manager.md) to understand background execution internals - - Learn about [Client Connection Manager](./client_connection_manager.md) for client registry management - - Review [AIP Protocol Documentation](../aip/overview.md) for message format specifications +After reading this guide, you should understand: + +- **AIP Protocol Integration** - Four specialized protocols handle different communication aspects +- **Registration Flow** - Validation → Registration → Confirmation +- **Message Routing** - Central dispatcher routes messages to specialized handlers +- **Dual Client Support** - Devices (self-execution) vs. Constellations (orchestration) +- **Background Task Dispatch** - Immediate ACK + async execution +- **Command Result Handling** - Unblocks command dispatcher waiting for device responses +- **Heartbeat Monitoring** - Lightweight connection health checks +- **Disconnection Cleanup** - Context-aware session cancellation and registry cleanup +- **Error Handling** - Graceful degradation without cascading failures + +**Next Steps:** + +- Explore [Session Manager](./session_manager.md) to understand background execution internals +- Learn about [Client Connection Manager](./client_connection_manager.md) for client registry management +- Review [AIP Protocol Documentation](../aip/overview.md) for message format specifications diff --git a/documents/docs/ufo2/lts_support_policy.md b/documents/docs/ufo2/lts_support_policy.md deleted file mode 100644 index 97271e7bc..000000000 --- a/documents/docs/ufo2/lts_support_policy.md +++ /dev/null @@ -1,24 +0,0 @@ -# Lts Support Policy - -!!!warning "Documentation In Progress" - This page is a placeholder. Content will be added soon. - -## Overview - -TODO: Add overview content for Lts Support Policy. - -## Key Concepts - -TODO: Add key concepts. - -## Usage - -TODO: Add usage instructions. - -## Examples - -TODO: Add examples. - -## Related Documentation - -TODO: Add links to related pages. diff --git a/documents/mkdocs.yml b/documents/mkdocs.yml index 51b274000..2649a510a 100644 --- a/documents/mkdocs.yml +++ b/documents/mkdocs.yml @@ -11,7 +11,6 @@ nav: - Quick Start (Linux Agent): getting_started/quick_start_linux.md - Migration UFO² → UFO³: getting_started/migration_ufo2_to_galaxy.md - More Guidance: getting_started/more_guidance.md - - FAQ: getting_started/faq.md - Configuration & Setup: - Configuration System: - Overview: configuration/system/overview.md @@ -132,7 +131,6 @@ nav: - Execution: ufo2/dataflow/execution.md - Windows App Environment: ufo2/dataflow/windows_app_env.md - Result: ufo2/dataflow/result.md - - LTS Support Policy: ufo2/lts_support_policy.md - Linux Agent: - Overview: linux/overview.md - Using as Galaxy Device: linux/as_galaxy_device.md diff --git a/galaxy/agents/constellation_agent.py b/galaxy/agents/constellation_agent.py index d935fd858..b1c4c4828 100644 --- a/galaxy/agents/constellation_agent.py +++ b/galaxy/agents/constellation_agent.py @@ -551,6 +551,17 @@ def print_response( # Publish event asynchronously (non-blocking) asyncio.create_task(get_event_bus().publish_event(event)) + @property + def default_state(self): + """ + Get the default state of the Constellation Agent. + + :return: The default StartConstellationAgentState + """ + from .constellation_agent_states import StartConstellationAgentState + + return StartConstellationAgentState() + @property def status_manager(self): """Get the status manager.""" diff --git a/galaxy/galaxy.py b/galaxy/galaxy.py index 1093fcff5..d8dbc1ab4 100644 --- a/galaxy/galaxy.py +++ b/galaxy/galaxy.py @@ -345,6 +345,19 @@ def find_free_port(start_port=8000, max_attempts=10): ) return + # Write port info to frontend config file for development mode + frontend_dir = Path(__file__).parent / "webui" / "frontend" + if frontend_dir.exists(): + env_file = frontend_dir / ".env.development.local" + try: + with open(env_file, "w", encoding="utf-8") as f: + f.write(f"# Auto-generated by Galaxy backend\n") + f.write(f"# This file is updated each time the backend starts\n") + f.write(f"VITE_BACKEND_URL=http://localhost:{port}\n") + client.display.print_info(f"📝 Updated frontend config: {env_file}") + except Exception as e: + client.display.print_warning(f"⚠️ Could not write frontend config: {e}") + # Display banner client.display.print_info("🌌 Galaxy WebUI Starting...") client.display.print_info(f"📡 Server: http://localhost:{port}") diff --git a/galaxy/session/galaxy_session.py b/galaxy/session/galaxy_session.py index f5d79a145..48484a4bb 100644 --- a/galaxy/session/galaxy_session.py +++ b/galaxy/session/galaxy_session.py @@ -485,8 +485,14 @@ def reset(self) -> None: # Save device info before clearing (should not be cleared on reset) device_info = self._context.get(ContextNames.DEVICE_INFO) - # Reset agent state - self._agent.set_state(self._agent.default_state) + # Reset agent state to default if available + default_state = self._agent.default_state + if default_state is not None: + self._agent.set_state(default_state) + else: + self.logger.warning( + f"Agent {type(self._agent).__name__} has no default_state defined, skipping state reset" + ) # Clear rounds and results self._rounds.clear() diff --git a/galaxy/webui/dependencies.py b/galaxy/webui/dependencies.py new file mode 100644 index 000000000..731da9821 --- /dev/null +++ b/galaxy/webui/dependencies.py @@ -0,0 +1,147 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Dependency management for Galaxy Web UI. + +This module manages global state and provides dependency injection +for FastAPI endpoints and WebSocket handlers. +""" + +import logging +from typing import TYPE_CHECKING, Optional + +from galaxy.webui.websocket_observer import WebSocketObserver + +if TYPE_CHECKING: + from galaxy.galaxy_client import GalaxyClient + from galaxy.session.galaxy_session import GalaxySession + + +class AppState: + """ + Application state container. + + Manages global state for the Web UI server including: + - WebSocket observer for event broadcasting + - Galaxy session and client instances + - Request counter for tracking user requests + + This class provides a centralized way to manage shared state + across the application instead of using global variables. + """ + + def __init__(self) -> None: + """Initialize the application state with default values.""" + self.logger: logging.Logger = logging.getLogger(__name__) + + # WebSocket observer for broadcasting events to clients + self._websocket_observer: Optional[WebSocketObserver] = None + + # Galaxy session and client instances + self._galaxy_session: Optional["GalaxySession"] = None + self._galaxy_client: Optional["GalaxyClient"] = None + + # Counter for generating unique task names in Web UI mode + self._request_counter: int = 0 + + @property + def websocket_observer(self) -> Optional[WebSocketObserver]: + """ + Get the WebSocket observer instance. + + :return: WebSocket observer or None if not initialized + """ + return self._websocket_observer + + @websocket_observer.setter + def websocket_observer(self, observer: WebSocketObserver) -> None: + """ + Set the WebSocket observer instance. + + :param observer: WebSocket observer to use for event broadcasting + """ + self._websocket_observer = observer + self.logger.info(f"WebSocket observer set: {observer}") + + @property + def galaxy_session(self) -> Optional["GalaxySession"]: + """ + Get the current Galaxy session. + + :return: Galaxy session or None if not initialized + """ + return self._galaxy_session + + @galaxy_session.setter + def galaxy_session(self, session: "GalaxySession") -> None: + """ + Set the Galaxy session. + + :param session: Galaxy session instance + """ + self._galaxy_session = session + self.logger.info("Galaxy session set") + + @property + def galaxy_client(self) -> Optional["GalaxyClient"]: + """ + Get the current Galaxy client. + + :return: Galaxy client or None if not initialized + """ + return self._galaxy_client + + @galaxy_client.setter + def galaxy_client(self, client: "GalaxyClient") -> None: + """ + Set the Galaxy client. + + :param client: Galaxy client instance + """ + self._galaxy_client = client + self.logger.info("Galaxy client set") + + @property + def request_counter(self) -> int: + """ + Get the current request counter value. + + :return: Current request counter + """ + return self._request_counter + + def increment_request_counter(self) -> int: + """ + Increment and return the request counter. + + :return: New counter value after increment + """ + self._request_counter += 1 + return self._request_counter + + def reset_request_counter(self) -> None: + """ + Reset the request counter to zero. + + Called when session is reset or task is stopped. + """ + self._request_counter = 0 + self.logger.info("Request counter reset to 0") + + +# Global application state instance +# This is initialized once and shared across the application +app_state = AppState() + + +def get_app_state() -> AppState: + """ + Get the application state instance. + + This function can be used as a FastAPI dependency to inject + the application state into route handlers. + + :return: Application state instance + """ + return app_state diff --git a/galaxy/webui/frontend/.env.example b/galaxy/webui/frontend/.env.example new file mode 100644 index 000000000..425ecf763 --- /dev/null +++ b/galaxy/webui/frontend/.env.example @@ -0,0 +1,4 @@ +# Backend API URL for development +# If your Galaxy backend is running on a different port, update this +# Example: VITE_BACKEND_URL=http://localhost:8001 +VITE_BACKEND_URL=http://localhost:8000 diff --git a/galaxy/webui/frontend/README.md b/galaxy/webui/frontend/README.md new file mode 100644 index 000000000..ab82737fc --- /dev/null +++ b/galaxy/webui/frontend/README.md @@ -0,0 +1,100 @@ +# Galaxy WebUI Frontend + +React-based frontend for Galaxy Framework with real-time WebSocket updates. + +## Development Mode + +### Prerequisites +- Node.js 16+ and npm +- Galaxy backend running + +### Quick Start + +1. **Start the Galaxy backend** (in a separate terminal): + ```bash + cd UFO + python -m galaxy --webui + ``` + + The backend will: + - Find an available port (8000-8009) + - Auto-generate `.env.development.local` with the backend URL + - Display the backend URL (e.g., `http://localhost:8001`) + +2. **Start the frontend development server**: + ```bash + cd galaxy/webui/frontend + npm install # First time only + npm run dev + ``` + + The frontend will: + - Read the backend URL from `.env.development.local` + - Start on port 3000 (or 3001 if 3000 is busy) + - Connect to the backend automatically + +3. **Open your browser**: + - Frontend: `http://localhost:3000` (or 3001) + - The frontend will connect to backend automatically + +### Manual Port Configuration + +If you need to manually specify the backend port: + +1. Copy `.env.example` to `.env.development.local`: + ```bash + cp .env.example .env.development.local + ``` + +2. Edit `.env.development.local`: + ``` + VITE_BACKEND_URL=http://localhost:8001 + ``` + +3. Restart the frontend dev server + +## Production Mode + +In production, the backend serves the built frontend automatically: + +```bash +# Build the frontend +cd galaxy/webui/frontend +npm run build + +# Start Galaxy with WebUI +cd ../../.. +python -m galaxy --webui +``` + +Then open `http://localhost:8000` (or whatever port the backend chooses). + +## Architecture + +- **Development**: Frontend (Vite) runs separately, connects to backend via direct HTTP/WebSocket +- **Production**: Backend (FastAPI) serves built frontend static files + +## Troubleshooting + +### Error: "Unexpected token '<', ":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-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * 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))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-\[24px\]{border-radius:24px}.rounded-\[28px\]{border-radius:28px}.rounded-\[30px\]{border-radius:30px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded-bl-xl{border-bottom-left-radius:.75rem}.rounded-br-xl{border-bottom-right-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[rgba\(10\,186\,181\,0\.35\)\]{border-color:#0abab559}.border-\[rgba\(10\,186\,181\,0\.4\)\]{border-color:#0abab566}.border-amber-400\/30{border-color:#fbbf244d}.border-amber-400\/40{border-color:#fbbf2466}.border-cyan-400\/30{border-color:#22d3ee4d}.border-cyan-400\/40{border-color:#22d3ee66}.border-cyan-400\/50{border-color:#22d3ee80}.border-cyan-500\/30{border-color:#06b6d44d}.border-emerald-400\/20{border-color:#34d39933}.border-emerald-400\/30{border-color:#34d3994d}.border-emerald-400\/40{border-color:#34d39966}.border-emerald-500\/50{border-color:#10b98180}.border-galaxy-blue\/50{border-color:#0f7bff80}.border-galaxy-blue\/60{border-color:#0f7bff99}.border-indigo-400\/30{border-color:#818cf84d}.border-purple-400\/20{border-color:#c084fc33}.border-purple-400\/30{border-color:#c084fc4d}.border-rose-400\/20{border-color:#fb718533}.border-rose-400\/30{border-color:#fb71854d}.border-rose-400\/40{border-color:#fb718566}.border-rose-500\/50{border-color:#f43f5e80}.border-rose-900\/40{border-color:#88133766}.border-slate-400\/30{border-color:#94a3b84d}.border-white\/10{border-color:#ffffff1a}.border-white\/20{border-color:#fff3}.border-white\/5{border-color:#ffffff0d}.border-yellow-400\/30{border-color:#facc154d}.bg-\[\#0a0e1a\]{--tw-bg-opacity: 1;background-color:rgb(10 14 26 / var(--tw-bg-opacity, 1))}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-500\/20{background-color:#f59e0b33}.bg-black\/20{background-color:#0003}.bg-black\/30{background-color:#0000004d}.bg-black\/40{background-color:#0006}.bg-black\/60{background-color:#0009}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-cyan-400{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity, 1))}.bg-cyan-500\/20{background-color:#06b6d433}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-emerald-500\/20{background-color:#10b98133}.bg-emerald-950\/30{background-color:#022c224d}.bg-galaxy-blue{--tw-bg-opacity: 1;background-color:rgb(15 123 255 / var(--tw-bg-opacity, 1))}.bg-galaxy-blue\/15{background-color:#0f7bff26}.bg-indigo-500\/20{background-color:#6366f133}.bg-purple-300\/80{background-color:#d8b4fecc}.bg-rose-400{--tw-bg-opacity: 1;background-color:rgb(251 113 133 / var(--tw-bg-opacity, 1))}.bg-rose-500{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity, 1))}.bg-rose-500\/10{background-color:#f43f5e1a}.bg-rose-500\/20{background-color:#f43f5e33}.bg-rose-950\/30{background-color:#4c05194d}.bg-slate-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity, 1))}.bg-slate-500\/20{background-color:#64748b33}.bg-slate-500\/30{background-color:#64748b4d}.bg-slate-600{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white\/10{background-color:#ffffff1a}.bg-white\/5{background-color:#ffffff0d}.bg-yellow-500\/10{background-color:#eab3081a}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.bg-starfield{background-image:radial-gradient(circle at 10% 20%,rgba(33,240,255,.18),transparent 45%),radial-gradient(circle at 80% 10%,rgba(147,51,234,.22),transparent 50%),radial-gradient(circle at 50% 80%,rgba(14,116,144,.3),transparent 55%)}.from-\[rgba\(10\,186\,181\,0\.12\)\]{--tw-gradient-from: rgba(10,186,181,.12) var(--tw-gradient-from-position);--tw-gradient-to: rgba(10, 186, 181, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(10\,186\,181\,0\.15\)\]{--tw-gradient-from: rgba(10,186,181,.15) var(--tw-gradient-from-position);--tw-gradient-to: rgba(10, 186, 181, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(11\,24\,44\,0\.82\)\]{--tw-gradient-from: rgba(11,24,44,.82) var(--tw-gradient-from-position);--tw-gradient-to: rgba(11, 24, 44, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(11\,30\,45\,0\.85\)\]{--tw-gradient-from: rgba(11,30,45,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(11, 30, 45, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(11\,30\,45\,0\.88\)\]{--tw-gradient-from: rgba(11,30,45,.88) var(--tw-gradient-from-position);--tw-gradient-to: rgba(11, 30, 45, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(25\,40\,60\,0\.75\)\]{--tw-gradient-from: rgba(25,40,60,.75) var(--tw-gradient-from-position);--tw-gradient-to: rgba(25, 40, 60, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(6\,182\,212\,0\.2\)\]{--tw-gradient-from: rgba(6,182,212,.2) var(--tw-gradient-from-position);--tw-gradient-to: rgba(6, 182, 212, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(6\,182\,212\,0\.85\)\]{--tw-gradient-from: rgba(6,182,212,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(6, 182, 212, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(80\,20\,30\,0\.75\)\]{--tw-gradient-from: rgba(80,20,30,.75) var(--tw-gradient-from-position);--tw-gradient-to: rgba(80, 20, 30, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-black\/30{--tw-gradient-from: rgb(0 0 0 / .3) var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-black\/50{--tw-gradient-from: rgb(0 0 0 / .5) var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-200{--tw-gradient-from: #a5f3fc var(--tw-gradient-from-position);--tw-gradient-to: rgb(165 243 252 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-300{--tw-gradient-from: #67e8f9 var(--tw-gradient-from-position);--tw-gradient-to: rgb(103 232 249 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-500\/20{--tw-gradient-from: rgb(6 182 212 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(6 182 212 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-950\/30{--tw-gradient-from: rgb(8 51 68 / .3) var(--tw-gradient-from-position);--tw-gradient-to: rgb(8 51 68 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500\/10{--tw-gradient-from: rgb(16 185 129 / .1) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500\/15{--tw-gradient-from: rgb(16 185 129 / .15) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500\/35{--tw-gradient-from: rgb(16 185 129 / .35) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-galaxy-blue{--tw-gradient-from: #0F7BFF var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 123 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-galaxy-blue\/25{--tw-gradient-from: rgb(15 123 255 / .25) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 123 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-galaxy-blue\/40{--tw-gradient-from: rgb(15 123 255 / .4) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 123 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-500\/10{--tw-gradient-from: rgb(168 85 247 / .1) var(--tw-gradient-from-position);--tw-gradient-to: rgb(168 85 247 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-500\/20{--tw-gradient-from: rgb(168 85 247 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(168 85 247 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-950\/20{--tw-gradient-from: rgb(59 7 100 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(59 7 100 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-rose-500\/15{--tw-gradient-from: rgb(244 63 94 / .15) var(--tw-gradient-from-position);--tw-gradient-to: rgb(244 63 94 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-rose-500\/35{--tw-gradient-from: rgb(244 63 94 / .35) var(--tw-gradient-from-position);--tw-gradient-to: rgb(244 63 94 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-\[rgba\(100\,25\,35\,0\.70\)\]{--tw-gradient-to: rgba(100, 25, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(100,25,35,.7) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(12\,50\,65\,0\.8\)\]{--tw-gradient-to: rgba(12, 50, 65, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(12,50,65,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(147\,51\,234\,0\.80\)\]{--tw-gradient-to: rgba(147, 51, 234, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(147,51,234,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(15\,123\,255\,0\.15\)\]{--tw-gradient-to: rgba(15, 123, 255, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(15,123,255,.15) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(20\,35\,52\,0\.7\)\]{--tw-gradient-to: rgba(20, 35, 52, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(20,35,52,.7) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(8\,20\,35\,0\.82\)\]{--tw-gradient-to: rgba(8, 20, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(8,20,35,.82) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(8\,20\,35\,0\.85\)\]{--tw-gradient-to: rgba(8, 20, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(8,20,35,.85) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-galaxy-purple\/25{--tw-gradient-to: rgb(123 44 191 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgb(123 44 191 / .25) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-purple-200{--tw-gradient-to: rgb(233 213 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #e9d5ff var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-white{--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #fff var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-\[rgba\(11\,30\,45\,0\.85\)\]{--tw-gradient-to: rgba(11,30,45,.85) var(--tw-gradient-to-position)}.to-\[rgba\(15\,28\,45\,0\.75\)\]{--tw-gradient-to: rgba(15,28,45,.75) var(--tw-gradient-to-position)}.to-\[rgba\(15\,28\,45\,0\.8\)\]{--tw-gradient-to: rgba(15,28,45,.8) var(--tw-gradient-to-position)}.to-\[rgba\(236\,72\,153\,0\.85\)\]{--tw-gradient-to: rgba(236,72,153,.85) var(--tw-gradient-to-position)}.to-\[rgba\(6\,15\,28\,0\.85\)\]{--tw-gradient-to: rgba(6,15,28,.85) var(--tw-gradient-to-position)}.to-\[rgba\(6\,15\,28\,0\.88\)\]{--tw-gradient-to: rgba(6,15,28,.88) var(--tw-gradient-to-position)}.to-\[rgba\(6\,182\,212\,0\.15\)\]{--tw-gradient-to: rgba(6,182,212,.15) var(--tw-gradient-to-position)}.to-\[rgba\(8\,15\,28\,0\.75\)\]{--tw-gradient-to: rgba(8,15,28,.75) var(--tw-gradient-to-position)}.to-\[rgba\(8\,15\,28\,0\.85\)\]{--tw-gradient-to: rgba(8,15,28,.85) var(--tw-gradient-to-position)}.to-\[rgba\(80\,20\,30\,0\.75\)\]{--tw-gradient-to: rgba(80,20,30,.75) var(--tw-gradient-to-position)}.to-black\/20{--tw-gradient-to: rgb(0 0 0 / .2) var(--tw-gradient-to-position)}.to-black\/30{--tw-gradient-to: rgb(0 0 0 / .3) var(--tw-gradient-to-position)}.to-blue-500\/10{--tw-gradient-to: rgb(59 130 246 / .1) var(--tw-gradient-to-position)}.to-blue-500\/20{--tw-gradient-to: rgb(59 130 246 / .2) var(--tw-gradient-to-position)}.to-blue-950\/20{--tw-gradient-to: rgb(23 37 84 / .2) var(--tw-gradient-to-position)}.to-cyan-200{--tw-gradient-to: #a5f3fc var(--tw-gradient-to-position)}.to-cyan-500\/10{--tw-gradient-to: rgb(6 182 212 / .1) var(--tw-gradient-to-position)}.to-cyan-500\/15{--tw-gradient-to: rgb(6 182 212 / .15) var(--tw-gradient-to-position)}.to-emerald-600\/10{--tw-gradient-to: rgb(5 150 105 / .1) var(--tw-gradient-to-position)}.to-emerald-600\/25{--tw-gradient-to: rgb(5 150 105 / .25) var(--tw-gradient-to-position)}.to-galaxy-blue\/15{--tw-gradient-to: rgb(15 123 255 / .15) var(--tw-gradient-to-position)}.to-galaxy-purple{--tw-gradient-to: #7b2cbf var(--tw-gradient-to-position)}.to-galaxy-purple\/40{--tw-gradient-to: rgb(123 44 191 / .4) var(--tw-gradient-to-position)}.to-indigo-950\/15{--tw-gradient-to: rgb(30 27 75 / .15) var(--tw-gradient-to-position)}.to-pink-500\/20{--tw-gradient-to: rgb(236 72 153 / .2) var(--tw-gradient-to-position)}.to-purple-300{--tw-gradient-to: #d8b4fe var(--tw-gradient-to-position)}.to-purple-500\/20{--tw-gradient-to: rgb(168 85 247 / .2) var(--tw-gradient-to-position)}.to-rose-600\/10{--tw-gradient-to: rgb(225 29 72 / .1) var(--tw-gradient-to-position)}.to-rose-600\/25{--tw-gradient-to: rgb(225 29 72 / .25) var(--tw-gradient-to-position)}.to-white\/5{--tw-gradient-to: rgb(255 255 255 / .05) var(--tw-gradient-to-position)}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.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-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-3{padding-bottom:.75rem}.pb-6{padding-bottom:1.5rem}.pr-1{padding-right:.25rem}.pr-6{padding-right:1.5rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-heading{font-family:IBM Plex Sans,Inter,system-ui,sans-serif}.font-mono{font-family:JetBrains Mono,Menlo,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.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-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.15em\]{letter-spacing:.15em}.tracking-\[0\.18em\]{letter-spacing:.18em}.tracking-\[0\.25em\]{letter-spacing:.25em}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-tight{letter-spacing:-.025em}.tracking-tighter{letter-spacing:-.05em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[rgb\(10\,186\,181\)\]{--tw-text-opacity: 1;color:rgb(10 186 181 / var(--tw-text-opacity, 1))}.text-amber-100{--tw-text-opacity: 1;color:rgb(254 243 199 / var(--tw-text-opacity, 1))}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-cyan-100{--tw-text-opacity: 1;color:rgb(207 250 254 / var(--tw-text-opacity, 1))}.text-cyan-200{--tw-text-opacity: 1;color:rgb(165 243 252 / var(--tw-text-opacity, 1))}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity, 1))}.text-cyan-300\/90{color:#67e8f9e6}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.text-emerald-100{--tw-text-opacity: 1;color:rgb(209 250 229 / var(--tw-text-opacity, 1))}.text-emerald-100\/90{color:#d1fae5e6}.text-emerald-200{--tw-text-opacity: 1;color:rgb(167 243 208 / var(--tw-text-opacity, 1))}.text-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-emerald-400\/40{color:#34d39966}.text-indigo-300{--tw-text-opacity: 1;color:rgb(165 180 252 / var(--tw-text-opacity, 1))}.text-purple-200\/80{color:#e9d5ffcc}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-rose-100{--tw-text-opacity: 1;color:rgb(255 228 230 / var(--tw-text-opacity, 1))}.text-rose-100\/90{color:#ffe4e6e6}.text-rose-200{--tw-text-opacity: 1;color:rgb(254 205 211 / var(--tw-text-opacity, 1))}.text-rose-300{--tw-text-opacity: 1;color:rgb(253 164 175 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.text-slate-200\/80{color:#e2e8f0cc}.text-slate-200\/90{color:#e2e8f0e6}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.text-slate-300\/70{color:#cbd5e1b3}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-400\/80{color:#94a3b8cc}.text-slate-50{--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.text-transparent{color:transparent}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px 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-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px 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-\[0_0_12px_rgba\(33\,240\,255\,0\.25\)\]{--tw-shadow: 0 0 12px rgba(33,240,255,.25);--tw-shadow-colored: 0 0 12px 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-\[0_0_15px_rgba\(16\,185\,129\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 0 15px rgba(16,185,129,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 0 15px var(--tw-shadow-color), inset 0 1px 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)}.shadow-\[0_0_16px_rgba\(139\,0\,0\,0\.25\)\,0_4px_12px_rgba\(0\,0\,0\,0\.4\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 0 16px rgba(139,0,0,.25),0 4px 12px rgba(0,0,0,.4),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 0 16px var(--tw-shadow-color), 0 4px 12px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_0_16px_rgba\(147\,51\,234\,0\.08\)\]{--tw-shadow: 0 0 16px rgba(147,51,234,.08);--tw-shadow-colored: 0 0 16px 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-\[0_0_20px_rgba\(15\,123\,255\,0\.4\)\,0_2px_8px_rgba\(123\,44\,191\,0\.3\)\]{--tw-shadow: 0 0 20px rgba(15,123,255,.4),0 2px 8px rgba(123,44,191,.3);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color), 0 2px 8px 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-\[0_0_20px_rgba\(244\,63\,94\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 0 20px rgba(244,63,94,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color), inset 0 1px 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)}.shadow-\[0_0_20px_rgba\(6\,182\,212\,0\.15\)\]{--tw-shadow: 0 0 20px rgba(6,182,212,.15);--tw-shadow-colored: 0 0 20px 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-\[0_0_20px_rgba\(6\,182\,212\,0\.3\)\,0_0_30px_rgba\(147\,51\,234\,0\.2\)\,0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.15\)\,inset_0_-1px_2px_rgba\(0\,0\,0\,0\.2\)\]{--tw-shadow: 0 0 20px rgba(6,182,212,.3),0 0 30px rgba(147,51,234,.2),0 4px 16px rgba(0,0,0,.3),inset 0 1px 2px rgba(255,255,255,.15),inset 0 -1px 2px rgba(0,0,0,.2);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color), 0 0 30px var(--tw-shadow-color), 0 4px 16px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color), inset 0 -1px 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)}.shadow-\[0_0_25px_rgba\(10\,186\,181\,0\.18\)\,inset_0_1px_0_rgba\(10\,186\,181\,0\.12\)\]{--tw-shadow: 0 0 25px rgba(10,186,181,.18),inset 0 1px 0 rgba(10,186,181,.12);--tw-shadow-colored: 0 0 25px var(--tw-shadow-color), inset 0 1px 0 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-\[0_0_30px_rgba\(15\,123\,255\,0\.2\)\,inset_0_1px_0_rgba\(147\,197\,253\,0\.15\)\]{--tw-shadow: 0 0 30px rgba(15,123,255,.2),inset 0 1px 0 rgba(147,197,253,.15);--tw-shadow-colored: 0 0 30px var(--tw-shadow-color), inset 0 1px 0 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-\[0_0_30px_rgba\(6\,182\,212\,0\.4\)\,0_0_40px_rgba\(6\,182\,212\,0\.25\)\,0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,inset_0_0_30px_rgba\(6\,182\,212\,0\.1\)\]{--tw-shadow: 0 0 30px rgba(6,182,212,.4),0 0 40px rgba(6,182,212,.25),0 4px 16px rgba(0,0,0,.3),inset 0 0 30px rgba(6,182,212,.1);--tw-shadow-colored: 0 0 30px var(--tw-shadow-color), 0 0 40px var(--tw-shadow-color), 0 4px 16px var(--tw-shadow-color), inset 0 0 30px 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-\[0_0_6px_currentColor\]{--tw-shadow: 0 0 6px currentColor;--tw-shadow-colored: 0 0 6px 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-\[0_0_8px_rgba\(99\,102\,241\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 0 8px rgba(99,102,241,.2),inset 0 1px 1px rgba(255,255,255,.1);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_2px_8px_rgba\(0\,0\,0\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 2px 8px rgba(0,0,0,.2),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_2px_8px_rgba\(0\,0\,0\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 2px 8px rgba(0,0,0,.2),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_2px_8px_rgba\(0\,0\,0\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 2px 8px rgba(0,0,0,.2),inset 0 1px 1px rgba(255,255,255,.1);--tw-shadow-colored: 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_4px_16px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(10\,186\,181\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.25),0 0 15px rgba(10,186,181,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color), inset 0 1px 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)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(16\,185\,129\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.25),0 0 15px rgba(16,185,129,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color), inset 0 1px 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)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,0_0_8px_rgba\(15\,123\,255\,0\.1\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\,inset_0_0_20px_rgba\(15\,123\,255\,0\.03\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.3),0 0 8px rgba(15,123,255,.1),inset 0 1px 2px rgba(255,255,255,.1),inset 0 0 20px rgba(15,123,255,.03);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 0 8px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color), inset 0 0 20px 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-\[0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,0_1px_4px_rgba\(15\,123\,255\,0\.1\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.3),0 1px 4px rgba(15,123,255,.1),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 1px 4px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.05\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.3),inset 0 1px 1px rgba(255,255,255,.05);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.35\)\,0_2px_8px_rgba\(15\,123\,255\,0\.1\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.35),0 2px 8px rgba(15,123,255,.1),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(147\,51\,234\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(147,51,234,.12),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(15\,123\,255\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(15,123,255,.12),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(15\,123\,255\,0\.15\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(15,123,255,.15),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(16\,185\,129\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(16,185,129,.12),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(6\,182\,212\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(6,182,212,.12),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[inset_0_1px_2px_rgba\(255\,255\,255\,0\.05\)\]{--tw-shadow: inset 0 1px 2px rgba(255,255,255,.05);--tw-shadow-colored: inset 0 1px 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)}.shadow-\[inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: inset 0 1px 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)}.shadow-\[inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]{--tw-shadow: inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: inset 0 2px 8px 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-glow{--tw-shadow: 0 0 25px rgba(33, 240, 255, .35);--tw-shadow-colored: 0 0 25px 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-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-neon{--tw-shadow: 0 0 15px rgba(15, 123, 255, .45);--tw-shadow-colored: 0 0 15px 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-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-1{--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(1px + 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)}.ring-inset{--tw-ring-inset: inset}.ring-white\/20{--tw-ring-color: rgb(255 255 255 / .2)}.ring-white\/5{--tw-ring-color: rgb(255 255 255 / .05)}.blur{--tw-blur: blur(8px);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)}.blur-xl{--tw-blur: blur(24px);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)}.drop-shadow{--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / .1)) drop-shadow(0 1px 1px rgb(0 0 0 / .06));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)}.drop-shadow-\[0_0_20px_rgba\(6\,182\,212\,0\.3\)\]{--tw-drop-shadow: drop-shadow(0 0 20px rgba(6,182,212,.3));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)}.drop-shadow-\[0_0_8px_rgba\(147\,51\,234\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(147,51,234,.5));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)}.drop-shadow-\[0_0_8px_rgba\(16\,185\,129\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(16,185,129,.5));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)}.drop-shadow-\[0_0_8px_rgba\(255\,255\,255\,0\.3\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(255,255,255,.3));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)}.drop-shadow-\[0_0_8px_rgba\(6\,182\,212\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(6,182,212,.5));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)}.drop-shadow-\[0_1px_4px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 1px 4px rgba(0,0,0,.5));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)}.drop-shadow-\[0_2px_12px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 2px 12px rgba(0,0,0,.5));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)}.drop-shadow-\[0_2px_4px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 2px 4px rgba(0,0,0,.5));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)}.drop-shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.6\)\]{--tw-drop-shadow: drop-shadow(0 2px 8px rgba(0,0,0,.6));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)}.grayscale{--tw-grayscale: grayscale(100%);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)}.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)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-md{--tw-backdrop-blur: blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-xl{--tw-backdrop-blur: blur(24px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}:root{font-family:Inter,system-ui,IBM Plex Sans,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:dark;color:#f7faffeb;background-color:#050816;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;min-width:320px;min-height:100vh;background:radial-gradient(circle at 15% 20%,rgba(100,150,255,.15),transparent 35%),radial-gradient(circle at 85% 15%,rgba(150,100,255,.12),transparent 40%),radial-gradient(circle at 50% 90%,rgba(33,240,255,.08),transparent 45%),radial-gradient(ellipse at 70% 60%,rgba(80,120,200,.06),transparent 50%),linear-gradient(to bottom,#000814,#001a33,#000a1a);overflow:hidden}#root{width:100vw;height:100vh}.high-contrast body,body.high-contrast{background:#000;color:#fff}.high-contrast *,body.high-contrast *{outline-offset:2px}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:linear-gradient(to right,#0000004d,#0a142340);border-radius:6px;box-shadow:inset 0 0 6px #0006}::-webkit-scrollbar-thumb{background:linear-gradient(135deg,#06b6d440,#0f7bff38,#9333ea33);border-radius:6px;border:1px solid rgba(6,182,212,.15);box-shadow:0 0 4px #06b6d426,inset 0 1px 1px #ffffff14,inset 0 -1px 1px #0000004d}::-webkit-scrollbar-thumb:hover{background:linear-gradient(135deg,#06b6d466,#0f7bff59,#9333ea4d);box-shadow:0 0 8px #06b6d440,0 0 12px #0f7bff26,inset 0 1px 1px #ffffff1f,inset 0 -1px 1px #0000004d}::-webkit-scrollbar-thumb:active{background:linear-gradient(135deg,#06b6d480,#0f7bff73,#9333ea66);box-shadow:0 0 10px #06b6d44d,0 0 16px #0f7bff33,inset 0 1px 2px #0006}.galaxy-bg{position:relative;background:radial-gradient(ellipse at 30% 20%,rgba(70,120,200,.08),transparent 60%),radial-gradient(ellipse at 80% 70%,rgba(120,80,200,.06),transparent 55%),linear-gradient(135deg,#0a1628,#0f2847 45%,#152e52)}.galaxy-bg:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:radial-gradient(circle at 25% 35%,rgba(100,150,255,.03),transparent 45%),radial-gradient(circle at 75% 65%,rgba(200,100,255,.02),transparent 40%);pointer-events:none}.glow-text{text-shadow:0 0 10px rgba(0,212,255,.5),0 0 20px rgba(123,44,191,.3)}.glow-border{border:1px solid rgba(0,212,255,.3);box-shadow:0 0 10px #00d4ff33,inset 0 0 10px #00d4ff1a}.frosted-panel{background:linear-gradient(140deg,#0b182cd9,#121220e6);border:1px solid rgba(33,240,255,.08);box-shadow:0 12px 30px #03070f73,inset 0 0 0 1px #93c5fd05}.glass-card{background:linear-gradient(165deg,#08192db8,#0a0d1ec7);border:1px solid rgba(15,123,255,.12);box-shadow:0 10px 35px #020a1899}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.animate-fade-in{animation:fadeIn .5s ease-out}@keyframes slideInLeft{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}.animate-slide-in-left{animation:slideInLeft .3s ease-out}.animate-slide-in-right{animation:slideInRight .3s ease-out}.star-static{position:absolute;border-radius:50%;will-change:transform;transform:translateZ(0)}.star-static[data-color=white]{background:radial-gradient(circle,rgba(240,245,255,1) 0%,rgba(200,220,255,.9) 20%,rgba(180,200,240,.4) 50%,transparent 100%);box-shadow:0 0 2px #f0f5ff,0 0 4px #c8dcffcc,0 0 8px #b4c8f080,0 0 12px #a0b4dc40}.star-static[data-color=blue]{background:radial-gradient(circle,rgba(220,235,255,1) 0%,rgba(180,210,255,.85) 20%,rgba(140,180,255,.4) 50%,transparent 100%);box-shadow:0 0 2px #dcebff,0 0 5px #b4d2ffb3,0 0 10px #8cb4ff66,0 0 15px #6496ff33}.star-static[data-color=yellow]{background:radial-gradient(circle,rgba(255,250,230,1) 0%,rgba(255,240,200,.9) 20%,rgba(255,220,150,.4) 50%,transparent 100%);box-shadow:0 0 2px #fffae6,0 0 4px #fff0c8cc,0 0 8px #ffdc9680,0 0 12px #ffc86440}.star-static[data-color=orange]{background:radial-gradient(circle,rgba(255,220,180,1) 0%,rgba(255,200,140,.9) 20%,rgba(255,180,100,.4) 50%,transparent 100%);box-shadow:0 0 2px #ffdcb4,0 0 4px #ffc88cbf,0 0 8px #ffb46473,0 0 12px #ffa05038}.star-static[data-color=red]{background:radial-gradient(circle,rgba(255,200,180,1) 0%,rgba(255,160,140,.9) 20%,rgba(255,120,100,.4) 50%,transparent 100%);box-shadow:0 0 2px #ffc8b4,0 0 4px #ffa08cb3,0 0 8px #ff786466,0 0 12px #ff503c33}.shooting-star-static{position:absolute;height:1.5px;background:linear-gradient(90deg,#dcebfff2,#c8dcffcc 25%,#b4c8ff66 60%,#c8dcff00);transform:rotate(15deg);box-shadow:0 0 8px #c8dcff99,0 0 4px #dcebff66}.noise-overlay:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.08'/%3E%3C/svg%3E");pointer-events:none;mix-blend-mode:screen}.compose-area-shadow{box-shadow:0 -12px 30px #050c1999}.react-flow__edge-path{stroke-linecap:round;stroke-linejoin:round;transition:stroke-width .3s ease,filter .3s ease}.react-flow__edge.animated .react-flow__edge-path{stroke-dasharray:8 4;animation:edgeFlow 1.5s linear infinite,edgePulse 2s ease-in-out infinite}@keyframes edgeFlow{0%{stroke-dashoffset:12}to{stroke-dashoffset:0}}@keyframes edgePulse{0%,to{opacity:.7}50%{opacity:1}}.react-flow__arrowhead polyline{stroke-linejoin:round;stroke-linecap:round}.react-flow__edge.selected .react-flow__edge-path{stroke-width:3px!important;filter:brightness(1.3) drop-shadow(0 0 6px currentColor)!important}.placeholder\:text-slate-500::-moz-placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.placeholder\:text-slate-500::placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.focus-within\:border-white\/15:focus-within{border-color:#ffffff26}.focus-within\:shadow-\[0_0_8px_rgba\(15\,123\,255\,0\.08\)\,inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]:focus-within{--tw-shadow: 0 0 8px rgba(15,123,255,.08),inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus-within\:shadow-\[0_0_8px_rgba\(16\,185\,129\,0\.08\)\,inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]:focus-within{--tw-shadow: 0 0 8px rgba(16,185,129,.08),inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:translate-y-\[-2px\]:hover{--tw-translate-y: -2px;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))}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;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))}.hover\:border-\[rgba\(10\,186\,181\,0\.6\)\]:hover{border-color:#0abab599}.hover\:border-amber-400\/40:hover{border-color:#fbbf2466}.hover\:border-emerald-400\/40:hover{border-color:#34d39966}.hover\:border-emerald-400\/60:hover{border-color:#34d39999}.hover\:border-purple-400\/40:hover{border-color:#c084fc66}.hover\:border-rose-800\/50:hover{border-color:#9f123980}.hover\:border-white\/20:hover{border-color:#fff3}.hover\:border-white\/25:hover{border-color:#ffffff40}.hover\:border-white\/30:hover{border-color:#ffffff4d}.hover\:border-white\/35:hover{border-color:#ffffff59}.hover\:bg-black\/40:hover{background-color:#0006}.hover\:bg-white\/10:hover{background-color:#ffffff1a}.hover\:bg-white\/5:hover{background-color:#ffffff0d}.hover\:from-\[rgba\(10\,186\,181\,0\.25\)\]:hover{--tw-gradient-from: rgba(10,186,181,.25) var(--tw-gradient-from-position);--tw-gradient-to: rgba(10, 186, 181, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-\[rgba\(100\,25\,35\,0\.85\)\]:hover{--tw-gradient-from: rgba(100,25,35,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(100, 25, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-\[rgba\(28\,45\,65\,0\.85\)\]:hover{--tw-gradient-from: rgba(28,45,65,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(28, 45, 65, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-\[rgba\(6\,182\,212\,0\.95\)\]:hover{--tw-gradient-from: rgba(6,182,212,.95) var(--tw-gradient-from-position);--tw-gradient-to: rgba(6, 182, 212, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-emerald-500\/25:hover{--tw-gradient-from: rgb(16 185 129 / .25) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:via-\[rgba\(120\,30\,40\,0\.80\)\]:hover{--tw-gradient-to: rgba(120, 30, 40, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(120,30,40,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.hover\:via-\[rgba\(147\,51\,234\,0\.90\)\]:hover{--tw-gradient-to: rgba(147, 51, 234, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(147,51,234,.9) var(--tw-gradient-via-position), var(--tw-gradient-to)}.hover\:via-\[rgba\(23\,38\,56\,0\.8\)\]:hover{--tw-gradient-to: rgba(23, 38, 56, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(23,38,56,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.hover\:to-\[rgba\(100\,25\,35\,0\.85\)\]:hover{--tw-gradient-to: rgba(100,25,35,.85) var(--tw-gradient-to-position)}.hover\:to-\[rgba\(18\,30\,48\,0\.85\)\]:hover{--tw-gradient-to: rgba(18,30,48,.85) var(--tw-gradient-to-position)}.hover\:to-\[rgba\(236\,72\,153\,0\.95\)\]:hover{--tw-gradient-to: rgba(236,72,153,.95) var(--tw-gradient-to-position)}.hover\:to-\[rgba\(6\,182\,212\,0\.25\)\]:hover{--tw-gradient-to: rgba(6,182,212,.25) var(--tw-gradient-to-position)}.hover\:to-cyan-500\/25:hover{--tw-gradient-to: rgb(6 182 212 / .25) var(--tw-gradient-to-position)}.hover\:text-slate-200:hover{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:shadow-\[0_0_10px_rgba\(15\,123\,255\,0\.15\)\]:hover{--tw-shadow: 0 0 10px rgba(15,123,255,.15);--tw-shadow-colored: 0 0 10px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(15\,123\,255\,0\.15\)\]:hover{--tw-shadow: 0 4px 12px rgba(0,0,0,.25),0 0 15px rgba(15,123,255,.15);--tw-shadow-colored: 0 4px 12px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(16\,185\,129\,0\.2\)\]:hover{--tw-shadow: 0 4px 12px rgba(0,0,0,.25),0 0 15px rgba(16,185,129,.2);--tw-shadow-colored: 0 4px 12px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(245\,158\,11\,0\.2\)\]:hover{--tw-shadow: 0 4px 12px rgba(0,0,0,.25),0 0 15px rgba(245,158,11,.2);--tw-shadow-colored: 0 4px 12px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.3\)\,0_0_25px_rgba\(10\,186\,181\,0\.3\)\]:hover{--tw-shadow: 0 8px 24px rgba(0,0,0,.3),0 0 25px rgba(10,186,181,.3);--tw-shadow-colored: 0 8px 24px var(--tw-shadow-color), 0 0 25px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.3\)\,0_0_25px_rgba\(16\,185\,129\,0\.3\)\]:hover{--tw-shadow: 0 8px 24px rgba(0,0,0,.3),0 0 25px rgba(16,185,129,.3);--tw-shadow-colored: 0 8px 24px var(--tw-shadow-color), 0 0 25px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.35\)\,0_0_20px_rgba\(15\,123\,255\,0\.2\)\,0_0_30px_rgba\(6\,182\,212\,0\.15\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.15\)\,inset_0_0_30px_rgba\(15\,123\,255\,0\.06\)\]:hover{--tw-shadow: 0 8px 24px rgba(0,0,0,.35),0 0 20px rgba(15,123,255,.2),0 0 30px rgba(6,182,212,.15),inset 0 1px 2px rgba(255,255,255,.15),inset 0 0 30px rgba(15,123,255,.06);--tw-shadow-colored: 0 8px 24px var(--tw-shadow-color), 0 0 20px var(--tw-shadow-color), 0 0 30px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color), inset 0 0 30px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:border-white\/15:focus{border-color:#ffffff26}.focus\:shadow-\[0_0_8px_rgba\(15\,123\,255\,0\.08\)\,inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]:focus{--tw-shadow: 0 0 8px rgba(15,123,255,.08),inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1: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(1px + 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-white\/10:focus{--tw-ring-color: rgb(255 255 255 / .1)}.active\:scale-95:active{--tw-scale-x: .95;--tw-scale-y: .95;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))}.active\:shadow-\[0_0_15px_rgba\(6\,182\,212\,0\.4\)\,0_2px_8px_rgba\(0\,0\,0\,0\.4\)\]:active{--tw-shadow: 0 0 15px rgba(6,182,212,.4),0 2px 8px rgba(0,0,0,.4);--tw-shadow-colored: 0 0 15px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group:hover .group-hover\:text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:drop-shadow-\[0_0_6px_rgba\(6\,182\,212\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 6px rgba(6,182,212,.5));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)}@media (min-width: 640px){.sm\:block{display:block}.sm\:h-16{height:4rem}.sm\:h-2\.5{height:.625rem}.sm\:w-16{width:4rem}.sm\:w-2\.5{width:.625rem}.sm\:w-\[74\%\]{width:74%}.sm\:w-\[calc\(74\%-3rem\)\]{width:calc(74% - 3rem)}.sm\:gap-4{gap:1rem}.sm\:px-5{padding-left:1.25rem;padding-right:1.25rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-\[11px\]{font-size:11px}.sm\:text-base{font-size:1rem;line-height:1.5rem}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}.sm\:text-xs{font-size:.75rem;line-height:1rem}}@media (min-width: 768px){.md\:inline{display:inline}}@media (min-width: 1024px){.lg\:ml-3{margin-left:.75rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:h-20{height:5rem}.lg\:w-20{width:5rem}.lg\:w-\[520px\]{width:520px}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:text-3xl{font-size:1.875rem;line-height:2.25rem}.lg\:text-lg{font-size:1.125rem;line-height:1.75rem}.lg\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media (min-width: 1280px){.xl\:flex{display:flex}.xl\:w-72{width:18rem}.xl\:w-\[560px\]{width:560px}}@media (min-width: 1536px){.\32xl\:w-80{width:20rem}.\32xl\:w-\[640px\]{width:640px}} diff --git a/galaxy/webui/frontend/dist/assets/index-BVxn_-SZ.js b/galaxy/webui/frontend/dist/assets/index-BVxn_-SZ.js deleted file mode 100644 index ab77b0a89..000000000 --- a/galaxy/webui/frontend/dist/assets/index-BVxn_-SZ.js +++ /dev/null @@ -1,339 +0,0 @@ -var Gb=Object.defineProperty;var Xb=(e,t,n)=>t in e?Gb(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var Cn=(e,t,n)=>Xb(e,typeof t!="symbol"?t+"":t,n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();var Qa=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Bl(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Gy={exports:{}},Hl={},Xy={exports:{}},ce={};/** - * @license React - * react.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var jo=Symbol.for("react.element"),Qb=Symbol.for("react.portal"),Zb=Symbol.for("react.fragment"),Jb=Symbol.for("react.strict_mode"),eS=Symbol.for("react.profiler"),tS=Symbol.for("react.provider"),nS=Symbol.for("react.context"),rS=Symbol.for("react.forward_ref"),iS=Symbol.for("react.suspense"),sS=Symbol.for("react.memo"),oS=Symbol.for("react.lazy"),Rp=Symbol.iterator;function aS(e){return e===null||typeof e!="object"?null:(e=Rp&&e[Rp]||e["@@iterator"],typeof e=="function"?e:null)}var Qy={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Zy=Object.assign,Jy={};function ls(e,t,n){this.props=e,this.context=t,this.refs=Jy,this.updater=n||Qy}ls.prototype.isReactComponent={};ls.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};ls.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function ex(){}ex.prototype=ls.prototype;function Md(e,t,n){this.props=e,this.context=t,this.refs=Jy,this.updater=n||Qy}var Dd=Md.prototype=new ex;Dd.constructor=Md;Zy(Dd,ls.prototype);Dd.isPureReactComponent=!0;var zp=Array.isArray,tx=Object.prototype.hasOwnProperty,Id={current:null},nx={key:!0,ref:!0,__self:!0,__source:!0};function rx(e,t,n){var r,i={},s=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(s=""+t.key),t)tx.call(t,r)&&!nx.hasOwnProperty(r)&&(i[r]=t[r]);var a=arguments.length-2;if(a===1)i.children=n;else if(1>>1,B=R[F];if(0>>1;Fi(X,b))Gi(ne,X)?(R[F]=ne,R[G]=b,F=G):(R[F]=X,R[q]=b,F=q);else if(Gi(ne,b))R[F]=ne,R[G]=b,F=G;else break e}}return M}function i(R,M){var b=R.sortIndex-M.sortIndex;return b!==0?b:R.id-M.id}if(typeof performance=="object"&&typeof performance.now=="function"){var s=performance;e.unstable_now=function(){return s.now()}}else{var o=Date,a=o.now();e.unstable_now=function(){return o.now()-a}}var l=[],u=[],c=1,f=null,d=3,h=!1,m=!1,g=!1,w=typeof setTimeout=="function"?setTimeout:null,p=typeof clearTimeout=="function"?clearTimeout:null,x=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function v(R){for(var M=n(u);M!==null;){if(M.callback===null)r(u);else if(M.startTime<=R)r(u),M.sortIndex=M.expirationTime,t(l,M);else break;M=n(u)}}function k(R){if(g=!1,v(R),!m)if(n(l)!==null)m=!0,I(N);else{var M=n(u);M!==null&&O(k,M.startTime-R)}}function N(R,M){m=!1,g&&(g=!1,p(P),P=-1),h=!0;var b=d;try{for(v(M),f=n(l);f!==null&&(!(f.expirationTime>M)||R&&!j());){var F=f.callback;if(typeof F=="function"){f.callback=null,d=f.priorityLevel;var B=F(f.expirationTime<=M);M=e.unstable_now(),typeof B=="function"?f.callback=B:f===n(l)&&r(l),v(M)}else r(l);f=n(l)}if(f!==null)var E=!0;else{var q=n(u);q!==null&&O(k,q.startTime-M),E=!1}return E}finally{f=null,d=b,h=!1}}var S=!1,A=null,P=-1,D=5,C=-1;function j(){return!(e.unstable_now()-CR||125F?(R.sortIndex=b,t(u,R),n(l)===null&&R===n(u)&&(g?(p(P),P=-1):g=!0,O(k,b-F))):(R.sortIndex=B,t(l,R),m||h||(m=!0,I(N))),R},e.unstable_shouldYield=j,e.unstable_wrapCallback=function(R){var M=d;return function(){var b=d;d=M;try{return R.apply(this,arguments)}finally{d=b}}}})(lx);ax.exports=lx;var xS=ax.exports;/** - * @license React - * react-dom.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var vS=T,Rt=xS;function U(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),qc=Object.prototype.hasOwnProperty,wS=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Op={},Vp={};function kS(e){return qc.call(Vp,e)?!0:qc.call(Op,e)?!1:wS.test(e)?Vp[e]=!0:(Op[e]=!0,!1)}function bS(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function SS(e,t,n,r){if(t===null||typeof t>"u"||bS(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function mt(e,t,n,r,i,s,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=i,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=s,this.removeEmptyString=o}var Je={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Je[e]=new mt(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Je[t]=new mt(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Je[e]=new mt(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Je[e]=new mt(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Je[e]=new mt(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Je[e]=new mt(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Je[e]=new mt(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Je[e]=new mt(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Je[e]=new mt(e,5,!1,e.toLowerCase(),null,!1,!1)});var Ld=/[\-:]([a-z])/g;function Rd(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Ld,Rd);Je[t]=new mt(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Ld,Rd);Je[t]=new mt(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Ld,Rd);Je[t]=new mt(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Je[e]=new mt(e,1,!1,e.toLowerCase(),null,!1,!1)});Je.xlinkHref=new mt("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Je[e]=new mt(e,1,!1,e.toLowerCase(),null,!0,!0)});function zd(e,t,n,r){var i=Je.hasOwnProperty(t)?Je[t]:null;(i!==null?i.type!==0:r||!(2a||i[o]!==s[a]){var l=` -`+i[o].replace(" at new "," at ");return e.displayName&&l.includes("")&&(l=l.replace("",e.displayName)),l}while(1<=o&&0<=a);break}}}finally{ju=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Ms(e):""}function _S(e){switch(e.tag){case 5:return Ms(e.type);case 16:return Ms("Lazy");case 13:return Ms("Suspense");case 19:return Ms("SuspenseList");case 0:case 2:case 15:return e=Lu(e.type,!1),e;case 11:return e=Lu(e.type.render,!1),e;case 1:return e=Lu(e.type,!0),e;default:return""}}function Qc(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case xi:return"Fragment";case yi:return"Portal";case Kc:return"Profiler";case Fd:return"StrictMode";case Gc:return"Suspense";case Xc:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case fx:return(e.displayName||"Context")+".Consumer";case cx:return(e._context.displayName||"Context")+".Provider";case Od:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Vd:return t=e.displayName||null,t!==null?t:Qc(e.type)||"Memo";case Zn:t=e._payload,e=e._init;try{return Qc(e(t))}catch{}}return null}function CS(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Qc(t);case 8:return t===Fd?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function vr(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function hx(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function ES(e){var t=hx(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var i=n.get,s=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return i.call(this)},set:function(o){r=""+o,s.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Zo(e){e._valueTracker||(e._valueTracker=ES(e))}function px(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=hx(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Za(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Zc(e,t){var n=t.checked;return De({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Bp(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=vr(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function mx(e,t){t=t.checked,t!=null&&zd(e,"checked",t,!1)}function Jc(e,t){mx(e,t);var n=vr(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?ef(e,t.type,n):t.hasOwnProperty("defaultValue")&&ef(e,t.type,vr(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Hp(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function ef(e,t,n){(t!=="number"||Za(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Ds=Array.isArray;function ji(e,t,n,r){if(e=e.options,t){t={};for(var i=0;i"+t.valueOf().toString()+"",t=Jo.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function io(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Os={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},NS=["Webkit","ms","Moz","O"];Object.keys(Os).forEach(function(e){NS.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Os[t]=Os[e]})});function vx(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Os.hasOwnProperty(e)&&Os[e]?(""+t).trim():t+"px"}function wx(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,i=vx(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,i):e[n]=i}}var TS=De({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function rf(e,t){if(t){if(TS[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(U(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(U(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(U(61))}if(t.style!=null&&typeof t.style!="object")throw Error(U(62))}}function sf(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var of=null;function $d(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var af=null,Li=null,Ri=null;function Yp(e){if(e=zo(e)){if(typeof af!="function")throw Error(U(280));var t=e.stateNode;t&&(t=Kl(t),af(e.stateNode,e.type,t))}}function kx(e){Li?Ri?Ri.push(e):Ri=[e]:Li=e}function bx(){if(Li){var e=Li,t=Ri;if(Ri=Li=null,Yp(e),t)for(e=0;e>>=0,e===0?32:31-(OS(e)/VS|0)|0}var ea=64,ta=4194304;function Is(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function nl(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,i=e.suspendedLanes,s=e.pingedLanes,o=n&268435455;if(o!==0){var a=o&~i;a!==0?r=Is(a):(s&=o,s!==0&&(r=Is(s)))}else o=n&~i,o!==0?r=Is(o):s!==0&&(r=Is(s));if(r===0)return 0;if(t!==0&&t!==r&&!(t&i)&&(i=r&-r,s=t&-t,i>=s||i===16&&(s&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Lo(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-on(t),e[t]=n}function US(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=$s),tm=" ",nm=!1;function Bx(e,t){switch(e){case"keyup":return x_.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Hx(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var vi=!1;function w_(e,t){switch(e){case"compositionend":return Hx(t);case"keypress":return t.which!==32?null:(nm=!0,tm);case"textInput":return e=t.data,e===tm&&nm?null:e;default:return null}}function k_(e,t){if(vi)return e==="compositionend"||!Gd&&Bx(e,t)?(e=Vx(),ja=Yd=or=null,vi=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=om(n)}}function qx(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?qx(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Kx(){for(var e=window,t=Za();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Za(e.document)}return t}function Xd(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function P_(e){var t=Kx(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&qx(n.ownerDocument.documentElement,n)){if(r!==null&&Xd(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var i=n.textContent.length,s=Math.min(r.start,i);r=r.end===void 0?s:Math.min(r.end,i),!e.extend&&s>r&&(i=r,r=s,s=i),i=am(n,s);var o=am(n,r);i&&o&&(e.rangeCount!==1||e.anchorNode!==i.node||e.anchorOffset!==i.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(i.node,i.offset),e.removeAllRanges(),s>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,wi=null,hf=null,Hs=null,pf=!1;function lm(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;pf||wi==null||wi!==Za(r)||(r=wi,"selectionStart"in r&&Xd(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Hs&&co(Hs,r)||(Hs=r,r=sl(hf,"onSelect"),0Si||(e.current=wf[Si],wf[Si]=null,Si--)}function ke(e,t){Si++,wf[Si]=e.current,e.current=t}var wr={},ot=_r(wr),bt=_r(!1),Qr=wr;function qi(e,t){var n=e.type.contextTypes;if(!n)return wr;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var i={},s;for(s in n)i[s]=t[s];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=i),i}function St(e){return e=e.childContextTypes,e!=null}function al(){_e(bt),_e(ot)}function mm(e,t,n){if(ot.current!==wr)throw Error(U(168));ke(ot,t),ke(bt,n)}function r1(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var i in r)if(!(i in t))throw Error(U(108,CS(e)||"Unknown",i));return De({},n,r)}function ll(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||wr,Qr=ot.current,ke(ot,e),ke(bt,bt.current),!0}function gm(e,t,n){var r=e.stateNode;if(!r)throw Error(U(169));n?(e=r1(e,t,Qr),r.__reactInternalMemoizedMergedChildContext=e,_e(bt),_e(ot),ke(ot,e)):_e(bt),ke(bt,n)}var Tn=null,Gl=!1,Gu=!1;function i1(e){Tn===null?Tn=[e]:Tn.push(e)}function B_(e){Gl=!0,i1(e)}function Cr(){if(!Gu&&Tn!==null){Gu=!0;var e=0,t=me;try{var n=Tn;for(me=1;e>=o,i-=o,An=1<<32-on(t)+i|n<P?(D=A,A=null):D=A.sibling;var C=d(p,A,v[P],k);if(C===null){A===null&&(A=D);break}e&&A&&C.alternate===null&&t(p,A),x=s(C,x,P),S===null?N=C:S.sibling=C,S=C,A=D}if(P===v.length)return n(p,A),Ne&&Ir(p,P),N;if(A===null){for(;PP?(D=A,A=null):D=A.sibling;var j=d(p,A,C.value,k);if(j===null){A===null&&(A=D);break}e&&A&&j.alternate===null&&t(p,A),x=s(j,x,P),S===null?N=j:S.sibling=j,S=j,A=D}if(C.done)return n(p,A),Ne&&Ir(p,P),N;if(A===null){for(;!C.done;P++,C=v.next())C=f(p,C.value,k),C!==null&&(x=s(C,x,P),S===null?N=C:S.sibling=C,S=C);return Ne&&Ir(p,P),N}for(A=r(p,A);!C.done;P++,C=v.next())C=h(A,p,P,C.value,k),C!==null&&(e&&C.alternate!==null&&A.delete(C.key===null?P:C.key),x=s(C,x,P),S===null?N=C:S.sibling=C,S=C);return e&&A.forEach(function(z){return t(p,z)}),Ne&&Ir(p,P),N}function w(p,x,v,k){if(typeof v=="object"&&v!==null&&v.type===xi&&v.key===null&&(v=v.props.children),typeof v=="object"&&v!==null){switch(v.$$typeof){case Qo:e:{for(var N=v.key,S=x;S!==null;){if(S.key===N){if(N=v.type,N===xi){if(S.tag===7){n(p,S.sibling),x=i(S,v.props.children),x.return=p,p=x;break e}}else if(S.elementType===N||typeof N=="object"&&N!==null&&N.$$typeof===Zn&&vm(N)===S.type){n(p,S.sibling),x=i(S,v.props),x.ref=vs(p,S,v),x.return=p,p=x;break e}n(p,S);break}else t(p,S);S=S.sibling}v.type===xi?(x=qr(v.props.children,p.mode,k,v.key),x.return=p,p=x):(k=Ba(v.type,v.key,v.props,null,p.mode,k),k.ref=vs(p,x,v),k.return=p,p=k)}return o(p);case yi:e:{for(S=v.key;x!==null;){if(x.key===S)if(x.tag===4&&x.stateNode.containerInfo===v.containerInfo&&x.stateNode.implementation===v.implementation){n(p,x.sibling),x=i(x,v.children||[]),x.return=p,p=x;break e}else{n(p,x);break}else t(p,x);x=x.sibling}x=rc(v,p.mode,k),x.return=p,p=x}return o(p);case Zn:return S=v._init,w(p,x,S(v._payload),k)}if(Ds(v))return m(p,x,v,k);if(ps(v))return g(p,x,v,k);la(p,v)}return typeof v=="string"&&v!==""||typeof v=="number"?(v=""+v,x!==null&&x.tag===6?(n(p,x.sibling),x=i(x,v),x.return=p,p=x):(n(p,x),x=nc(v,p.mode,k),x.return=p,p=x),o(p)):n(p,x)}return w}var Gi=l1(!0),u1=l1(!1),fl=_r(null),dl=null,Ei=null,eh=null;function th(){eh=Ei=dl=null}function nh(e){var t=fl.current;_e(fl),e._currentValue=t}function Sf(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Fi(e,t){dl=e,eh=Ei=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(wt=!0),e.firstContext=null)}function Gt(e){var t=e._currentValue;if(eh!==e)if(e={context:e,memoizedValue:t,next:null},Ei===null){if(dl===null)throw Error(U(308));Ei=e,dl.dependencies={lanes:0,firstContext:e}}else Ei=Ei.next=e;return t}var $r=null;function rh(e){$r===null?$r=[e]:$r.push(e)}function c1(e,t,n,r){var i=t.interleaved;return i===null?(n.next=n,rh(t)):(n.next=i.next,i.next=n),t.interleaved=n,Fn(e,r)}function Fn(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Jn=!1;function ih(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function f1(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function In(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function dr(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,pe&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,Fn(e,n)}return i=r.interleaved,i===null?(t.next=t,rh(r)):(t.next=i.next,i.next=t),r.interleaved=t,Fn(e,n)}function Ra(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Hd(e,n)}}function wm(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,s=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};s===null?i=s=o:s=s.next=o,n=n.next}while(n!==null);s===null?i=s=t:s=s.next=t}else i=s=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:s,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function hl(e,t,n,r){var i=e.updateQueue;Jn=!1;var s=i.firstBaseUpdate,o=i.lastBaseUpdate,a=i.shared.pending;if(a!==null){i.shared.pending=null;var l=a,u=l.next;l.next=null,o===null?s=u:o.next=u,o=l;var c=e.alternate;c!==null&&(c=c.updateQueue,a=c.lastBaseUpdate,a!==o&&(a===null?c.firstBaseUpdate=u:a.next=u,c.lastBaseUpdate=l))}if(s!==null){var f=i.baseState;o=0,c=u=l=null,a=s;do{var d=a.lane,h=a.eventTime;if((r&d)===d){c!==null&&(c=c.next={eventTime:h,lane:0,tag:a.tag,payload:a.payload,callback:a.callback,next:null});e:{var m=e,g=a;switch(d=t,h=n,g.tag){case 1:if(m=g.payload,typeof m=="function"){f=m.call(h,f,d);break e}f=m;break e;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,d=typeof m=="function"?m.call(h,f,d):m,d==null)break e;f=De({},f,d);break e;case 2:Jn=!0}}a.callback!==null&&a.lane!==0&&(e.flags|=64,d=i.effects,d===null?i.effects=[a]:d.push(a))}else h={eventTime:h,lane:d,tag:a.tag,payload:a.payload,callback:a.callback,next:null},c===null?(u=c=h,l=f):c=c.next=h,o|=d;if(a=a.next,a===null){if(a=i.shared.pending,a===null)break;d=a,a=d.next,d.next=null,i.lastBaseUpdate=d,i.shared.pending=null}}while(!0);if(c===null&&(l=f),i.baseState=l,i.firstBaseUpdate=u,i.lastBaseUpdate=c,t=i.shared.interleaved,t!==null){i=t;do o|=i.lane,i=i.next;while(i!==t)}else s===null&&(i.shared.lanes=0);ei|=o,e.lanes=o,e.memoizedState=f}}function km(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Qu.transition;Qu.transition={};try{e(!1),t()}finally{me=n,Qu.transition=r}}function T1(){return Xt().memoizedState}function Y_(e,t,n){var r=pr(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},A1(e))P1(t,n);else if(n=c1(e,t,n,r),n!==null){var i=ht();an(n,e,r,i),M1(n,t,r)}}function q_(e,t,n){var r=pr(e),i={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(A1(e))P1(t,i);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var o=t.lastRenderedState,a=s(o,n);if(i.hasEagerState=!0,i.eagerState=a,cn(a,o)){var l=t.interleaved;l===null?(i.next=i,rh(t)):(i.next=l.next,l.next=i),t.interleaved=i;return}}catch{}finally{}n=c1(e,t,i,r),n!==null&&(i=ht(),an(n,e,r,i),M1(n,t,r))}}function A1(e){var t=e.alternate;return e===Me||t!==null&&t===Me}function P1(e,t){Us=ml=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function M1(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Hd(e,n)}}var gl={readContext:Gt,useCallback:tt,useContext:tt,useEffect:tt,useImperativeHandle:tt,useInsertionEffect:tt,useLayoutEffect:tt,useMemo:tt,useReducer:tt,useRef:tt,useState:tt,useDebugValue:tt,useDeferredValue:tt,useTransition:tt,useMutableSource:tt,useSyncExternalStore:tt,useId:tt,unstable_isNewReconciler:!1},K_={readContext:Gt,useCallback:function(e,t){return mn().memoizedState=[e,t===void 0?null:t],e},useContext:Gt,useEffect:Sm,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Fa(4194308,4,S1.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Fa(4194308,4,e,t)},useInsertionEffect:function(e,t){return Fa(4,2,e,t)},useMemo:function(e,t){var n=mn();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=mn();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Y_.bind(null,Me,e),[r.memoizedState,e]},useRef:function(e){var t=mn();return e={current:e},t.memoizedState=e},useState:bm,useDebugValue:dh,useDeferredValue:function(e){return mn().memoizedState=e},useTransition:function(){var e=bm(!1),t=e[0];return e=W_.bind(null,e[1]),mn().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=Me,i=mn();if(Ne){if(n===void 0)throw Error(U(407));n=n()}else{if(n=t(),qe===null)throw Error(U(349));Jr&30||m1(r,t,n)}i.memoizedState=n;var s={value:n,getSnapshot:t};return i.queue=s,Sm(y1.bind(null,r,s,e),[e]),r.flags|=2048,vo(9,g1.bind(null,r,s,n,t),void 0,null),n},useId:function(){var e=mn(),t=qe.identifierPrefix;if(Ne){var n=Pn,r=An;n=(r&~(1<<32-on(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=yo++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[xn]=t,e[po]=r,$1(e,t,!1,!1),t.stateNode=e;e:{switch(o=sf(n,r),n){case"dialog":Se("cancel",e),Se("close",e),i=r;break;case"iframe":case"object":case"embed":Se("load",e),i=r;break;case"video":case"audio":for(i=0;iZi&&(t.flags|=128,r=!0,ws(s,!1),t.lanes=4194304)}else{if(!r)if(e=pl(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),ws(s,!0),s.tail===null&&s.tailMode==="hidden"&&!o.alternate&&!Ne)return nt(t),null}else 2*ze()-s.renderingStartTime>Zi&&n!==1073741824&&(t.flags|=128,r=!0,ws(s,!1),t.lanes=4194304);s.isBackwards?(o.sibling=t.child,t.child=o):(n=s.last,n!==null?n.sibling=o:t.child=o,s.last=o)}return s.tail!==null?(t=s.tail,s.rendering=t,s.tail=t.sibling,s.renderingStartTime=ze(),t.sibling=null,n=Ae.current,ke(Ae,r?n&1|2:n&1),t):(nt(t),null);case 22:case 23:return xh(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Dt&1073741824&&(nt(t),t.subtreeFlags&6&&(t.flags|=8192)):nt(t),null;case 24:return null;case 25:return null}throw Error(U(156,t.tag))}function nC(e,t){switch(Zd(t),t.tag){case 1:return St(t.type)&&al(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Xi(),_e(bt),_e(ot),ah(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return oh(t),null;case 13:if(_e(Ae),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(U(340));Ki()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return _e(Ae),null;case 4:return Xi(),null;case 10:return nh(t.type._context),null;case 22:case 23:return xh(),null;case 24:return null;default:return null}}var ca=!1,it=!1,rC=typeof WeakSet=="function"?WeakSet:Set,K=null;function Ni(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Le(e,t,r)}else n.current=null}function Df(e,t,n){try{n()}catch(r){Le(e,t,r)}}var jm=!1;function iC(e,t){if(mf=rl,e=Kx(),Xd(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,s=r.focusNode;r=r.focusOffset;try{n.nodeType,s.nodeType}catch{n=null;break e}var o=0,a=-1,l=-1,u=0,c=0,f=e,d=null;t:for(;;){for(var h;f!==n||i!==0&&f.nodeType!==3||(a=o+i),f!==s||r!==0&&f.nodeType!==3||(l=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(h=f.firstChild)!==null;)d=f,f=h;for(;;){if(f===e)break t;if(d===n&&++u===i&&(a=o),d===s&&++c===r&&(l=o),(h=f.nextSibling)!==null)break;f=d,d=f.parentNode}f=h}n=a===-1||l===-1?null:{start:a,end:l}}else n=null}n=n||{start:0,end:0}}else n=null;for(gf={focusedElem:e,selectionRange:n},rl=!1,K=t;K!==null;)if(t=K,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,K=e;else for(;K!==null;){t=K;try{var m=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(m!==null){var g=m.memoizedProps,w=m.memoizedState,p=t.stateNode,x=p.getSnapshotBeforeUpdate(t.elementType===t.type?g:Jt(t.type,g),w);p.__reactInternalSnapshotBeforeUpdate=x}break;case 3:var v=t.stateNode.containerInfo;v.nodeType===1?v.textContent="":v.nodeType===9&&v.documentElement&&v.removeChild(v.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(U(163))}}catch(k){Le(t,t.return,k)}if(e=t.sibling,e!==null){e.return=t.return,K=e;break}K=t.return}return m=jm,jm=!1,m}function Ws(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var i=r=r.next;do{if((i.tag&e)===e){var s=i.destroy;i.destroy=void 0,s!==void 0&&Df(t,n,s)}i=i.next}while(i!==r)}}function Zl(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function If(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function U1(e){var t=e.alternate;t!==null&&(e.alternate=null,U1(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[xn],delete t[po],delete t[vf],delete t[V_],delete t[$_])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function W1(e){return e.tag===5||e.tag===3||e.tag===4}function Lm(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||W1(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function jf(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ol));else if(r!==4&&(e=e.child,e!==null))for(jf(e,t,n),e=e.sibling;e!==null;)jf(e,t,n),e=e.sibling}function Lf(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Lf(e,t,n),e=e.sibling;e!==null;)Lf(e,t,n),e=e.sibling}var Qe=null,en=!1;function Kn(e,t,n){for(n=n.child;n!==null;)Y1(e,t,n),n=n.sibling}function Y1(e,t,n){if(vn&&typeof vn.onCommitFiberUnmount=="function")try{vn.onCommitFiberUnmount(Ul,n)}catch{}switch(n.tag){case 5:it||Ni(n,t);case 6:var r=Qe,i=en;Qe=null,Kn(e,t,n),Qe=r,en=i,Qe!==null&&(en?(e=Qe,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Qe.removeChild(n.stateNode));break;case 18:Qe!==null&&(en?(e=Qe,n=n.stateNode,e.nodeType===8?Ku(e.parentNode,n):e.nodeType===1&&Ku(e,n),lo(e)):Ku(Qe,n.stateNode));break;case 4:r=Qe,i=en,Qe=n.stateNode.containerInfo,en=!0,Kn(e,t,n),Qe=r,en=i;break;case 0:case 11:case 14:case 15:if(!it&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){i=r=r.next;do{var s=i,o=s.destroy;s=s.tag,o!==void 0&&(s&2||s&4)&&Df(n,t,o),i=i.next}while(i!==r)}Kn(e,t,n);break;case 1:if(!it&&(Ni(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(a){Le(n,t,a)}Kn(e,t,n);break;case 21:Kn(e,t,n);break;case 22:n.mode&1?(it=(r=it)||n.memoizedState!==null,Kn(e,t,n),it=r):Kn(e,t,n);break;default:Kn(e,t,n)}}function Rm(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new rC),t.forEach(function(r){var i=hC.bind(null,e,r);n.has(r)||(n.add(r),r.then(i,i))})}}function Zt(e,t){var n=t.deletions;if(n!==null)for(var r=0;ri&&(i=o),r&=~s}if(r=i,r=ze()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*oC(r/1960))-r,10e?16:e,ar===null)var r=!1;else{if(e=ar,ar=null,vl=0,pe&6)throw Error(U(331));var i=pe;for(pe|=4,K=e.current;K!==null;){var s=K,o=s.child;if(K.flags&16){var a=s.deletions;if(a!==null){for(var l=0;lze()-gh?Yr(e,0):mh|=n),_t(e,t)}function ev(e,t){t===0&&(e.mode&1?(t=ta,ta<<=1,!(ta&130023424)&&(ta=4194304)):t=1);var n=ht();e=Fn(e,t),e!==null&&(Lo(e,t,n),_t(e,n))}function dC(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),ev(e,n)}function hC(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(U(314))}r!==null&&r.delete(t),ev(e,n)}var tv;tv=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||bt.current)wt=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return wt=!1,eC(e,t,n);wt=!!(e.flags&131072)}else wt=!1,Ne&&t.flags&1048576&&s1(t,cl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Oa(e,t),e=t.pendingProps;var i=qi(t,ot.current);Fi(t,n),i=uh(null,t,r,e,i,n);var s=ch();return t.flags|=1,typeof i=="object"&&i!==null&&typeof i.render=="function"&&i.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,St(r)?(s=!0,ll(t)):s=!1,t.memoizedState=i.state!==null&&i.state!==void 0?i.state:null,ih(t),i.updater=Ql,t.stateNode=i,i._reactInternals=t,Cf(t,r,e,n),t=Tf(null,t,r,!0,s,n)):(t.tag=0,Ne&&s&&Qd(t),ft(null,t,i,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Oa(e,t),e=t.pendingProps,i=r._init,r=i(r._payload),t.type=r,i=t.tag=mC(r),e=Jt(r,e),i){case 0:t=Nf(null,t,r,e,n);break e;case 1:t=Mm(null,t,r,e,n);break e;case 11:t=Am(null,t,r,e,n);break e;case 14:t=Pm(null,t,r,Jt(r.type,e),n);break e}throw Error(U(306,r,""))}return t;case 0:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Jt(r,i),Nf(e,t,r,i,n);case 1:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Jt(r,i),Mm(e,t,r,i,n);case 3:e:{if(F1(t),e===null)throw Error(U(387));r=t.pendingProps,s=t.memoizedState,i=s.element,f1(e,t),hl(t,r,null,n);var o=t.memoizedState;if(r=o.element,s.isDehydrated)if(s={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){i=Qi(Error(U(423)),t),t=Dm(e,t,r,n,i);break e}else if(r!==i){i=Qi(Error(U(424)),t),t=Dm(e,t,r,n,i);break e}else for(It=fr(t.stateNode.containerInfo.firstChild),jt=t,Ne=!0,tn=null,n=u1(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Ki(),r===i){t=On(e,t,n);break e}ft(e,t,r,n)}t=t.child}return t;case 5:return d1(t),e===null&&bf(t),r=t.type,i=t.pendingProps,s=e!==null?e.memoizedProps:null,o=i.children,yf(r,i)?o=null:s!==null&&yf(r,s)&&(t.flags|=32),z1(e,t),ft(e,t,o,n),t.child;case 6:return e===null&&bf(t),null;case 13:return O1(e,t,n);case 4:return sh(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Gi(t,null,r,n):ft(e,t,r,n),t.child;case 11:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Jt(r,i),Am(e,t,r,i,n);case 7:return ft(e,t,t.pendingProps,n),t.child;case 8:return ft(e,t,t.pendingProps.children,n),t.child;case 12:return ft(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,i=t.pendingProps,s=t.memoizedProps,o=i.value,ke(fl,r._currentValue),r._currentValue=o,s!==null)if(cn(s.value,o)){if(s.children===i.children&&!bt.current){t=On(e,t,n);break e}}else for(s=t.child,s!==null&&(s.return=t);s!==null;){var a=s.dependencies;if(a!==null){o=s.child;for(var l=a.firstContext;l!==null;){if(l.context===r){if(s.tag===1){l=In(-1,n&-n),l.tag=2;var u=s.updateQueue;if(u!==null){u=u.shared;var c=u.pending;c===null?l.next=l:(l.next=c.next,c.next=l),u.pending=l}}s.lanes|=n,l=s.alternate,l!==null&&(l.lanes|=n),Sf(s.return,n,t),a.lanes|=n;break}l=l.next}}else if(s.tag===10)o=s.type===t.type?null:s.child;else if(s.tag===18){if(o=s.return,o===null)throw Error(U(341));o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),Sf(o,n,t),o=s.sibling}else o=s.child;if(o!==null)o.return=s;else for(o=s;o!==null;){if(o===t){o=null;break}if(s=o.sibling,s!==null){s.return=o.return,o=s;break}o=o.return}s=o}ft(e,t,i.children,n),t=t.child}return t;case 9:return i=t.type,r=t.pendingProps.children,Fi(t,n),i=Gt(i),r=r(i),t.flags|=1,ft(e,t,r,n),t.child;case 14:return r=t.type,i=Jt(r,t.pendingProps),i=Jt(r.type,i),Pm(e,t,r,i,n);case 15:return L1(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Jt(r,i),Oa(e,t),t.tag=1,St(r)?(e=!0,ll(t)):e=!1,Fi(t,n),D1(t,r,i),Cf(t,r,i,n),Tf(null,t,r,!0,e,n);case 19:return V1(e,t,n);case 22:return R1(e,t,n)}throw Error(U(156,t.tag))};function nv(e,t){return Ax(e,t)}function pC(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Yt(e,t,n,r){return new pC(e,t,n,r)}function wh(e){return e=e.prototype,!(!e||!e.isReactComponent)}function mC(e){if(typeof e=="function")return wh(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Od)return 11;if(e===Vd)return 14}return 2}function mr(e,t){var n=e.alternate;return n===null?(n=Yt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Ba(e,t,n,r,i,s){var o=2;if(r=e,typeof e=="function")wh(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case xi:return qr(n.children,i,s,t);case Fd:o=8,i|=8;break;case Kc:return e=Yt(12,n,t,i|2),e.elementType=Kc,e.lanes=s,e;case Gc:return e=Yt(13,n,t,i),e.elementType=Gc,e.lanes=s,e;case Xc:return e=Yt(19,n,t,i),e.elementType=Xc,e.lanes=s,e;case dx:return eu(n,i,s,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case cx:o=10;break e;case fx:o=9;break e;case Od:o=11;break e;case Vd:o=14;break e;case Zn:o=16,r=null;break e}throw Error(U(130,e==null?e:typeof e,""))}return t=Yt(o,n,t,i),t.elementType=e,t.type=r,t.lanes=s,t}function qr(e,t,n,r){return e=Yt(7,e,r,t),e.lanes=n,e}function eu(e,t,n,r){return e=Yt(22,e,r,t),e.elementType=dx,e.lanes=n,e.stateNode={isHidden:!1},e}function nc(e,t,n){return e=Yt(6,e,null,t),e.lanes=n,e}function rc(e,t,n){return t=Yt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function gC(e,t,n,r,i){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=zu(0),this.expirationTimes=zu(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=zu(0),this.identifierPrefix=r,this.onRecoverableError=i,this.mutableSourceEagerHydrationData=null}function kh(e,t,n,r,i,s,o,a,l){return e=new gC(e,t,n,a,l),t===1?(t=1,s===!0&&(t|=8)):t=0,s=Yt(3,null,null,t),e.current=s,s.stateNode=e,s.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},ih(s),e}function yC(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(ov)}catch(e){console.error(e)}}ov(),ox.exports=Ot;var bC=ox.exports,Um=bC;Yc.createRoot=Um.createRoot,Yc.hydrateRoot=Um.hydrateRoot;function Oe(e,t){if(Object.is(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(const[r,i]of e)if(!Object.is(i,t.get(r)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(const r of e)if(!t.has(r))return!1;return!0}const n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(const r of n)if(!Object.prototype.hasOwnProperty.call(t,r)||!Object.is(e[r],t[r]))return!1;return!0}/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */var SC={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const _C=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().trim(),ie=(e,t)=>{const n=T.forwardRef(({color:r="currentColor",size:i=24,strokeWidth:s=2,absoluteStrokeWidth:o,className:a="",children:l,...u},c)=>T.createElement("svg",{ref:c,...SC,width:i,height:i,stroke:r,strokeWidth:o?Number(s)*24/Number(i):s,className:["lucide",`lucide-${_C(e)}`,a].join(" "),...u},[...t.map(([f,d])=>T.createElement(f,d)),...Array.isArray(l)?l:[l]]));return n.displayName=`${e}`,n};/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Wm=ie("AlertCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const CC=ie("AlertTriangle",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z",key:"c3ski4"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const av=ie("ArrowLeft",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const EC=ie("BarChart3",[["path",{d:"M3 3v18h18",key:"1s2lah"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const NC=ie("Bot",[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const TC=ie("Brain",[["path",{d:"M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z",key:"1mhkh5"}],["path",{d:"M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z",key:"1d6s00"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Kr=ie("CheckCircle2",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const AC=ie("CheckCircle",[["path",{d:"M22 11.08V12a10 10 0 1 1-5.93-9.14",key:"g774vq"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Vf=ie("ChevronDown",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Ym=ie("ChevronUp",[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Ks=ie("CircleDashed",[["path",{d:"M10.1 2.18a9.93 9.93 0 0 1 3.8 0",key:"1qdqn0"}],["path",{d:"M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7",key:"1bq7p6"}],["path",{d:"M21.82 10.1a9.93 9.93 0 0 1 0 3.8",key:"1rlaqf"}],["path",{d:"M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69",key:"1xk03u"}],["path",{d:"M13.9 21.82a9.94 9.94 0 0 1-3.8 0",key:"l7re25"}],["path",{d:"M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7",key:"1v18p6"}],["path",{d:"M2.18 13.9a9.93 9.93 0 0 1 0-3.8",key:"xdo6bj"}],["path",{d:"M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69",key:"1jjmaz"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const su=ie("Clock",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["polyline",{points:"12 6 12 12 16 14",key:"68esgv"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const PC=ie("Command",[["path",{d:"M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3",key:"11bfej"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const qm=ie("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const MC=ie("Cpu",[["rect",{x:"4",y:"4",width:"16",height:"16",rx:"2",key:"1vbyd7"}],["rect",{x:"9",y:"9",width:"6",height:"6",key:"o3kz5p"}],["path",{d:"M15 2v2",key:"13l42r"}],["path",{d:"M15 20v2",key:"15mkzm"}],["path",{d:"M2 15h2",key:"1gxd5l"}],["path",{d:"M2 9h2",key:"1bbxkp"}],["path",{d:"M20 15h2",key:"19e6y8"}],["path",{d:"M20 9h2",key:"19tzq7"}],["path",{d:"M9 2v2",key:"165o2o"}],["path",{d:"M9 20v2",key:"i2bqo8"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const DC=ie("Filter",[["polygon",{points:"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3",key:"1yg77f"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const IC=ie("GitBranch",[["line",{x1:"6",x2:"6",y1:"3",y2:"15",key:"17qcm7"}],["circle",{cx:"18",cy:"6",r:"3",key:"1h7g24"}],["circle",{cx:"6",cy:"18",r:"3",key:"fqmcym"}],["path",{d:"M18 9a9 9 0 0 1-9 9",key:"n2h4wq"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const jC=ie("Info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const LC=ie("LayoutDashboard",[["rect",{width:"7",height:"9",x:"3",y:"3",rx:"1",key:"10lvy0"}],["rect",{width:"7",height:"5",x:"14",y:"3",rx:"1",key:"16une8"}],["rect",{width:"7",height:"9",x:"14",y:"12",rx:"1",key:"1hutg5"}],["rect",{width:"7",height:"5",x:"3",y:"16",rx:"1",key:"ldoo1y"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const RC=ie("ListTree",[["path",{d:"M21 12h-8",key:"1bmf0i"}],["path",{d:"M21 6H8",key:"1pqkrb"}],["path",{d:"M21 18h-8",key:"1tm79t"}],["path",{d:"M3 6v4c0 1.1.9 2 2 2h3",key:"1ywdgy"}],["path",{d:"M3 10v6c0 1.1.9 2 2 2h3",key:"2wc746"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const ou=ie("Loader2",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const ic=ie("Loader",[["line",{x1:"12",x2:"12",y1:"2",y2:"6",key:"gza1u7"}],["line",{x1:"12",x2:"12",y1:"18",y2:"22",key:"1qhbu9"}],["line",{x1:"4.93",x2:"7.76",y1:"4.93",y2:"7.76",key:"xae44r"}],["line",{x1:"16.24",x2:"19.07",y1:"16.24",y2:"19.07",key:"bxnmvf"}],["line",{x1:"2",x2:"6",y1:"12",y2:"12",key:"89khin"}],["line",{x1:"18",x2:"22",y1:"12",y2:"12",key:"pb8tfm"}],["line",{x1:"4.93",x2:"7.76",y1:"19.07",y2:"16.24",key:"1uxjnu"}],["line",{x1:"16.24",x2:"19.07",y1:"7.76",y2:"4.93",key:"6duxfx"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const zC=ie("Network",[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const FC=ie("PanelLeft",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M9 3v18",key:"fh3hqa"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const OC=ie("Play",[["polygon",{points:"5 3 19 12 5 21 5 3",key:"191637"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const lv=ie("RefreshCcw",[["path",{d:"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"14sxne"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16",key:"1hlbsb"}],["path",{d:"M16 16h5v5",key:"ccwih5"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const uv=ie("Rocket",[["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",key:"m3kijz"}],["path",{d:"m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z",key:"1fmvmk"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0",key:"1f8sc4"}],["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const cv=ie("Search",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["path",{d:"m21 21-4.3-4.3",key:"1qie3q"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const VC=ie("SendHorizontal",[["path",{d:"m3 3 3 9-3 9 19-9Z",key:"1aobqy"}],["path",{d:"M6 12h16",key:"s4cdu5"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const $C=ie("ShieldAlert",[["path",{d:"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10",key:"1irkt0"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const BC=ie("SkipForward",[["polygon",{points:"5 4 15 12 5 20 5 4",key:"16p6eg"}],["line",{x1:"19",x2:"19",y1:"5",y2:"19",key:"futhcm"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const $f=ie("Sparkles",[["path",{d:"m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z",key:"17u4zn"}],["path",{d:"M5 3v4",key:"bklmnn"}],["path",{d:"M19 17v4",key:"iiml17"}],["path",{d:"M3 5h4",key:"nem4j1"}],["path",{d:"M17 19h4",key:"lbex7p"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const HC=ie("Star",[["polygon",{points:"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2",key:"8f66p6"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const UC=ie("StopCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["rect",{width:"6",height:"6",x:"9",y:"9",key:"1wrtvo"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const WC=ie("Timer",[["line",{x1:"10",x2:"14",y1:"2",y2:"2",key:"14vaq8"}],["line",{x1:"12",x2:"15",y1:"14",y2:"11",key:"17fdiu"}],["circle",{cx:"12",cy:"14",r:"8",key:"1e1u0o"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const YC=ie("TrendingUp",[["polyline",{points:"22 7 13.5 15.5 8.5 10.5 2 17",key:"126l90"}],["polyline",{points:"16 7 22 7 22 13",key:"kwv8wd"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const qC=ie("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const KC=ie("Wand2",[["path",{d:"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z",key:"1bcowg"}],["path",{d:"m14 7 3 3",key:"1r5n42"}],["path",{d:"M5 6v4",key:"ilb8ba"}],["path",{d:"M19 14v4",key:"blhpug"}],["path",{d:"M10 2v2",key:"7u0qdc"}],["path",{d:"M7 8H3",key:"zfb6yr"}],["path",{d:"M21 16h-4",key:"1cnmox"}],["path",{d:"M11 3H9",key:"1obp7u"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const GC=ie("WifiOff",[["line",{x1:"2",x2:"22",y1:"2",y2:"22",key:"a6p6uj"}],["path",{d:"M8.5 16.5a5 5 0 0 1 7 0",key:"sej527"}],["path",{d:"M2 8.82a15 15 0 0 1 4.17-2.65",key:"11utq1"}],["path",{d:"M10.66 5c4.01-.36 8.14.9 11.34 3.76",key:"hxefdu"}],["path",{d:"M16.85 11.25a10 10 0 0 1 2.22 1.68",key:"q734kn"}],["path",{d:"M5 13a10 10 0 0 1 5.24-2.76",key:"piq4yl"}],["line",{x1:"12",x2:"12.01",y1:"20",y2:"20",key:"of4bc4"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Oo=ie("XCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Bf=ie("X",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]);/** - * @license lucide-react v0.303.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const XC=ie("Zap",[["polygon",{points:"13 2 3 14 12 14 11 22 21 10 12 10 13 2",key:"45s27k"}]]);function fv(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var i=e.length;for(t=0;t{let t;const n=new Set,r=(c,f)=>{const d=typeof c=="function"?c(t):c;if(!Object.is(d,t)){const h=t;t=f??(typeof d!="object"||d===null)?d:Object.assign({},t,d),n.forEach(m=>m(t,h))}},i=()=>t,l={setState:r,getState:i,getInitialState:()=>u,subscribe:c=>(n.add(c),()=>n.delete(c)),destroy:()=>{(QC?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),n.clear()}},u=t=e(r,i,l);return l},dv=e=>e?Km(e):Km;var hv={exports:{}},pv={},mv={exports:{}},gv={};/** - * @license React - * use-sync-external-store-shim.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Ji=T;function ZC(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var JC=typeof Object.is=="function"?Object.is:ZC,eE=Ji.useState,tE=Ji.useEffect,nE=Ji.useLayoutEffect,rE=Ji.useDebugValue;function iE(e,t){var n=t(),r=eE({inst:{value:n,getSnapshot:t}}),i=r[0].inst,s=r[1];return nE(function(){i.value=n,i.getSnapshot=t,sc(i)&&s({inst:i})},[e,n,t]),tE(function(){return sc(i)&&s({inst:i}),e(function(){sc(i)&&s({inst:i})})},[e]),rE(n),n}function sc(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!JC(e,n)}catch{return!0}}function sE(e,t){return t()}var oE=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?sE:iE;gv.useSyncExternalStore=Ji.useSyncExternalStore!==void 0?Ji.useSyncExternalStore:oE;mv.exports=gv;var aE=mv.exports;/** - * @license React - * use-sync-external-store-shim/with-selector.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var au=T,lE=aE;function uE(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var cE=typeof Object.is=="function"?Object.is:uE,fE=lE.useSyncExternalStore,dE=au.useRef,hE=au.useEffect,pE=au.useMemo,mE=au.useDebugValue;pv.useSyncExternalStoreWithSelector=function(e,t,n,r,i){var s=dE(null);if(s.current===null){var o={hasValue:!1,value:null};s.current=o}else o=s.current;s=pE(function(){function l(h){if(!u){if(u=!0,c=h,h=r(h),i!==void 0&&o.hasValue){var m=o.value;if(i(m,h))return f=m}return f=h}if(m=f,cE(c,h))return m;var g=r(h);return i!==void 0&&i(m,g)?(c=h,m):(c=h,f=g)}var u=!1,c,f,d=n===void 0?null:n;return[function(){return l(t())},d===null?void 0:function(){return l(d())}]},[t,n,r,i]);var a=fE(e,s[0],s[1]);return hE(function(){o.hasValue=!0,o.value=a},[a]),mE(a),a};hv.exports=pv;var gE=hv.exports;const yv=Bl(gE),xv={},{useDebugValue:yE}=$,{useSyncExternalStoreWithSelector:xE}=yv;let Gm=!1;const vE=e=>e;function wE(e,t=vE,n){(xv?"production":void 0)!=="production"&&n&&!Gm&&(console.warn("[DEPRECATED] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`. They can be imported from 'zustand/traditional'. https://github.com/pmndrs/zustand/discussions/1937"),Gm=!0);const r=xE(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return yE(r),r}const kE=e=>{(xv?"production":void 0)!=="production"&&typeof e!="function"&&console.warn("[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`.");const t=typeof e=="function"?dv(e):e,n=(r,i)=>wE(t,r,i);return Object.assign(n,t),n},bE=e=>kE;class SE{constructor(t){Cn(this,"ws",null);Cn(this,"url");Cn(this,"reconnectAttempts",0);Cn(this,"maxReconnectAttempts",5);Cn(this,"reconnectDelay",1e3);Cn(this,"eventCallbacks",new Set);Cn(this,"isIntentionalClose",!1);Cn(this,"statusCallbacks",new Set);if(t)this.url=t;else{const n=window.location.protocol==="https:"?"wss:":"ws:",r=window.location.host;this.url=`${n}//${r}/ws`}}connect(){return new Promise((t,n)=>{try{this.notifyStatus("connecting"),this.ws=new WebSocket(this.url),this.isIntentionalClose=!1,this.ws.onopen=()=>{console.log("🌌 Connected to Galaxy WebSocket"),this.reconnectAttempts=0,this.notifyStatus("connected"),t()},this.ws.onmessage=r=>{try{console.log("📨 Raw WebSocket message received:",r.data);const i=JSON.parse(r.data);console.log("📦 Parsed event data:",i),console.log("🔔 Notifying",this.eventCallbacks.size,"callbacks"),this.notifyCallbacks(i)}catch(i){console.error("Failed to parse WebSocket message:",i)}},this.ws.onerror=r=>{console.error("WebSocket error:",r),this.notifyStatus("disconnected"),n(r)},this.ws.onclose=()=>{console.log("WebSocket connection closed"),this.notifyStatus("disconnected"),this.isIntentionalClose||this.attemptReconnect()}}catch(r){n(r)}})}attemptReconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts){console.error("Max reconnection attempts reached");return}this.reconnectAttempts++;const t=this.reconnectDelay*Math.pow(2,this.reconnectAttempts-1);console.log(`Attempting to reconnect in ${t}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`),this.notifyStatus("reconnecting"),setTimeout(()=>{this.connect().catch(()=>{})},t)}disconnect(){this.isIntentionalClose=!0,this.ws&&(this.ws.close(),this.ws=null,this.notifyStatus("disconnected"))}send(t){this.ws&&this.ws.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify(t)):console.error("WebSocket is not connected")}sendRequest(t){this.send({type:"request",text:t,timestamp:Date.now()})}sendReset(){this.send({type:"reset",timestamp:Date.now()})}sendPing(){this.send({type:"ping",timestamp:Date.now()})}onEvent(t){return this.eventCallbacks.add(t),()=>{this.eventCallbacks.delete(t)}}onStatusChange(t){return this.statusCallbacks.add(t),()=>{this.statusCallbacks.delete(t)}}notifyCallbacks(t){console.log("🎯 notifyCallbacks called with event:",t.event_type),console.log("📋 Number of registered callbacks:",this.eventCallbacks.size);let n=0;this.eventCallbacks.forEach(r=>{n++;try{console.log(`🔄 Executing callback ${n}/${this.eventCallbacks.size}`),r(t),console.log(`✅ Callback ${n} executed successfully`)}catch(i){console.error("Error in event callback:",i)}})}notifyStatus(t){this.statusCallbacks.forEach(n=>{try{n(t)}catch(r){console.error("Error in status callback:",r)}})}get isConnected(){return this.ws!==null&&this.ws.readyState===WebSocket.OPEN}}let oc=null;function sn(){return oc||(oc=new SE),oc}new Date(Date.now()-5e3).toISOString(),Date.now()-5e3,new Date(Date.now()-3e3).toISOString(),Date.now()-3e3,new Date(Date.now()-1e4).toISOString(),Date.now()-1e4,new Date(Date.now()-8e3).toISOString(),Date.now()-8e3;const _E=30,pn=()=>Date.now(),Xm=e=>{switch((e||"pending").toString().toLowerCase()){case"completed":case"complete":case"success":return"completed";case"running":case"in_progress":case"active":return"running";case"failed":case"error":return"failed";case"skipped":return"skipped";default:return"pending"}},Qm=e=>{switch((e||"unknown").toString().toLowerCase()){case"idle":return"idle";case"busy":case"running":return"busy";case"connected":case"online":return"connected";case"connecting":return"connecting";case"disconnected":return"disconnected";case"failed":return"failed";case"offline":return"offline";default:return"unknown"}},ac=(e,t,n)=>{const r={total:0,pending:0,running:0,completed:0,failed:0};return t.forEach(i=>{const s=n[i];if(!(!s||s.constellationId!==e))switch(r.total+=1,s.status){case"pending":r.pending+=1;break;case"running":r.running+=1;break;case"completed":r.completed+=1;break;case"failed":r.failed+=1;break}}),r},CE=()=>({id:null,displayName:"Galaxy Session",welcomeText:"Launch a request to orchestrate a new TaskConstellation.",startedAt:null,debugMode:!1,highContrast:!1}),Zm=()=>({searchQuery:"",messageKindFilter:"all",rightPanelTab:"constellation",activeConstellationId:null,activeTaskId:null,activeDeviceId:null,showDeviceDrawer:!1,showComposerShortcuts:!0,isTaskRunning:!1,isTaskStopped:!1,showLeftDrawer:!1,showRightDrawer:!1}),es=()=>typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`id_${Math.random().toString(36).slice(2,10)}_${Date.now()}`,Ce=bE()((e,t)=>({connected:!1,connectionStatus:"idle",setConnected:n=>e({connected:n,connectionStatus:n?"connected":"disconnected"}),setConnectionStatus:n=>e({connectionStatus:n,connected:n==="connected"}),session:CE(),setSessionInfo:n=>e(r=>({session:{...r.session,...n}})),ensureSession:(n,r)=>{const i=t().session;if(i.id&&!n)return i.id;const s=n||`session-${es()}`;return e(o=>({session:{...o.session,id:s,displayName:r||o.session.displayName,startedAt:o.session.startedAt||pn()}})),s},endSession:()=>e(n=>({session:{...n.session,id:null,startedAt:null}})),messages:[],addMessage:n=>e(r=>({messages:[...r.messages,n].slice(-500)})),updateMessage:(n,r)=>e(i=>({messages:i.messages.map(s=>s.id===n?{...s,...r}:s)})),clearMessages:()=>e({messages:[]}),eventLog:[],addEventToLog:n=>e(r=>({eventLog:[...r.eventLog,n].slice(-200)})),clearEventLog:()=>e({eventLog:[]}),constellations:{},upsertConstellation:n=>{e(r=>{var l,u;const i=r.constellations[n.id],s=n.taskIds||(i==null?void 0:i.taskIds)||[],o=ac(n.id,s,r.tasks),a={id:n.id,name:n.name||(i==null?void 0:i.name)||n.id,status:n.status||(i==null?void 0:i.status)||"pending",description:n.description??(i==null?void 0:i.description),metadata:n.metadata??(i==null?void 0:i.metadata),createdAt:n.createdAt??(i==null?void 0:i.createdAt)??pn(),updatedAt:pn(),taskIds:s,dag:{nodes:((l=n.dag)==null?void 0:l.nodes)??(i==null?void 0:i.dag.nodes)??[],edges:((u=n.dag)==null?void 0:u.edges)??(i==null?void 0:i.dag.edges)??[]},statistics:o};return{constellations:{...r.constellations,[n.id]:a},ui:{...r.ui,activeConstellationId:r.ui.activeConstellationId||n.id}}})},removeConstellation:n=>e(r=>{const{[n]:i,...s}=r.constellations;return{constellations:s,ui:{...r.ui,activeConstellationId:r.ui.activeConstellationId===n?null:r.ui.activeConstellationId}}}),setActiveConstellation:n=>e(r=>({ui:{...r.ui,activeConstellationId:n,activeTaskId:n?r.ui.activeTaskId:null}})),tasks:{},bulkUpsertTasks:(n,r,i={})=>{e(s=>{var d;const o={...s.tasks},a={};Object.entries(i).forEach(([h,m])=>{a[h]=Array.isArray(m)?m:[]});const l=new Set(((d=s.constellations[n])==null?void 0:d.taskIds)??[]);r.forEach(h=>{const m=Xm(h.status),g=a[h.id]||h.dependencies||[],w=s.tasks[h.id],p=new Set((w==null?void 0:w.dependents)??[]);Object.entries(a).forEach(([x,v])=>{v!=null&&v.includes(h.id)&&p.add(x)}),o[h.id]={id:h.id,constellationId:n,name:h.name||(w==null?void 0:w.name)||h.id,description:h.description??(w==null?void 0:w.description),status:m,deviceId:h.deviceId??h.device??(w==null?void 0:w.deviceId),input:h.input??(w==null?void 0:w.input),output:h.output??(w==null?void 0:w.output),result:h.result??(w==null?void 0:w.result),error:h.error??(w==null?void 0:w.error)??null,startedAt:h.startedAt??(w==null?void 0:w.startedAt),completedAt:h.completedAt??(w==null?void 0:w.completedAt),retries:h.retries??(w==null?void 0:w.retries),dependencies:g,dependents:Array.from(p),logs:h.logs??(w==null?void 0:w.logs)??[]},l.add(h.id)});const u=Array.from(l),c=ac(n,u,o),f=s.constellations[n];return{tasks:o,constellations:{...s.constellations,[n]:f?{...f,taskIds:u,statistics:c,updatedAt:pn()}:{id:n,name:n,status:"pending",taskIds:u,dag:{nodes:[],edges:[]},statistics:c,createdAt:pn(),updatedAt:pn()}}}})},updateTask:(n,r)=>{e(i=>{const s=i.tasks[n];if(!s)return i;const o={...s,...r,status:r.status?Xm(r.status):s.status},a=i.constellations[s.constellationId],l={tasks:{...i.tasks,[n]:o}};return a&&(l.constellations={...i.constellations,[a.id]:{...a,statistics:ac(a.id,a.taskIds,{...i.tasks,[n]:o}),updatedAt:pn()}}),l})},appendTaskLog:(n,r)=>e(i=>{const s=i.tasks[n];if(!s)return i;const o=[...s.logs,r];return{tasks:{...i.tasks,[n]:{...s,logs:o}}}}),devices:{},setDevicesFromSnapshot:n=>{e(r=>{const i={...r.devices};return Object.entries(n||{}).forEach(([s,o])=>{var l;const a=Qm(o==null?void 0:o.status);i[s]={id:s,name:(o==null?void 0:o.device_id)||s,status:a,os:o==null?void 0:o.os,serverUrl:o==null?void 0:o.server_url,capabilities:(o==null?void 0:o.capabilities)||[],metadata:(o==null?void 0:o.metadata)||{},lastHeartbeat:(o==null?void 0:o.last_heartbeat)||null,connectionAttempts:o==null?void 0:o.connection_attempts,maxRetries:o==null?void 0:o.max_retries,currentTaskId:o==null?void 0:o.current_task_id,tags:((l=o==null?void 0:o.metadata)==null?void 0:l.tags)||[],metrics:(o==null?void 0:o.metrics)||{},updatedAt:pn()}}),{devices:i}})},upsertDevice:n=>{const r=t().devices[n.id],i=Qm(n.status||(r==null?void 0:r.status));return e(s=>({devices:{...s.devices,[n.id]:{id:n.id,name:n.name||(r==null?void 0:r.name)||n.id,status:i,os:n.os??(r==null?void 0:r.os),serverUrl:n.serverUrl??(r==null?void 0:r.serverUrl),capabilities:n.capabilities??(r==null?void 0:r.capabilities)??[],metadata:n.metadata??(r==null?void 0:r.metadata)??{},lastHeartbeat:n.lastHeartbeat??(r==null?void 0:r.lastHeartbeat)??null,connectionAttempts:n.connectionAttempts??(r==null?void 0:r.connectionAttempts),maxRetries:n.maxRetries??(r==null?void 0:r.maxRetries),currentTaskId:n.currentTaskId??(r==null?void 0:r.currentTaskId)??null,tags:n.tags??(r==null?void 0:r.tags)??[],metrics:n.metrics??(r==null?void 0:r.metrics)??{},updatedAt:pn(),highlightUntil:pn()+4e3}}})),{statusChanged:(r==null?void 0:r.status)!==i,previousStatus:r==null?void 0:r.status}},clearDeviceHighlight:n=>e(r=>{const i=r.devices[n];return i?{devices:{...r.devices,[n]:{...i,highlightUntil:0}}}:r}),notifications:[],pushNotification:n=>e(r=>({notifications:[n,...r.notifications].slice(0,_E)})),dismissNotification:n=>e(r=>({notifications:r.notifications.filter(i=>i.id!==n)})),markNotificationRead:n=>e(r=>({notifications:r.notifications.map(i=>i.id===n?{...i,read:!0}:i)})),markAllNotificationsRead:()=>e(n=>({notifications:n.notifications.map(r=>({...r,read:!0}))})),ui:{...Zm(),activeConstellationId:null},setSearchQuery:n=>e(r=>({ui:{...r.ui,searchQuery:n}})),setMessageKindFilter:n=>e(r=>({ui:{...r.ui,messageKindFilter:n}})),setRightPanelTab:n=>e(r=>({ui:{...r.ui,rightPanelTab:n}})),setActiveTask:n=>e(r=>({ui:{...r.ui,activeTaskId:n,rightPanelTab:n?"details":r.ui.rightPanelTab}})),setActiveDevice:n=>e(r=>({ui:{...r.ui,activeDeviceId:n}})),toggleDeviceDrawer:n=>e(r=>({ui:{...r.ui,showDeviceDrawer:typeof n=="boolean"?n:!r.ui.showDeviceDrawer}})),toggleComposerShortcuts:()=>e(n=>({ui:{...n.ui,showComposerShortcuts:!n.ui.showComposerShortcuts}})),setTaskRunning:n=>e(r=>({ui:{...r.ui,isTaskRunning:n,isTaskStopped:n?!1:r.ui.isTaskStopped}})),stopCurrentTask:()=>{sn().send({type:"stop_task",timestamp:Date.now()}),e(r=>({ui:{...r.ui,isTaskRunning:!1,isTaskStopped:!0}}))},toggleLeftDrawer:n=>e(r=>({ui:{...r.ui,showLeftDrawer:typeof n=="boolean"?n:!r.ui.showLeftDrawer}})),toggleRightDrawer:n=>e(r=>({ui:{...r.ui,showRightDrawer:typeof n=="boolean"?n:!r.ui.showRightDrawer}})),toggleDebugMode:()=>e(n=>({session:{...n.session,debugMode:!n.session.debugMode}})),toggleHighContrast:()=>e(n=>({session:{...n.session,highContrast:!n.session.highContrast}})),resetSessionState:()=>e(n=>({messages:[],eventLog:[],constellations:{},tasks:{},notifications:[],ui:{...Zm(),showComposerShortcuts:n.ui.showComposerShortcuts},session:{...n.session,id:null,startedAt:null}}))})),EE=[{label:"All",value:"all"},{label:"Responses",value:"response"},{label:"User",value:"user"}],NE=()=>{const{searchQuery:e,messageKindFilter:t,setSearchQuery:n,setMessageKindFilter:r}=Ce(s=>({searchQuery:s.ui.searchQuery,messageKindFilter:s.ui.messageKindFilter,setSearchQuery:s.setSearchQuery,setMessageKindFilter:s.setMessageKindFilter}),Oe),i=s=>{n(s.target.value)};return y.jsxs("div",{className:"flex flex-col gap-3 rounded-[24px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.85)] via-[rgba(8,20,35,0.82)] to-[rgba(6,15,28,0.85)] p-4 shadow-[0_8px_32px_rgba(0,0,0,0.35),0_2px_8px_rgba(15,123,255,0.1),inset_0_1px_1px_rgba(255,255,255,0.06)] ring-1 ring-inset ring-white/5",children:[y.jsxs("div",{className:"flex items-center gap-3 rounded-xl border border-white/5 bg-gradient-to-r from-black/30 to-black/20 px-3 py-2.5 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus-within:border-white/15 focus-within:shadow-[0_0_8px_rgba(15,123,255,0.08),inset_0_2px_8px_rgba(0,0,0,0.3)]",children:[y.jsx(cv,{className:"h-4 w-4 text-slate-400","aria-hidden":!0}),y.jsx("input",{type:"search",value:e,onChange:i,placeholder:"Search messages, tasks, or devices",className:"w-full bg-transparent text-sm text-slate-100 placeholder:text-slate-500 focus:outline-none"})]}),y.jsxs("div",{className:"flex flex-wrap items-center gap-2 text-xs",children:[y.jsxs("span",{className:"flex items-center gap-1 rounded-full border border-white/10 bg-white/10 px-2.5 py-1 text-[11px] uppercase tracking-[0.2em] text-slate-300 shadow-[inset_0_1px_2px_rgba(255,255,255,0.1)]",children:[y.jsx(DC,{className:"h-3 w-3","aria-hidden":!0}),"Filter"]}),EE.map(({label:s,value:o})=>y.jsx("button",{type:"button",className:we("rounded-full px-3 py-1.5 transition-all duration-200",t===o?"bg-gradient-to-r from-galaxy-blue to-galaxy-purple text-white shadow-[0_0_20px_rgba(15,123,255,0.4),0_2px_8px_rgba(123,44,191,0.3)] ring-1 ring-white/20":"border border-white/10 bg-white/5 text-slate-300 shadow-[inset_0_1px_2px_rgba(255,255,255,0.05)] hover:border-white/20 hover:bg-white/10 hover:text-white hover:shadow-[0_0_10px_rgba(15,123,255,0.15)]"),onClick:()=>r(o),children:s},o))]})]})};function TE(e,t){const n={};return(e[e.length-1]===""?[...e,""]:e).join((n.padRight?" ":"")+","+(n.padLeft===!1?"":" ")).trim()}const AE=/^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,PE=/^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,ME={};function Jm(e,t){return(ME.jsx?PE:AE).test(e)}const DE=/[ \t\n\f\r]/g;function IE(e){return typeof e=="object"?e.type==="text"?eg(e.value):!1:eg(e)}function eg(e){return e.replace(DE,"")===""}class Vo{constructor(t,n,r){this.normal=n,this.property=t,r&&(this.space=r)}}Vo.prototype.normal={};Vo.prototype.property={};Vo.prototype.space=void 0;function vv(e,t){const n={},r={};for(const i of e)Object.assign(n,i.property),Object.assign(r,i.normal);return new Vo(n,r,t)}function Hf(e){return e.toLowerCase()}class Et{constructor(t,n){this.attribute=n,this.property=t}}Et.prototype.attribute="";Et.prototype.booleanish=!1;Et.prototype.boolean=!1;Et.prototype.commaOrSpaceSeparated=!1;Et.prototype.commaSeparated=!1;Et.prototype.defined=!1;Et.prototype.mustUseProperty=!1;Et.prototype.number=!1;Et.prototype.overloadedBoolean=!1;Et.prototype.property="";Et.prototype.spaceSeparated=!1;Et.prototype.space=void 0;let jE=0;const se=ai(),$e=ai(),Uf=ai(),W=ai(),ve=ai(),Vi=ai(),Mt=ai();function ai(){return 2**++jE}const Wf=Object.freeze(Object.defineProperty({__proto__:null,boolean:se,booleanish:$e,commaOrSpaceSeparated:Mt,commaSeparated:Vi,number:W,overloadedBoolean:Uf,spaceSeparated:ve},Symbol.toStringTag,{value:"Module"})),lc=Object.keys(Wf);class Ch extends Et{constructor(t,n,r,i){let s=-1;if(super(t,n),tg(this,"space",i),typeof r=="number")for(;++s4&&n.slice(0,4)==="data"&&OE.test(t)){if(t.charAt(4)==="-"){const s=t.slice(5).replace(ng,BE);r="data"+s.charAt(0).toUpperCase()+s.slice(1)}else{const s=t.slice(4);if(!ng.test(s)){let o=s.replace(FE,$E);o.charAt(0)!=="-"&&(o="-"+o),t="data"+o}}i=Ch}return new i(r,t)}function $E(e){return"-"+e.toLowerCase()}function BE(e){return e.charAt(1).toUpperCase()}const HE=vv([wv,LE,Sv,_v,Cv],"html"),Eh=vv([wv,RE,Sv,_v,Cv],"svg");function UE(e){return e.join(" ").trim()}var Nh={},rg=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,WE=/\n/g,YE=/^\s*/,qE=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,KE=/^:\s*/,GE=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,XE=/^[;\s]*/,QE=/^\s+|\s+$/g,ZE=` -`,ig="/",sg="*",Or="",JE="comment",e5="declaration";function t5(e,t){if(typeof e!="string")throw new TypeError("First argument must be a string");if(!e)return[];t=t||{};var n=1,r=1;function i(m){var g=m.match(WE);g&&(n+=g.length);var w=m.lastIndexOf(ZE);r=~w?m.length-w:r+m.length}function s(){var m={line:n,column:r};return function(g){return g.position=new o(m),u(),g}}function o(m){this.start=m,this.end={line:n,column:r},this.source=t.source}o.prototype.content=e;function a(m){var g=new Error(t.source+":"+n+":"+r+": "+m);if(g.reason=m,g.filename=t.source,g.line=n,g.column=r,g.source=e,!t.silent)throw g}function l(m){var g=m.exec(e);if(g){var w=g[0];return i(w),e=e.slice(w.length),g}}function u(){l(YE)}function c(m){var g;for(m=m||[];g=f();)g!==!1&&m.push(g);return m}function f(){var m=s();if(!(ig!=e.charAt(0)||sg!=e.charAt(1))){for(var g=2;Or!=e.charAt(g)&&(sg!=e.charAt(g)||ig!=e.charAt(g+1));)++g;if(g+=2,Or===e.charAt(g-1))return a("End of comment missing");var w=e.slice(2,g-2);return r+=2,i(w),e=e.slice(g),r+=2,m({type:JE,comment:w})}}function d(){var m=s(),g=l(qE);if(g){if(f(),!l(KE))return a("property missing ':'");var w=l(GE),p=m({type:e5,property:og(g[0].replace(rg,Or)),value:w?og(w[0].replace(rg,Or)):Or});return l(XE),p}}function h(){var m=[];c(m);for(var g;g=d();)g!==!1&&(m.push(g),c(m));return m}return u(),h()}function og(e){return e?e.replace(QE,Or):Or}var n5=t5,r5=Qa&&Qa.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(Nh,"__esModule",{value:!0});Nh.default=s5;const i5=r5(n5);function s5(e,t){let n=null;if(!e||typeof e!="string")return n;const r=(0,i5.default)(e),i=typeof t=="function";return r.forEach(s=>{if(s.type!=="declaration")return;const{property:o,value:a}=s;i?t(o,a,s):a&&(n=n||{},n[o]=a)}),n}var lu={};Object.defineProperty(lu,"__esModule",{value:!0});lu.camelCase=void 0;var o5=/^--[a-zA-Z0-9_-]+$/,a5=/-([a-z])/g,l5=/^[^-]+$/,u5=/^-(webkit|moz|ms|o|khtml)-/,c5=/^-(ms)-/,f5=function(e){return!e||l5.test(e)||o5.test(e)},d5=function(e,t){return t.toUpperCase()},ag=function(e,t){return"".concat(t,"-")},h5=function(e,t){return t===void 0&&(t={}),f5(e)?e:(e=e.toLowerCase(),t.reactCompat?e=e.replace(c5,ag):e=e.replace(u5,ag),e.replace(a5,d5))};lu.camelCase=h5;var p5=Qa&&Qa.__importDefault||function(e){return e&&e.__esModule?e:{default:e}},m5=p5(Nh),g5=lu;function Yf(e,t){var n={};return!e||typeof e!="string"||(0,m5.default)(e,function(r,i){r&&i&&(n[(0,g5.camelCase)(r,t)]=i)}),n}Yf.default=Yf;var y5=Yf;const x5=Bl(y5),Ev=Nv("end"),Th=Nv("start");function Nv(e){return t;function t(n){const r=n&&n.position&&n.position[e]||{};if(typeof r.line=="number"&&r.line>0&&typeof r.column=="number"&&r.column>0)return{line:r.line,column:r.column,offset:typeof r.offset=="number"&&r.offset>-1?r.offset:void 0}}}function v5(e){const t=Th(e),n=Ev(e);if(t&&n)return{start:t,end:n}}function Gs(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?lg(e.position):"start"in e||"end"in e?lg(e):"line"in e||"column"in e?qf(e):""}function qf(e){return ug(e&&e.line)+":"+ug(e&&e.column)}function lg(e){return qf(e&&e.start)+"-"+qf(e&&e.end)}function ug(e){return e&&typeof e=="number"?e:1}class at extends Error{constructor(t,n,r){super(),typeof n=="string"&&(r=n,n=void 0);let i="",s={},o=!1;if(n&&("line"in n&&"column"in n?s={place:n}:"start"in n&&"end"in n?s={place:n}:"type"in n?s={ancestors:[n],place:n.position}:s={...n}),typeof t=="string"?i=t:!s.cause&&t&&(o=!0,i=t.message,s.cause=t),!s.ruleId&&!s.source&&typeof r=="string"){const l=r.indexOf(":");l===-1?s.ruleId=r:(s.source=r.slice(0,l),s.ruleId=r.slice(l+1))}if(!s.place&&s.ancestors&&s.ancestors){const l=s.ancestors[s.ancestors.length-1];l&&(s.place=l.position)}const a=s.place&&"start"in s.place?s.place.start:s.place;this.ancestors=s.ancestors||void 0,this.cause=s.cause||void 0,this.column=a?a.column:void 0,this.fatal=void 0,this.file="",this.message=i,this.line=a?a.line:void 0,this.name=Gs(s.place)||"1:1",this.place=s.place||void 0,this.reason=this.message,this.ruleId=s.ruleId||void 0,this.source=s.source||void 0,this.stack=o&&s.cause&&typeof s.cause.stack=="string"?s.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}at.prototype.file="";at.prototype.name="";at.prototype.reason="";at.prototype.message="";at.prototype.stack="";at.prototype.column=void 0;at.prototype.line=void 0;at.prototype.ancestors=void 0;at.prototype.cause=void 0;at.prototype.fatal=void 0;at.prototype.place=void 0;at.prototype.ruleId=void 0;at.prototype.source=void 0;const Ah={}.hasOwnProperty,w5=new Map,k5=/[A-Z]/g,b5=new Set(["table","tbody","thead","tfoot","tr"]),S5=new Set(["td","th"]),Tv="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function _5(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const n=t.filePath||void 0;let r;if(t.development){if(typeof t.jsxDEV!="function")throw new TypeError("Expected `jsxDEV` in options when `development: true`");r=D5(n,t.jsxDEV)}else{if(typeof t.jsx!="function")throw new TypeError("Expected `jsx` in production options");if(typeof t.jsxs!="function")throw new TypeError("Expected `jsxs` in production options");r=M5(n,t.jsx,t.jsxs)}const i={Fragment:t.Fragment,ancestors:[],components:t.components||{},create:r,elementAttributeNameCase:t.elementAttributeNameCase||"react",evaluater:t.createEvaluater?t.createEvaluater():void 0,filePath:n,ignoreInvalidStyle:t.ignoreInvalidStyle||!1,passKeys:t.passKeys!==!1,passNode:t.passNode||!1,schema:t.space==="svg"?Eh:HE,stylePropertyNameCase:t.stylePropertyNameCase||"dom",tableCellAlignToStyle:t.tableCellAlignToStyle!==!1},s=Av(i,e,void 0);return s&&typeof s!="string"?s:i.create(e,i.Fragment,{children:s||void 0},void 0)}function Av(e,t,n){if(t.type==="element")return C5(e,t,n);if(t.type==="mdxFlowExpression"||t.type==="mdxTextExpression")return E5(e,t);if(t.type==="mdxJsxFlowElement"||t.type==="mdxJsxTextElement")return T5(e,t,n);if(t.type==="mdxjsEsm")return N5(e,t);if(t.type==="root")return A5(e,t,n);if(t.type==="text")return P5(e,t)}function C5(e,t,n){const r=e.schema;let i=r;t.tagName.toLowerCase()==="svg"&&r.space==="html"&&(i=Eh,e.schema=i),e.ancestors.push(t);const s=Mv(e,t.tagName,!1),o=I5(e,t);let a=Mh(e,t);return b5.has(t.tagName)&&(a=a.filter(function(l){return typeof l=="string"?!IE(l):!0})),Pv(e,o,s,t),Ph(o,a),e.ancestors.pop(),e.schema=r,e.create(t,s,o,n)}function E5(e,t){if(t.data&&t.data.estree&&e.evaluater){const r=t.data.estree.body[0];return r.type,e.evaluater.evaluateExpression(r.expression)}ko(e,t.position)}function N5(e,t){if(t.data&&t.data.estree&&e.evaluater)return e.evaluater.evaluateProgram(t.data.estree);ko(e,t.position)}function T5(e,t,n){const r=e.schema;let i=r;t.name==="svg"&&r.space==="html"&&(i=Eh,e.schema=i),e.ancestors.push(t);const s=t.name===null?e.Fragment:Mv(e,t.name,!0),o=j5(e,t),a=Mh(e,t);return Pv(e,o,s,t),Ph(o,a),e.ancestors.pop(),e.schema=r,e.create(t,s,o,n)}function A5(e,t,n){const r={};return Ph(r,Mh(e,t)),e.create(t,e.Fragment,r,n)}function P5(e,t){return t.value}function Pv(e,t,n,r){typeof n!="string"&&n!==e.Fragment&&e.passNode&&(t.node=r)}function Ph(e,t){if(t.length>0){const n=t.length>1?t:t[0];n&&(e.children=n)}}function M5(e,t,n){return r;function r(i,s,o,a){const u=Array.isArray(o.children)?n:t;return a?u(s,o,a):u(s,o)}}function D5(e,t){return n;function n(r,i,s,o){const a=Array.isArray(s.children),l=Th(r);return t(i,s,o,a,{columnNumber:l?l.column-1:void 0,fileName:e,lineNumber:l?l.line:void 0},void 0)}}function I5(e,t){const n={};let r,i;for(i in t.properties)if(i!=="children"&&Ah.call(t.properties,i)){const s=L5(e,i,t.properties[i]);if(s){const[o,a]=s;e.tableCellAlignToStyle&&o==="align"&&typeof a=="string"&&S5.has(t.tagName)?r=a:n[o]=a}}if(r){const s=n.style||(n.style={});s[e.stylePropertyNameCase==="css"?"text-align":"textAlign"]=r}return n}function j5(e,t){const n={};for(const r of t.attributes)if(r.type==="mdxJsxExpressionAttribute")if(r.data&&r.data.estree&&e.evaluater){const s=r.data.estree.body[0];s.type;const o=s.expression;o.type;const a=o.properties[0];a.type,Object.assign(n,e.evaluater.evaluateExpression(a.argument))}else ko(e,t.position);else{const i=r.name;let s;if(r.value&&typeof r.value=="object")if(r.value.data&&r.value.data.estree&&e.evaluater){const a=r.value.data.estree.body[0];a.type,s=e.evaluater.evaluateExpression(a.expression)}else ko(e,t.position);else s=r.value===null?!0:r.value;n[i]=s}return n}function Mh(e,t){const n=[];let r=-1;const i=e.passKeys?new Map:w5;for(;++ri?0:i+t:t=t>i?i:t,n=n>0?n:0,r.length<1e4)o=Array.from(r),o.unshift(t,n),e.splice(...o);else for(n&&e.splice(t,n);s0?(Lt(e,e.length,0,t),e):t}const dg={}.hasOwnProperty;function Iv(e){const t={};let n=-1;for(;++n13&&n<32||n>126&&n<160||n>55295&&n<57344||n>64975&&n<65008||(n&65535)===65535||(n&65535)===65534||n>1114111?"�":String.fromCodePoint(n)}function ln(e){return e.replace(/[\t\n\r ]+/g," ").replace(/^ | $/g,"").toLowerCase().toUpperCase()}const dt=Er(/[A-Za-z]/),st=Er(/[\dA-Za-z]/),U5=Er(/[#-'*+\--9=?A-Z^-~]/);function bl(e){return e!==null&&(e<32||e===127)}const Kf=Er(/\d/),W5=Er(/[\dA-Fa-f]/),Y5=Er(/[!-/:-@[-`{-~]/);function J(e){return e!==null&&e<-2}function xe(e){return e!==null&&(e<0||e===32)}function ue(e){return e===-2||e===-1||e===32}const uu=Er(new RegExp("\\p{P}|\\p{S}","u")),ni=Er(/\s/);function Er(e){return t;function t(n){return n!==null&&n>-1&&e.test(String.fromCharCode(n))}}function ds(e){const t=[];let n=-1,r=0,i=0;for(;++n55295&&s<57344){const a=e.charCodeAt(n+1);s<56320&&a>56319&&a<57344?(o=String.fromCharCode(s,a),i=1):o="�"}else o=String.fromCharCode(s);o&&(t.push(e.slice(r,n),encodeURIComponent(o)),r=n+i+1,o=""),i&&(n+=i,i=0)}return t.join("")+e.slice(r)}function fe(e,t,n,r){const i=r?r-1:Number.POSITIVE_INFINITY;let s=0;return o;function o(l){return ue(l)?(e.enter(n),a(l)):t(l)}function a(l){return ue(l)&&s++o))return;const A=t.events.length;let P=A,D,C;for(;P--;)if(t.events[P][0]==="exit"&&t.events[P][1].type==="chunkFlow"){if(D){C=t.events[P][1].end;break}D=!0}for(p(r),S=A;Sv;){const N=n[k];t.containerState=N[1],N[0].exit.call(t,e)}n.length=v}function x(){i.write([null]),s=void 0,i=void 0,t.containerState._closeFlow=void 0}}function Q5(e,t,n){return fe(e,e.attempt(this.parser.constructs.document,t,n),"linePrefix",this.parser.constructs.disable.null.includes("codeIndented")?void 0:4)}function ts(e){if(e===null||xe(e)||ni(e))return 1;if(uu(e))return 2}function cu(e,t,n){const r=[];let i=-1;for(;++i1&&e[n][1].end.offset-e[n][1].start.offset>1?2:1;const f={...e[r][1].end},d={...e[n][1].start};pg(f,-l),pg(d,l),o={type:l>1?"strongSequence":"emphasisSequence",start:f,end:{...e[r][1].end}},a={type:l>1?"strongSequence":"emphasisSequence",start:{...e[n][1].start},end:d},s={type:l>1?"strongText":"emphasisText",start:{...e[r][1].end},end:{...e[n][1].start}},i={type:l>1?"strong":"emphasis",start:{...o.start},end:{...a.end}},e[r][1].end={...o.start},e[n][1].start={...a.end},u=[],e[r][1].end.offset-e[r][1].start.offset&&(u=Wt(u,[["enter",e[r][1],t],["exit",e[r][1],t]])),u=Wt(u,[["enter",i,t],["enter",o,t],["exit",o,t],["enter",s,t]]),u=Wt(u,cu(t.parser.constructs.insideSpan.null,e.slice(r+1,n),t)),u=Wt(u,[["exit",s,t],["enter",a,t],["exit",a,t],["exit",i,t]]),e[n][1].end.offset-e[n][1].start.offset?(c=2,u=Wt(u,[["enter",e[n][1],t],["exit",e[n][1],t]])):c=0,Lt(e,r-1,n-r+3,u),n=r+u.length-c-2;break}}for(n=-1;++n0&&ue(S)?fe(e,x,"linePrefix",s+1)(S):x(S)}function x(S){return S===null||J(S)?e.check(mg,g,k)(S):(e.enter("codeFlowValue"),v(S))}function v(S){return S===null||J(S)?(e.exit("codeFlowValue"),x(S)):(e.consume(S),v)}function k(S){return e.exit("codeFenced"),t(S)}function N(S,A,P){let D=0;return C;function C(L){return S.enter("lineEnding"),S.consume(L),S.exit("lineEnding"),j}function j(L){return S.enter("codeFencedFence"),ue(L)?fe(S,z,"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(L):z(L)}function z(L){return L===a?(S.enter("codeFencedFenceSequence"),H(L)):P(L)}function H(L){return L===a?(D++,S.consume(L),H):D>=o?(S.exit("codeFencedFenceSequence"),ue(L)?fe(S,_,"whitespace")(L):_(L)):P(L)}function _(L){return L===null||J(L)?(S.exit("codeFencedFence"),A(L)):P(L)}}}function u3(e,t,n){const r=this;return i;function i(o){return o===null?n(o):(e.enter("lineEnding"),e.consume(o),e.exit("lineEnding"),s)}function s(o){return r.parser.lazy[r.now().line]?n(o):t(o)}}const cc={name:"codeIndented",tokenize:f3},c3={partial:!0,tokenize:d3};function f3(e,t,n){const r=this;return i;function i(u){return e.enter("codeIndented"),fe(e,s,"linePrefix",5)(u)}function s(u){const c=r.events[r.events.length-1];return c&&c[1].type==="linePrefix"&&c[2].sliceSerialize(c[1],!0).length>=4?o(u):n(u)}function o(u){return u===null?l(u):J(u)?e.attempt(c3,o,l)(u):(e.enter("codeFlowValue"),a(u))}function a(u){return u===null||J(u)?(e.exit("codeFlowValue"),o(u)):(e.consume(u),a)}function l(u){return e.exit("codeIndented"),t(u)}}function d3(e,t,n){const r=this;return i;function i(o){return r.parser.lazy[r.now().line]?n(o):J(o)?(e.enter("lineEnding"),e.consume(o),e.exit("lineEnding"),i):fe(e,s,"linePrefix",5)(o)}function s(o){const a=r.events[r.events.length-1];return a&&a[1].type==="linePrefix"&&a[2].sliceSerialize(a[1],!0).length>=4?t(o):J(o)?i(o):n(o)}}const h3={name:"codeText",previous:m3,resolve:p3,tokenize:g3};function p3(e){let t=e.length-4,n=3,r,i;if((e[n][1].type==="lineEnding"||e[n][1].type==="space")&&(e[t][1].type==="lineEnding"||e[t][1].type==="space")){for(r=n;++r=this.left.length+this.right.length)throw new RangeError("Cannot access index `"+t+"` in a splice buffer of size `"+(this.left.length+this.right.length)+"`");return tthis.left.length?this.right.slice(this.right.length-r+this.left.length,this.right.length-t+this.left.length).reverse():this.left.slice(t).concat(this.right.slice(this.right.length-r+this.left.length).reverse())}splice(t,n,r){const i=n||0;this.setCursor(Math.trunc(t));const s=this.right.splice(this.right.length-i,Number.POSITIVE_INFINITY);return r&&bs(this.left,r),s.reverse()}pop(){return this.setCursor(Number.POSITIVE_INFINITY),this.left.pop()}push(t){this.setCursor(Number.POSITIVE_INFINITY),this.left.push(t)}pushMany(t){this.setCursor(Number.POSITIVE_INFINITY),bs(this.left,t)}unshift(t){this.setCursor(0),this.right.push(t)}unshiftMany(t){this.setCursor(0),bs(this.right,t.reverse())}setCursor(t){if(!(t===this.left.length||t>this.left.length&&this.right.length===0||t<0&&this.left.length===0))if(t=4?t(o):e.interrupt(r.parser.constructs.flow,n,t)(o)}}function Ov(e,t,n,r,i,s,o,a,l){const u=l||Number.POSITIVE_INFINITY;let c=0;return f;function f(p){return p===60?(e.enter(r),e.enter(i),e.enter(s),e.consume(p),e.exit(s),d):p===null||p===32||p===41||bl(p)?n(p):(e.enter(r),e.enter(o),e.enter(a),e.enter("chunkString",{contentType:"string"}),g(p))}function d(p){return p===62?(e.enter(s),e.consume(p),e.exit(s),e.exit(i),e.exit(r),t):(e.enter(a),e.enter("chunkString",{contentType:"string"}),h(p))}function h(p){return p===62?(e.exit("chunkString"),e.exit(a),d(p)):p===null||p===60||J(p)?n(p):(e.consume(p),p===92?m:h)}function m(p){return p===60||p===62||p===92?(e.consume(p),h):h(p)}function g(p){return!c&&(p===null||p===41||xe(p))?(e.exit("chunkString"),e.exit(a),e.exit(o),e.exit(r),t(p)):c999||h===null||h===91||h===93&&!l||h===94&&!a&&"_hiddenFootnoteSupport"in o.parser.constructs?n(h):h===93?(e.exit(s),e.enter(i),e.consume(h),e.exit(i),e.exit(r),t):J(h)?(e.enter("lineEnding"),e.consume(h),e.exit("lineEnding"),c):(e.enter("chunkString",{contentType:"string"}),f(h))}function f(h){return h===null||h===91||h===93||J(h)||a++>999?(e.exit("chunkString"),c(h)):(e.consume(h),l||(l=!ue(h)),h===92?d:f)}function d(h){return h===91||h===92||h===93?(e.consume(h),a++,f):f(h)}}function $v(e,t,n,r,i,s){let o;return a;function a(d){return d===34||d===39||d===40?(e.enter(r),e.enter(i),e.consume(d),e.exit(i),o=d===40?41:d,l):n(d)}function l(d){return d===o?(e.enter(i),e.consume(d),e.exit(i),e.exit(r),t):(e.enter(s),u(d))}function u(d){return d===o?(e.exit(s),l(o)):d===null?n(d):J(d)?(e.enter("lineEnding"),e.consume(d),e.exit("lineEnding"),fe(e,u,"linePrefix")):(e.enter("chunkString",{contentType:"string"}),c(d))}function c(d){return d===o||d===null||J(d)?(e.exit("chunkString"),u(d)):(e.consume(d),d===92?f:c)}function f(d){return d===o||d===92?(e.consume(d),c):c(d)}}function Xs(e,t){let n;return r;function r(i){return J(i)?(e.enter("lineEnding"),e.consume(i),e.exit("lineEnding"),n=!0,r):ue(i)?fe(e,r,n?"linePrefix":"lineSuffix")(i):t(i)}}const _3={name:"definition",tokenize:E3},C3={partial:!0,tokenize:N3};function E3(e,t,n){const r=this;let i;return s;function s(h){return e.enter("definition"),o(h)}function o(h){return Vv.call(r,e,a,n,"definitionLabel","definitionLabelMarker","definitionLabelString")(h)}function a(h){return i=ln(r.sliceSerialize(r.events[r.events.length-1][1]).slice(1,-1)),h===58?(e.enter("definitionMarker"),e.consume(h),e.exit("definitionMarker"),l):n(h)}function l(h){return xe(h)?Xs(e,u)(h):u(h)}function u(h){return Ov(e,c,n,"definitionDestination","definitionDestinationLiteral","definitionDestinationLiteralMarker","definitionDestinationRaw","definitionDestinationString")(h)}function c(h){return e.attempt(C3,f,f)(h)}function f(h){return ue(h)?fe(e,d,"whitespace")(h):d(h)}function d(h){return h===null||J(h)?(e.exit("definition"),r.parser.defined.push(i),t(h)):n(h)}}function N3(e,t,n){return r;function r(a){return xe(a)?Xs(e,i)(a):n(a)}function i(a){return $v(e,s,n,"definitionTitle","definitionTitleMarker","definitionTitleString")(a)}function s(a){return ue(a)?fe(e,o,"whitespace")(a):o(a)}function o(a){return a===null||J(a)?t(a):n(a)}}const T3={name:"hardBreakEscape",tokenize:A3};function A3(e,t,n){return r;function r(s){return e.enter("hardBreakEscape"),e.consume(s),i}function i(s){return J(s)?(e.exit("hardBreakEscape"),t(s)):n(s)}}const P3={name:"headingAtx",resolve:M3,tokenize:D3};function M3(e,t){let n=e.length-2,r=3,i,s;return e[r][1].type==="whitespace"&&(r+=2),n-2>r&&e[n][1].type==="whitespace"&&(n-=2),e[n][1].type==="atxHeadingSequence"&&(r===n-1||n-4>r&&e[n-2][1].type==="whitespace")&&(n-=r+1===n?2:4),n>r&&(i={type:"atxHeadingText",start:e[r][1].start,end:e[n][1].end},s={type:"chunkText",start:e[r][1].start,end:e[n][1].end,contentType:"text"},Lt(e,r,n-r+1,[["enter",i,t],["enter",s,t],["exit",s,t],["exit",i,t]])),e}function D3(e,t,n){let r=0;return i;function i(c){return e.enter("atxHeading"),s(c)}function s(c){return e.enter("atxHeadingSequence"),o(c)}function o(c){return c===35&&r++<6?(e.consume(c),o):c===null||xe(c)?(e.exit("atxHeadingSequence"),a(c)):n(c)}function a(c){return c===35?(e.enter("atxHeadingSequence"),l(c)):c===null||J(c)?(e.exit("atxHeading"),t(c)):ue(c)?fe(e,a,"whitespace")(c):(e.enter("atxHeadingText"),u(c))}function l(c){return c===35?(e.consume(c),l):(e.exit("atxHeadingSequence"),a(c))}function u(c){return c===null||c===35||xe(c)?(e.exit("atxHeadingText"),a(c)):(e.consume(c),u)}}const I3=["address","article","aside","base","basefont","blockquote","body","caption","center","col","colgroup","dd","details","dialog","dir","div","dl","dt","fieldset","figcaption","figure","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hr","html","iframe","legend","li","link","main","menu","menuitem","nav","noframes","ol","optgroup","option","p","param","search","section","summary","table","tbody","td","tfoot","th","thead","title","tr","track","ul"],yg=["pre","script","style","textarea"],j3={concrete:!0,name:"htmlFlow",resolveTo:z3,tokenize:F3},L3={partial:!0,tokenize:V3},R3={partial:!0,tokenize:O3};function z3(e){let t=e.length;for(;t--&&!(e[t][0]==="enter"&&e[t][1].type==="htmlFlow"););return t>1&&e[t-2][1].type==="linePrefix"&&(e[t][1].start=e[t-2][1].start,e[t+1][1].start=e[t-2][1].start,e.splice(t-2,2)),e}function F3(e,t,n){const r=this;let i,s,o,a,l;return u;function u(E){return c(E)}function c(E){return e.enter("htmlFlow"),e.enter("htmlFlowData"),e.consume(E),f}function f(E){return E===33?(e.consume(E),d):E===47?(e.consume(E),s=!0,g):E===63?(e.consume(E),i=3,r.interrupt?t:b):dt(E)?(e.consume(E),o=String.fromCharCode(E),w):n(E)}function d(E){return E===45?(e.consume(E),i=2,h):E===91?(e.consume(E),i=5,a=0,m):dt(E)?(e.consume(E),i=4,r.interrupt?t:b):n(E)}function h(E){return E===45?(e.consume(E),r.interrupt?t:b):n(E)}function m(E){const q="CDATA[";return E===q.charCodeAt(a++)?(e.consume(E),a===q.length?r.interrupt?t:z:m):n(E)}function g(E){return dt(E)?(e.consume(E),o=String.fromCharCode(E),w):n(E)}function w(E){if(E===null||E===47||E===62||xe(E)){const q=E===47,X=o.toLowerCase();return!q&&!s&&yg.includes(X)?(i=1,r.interrupt?t(E):z(E)):I3.includes(o.toLowerCase())?(i=6,q?(e.consume(E),p):r.interrupt?t(E):z(E)):(i=7,r.interrupt&&!r.parser.lazy[r.now().line]?n(E):s?x(E):v(E))}return E===45||st(E)?(e.consume(E),o+=String.fromCharCode(E),w):n(E)}function p(E){return E===62?(e.consume(E),r.interrupt?t:z):n(E)}function x(E){return ue(E)?(e.consume(E),x):C(E)}function v(E){return E===47?(e.consume(E),C):E===58||E===95||dt(E)?(e.consume(E),k):ue(E)?(e.consume(E),v):C(E)}function k(E){return E===45||E===46||E===58||E===95||st(E)?(e.consume(E),k):N(E)}function N(E){return E===61?(e.consume(E),S):ue(E)?(e.consume(E),N):v(E)}function S(E){return E===null||E===60||E===61||E===62||E===96?n(E):E===34||E===39?(e.consume(E),l=E,A):ue(E)?(e.consume(E),S):P(E)}function A(E){return E===l?(e.consume(E),l=null,D):E===null||J(E)?n(E):(e.consume(E),A)}function P(E){return E===null||E===34||E===39||E===47||E===60||E===61||E===62||E===96||xe(E)?N(E):(e.consume(E),P)}function D(E){return E===47||E===62||ue(E)?v(E):n(E)}function C(E){return E===62?(e.consume(E),j):n(E)}function j(E){return E===null||J(E)?z(E):ue(E)?(e.consume(E),j):n(E)}function z(E){return E===45&&i===2?(e.consume(E),I):E===60&&i===1?(e.consume(E),O):E===62&&i===4?(e.consume(E),F):E===63&&i===3?(e.consume(E),b):E===93&&i===5?(e.consume(E),M):J(E)&&(i===6||i===7)?(e.exit("htmlFlowData"),e.check(L3,B,H)(E)):E===null||J(E)?(e.exit("htmlFlowData"),H(E)):(e.consume(E),z)}function H(E){return e.check(R3,_,B)(E)}function _(E){return e.enter("lineEnding"),e.consume(E),e.exit("lineEnding"),L}function L(E){return E===null||J(E)?H(E):(e.enter("htmlFlowData"),z(E))}function I(E){return E===45?(e.consume(E),b):z(E)}function O(E){return E===47?(e.consume(E),o="",R):z(E)}function R(E){if(E===62){const q=o.toLowerCase();return yg.includes(q)?(e.consume(E),F):z(E)}return dt(E)&&o.length<8?(e.consume(E),o+=String.fromCharCode(E),R):z(E)}function M(E){return E===93?(e.consume(E),b):z(E)}function b(E){return E===62?(e.consume(E),F):E===45&&i===2?(e.consume(E),b):z(E)}function F(E){return E===null||J(E)?(e.exit("htmlFlowData"),B(E)):(e.consume(E),F)}function B(E){return e.exit("htmlFlow"),t(E)}}function O3(e,t,n){const r=this;return i;function i(o){return J(o)?(e.enter("lineEnding"),e.consume(o),e.exit("lineEnding"),s):n(o)}function s(o){return r.parser.lazy[r.now().line]?n(o):t(o)}}function V3(e,t,n){return r;function r(i){return e.enter("lineEnding"),e.consume(i),e.exit("lineEnding"),e.attempt($o,t,n)}}const $3={name:"htmlText",tokenize:B3};function B3(e,t,n){const r=this;let i,s,o;return a;function a(b){return e.enter("htmlText"),e.enter("htmlTextData"),e.consume(b),l}function l(b){return b===33?(e.consume(b),u):b===47?(e.consume(b),N):b===63?(e.consume(b),v):dt(b)?(e.consume(b),P):n(b)}function u(b){return b===45?(e.consume(b),c):b===91?(e.consume(b),s=0,m):dt(b)?(e.consume(b),x):n(b)}function c(b){return b===45?(e.consume(b),h):n(b)}function f(b){return b===null?n(b):b===45?(e.consume(b),d):J(b)?(o=f,O(b)):(e.consume(b),f)}function d(b){return b===45?(e.consume(b),h):f(b)}function h(b){return b===62?I(b):b===45?d(b):f(b)}function m(b){const F="CDATA[";return b===F.charCodeAt(s++)?(e.consume(b),s===F.length?g:m):n(b)}function g(b){return b===null?n(b):b===93?(e.consume(b),w):J(b)?(o=g,O(b)):(e.consume(b),g)}function w(b){return b===93?(e.consume(b),p):g(b)}function p(b){return b===62?I(b):b===93?(e.consume(b),p):g(b)}function x(b){return b===null||b===62?I(b):J(b)?(o=x,O(b)):(e.consume(b),x)}function v(b){return b===null?n(b):b===63?(e.consume(b),k):J(b)?(o=v,O(b)):(e.consume(b),v)}function k(b){return b===62?I(b):v(b)}function N(b){return dt(b)?(e.consume(b),S):n(b)}function S(b){return b===45||st(b)?(e.consume(b),S):A(b)}function A(b){return J(b)?(o=A,O(b)):ue(b)?(e.consume(b),A):I(b)}function P(b){return b===45||st(b)?(e.consume(b),P):b===47||b===62||xe(b)?D(b):n(b)}function D(b){return b===47?(e.consume(b),I):b===58||b===95||dt(b)?(e.consume(b),C):J(b)?(o=D,O(b)):ue(b)?(e.consume(b),D):I(b)}function C(b){return b===45||b===46||b===58||b===95||st(b)?(e.consume(b),C):j(b)}function j(b){return b===61?(e.consume(b),z):J(b)?(o=j,O(b)):ue(b)?(e.consume(b),j):D(b)}function z(b){return b===null||b===60||b===61||b===62||b===96?n(b):b===34||b===39?(e.consume(b),i=b,H):J(b)?(o=z,O(b)):ue(b)?(e.consume(b),z):(e.consume(b),_)}function H(b){return b===i?(e.consume(b),i=void 0,L):b===null?n(b):J(b)?(o=H,O(b)):(e.consume(b),H)}function _(b){return b===null||b===34||b===39||b===60||b===61||b===96?n(b):b===47||b===62||xe(b)?D(b):(e.consume(b),_)}function L(b){return b===47||b===62||xe(b)?D(b):n(b)}function I(b){return b===62?(e.consume(b),e.exit("htmlTextData"),e.exit("htmlText"),t):n(b)}function O(b){return e.exit("htmlTextData"),e.enter("lineEnding"),e.consume(b),e.exit("lineEnding"),R}function R(b){return ue(b)?fe(e,M,"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(b):M(b)}function M(b){return e.enter("htmlTextData"),o(b)}}const jh={name:"labelEnd",resolveAll:Y3,resolveTo:q3,tokenize:K3},H3={tokenize:G3},U3={tokenize:X3},W3={tokenize:Q3};function Y3(e){let t=-1;const n=[];for(;++t=3&&(u===null||J(u))?(e.exit("thematicBreak"),t(u)):n(u)}function l(u){return u===i?(e.consume(u),r++,l):(e.exit("thematicBreakSequence"),ue(u)?fe(e,a,"whitespace")(u):a(u))}}const xt={continuation:{tokenize:aN},exit:uN,name:"list",tokenize:oN},iN={partial:!0,tokenize:cN},sN={partial:!0,tokenize:lN};function oN(e,t,n){const r=this,i=r.events[r.events.length-1];let s=i&&i[1].type==="linePrefix"?i[2].sliceSerialize(i[1],!0).length:0,o=0;return a;function a(h){const m=r.containerState.type||(h===42||h===43||h===45?"listUnordered":"listOrdered");if(m==="listUnordered"?!r.containerState.marker||h===r.containerState.marker:Kf(h)){if(r.containerState.type||(r.containerState.type=m,e.enter(m,{_container:!0})),m==="listUnordered")return e.enter("listItemPrefix"),h===42||h===45?e.check(Ha,n,u)(h):u(h);if(!r.interrupt||h===49)return e.enter("listItemPrefix"),e.enter("listItemValue"),l(h)}return n(h)}function l(h){return Kf(h)&&++o<10?(e.consume(h),l):(!r.interrupt||o<2)&&(r.containerState.marker?h===r.containerState.marker:h===41||h===46)?(e.exit("listItemValue"),u(h)):n(h)}function u(h){return e.enter("listItemMarker"),e.consume(h),e.exit("listItemMarker"),r.containerState.marker=r.containerState.marker||h,e.check($o,r.interrupt?n:c,e.attempt(iN,d,f))}function c(h){return r.containerState.initialBlankLine=!0,s++,d(h)}function f(h){return ue(h)?(e.enter("listItemPrefixWhitespace"),e.consume(h),e.exit("listItemPrefixWhitespace"),d):n(h)}function d(h){return r.containerState.size=s+r.sliceSerialize(e.exit("listItemPrefix"),!0).length,t(h)}}function aN(e,t,n){const r=this;return r.containerState._closeFlow=void 0,e.check($o,i,s);function i(a){return r.containerState.furtherBlankLines=r.containerState.furtherBlankLines||r.containerState.initialBlankLine,fe(e,t,"listItemIndent",r.containerState.size+1)(a)}function s(a){return r.containerState.furtherBlankLines||!ue(a)?(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,o(a)):(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,e.attempt(sN,t,o)(a))}function o(a){return r.containerState._closeFlow=!0,r.interrupt=void 0,fe(e,e.attempt(xt,t,n),"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(a)}}function lN(e,t,n){const r=this;return fe(e,i,"listItemIndent",r.containerState.size+1);function i(s){const o=r.events[r.events.length-1];return o&&o[1].type==="listItemIndent"&&o[2].sliceSerialize(o[1],!0).length===r.containerState.size?t(s):n(s)}}function uN(e){e.exit(this.containerState.type)}function cN(e,t,n){const r=this;return fe(e,i,"listItemPrefixWhitespace",r.parser.constructs.disable.null.includes("codeIndented")?void 0:5);function i(s){const o=r.events[r.events.length-1];return!ue(s)&&o&&o[1].type==="listItemPrefixWhitespace"?t(s):n(s)}}const xg={name:"setextUnderline",resolveTo:fN,tokenize:dN};function fN(e,t){let n=e.length,r,i,s;for(;n--;)if(e[n][0]==="enter"){if(e[n][1].type==="content"){r=n;break}e[n][1].type==="paragraph"&&(i=n)}else e[n][1].type==="content"&&e.splice(n,1),!s&&e[n][1].type==="definition"&&(s=n);const o={type:"setextHeading",start:{...e[r][1].start},end:{...e[e.length-1][1].end}};return e[i][1].type="setextHeadingText",s?(e.splice(i,0,["enter",o,t]),e.splice(s+1,0,["exit",e[r][1],t]),e[r][1].end={...e[s][1].end}):e[r][1]=o,e.push(["exit",o,t]),e}function dN(e,t,n){const r=this;let i;return s;function s(u){let c=r.events.length,f;for(;c--;)if(r.events[c][1].type!=="lineEnding"&&r.events[c][1].type!=="linePrefix"&&r.events[c][1].type!=="content"){f=r.events[c][1].type==="paragraph";break}return!r.parser.lazy[r.now().line]&&(r.interrupt||f)?(e.enter("setextHeadingLine"),i=u,o(u)):n(u)}function o(u){return e.enter("setextHeadingLineSequence"),a(u)}function a(u){return u===i?(e.consume(u),a):(e.exit("setextHeadingLineSequence"),ue(u)?fe(e,l,"lineSuffix")(u):l(u))}function l(u){return u===null||J(u)?(e.exit("setextHeadingLine"),t(u)):n(u)}}const hN={tokenize:pN};function pN(e){const t=this,n=e.attempt($o,r,e.attempt(this.parser.constructs.flowInitial,i,fe(e,e.attempt(this.parser.constructs.flow,i,e.attempt(v3,i)),"linePrefix")));return n;function r(s){if(s===null){e.consume(s);return}return e.enter("lineEndingBlank"),e.consume(s),e.exit("lineEndingBlank"),t.currentConstruct=void 0,n}function i(s){if(s===null){e.consume(s);return}return e.enter("lineEnding"),e.consume(s),e.exit("lineEnding"),t.currentConstruct=void 0,n}}const mN={resolveAll:Hv()},gN=Bv("string"),yN=Bv("text");function Bv(e){return{resolveAll:Hv(e==="text"?xN:void 0),tokenize:t};function t(n){const r=this,i=this.parser.constructs[e],s=n.attempt(i,o,a);return o;function o(c){return u(c)?s(c):a(c)}function a(c){if(c===null){n.consume(c);return}return n.enter("data"),n.consume(c),l}function l(c){return u(c)?(n.exit("data"),s(c)):(n.consume(c),l)}function u(c){if(c===null)return!0;const f=i[c];let d=-1;if(f)for(;++d-1){const a=o[0];typeof a=="string"?o[0]=a.slice(r):o.shift()}s>0&&o.push(e[i].slice(0,s))}return o}function MN(e,t){let n=-1;const r=[];let i;for(;++n0){const ye=ee.tokenStack[ee.tokenStack.length-1];(ye[1]||wg).call(ee,void 0,ye[0])}for(Y.position={start:Gn(V.length>0?V[0][1].start:{line:1,column:1,offset:0}),end:Gn(V.length>0?V[V.length-2][1].end:{line:1,column:1,offset:0})},he=-1;++he1?"-"+a:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(o)}]};e.patch(t,l);const u={type:"element",tagName:"sup",properties:{},children:[l]};return e.patch(t,u),e.applyData(t,u)}function KN(e,t){const n={type:"element",tagName:"h"+t.depth,properties:{},children:e.all(t)};return e.patch(t,n),e.applyData(t,n)}function GN(e,t){if(e.options.allowDangerousHtml){const n={type:"raw",value:t.value};return e.patch(t,n),e.applyData(t,n)}}function Yv(e,t){const n=t.referenceType;let r="]";if(n==="collapsed"?r+="[]":n==="full"&&(r+="["+(t.label||t.identifier)+"]"),t.type==="imageReference")return[{type:"text",value:"!["+t.alt+r}];const i=e.all(t),s=i[0];s&&s.type==="text"?s.value="["+s.value:i.unshift({type:"text",value:"["});const o=i[i.length-1];return o&&o.type==="text"?o.value+=r:i.push({type:"text",value:r}),i}function XN(e,t){const n=String(t.identifier).toUpperCase(),r=e.definitionById.get(n);if(!r)return Yv(e,t);const i={src:ds(r.url||""),alt:t.alt};r.title!==null&&r.title!==void 0&&(i.title=r.title);const s={type:"element",tagName:"img",properties:i,children:[]};return e.patch(t,s),e.applyData(t,s)}function QN(e,t){const n={src:ds(t.url)};t.alt!==null&&t.alt!==void 0&&(n.alt=t.alt),t.title!==null&&t.title!==void 0&&(n.title=t.title);const r={type:"element",tagName:"img",properties:n,children:[]};return e.patch(t,r),e.applyData(t,r)}function ZN(e,t){const n={type:"text",value:t.value.replace(/\r?\n|\r/g," ")};e.patch(t,n);const r={type:"element",tagName:"code",properties:{},children:[n]};return e.patch(t,r),e.applyData(t,r)}function JN(e,t){const n=String(t.identifier).toUpperCase(),r=e.definitionById.get(n);if(!r)return Yv(e,t);const i={href:ds(r.url||"")};r.title!==null&&r.title!==void 0&&(i.title=r.title);const s={type:"element",tagName:"a",properties:i,children:e.all(t)};return e.patch(t,s),e.applyData(t,s)}function eT(e,t){const n={href:ds(t.url)};t.title!==null&&t.title!==void 0&&(n.title=t.title);const r={type:"element",tagName:"a",properties:n,children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function tT(e,t,n){const r=e.all(t),i=n?nT(n):qv(t),s={},o=[];if(typeof t.checked=="boolean"){const c=r[0];let f;c&&c.type==="element"&&c.tagName==="p"?f=c:(f={type:"element",tagName:"p",properties:{},children:[]},r.unshift(f)),f.children.length>0&&f.children.unshift({type:"text",value:" "}),f.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),s.className=["task-list-item"]}let a=-1;for(;++a1}function rT(e,t){const n={},r=e.all(t);let i=-1;for(typeof t.start=="number"&&t.start!==1&&(n.start=t.start);++i0){const o={type:"element",tagName:"tbody",properties:{},children:e.wrap(n,!0)},a=Th(t.children[1]),l=Ev(t.children[t.children.length-1]);a&&l&&(o.position={start:a,end:l}),i.push(o)}const s={type:"element",tagName:"table",properties:{},children:e.wrap(i,!0)};return e.patch(t,s),e.applyData(t,s)}function lT(e,t,n){const r=n?n.children:void 0,s=(r?r.indexOf(t):1)===0?"th":"td",o=n&&n.type==="table"?n.align:void 0,a=o?o.length:t.children.length;let l=-1;const u=[];for(;++l0,!0),r[0]),i=r.index+r[0].length,r=n.exec(t);return s.push(Sg(t.slice(i),i>0,!1)),s.join("")}function Sg(e,t,n){let r=0,i=e.length;if(t){let s=e.codePointAt(r);for(;s===kg||s===bg;)r++,s=e.codePointAt(r)}if(n){let s=e.codePointAt(i-1);for(;s===kg||s===bg;)i--,s=e.codePointAt(i-1)}return i>r?e.slice(r,i):""}function fT(e,t){const n={type:"text",value:cT(String(t.value))};return e.patch(t,n),e.applyData(t,n)}function dT(e,t){const n={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,n),e.applyData(t,n)}const hT={blockquote:BN,break:HN,code:UN,delete:WN,emphasis:YN,footnoteReference:qN,heading:KN,html:GN,imageReference:XN,image:QN,inlineCode:ZN,linkReference:JN,link:eT,listItem:tT,list:rT,paragraph:iT,root:sT,strong:oT,table:aT,tableCell:uT,tableRow:lT,text:fT,thematicBreak:dT,toml:ha,yaml:ha,definition:ha,footnoteDefinition:ha};function ha(){}const Kv=-1,fu=0,Qs=1,Sl=2,Lh=3,Rh=4,zh=5,Fh=6,Gv=7,Xv=8,_g=typeof self=="object"?self:globalThis,pT=(e,t)=>{const n=(i,s)=>(e.set(s,i),i),r=i=>{if(e.has(i))return e.get(i);const[s,o]=t[i];switch(s){case fu:case Kv:return n(o,i);case Qs:{const a=n([],i);for(const l of o)a.push(r(l));return a}case Sl:{const a=n({},i);for(const[l,u]of o)a[r(l)]=r(u);return a}case Lh:return n(new Date(o),i);case Rh:{const{source:a,flags:l}=o;return n(new RegExp(a,l),i)}case zh:{const a=n(new Map,i);for(const[l,u]of o)a.set(r(l),r(u));return a}case Fh:{const a=n(new Set,i);for(const l of o)a.add(r(l));return a}case Gv:{const{name:a,message:l}=o;return n(new _g[a](l),i)}case Xv:return n(BigInt(o),i);case"BigInt":return n(Object(BigInt(o)),i);case"ArrayBuffer":return n(new Uint8Array(o).buffer,o);case"DataView":{const{buffer:a}=new Uint8Array(o);return n(new DataView(a),o)}}return n(new _g[s](o),i)};return r},Cg=e=>pT(new Map,e)(0),fi="",{toString:mT}={},{keys:gT}=Object,Ss=e=>{const t=typeof e;if(t!=="object"||!e)return[fu,t];const n=mT.call(e).slice(8,-1);switch(n){case"Array":return[Qs,fi];case"Object":return[Sl,fi];case"Date":return[Lh,fi];case"RegExp":return[Rh,fi];case"Map":return[zh,fi];case"Set":return[Fh,fi];case"DataView":return[Qs,n]}return n.includes("Array")?[Qs,n]:n.includes("Error")?[Gv,n]:[Sl,n]},pa=([e,t])=>e===fu&&(t==="function"||t==="symbol"),yT=(e,t,n,r)=>{const i=(o,a)=>{const l=r.push(o)-1;return n.set(a,l),l},s=o=>{if(n.has(o))return n.get(o);let[a,l]=Ss(o);switch(a){case fu:{let c=o;switch(l){case"bigint":a=Xv,c=o.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+l);c=null;break;case"undefined":return i([Kv],o)}return i([a,c],o)}case Qs:{if(l){let d=o;return l==="DataView"?d=new Uint8Array(o.buffer):l==="ArrayBuffer"&&(d=new Uint8Array(o)),i([l,[...d]],o)}const c=[],f=i([a,c],o);for(const d of o)c.push(s(d));return f}case Sl:{if(l)switch(l){case"BigInt":return i([l,o.toString()],o);case"Boolean":case"Number":case"String":return i([l,o.valueOf()],o)}if(t&&"toJSON"in o)return s(o.toJSON());const c=[],f=i([a,c],o);for(const d of gT(o))(e||!pa(Ss(o[d])))&&c.push([s(d),s(o[d])]);return f}case Lh:return i([a,o.toISOString()],o);case Rh:{const{source:c,flags:f}=o;return i([a,{source:c,flags:f}],o)}case zh:{const c=[],f=i([a,c],o);for(const[d,h]of o)(e||!(pa(Ss(d))||pa(Ss(h))))&&c.push([s(d),s(h)]);return f}case Fh:{const c=[],f=i([a,c],o);for(const d of o)(e||!pa(Ss(d)))&&c.push(s(d));return f}}const{message:u}=o;return i([a,{name:l,message:u}],o)};return s},Eg=(e,{json:t,lossy:n}={})=>{const r=[];return yT(!(t||n),!!t,new Map,r)(e),r},_l=typeof structuredClone=="function"?(e,t)=>t&&("json"in t||"lossy"in t)?Cg(Eg(e,t)):structuredClone(e):(e,t)=>Cg(Eg(e,t));function xT(e,t){const n=[{type:"text",value:"↩"}];return t>1&&n.push({type:"element",tagName:"sup",properties:{},children:[{type:"text",value:String(t)}]}),n}function vT(e,t){return"Back to reference "+(e+1)+(t>1?"-"+t:"")}function wT(e){const t=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",n=e.options.footnoteBackContent||xT,r=e.options.footnoteBackLabel||vT,i=e.options.footnoteLabel||"Footnotes",s=e.options.footnoteLabelTagName||"h2",o=e.options.footnoteLabelProperties||{className:["sr-only"]},a=[];let l=-1;for(;++l0&&m.push({type:"text",value:" "});let x=typeof n=="string"?n:n(l,h);typeof x=="string"&&(x={type:"text",value:x}),m.push({type:"element",tagName:"a",properties:{href:"#"+t+"fnref-"+d+(h>1?"-"+h:""),dataFootnoteBackref:"",ariaLabel:typeof r=="string"?r:r(l,h),className:["data-footnote-backref"]},children:Array.isArray(x)?x:[x]})}const w=c[c.length-1];if(w&&w.type==="element"&&w.tagName==="p"){const x=w.children[w.children.length-1];x&&x.type==="text"?x.value+=" ":w.children.push({type:"text",value:" "}),w.children.push(...m)}else c.push(...m);const p={type:"element",tagName:"li",properties:{id:t+"fn-"+d},children:e.wrap(c,!0)};e.patch(u,p),a.push(p)}if(a.length!==0)return{type:"element",tagName:"section",properties:{dataFootnotes:!0,className:["footnotes"]},children:[{type:"element",tagName:s,properties:{..._l(o),id:"footnote-label"},children:[{type:"text",value:i}]},{type:"text",value:` -`},{type:"element",tagName:"ol",properties:{},children:e.wrap(a,!0)},{type:"text",value:` -`}]}}const du=function(e){if(e==null)return _T;if(typeof e=="function")return hu(e);if(typeof e=="object")return Array.isArray(e)?kT(e):bT(e);if(typeof e=="string")return ST(e);throw new Error("Expected function, string, or object as test")};function kT(e){const t=[];let n=-1;for(;++n":""))+")"})}return d;function d(){let h=Qv,m,g,w;if((!t||s(l,u,c[c.length-1]||void 0))&&(h=TT(n(l,c)),h[0]===Xf))return h;if("children"in l&&l.children){const p=l;if(p.children&&h[0]!==NT)for(g=(r?p.children.length:-1)+o,w=c.concat(p);g>-1&&g0&&n.push({type:"text",value:` -`}),n}function Ng(e){let t=0,n=e.charCodeAt(t);for(;n===9||n===32;)t++,n=e.charCodeAt(t);return e.slice(t)}function Tg(e,t){const n=PT(e,t),r=n.one(e,void 0),i=wT(n),s=Array.isArray(r)?{type:"root",children:r}:r||{type:"root",children:[]};return i&&s.children.push({type:"text",value:` -`},i),s}function LT(e,t){return e&&"run"in e?async function(n,r){const i=Tg(n,{file:r,...t});await e.run(i,r)}:function(n,r){return Tg(n,{file:r,...e||t})}}function Ag(e){if(e)throw e}var Ua=Object.prototype.hasOwnProperty,Jv=Object.prototype.toString,Pg=Object.defineProperty,Mg=Object.getOwnPropertyDescriptor,Dg=function(t){return typeof Array.isArray=="function"?Array.isArray(t):Jv.call(t)==="[object Array]"},Ig=function(t){if(!t||Jv.call(t)!=="[object Object]")return!1;var n=Ua.call(t,"constructor"),r=t.constructor&&t.constructor.prototype&&Ua.call(t.constructor.prototype,"isPrototypeOf");if(t.constructor&&!n&&!r)return!1;var i;for(i in t);return typeof i>"u"||Ua.call(t,i)},jg=function(t,n){Pg&&n.name==="__proto__"?Pg(t,n.name,{enumerable:!0,configurable:!0,value:n.newValue,writable:!0}):t[n.name]=n.newValue},Lg=function(t,n){if(n==="__proto__")if(Ua.call(t,n)){if(Mg)return Mg(t,n).value}else return;return t[n]},RT=function e(){var t,n,r,i,s,o,a=arguments[0],l=1,u=arguments.length,c=!1;for(typeof a=="boolean"&&(c=a,a=arguments[1]||{},l=2),(a==null||typeof a!="object"&&typeof a!="function")&&(a={});lo.length;let l;a&&o.push(i);try{l=e.apply(this,o)}catch(u){const c=u;if(a&&n)throw c;return i(c)}a||(l&&l.then&&typeof l.then=="function"?l.then(s,i):l instanceof Error?i(l):s(l))}function i(o,...a){n||(n=!0,t(o,...a))}function s(o){i(null,o)}}const gn={basename:OT,dirname:VT,extname:$T,join:BT,sep:"/"};function OT(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Bo(e);let n=0,r=-1,i=e.length,s;if(t===void 0||t.length===0||t.length>e.length){for(;i--;)if(e.codePointAt(i)===47){if(s){n=i+1;break}}else r<0&&(s=!0,r=i+1);return r<0?"":e.slice(n,r)}if(t===e)return"";let o=-1,a=t.length-1;for(;i--;)if(e.codePointAt(i)===47){if(s){n=i+1;break}}else o<0&&(s=!0,o=i+1),a>-1&&(e.codePointAt(i)===t.codePointAt(a--)?a<0&&(r=i):(a=-1,r=o));return n===r?r=o:r<0&&(r=e.length),e.slice(n,r)}function VT(e){if(Bo(e),e.length===0)return".";let t=-1,n=e.length,r;for(;--n;)if(e.codePointAt(n)===47){if(r){t=n;break}}else r||(r=!0);return t<0?e.codePointAt(0)===47?"/":".":t===1&&e.codePointAt(0)===47?"//":e.slice(0,t)}function $T(e){Bo(e);let t=e.length,n=-1,r=0,i=-1,s=0,o;for(;t--;){const a=e.codePointAt(t);if(a===47){if(o){r=t+1;break}continue}n<0&&(o=!0,n=t+1),a===46?i<0?i=t:s!==1&&(s=1):i>-1&&(s=-1)}return i<0||n<0||s===0||s===1&&i===n-1&&i===r+1?"":e.slice(i,n)}function BT(...e){let t=-1,n;for(;++t0&&e.codePointAt(e.length-1)===47&&(n+="/"),t?"/"+n:n}function UT(e,t){let n="",r=0,i=-1,s=0,o=-1,a,l;for(;++o<=e.length;){if(o2){if(l=n.lastIndexOf("/"),l!==n.length-1){l<0?(n="",r=0):(n=n.slice(0,l),r=n.length-1-n.lastIndexOf("/")),i=o,s=0;continue}}else if(n.length>0){n="",r=0,i=o,s=0;continue}}t&&(n=n.length>0?n+"/..":"..",r=2)}else n.length>0?n+="/"+e.slice(i+1,o):n=e.slice(i+1,o),r=o-i-1;i=o,s=0}else a===46&&s>-1?s++:s=-1}return n}function Bo(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const WT={cwd:YT};function YT(){return"/"}function Jf(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function qT(e){if(typeof e=="string")e=new URL(e);else if(!Jf(e)){const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code="ERR_INVALID_ARG_TYPE",t}if(e.protocol!=="file:"){const t=new TypeError("The URL must be of scheme file");throw t.code="ERR_INVALID_URL_SCHEME",t}return KT(e)}function KT(e){if(e.hostname!==""){const r=new TypeError('File URL host must be "localhost" or empty on darwin');throw r.code="ERR_INVALID_FILE_URL_HOST",r}const t=e.pathname;let n=-1;for(;++n0){let[h,...m]=c;const g=r[d][1];Zf(g)&&Zf(h)&&(h=dc(!0,g,h)),r[d]=[u,h,...m]}}}}const ZT=new Vh().freeze();function gc(e,t){if(typeof t!="function")throw new TypeError("Cannot `"+e+"` without `parser`")}function yc(e,t){if(typeof t!="function")throw new TypeError("Cannot `"+e+"` without `compiler`")}function xc(e,t){if(t)throw new Error("Cannot call `"+e+"` on a frozen processor.\nCreate a new processor first, by calling it: use `processor()` instead of `processor`.")}function zg(e){if(!Zf(e)||typeof e.type!="string")throw new TypeError("Expected node, got `"+e+"`")}function Fg(e,t,n){if(!n)throw new Error("`"+e+"` finished async. Use `"+t+"` instead")}function ma(e){return JT(e)?e:new ew(e)}function JT(e){return!!(e&&typeof e=="object"&&"message"in e&&"messages"in e)}function e4(e){return typeof e=="string"||t4(e)}function t4(e){return!!(e&&typeof e=="object"&&"byteLength"in e&&"byteOffset"in e)}const n4="https://github.com/remarkjs/react-markdown/blob/main/changelog.md",Og=[],Vg={allowDangerousHtml:!0},r4=/^(https?|ircs?|mailto|xmpp)$/i,i4=[{from:"astPlugins",id:"remove-buggy-html-in-markdown-parser"},{from:"allowDangerousHtml",id:"remove-buggy-html-in-markdown-parser"},{from:"allowNode",id:"replace-allownode-allowedtypes-and-disallowedtypes",to:"allowElement"},{from:"allowedTypes",id:"replace-allownode-allowedtypes-and-disallowedtypes",to:"allowedElements"},{from:"disallowedTypes",id:"replace-allownode-allowedtypes-and-disallowedtypes",to:"disallowedElements"},{from:"escapeHtml",id:"remove-buggy-html-in-markdown-parser"},{from:"includeElementIndex",id:"#remove-includeelementindex"},{from:"includeNodeIndex",id:"change-includenodeindex-to-includeelementindex"},{from:"linkTarget",id:"remove-linktarget"},{from:"plugins",id:"change-plugins-to-remarkplugins",to:"remarkPlugins"},{from:"rawSourcePos",id:"#remove-rawsourcepos"},{from:"renderers",id:"change-renderers-to-components",to:"components"},{from:"source",id:"change-source-to-children",to:"children"},{from:"sourcePos",id:"#remove-sourcepos"},{from:"transformImageUri",id:"#add-urltransform",to:"urlTransform"},{from:"transformLinkUri",id:"#add-urltransform",to:"urlTransform"}];function $g(e){const t=s4(e),n=o4(e);return a4(t.runSync(t.parse(n),n),e)}function s4(e){const t=e.rehypePlugins||Og,n=e.remarkPlugins||Og,r=e.remarkRehypeOptions?{...e.remarkRehypeOptions,...Vg}:Vg;return ZT().use($N).use(n).use(LT,r).use(t)}function o4(e){const t=e.children||"",n=new ew;return typeof t=="string"&&(n.value=t),n}function a4(e,t){const n=t.allowedElements,r=t.allowElement,i=t.components,s=t.disallowedElements,o=t.skipHtml,a=t.unwrapDisallowed,l=t.urlTransform||l4;for(const c of i4)Object.hasOwn(t,c.from)&&(""+c.from+(c.to?"use `"+c.to+"` instead":"remove it")+n4+c.id,void 0);return t.className&&(e={type:"element",tagName:"div",properties:{className:t.className},children:e.type==="root"?e.children:[e]}),Oh(e,u),_5(e,{Fragment:y.Fragment,components:i,ignoreInvalidStyle:!0,jsx:y.jsx,jsxs:y.jsxs,passKeys:!0,passNode:!0});function u(c,f,d){if(c.type==="raw"&&d&&typeof f=="number")return o?d.children.splice(f,1):d.children[f]={type:"text",value:c.value},f;if(c.type==="element"){let h;for(h in uc)if(Object.hasOwn(uc,h)&&Object.hasOwn(c.properties,h)){const m=c.properties[h],g=uc[h];(g===null||g.includes(c.tagName))&&(c.properties[h]=l(String(m||""),h,c))}}if(c.type==="element"){let h=n?!n.includes(c.tagName):s?s.includes(c.tagName):!1;if(!h&&r&&typeof f=="number"&&(h=!r(c,f,d)),h&&d&&typeof f=="number")return a&&c.children?d.children.splice(f,1,...c.children):d.children.splice(f,1),f}}}function l4(e){const t=e.indexOf(":"),n=e.indexOf("?"),r=e.indexOf("#"),i=e.indexOf("/");return t===-1||i!==-1&&t>i||n!==-1&&t>n||r!==-1&&t>r||r4.test(e.slice(0,t))?e:""}function Bg(e,t){const n=String(e);if(typeof t!="string")throw new TypeError("Expected character");let r=0,i=n.indexOf(t);for(;i!==-1;)r++,i=n.indexOf(t,i+t.length);return r}function u4(e){if(typeof e!="string")throw new TypeError("Expected a string");return e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&").replace(/-/g,"\\x2d")}function c4(e,t,n){const i=du((n||{}).ignore||[]),s=f4(t);let o=-1;for(;++o0?{type:"text",value:S}:void 0),S===!1?d.lastIndex=k+1:(m!==k&&x.push({type:"text",value:u.value.slice(m,k)}),Array.isArray(S)?x.push(...S):S&&x.push(S),m=k+v[0].length,p=!0),!d.global)break;v=d.exec(u.value)}return p?(m?\]}]+$/.exec(e);if(!t)return[e,void 0];e=e.slice(0,t.index);let n=t[0],r=n.indexOf(")");const i=Bg(e,"(");let s=Bg(e,")");for(;r!==-1&&i>s;)e+=n.slice(0,r+1),n=n.slice(r+1),r=n.indexOf(")"),s++;return[e,n]}function tw(e,t){const n=e.input.charCodeAt(e.index-1);return(e.index===0||ni(n)||uu(n))&&(!t||n!==47)}nw.peek=j4;function E4(){this.buffer()}function N4(e){this.enter({type:"footnoteReference",identifier:"",label:""},e)}function T4(){this.buffer()}function A4(e){this.enter({type:"footnoteDefinition",identifier:"",label:"",children:[]},e)}function P4(e){const t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=ln(this.sliceSerialize(e)).toLowerCase(),n.label=t}function M4(e){this.exit(e)}function D4(e){const t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=ln(this.sliceSerialize(e)).toLowerCase(),n.label=t}function I4(e){this.exit(e)}function j4(){return"["}function nw(e,t,n,r){const i=n.createTracker(r);let s=i.move("[^");const o=n.enter("footnoteReference"),a=n.enter("reference");return s+=i.move(n.safe(n.associationId(e),{after:"]",before:s})),a(),o(),s+=i.move("]"),s}function L4(){return{enter:{gfmFootnoteCallString:E4,gfmFootnoteCall:N4,gfmFootnoteDefinitionLabelString:T4,gfmFootnoteDefinition:A4},exit:{gfmFootnoteCallString:P4,gfmFootnoteCall:M4,gfmFootnoteDefinitionLabelString:D4,gfmFootnoteDefinition:I4}}}function R4(e){let t=!1;return e&&e.firstLineBlank&&(t=!0),{handlers:{footnoteDefinition:n,footnoteReference:nw},unsafe:[{character:"[",inConstruct:["label","phrasing","reference"]}]};function n(r,i,s,o){const a=s.createTracker(o);let l=a.move("[^");const u=s.enter("footnoteDefinition"),c=s.enter("label");return l+=a.move(s.safe(s.associationId(r),{before:l,after:"]"})),c(),l+=a.move("]:"),r.children&&r.children.length>0&&(a.shift(4),l+=a.move((t?` -`:" ")+s.indentLines(s.containerFlow(r,a.current()),t?rw:z4))),u(),l}}function z4(e,t,n){return t===0?e:rw(e,t,n)}function rw(e,t,n){return(n?"":" ")+e}const F4=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];iw.peek=H4;function O4(){return{canContainEols:["delete"],enter:{strikethrough:$4},exit:{strikethrough:B4}}}function V4(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:F4}],handlers:{delete:iw}}}function $4(e){this.enter({type:"delete",children:[]},e)}function B4(e){this.exit(e)}function iw(e,t,n,r){const i=n.createTracker(r),s=n.enter("strikethrough");let o=i.move("~~");return o+=n.containerPhrasing(e,{...i.current(),before:o,after:"~"}),o+=i.move("~~"),s(),o}function H4(){return"~"}function U4(e){return e.length}function W4(e,t){const n=t||{},r=(n.align||[]).concat(),i=n.stringLength||U4,s=[],o=[],a=[],l=[];let u=0,c=-1;for(;++cu&&(u=e[c].length);++pl[p])&&(l[p]=v)}g.push(x)}o[c]=g,a[c]=w}let f=-1;if(typeof r=="object"&&"length"in r)for(;++fl[f]&&(l[f]=x),h[f]=x),d[f]=v}o.splice(1,0,d),a.splice(1,0,h),c=-1;const m=[];for(;++c "),s.shift(2);const o=n.indentLines(n.containerFlow(e,s.current()),K4);return i(),o}function K4(e,t,n){return">"+(n?"":" ")+e}function G4(e,t){return Ug(e,t.inConstruct,!0)&&!Ug(e,t.notInConstruct,!1)}function Ug(e,t,n){if(typeof t=="string"&&(t=[t]),!t||t.length===0)return n;let r=-1;for(;++ro&&(o=s):s=1,i=r+t.length,r=n.indexOf(t,i);return o}function Q4(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function Z4(e){const t=e.options.fence||"`";if(t!=="`"&&t!=="~")throw new Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function J4(e,t,n,r){const i=Z4(n),s=e.value||"",o=i==="`"?"GraveAccent":"Tilde";if(Q4(e,n)){const f=n.enter("codeIndented"),d=n.indentLines(s,eA);return f(),d}const a=n.createTracker(r),l=i.repeat(Math.max(X4(s,i)+1,3)),u=n.enter("codeFenced");let c=a.move(l);if(e.lang){const f=n.enter(`codeFencedLang${o}`);c+=a.move(n.safe(e.lang,{before:c,after:" ",encode:["`"],...a.current()})),f()}if(e.lang&&e.meta){const f=n.enter(`codeFencedMeta${o}`);c+=a.move(" "),c+=a.move(n.safe(e.meta,{before:c,after:` -`,encode:["`"],...a.current()})),f()}return c+=a.move(` -`),s&&(c+=a.move(s+` -`)),c+=a.move(l),u(),c}function eA(e,t,n){return(n?"":" ")+e}function $h(e){const t=e.options.quote||'"';if(t!=='"'&&t!=="'")throw new Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function tA(e,t,n,r){const i=$h(n),s=i==='"'?"Quote":"Apostrophe",o=n.enter("definition");let a=n.enter("label");const l=n.createTracker(r);let u=l.move("[");return u+=l.move(n.safe(n.associationId(e),{before:u,after:"]",...l.current()})),u+=l.move("]: "),a(),!e.url||/[\0- \u007F]/.test(e.url)?(a=n.enter("destinationLiteral"),u+=l.move("<"),u+=l.move(n.safe(e.url,{before:u,after:">",...l.current()})),u+=l.move(">")):(a=n.enter("destinationRaw"),u+=l.move(n.safe(e.url,{before:u,after:e.title?" ":` -`,...l.current()}))),a(),e.title&&(a=n.enter(`title${s}`),u+=l.move(" "+i),u+=l.move(n.safe(e.title,{before:u,after:i,...l.current()})),u+=l.move(i),a()),o(),u}function nA(e){const t=e.options.emphasis||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function bo(e){return"&#x"+e.toString(16).toUpperCase()+";"}function Cl(e,t,n){const r=ts(e),i=ts(t);return r===void 0?i===void 0?n==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:i===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:r===1?i===void 0?{inside:!1,outside:!1}:i===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:i===void 0?{inside:!1,outside:!1}:i===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}sw.peek=rA;function sw(e,t,n,r){const i=nA(n),s=n.enter("emphasis"),o=n.createTracker(r),a=o.move(i);let l=o.move(n.containerPhrasing(e,{after:i,before:a,...o.current()}));const u=l.charCodeAt(0),c=Cl(r.before.charCodeAt(r.before.length-1),u,i);c.inside&&(l=bo(u)+l.slice(1));const f=l.charCodeAt(l.length-1),d=Cl(r.after.charCodeAt(0),f,i);d.inside&&(l=l.slice(0,-1)+bo(f));const h=o.move(i);return s(),n.attentionEncodeSurroundingInfo={after:d.outside,before:c.outside},a+l+h}function rA(e,t,n){return n.options.emphasis||"*"}function iA(e,t){let n=!1;return Oh(e,function(r){if("value"in r&&/\r?\n|\r/.test(r.value)||r.type==="break")return n=!0,Xf}),!!((!e.depth||e.depth<3)&&Dh(e)&&(t.options.setext||n))}function sA(e,t,n,r){const i=Math.max(Math.min(6,e.depth||1),1),s=n.createTracker(r);if(iA(e,n)){const c=n.enter("headingSetext"),f=n.enter("phrasing"),d=n.containerPhrasing(e,{...s.current(),before:` -`,after:` -`});return f(),c(),d+` -`+(i===1?"=":"-").repeat(d.length-(Math.max(d.lastIndexOf("\r"),d.lastIndexOf(` -`))+1))}const o="#".repeat(i),a=n.enter("headingAtx"),l=n.enter("phrasing");s.move(o+" ");let u=n.containerPhrasing(e,{before:"# ",after:` -`,...s.current()});return/^[\t ]/.test(u)&&(u=bo(u.charCodeAt(0))+u.slice(1)),u=u?o+" "+u:o,n.options.closeAtx&&(u+=" "+o),l(),a(),u}ow.peek=oA;function ow(e){return e.value||""}function oA(){return"<"}aw.peek=aA;function aw(e,t,n,r){const i=$h(n),s=i==='"'?"Quote":"Apostrophe",o=n.enter("image");let a=n.enter("label");const l=n.createTracker(r);let u=l.move("![");return u+=l.move(n.safe(e.alt,{before:u,after:"]",...l.current()})),u+=l.move("]("),a(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(a=n.enter("destinationLiteral"),u+=l.move("<"),u+=l.move(n.safe(e.url,{before:u,after:">",...l.current()})),u+=l.move(">")):(a=n.enter("destinationRaw"),u+=l.move(n.safe(e.url,{before:u,after:e.title?" ":")",...l.current()}))),a(),e.title&&(a=n.enter(`title${s}`),u+=l.move(" "+i),u+=l.move(n.safe(e.title,{before:u,after:i,...l.current()})),u+=l.move(i),a()),u+=l.move(")"),o(),u}function aA(){return"!"}lw.peek=lA;function lw(e,t,n,r){const i=e.referenceType,s=n.enter("imageReference");let o=n.enter("label");const a=n.createTracker(r);let l=a.move("![");const u=n.safe(e.alt,{before:l,after:"]",...a.current()});l+=a.move(u+"]["),o();const c=n.stack;n.stack=[],o=n.enter("reference");const f=n.safe(n.associationId(e),{before:l,after:"]",...a.current()});return o(),n.stack=c,s(),i==="full"||!u||u!==f?l+=a.move(f+"]"):i==="shortcut"?l=l.slice(0,-1):l+=a.move("]"),l}function lA(){return"!"}uw.peek=uA;function uw(e,t,n){let r=e.value||"",i="`",s=-1;for(;new RegExp("(^|[^`])"+i+"([^`]|$)").test(r);)i+="`";for(/[^ \r\n]/.test(r)&&(/^[ \r\n]/.test(r)&&/[ \r\n]$/.test(r)||/^`|`$/.test(r))&&(r=" "+r+" ");++s\u007F]/.test(e.url))}fw.peek=cA;function fw(e,t,n,r){const i=$h(n),s=i==='"'?"Quote":"Apostrophe",o=n.createTracker(r);let a,l;if(cw(e,n)){const c=n.stack;n.stack=[],a=n.enter("autolink");let f=o.move("<");return f+=o.move(n.containerPhrasing(e,{before:f,after:">",...o.current()})),f+=o.move(">"),a(),n.stack=c,f}a=n.enter("link"),l=n.enter("label");let u=o.move("[");return u+=o.move(n.containerPhrasing(e,{before:u,after:"](",...o.current()})),u+=o.move("]("),l(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(l=n.enter("destinationLiteral"),u+=o.move("<"),u+=o.move(n.safe(e.url,{before:u,after:">",...o.current()})),u+=o.move(">")):(l=n.enter("destinationRaw"),u+=o.move(n.safe(e.url,{before:u,after:e.title?" ":")",...o.current()}))),l(),e.title&&(l=n.enter(`title${s}`),u+=o.move(" "+i),u+=o.move(n.safe(e.title,{before:u,after:i,...o.current()})),u+=o.move(i),l()),u+=o.move(")"),a(),u}function cA(e,t,n){return cw(e,n)?"<":"["}dw.peek=fA;function dw(e,t,n,r){const i=e.referenceType,s=n.enter("linkReference");let o=n.enter("label");const a=n.createTracker(r);let l=a.move("[");const u=n.containerPhrasing(e,{before:l,after:"]",...a.current()});l+=a.move(u+"]["),o();const c=n.stack;n.stack=[],o=n.enter("reference");const f=n.safe(n.associationId(e),{before:l,after:"]",...a.current()});return o(),n.stack=c,s(),i==="full"||!u||u!==f?l+=a.move(f+"]"):i==="shortcut"?l=l.slice(0,-1):l+=a.move("]"),l}function fA(){return"["}function Bh(e){const t=e.options.bullet||"*";if(t!=="*"&&t!=="+"&&t!=="-")throw new Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function dA(e){const t=Bh(e),n=e.options.bulletOther;if(!n)return t==="*"?"-":"*";if(n!=="*"&&n!=="+"&&n!=="-")throw new Error("Cannot serialize items with `"+n+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(n===t)throw new Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+n+"`) to be different");return n}function hA(e){const t=e.options.bulletOrdered||".";if(t!=="."&&t!==")")throw new Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function hw(e){const t=e.options.rule||"*";if(t!=="*"&&t!=="-"&&t!=="_")throw new Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function pA(e,t,n,r){const i=n.enter("list"),s=n.bulletCurrent;let o=e.ordered?hA(n):Bh(n);const a=e.ordered?o==="."?")":".":dA(n);let l=t&&n.bulletLastUsed?o===n.bulletLastUsed:!1;if(!e.ordered){const c=e.children?e.children[0]:void 0;if((o==="*"||o==="-")&&c&&(!c.children||!c.children[0])&&n.stack[n.stack.length-1]==="list"&&n.stack[n.stack.length-2]==="listItem"&&n.stack[n.stack.length-3]==="list"&&n.stack[n.stack.length-4]==="listItem"&&n.indexStack[n.indexStack.length-1]===0&&n.indexStack[n.indexStack.length-2]===0&&n.indexStack[n.indexStack.length-3]===0&&(l=!0),hw(n)===o&&c){let f=-1;for(;++f-1?t.start:1)+(n.options.incrementListMarker===!1?0:t.children.indexOf(e))+s);let o=s.length+1;(i==="tab"||i==="mixed"&&(t&&t.type==="list"&&t.spread||e.spread))&&(o=Math.ceil(o/4)*4);const a=n.createTracker(r);a.move(s+" ".repeat(o-s.length)),a.shift(o);const l=n.enter("listItem"),u=n.indentLines(n.containerFlow(e,a.current()),c);return l(),u;function c(f,d,h){return d?(h?"":" ".repeat(o))+f:(h?s:s+" ".repeat(o-s.length))+f}}function yA(e,t,n,r){const i=n.enter("paragraph"),s=n.enter("phrasing"),o=n.containerPhrasing(e,r);return s(),i(),o}const xA=du(["break","delete","emphasis","footnote","footnoteReference","image","imageReference","inlineCode","inlineMath","link","linkReference","mdxJsxTextElement","mdxTextExpression","strong","text","textDirective"]);function vA(e,t,n,r){return(e.children.some(function(o){return xA(o)})?n.containerPhrasing:n.containerFlow).call(n,e,r)}function wA(e){const t=e.options.strong||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize strong with `"+t+"` for `options.strong`, expected `*`, or `_`");return t}pw.peek=kA;function pw(e,t,n,r){const i=wA(n),s=n.enter("strong"),o=n.createTracker(r),a=o.move(i+i);let l=o.move(n.containerPhrasing(e,{after:i,before:a,...o.current()}));const u=l.charCodeAt(0),c=Cl(r.before.charCodeAt(r.before.length-1),u,i);c.inside&&(l=bo(u)+l.slice(1));const f=l.charCodeAt(l.length-1),d=Cl(r.after.charCodeAt(0),f,i);d.inside&&(l=l.slice(0,-1)+bo(f));const h=o.move(i+i);return s(),n.attentionEncodeSurroundingInfo={after:d.outside,before:c.outside},a+l+h}function kA(e,t,n){return n.options.strong||"*"}function bA(e,t,n,r){return n.safe(e.value,r)}function SA(e){const t=e.options.ruleRepetition||3;if(t<3)throw new Error("Cannot serialize rules with repetition `"+t+"` for `options.ruleRepetition`, expected `3` or more");return t}function _A(e,t,n){const r=(hw(n)+(n.options.ruleSpaces?" ":"")).repeat(SA(n));return n.options.ruleSpaces?r.slice(0,-1):r}const mw={blockquote:q4,break:Wg,code:J4,definition:tA,emphasis:sw,hardBreak:Wg,heading:sA,html:ow,image:aw,imageReference:lw,inlineCode:uw,link:fw,linkReference:dw,list:pA,listItem:gA,paragraph:yA,root:vA,strong:pw,text:bA,thematicBreak:_A};function CA(){return{enter:{table:EA,tableData:Yg,tableHeader:Yg,tableRow:TA},exit:{codeText:AA,table:NA,tableData:bc,tableHeader:bc,tableRow:bc}}}function EA(e){const t=e._align;this.enter({type:"table",align:t.map(function(n){return n==="none"?null:n}),children:[]},e),this.data.inTable=!0}function NA(e){this.exit(e),this.data.inTable=void 0}function TA(e){this.enter({type:"tableRow",children:[]},e)}function bc(e){this.exit(e)}function Yg(e){this.enter({type:"tableCell",children:[]},e)}function AA(e){let t=this.resume();this.data.inTable&&(t=t.replace(/\\([\\|])/g,PA));const n=this.stack[this.stack.length-1];n.type,n.value=t,this.exit(e)}function PA(e,t){return t==="|"?t:e}function MA(e){const t=e||{},n=t.tableCellPadding,r=t.tablePipeAlign,i=t.stringLength,s=n?" ":"|";return{unsafe:[{character:"\r",inConstruct:"tableCell"},{character:` -`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:d,table:o,tableCell:l,tableRow:a}};function o(h,m,g,w){return u(c(h,g,w),h.align)}function a(h,m,g,w){const p=f(h,g,w),x=u([p]);return x.slice(0,x.indexOf(` -`))}function l(h,m,g,w){const p=g.enter("tableCell"),x=g.enter("phrasing"),v=g.containerPhrasing(h,{...w,before:s,after:s});return x(),p(),v}function u(h,m){return W4(h,{align:m,alignDelimiters:r,padding:n,stringLength:i})}function c(h,m,g){const w=h.children;let p=-1;const x=[],v=m.enter("table");for(;++p0&&!n&&(e[e.length-1][1]._gfmAutolinkLiteralWalkedInto=!0),n}const GA={tokenize:rP,partial:!0};function XA(){return{document:{91:{name:"gfmFootnoteDefinition",tokenize:eP,continuation:{tokenize:tP},exit:nP}},text:{91:{name:"gfmFootnoteCall",tokenize:JA},93:{name:"gfmPotentialFootnoteCall",add:"after",tokenize:QA,resolveTo:ZA}}}}function QA(e,t,n){const r=this;let i=r.events.length;const s=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]);let o;for(;i--;){const l=r.events[i][1];if(l.type==="labelImage"){o=l;break}if(l.type==="gfmFootnoteCall"||l.type==="labelLink"||l.type==="label"||l.type==="image"||l.type==="link")break}return a;function a(l){if(!o||!o._balanced)return n(l);const u=ln(r.sliceSerialize({start:o.end,end:r.now()}));return u.codePointAt(0)!==94||!s.includes(u.slice(1))?n(l):(e.enter("gfmFootnoteCallLabelMarker"),e.consume(l),e.exit("gfmFootnoteCallLabelMarker"),t(l))}}function ZA(e,t){let n=e.length;for(;n--;)if(e[n][1].type==="labelImage"&&e[n][0]==="enter"){e[n][1];break}e[n+1][1].type="data",e[n+3][1].type="gfmFootnoteCallLabelMarker";const r={type:"gfmFootnoteCall",start:Object.assign({},e[n+3][1].start),end:Object.assign({},e[e.length-1][1].end)},i={type:"gfmFootnoteCallMarker",start:Object.assign({},e[n+3][1].end),end:Object.assign({},e[n+3][1].end)};i.end.column++,i.end.offset++,i.end._bufferIndex++;const s={type:"gfmFootnoteCallString",start:Object.assign({},i.end),end:Object.assign({},e[e.length-1][1].start)},o={type:"chunkString",contentType:"string",start:Object.assign({},s.start),end:Object.assign({},s.end)},a=[e[n+1],e[n+2],["enter",r,t],e[n+3],e[n+4],["enter",i,t],["exit",i,t],["enter",s,t],["enter",o,t],["exit",o,t],["exit",s,t],e[e.length-2],e[e.length-1],["exit",r,t]];return e.splice(n,e.length-n+1,...a),e}function JA(e,t,n){const r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]);let s=0,o;return a;function a(f){return e.enter("gfmFootnoteCall"),e.enter("gfmFootnoteCallLabelMarker"),e.consume(f),e.exit("gfmFootnoteCallLabelMarker"),l}function l(f){return f!==94?n(f):(e.enter("gfmFootnoteCallMarker"),e.consume(f),e.exit("gfmFootnoteCallMarker"),e.enter("gfmFootnoteCallString"),e.enter("chunkString").contentType="string",u)}function u(f){if(s>999||f===93&&!o||f===null||f===91||xe(f))return n(f);if(f===93){e.exit("chunkString");const d=e.exit("gfmFootnoteCallString");return i.includes(ln(r.sliceSerialize(d)))?(e.enter("gfmFootnoteCallLabelMarker"),e.consume(f),e.exit("gfmFootnoteCallLabelMarker"),e.exit("gfmFootnoteCall"),t):n(f)}return xe(f)||(o=!0),s++,e.consume(f),f===92?c:u}function c(f){return f===91||f===92||f===93?(e.consume(f),s++,u):u(f)}}function eP(e,t,n){const r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]);let s,o=0,a;return l;function l(m){return e.enter("gfmFootnoteDefinition")._container=!0,e.enter("gfmFootnoteDefinitionLabel"),e.enter("gfmFootnoteDefinitionLabelMarker"),e.consume(m),e.exit("gfmFootnoteDefinitionLabelMarker"),u}function u(m){return m===94?(e.enter("gfmFootnoteDefinitionMarker"),e.consume(m),e.exit("gfmFootnoteDefinitionMarker"),e.enter("gfmFootnoteDefinitionLabelString"),e.enter("chunkString").contentType="string",c):n(m)}function c(m){if(o>999||m===93&&!a||m===null||m===91||xe(m))return n(m);if(m===93){e.exit("chunkString");const g=e.exit("gfmFootnoteDefinitionLabelString");return s=ln(r.sliceSerialize(g)),e.enter("gfmFootnoteDefinitionLabelMarker"),e.consume(m),e.exit("gfmFootnoteDefinitionLabelMarker"),e.exit("gfmFootnoteDefinitionLabel"),d}return xe(m)||(a=!0),o++,e.consume(m),m===92?f:c}function f(m){return m===91||m===92||m===93?(e.consume(m),o++,c):c(m)}function d(m){return m===58?(e.enter("definitionMarker"),e.consume(m),e.exit("definitionMarker"),i.includes(s)||i.push(s),fe(e,h,"gfmFootnoteDefinitionWhitespace")):n(m)}function h(m){return t(m)}}function tP(e,t,n){return e.check($o,t,e.attempt(GA,t,n))}function nP(e){e.exit("gfmFootnoteDefinition")}function rP(e,t,n){const r=this;return fe(e,i,"gfmFootnoteDefinitionIndent",5);function i(s){const o=r.events[r.events.length-1];return o&&o[1].type==="gfmFootnoteDefinitionIndent"&&o[2].sliceSerialize(o[1],!0).length===4?t(s):n(s)}}function iP(e){let n=(e||{}).singleTilde;const r={name:"strikethrough",tokenize:s,resolveAll:i};return n==null&&(n=!0),{text:{126:r},insideSpan:{null:[r]},attentionMarkers:{null:[126]}};function i(o,a){let l=-1;for(;++l1?l(m):(o.consume(m),f++,h);if(f<2&&!n)return l(m);const w=o.exit("strikethroughSequenceTemporary"),p=ts(m);return w._open=!p||p===2&&!!g,w._close=!g||g===2&&!!p,a(m)}}}class sP{constructor(){this.map=[]}add(t,n,r){oP(this,t,n,r)}consume(t){if(this.map.sort(function(s,o){return s[0]-o[0]}),this.map.length===0)return;let n=this.map.length;const r=[];for(;n>0;)n-=1,r.push(t.slice(this.map[n][0]+this.map[n][1]),this.map[n][2]),t.length=this.map[n][0];r.push(t.slice()),t.length=0;let i=r.pop();for(;i;){for(const s of i)t.push(s);i=r.pop()}this.map.length=0}}function oP(e,t,n,r){let i=0;if(!(n===0&&r.length===0)){for(;i-1;){const _=r.events[j][1].type;if(_==="lineEnding"||_==="linePrefix")j--;else break}const z=j>-1?r.events[j][1].type:null,H=z==="tableHead"||z==="tableRow"?S:l;return H===S&&r.parser.lazy[r.now().line]?n(C):H(C)}function l(C){return e.enter("tableHead"),e.enter("tableRow"),u(C)}function u(C){return C===124||(o=!0,s+=1),c(C)}function c(C){return C===null?n(C):J(C)?s>1?(s=0,r.interrupt=!0,e.exit("tableRow"),e.enter("lineEnding"),e.consume(C),e.exit("lineEnding"),h):n(C):ue(C)?fe(e,c,"whitespace")(C):(s+=1,o&&(o=!1,i+=1),C===124?(e.enter("tableCellDivider"),e.consume(C),e.exit("tableCellDivider"),o=!0,c):(e.enter("data"),f(C)))}function f(C){return C===null||C===124||xe(C)?(e.exit("data"),c(C)):(e.consume(C),C===92?d:f)}function d(C){return C===92||C===124?(e.consume(C),f):f(C)}function h(C){return r.interrupt=!1,r.parser.lazy[r.now().line]?n(C):(e.enter("tableDelimiterRow"),o=!1,ue(C)?fe(e,m,"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(C):m(C))}function m(C){return C===45||C===58?w(C):C===124?(o=!0,e.enter("tableCellDivider"),e.consume(C),e.exit("tableCellDivider"),g):N(C)}function g(C){return ue(C)?fe(e,w,"whitespace")(C):w(C)}function w(C){return C===58?(s+=1,o=!0,e.enter("tableDelimiterMarker"),e.consume(C),e.exit("tableDelimiterMarker"),p):C===45?(s+=1,p(C)):C===null||J(C)?k(C):N(C)}function p(C){return C===45?(e.enter("tableDelimiterFiller"),x(C)):N(C)}function x(C){return C===45?(e.consume(C),x):C===58?(o=!0,e.exit("tableDelimiterFiller"),e.enter("tableDelimiterMarker"),e.consume(C),e.exit("tableDelimiterMarker"),v):(e.exit("tableDelimiterFiller"),v(C))}function v(C){return ue(C)?fe(e,k,"whitespace")(C):k(C)}function k(C){return C===124?m(C):C===null||J(C)?!o||i!==s?N(C):(e.exit("tableDelimiterRow"),e.exit("tableHead"),t(C)):N(C)}function N(C){return n(C)}function S(C){return e.enter("tableRow"),A(C)}function A(C){return C===124?(e.enter("tableCellDivider"),e.consume(C),e.exit("tableCellDivider"),A):C===null||J(C)?(e.exit("tableRow"),t(C)):ue(C)?fe(e,A,"whitespace")(C):(e.enter("data"),P(C))}function P(C){return C===null||C===124||xe(C)?(e.exit("data"),A(C)):(e.consume(C),C===92?D:P)}function D(C){return C===92||C===124?(e.consume(C),P):P(C)}}function cP(e,t){let n=-1,r=!0,i=0,s=[0,0,0,0],o=[0,0,0,0],a=!1,l=0,u,c,f;const d=new sP;for(;++nn[2]+1){const m=n[2]+1,g=n[3]-n[2]-1;e.add(m,g,[])}}e.add(n[3]+1,0,[["exit",f,t]])}return i!==void 0&&(s.end=Object.assign({},gi(t.events,i)),e.add(i,0,[["exit",s,t]]),s=void 0),s}function Kg(e,t,n,r,i){const s=[],o=gi(t.events,n);i&&(i.end=Object.assign({},o),s.push(["exit",i,t])),r.end=Object.assign({},o),s.push(["exit",r,t]),e.add(n+1,0,s)}function gi(e,t){const n=e[t],r=n[0]==="enter"?"start":"end";return n[1][r]}const fP={name:"tasklistCheck",tokenize:hP};function dP(){return{text:{91:fP}}}function hP(e,t,n){const r=this;return i;function i(l){return r.previous!==null||!r._gfmTasklistFirstContentOfListItem?n(l):(e.enter("taskListCheck"),e.enter("taskListCheckMarker"),e.consume(l),e.exit("taskListCheckMarker"),s)}function s(l){return xe(l)?(e.enter("taskListCheckValueUnchecked"),e.consume(l),e.exit("taskListCheckValueUnchecked"),o):l===88||l===120?(e.enter("taskListCheckValueChecked"),e.consume(l),e.exit("taskListCheckValueChecked"),o):n(l)}function o(l){return l===93?(e.enter("taskListCheckMarker"),e.consume(l),e.exit("taskListCheckMarker"),e.exit("taskListCheck"),a):n(l)}function a(l){return J(l)?t(l):ue(l)?e.check({tokenize:pP},t,n)(l):n(l)}}function pP(e,t,n){return fe(e,r,"whitespace");function r(i){return i===null?n(i):t(i)}}function mP(e){return Iv([VA(),XA(),iP(e),lP(),dP()])}const gP={};function Gg(e){const t=this,n=e||gP,r=t.data(),i=r.micromarkExtensions||(r.micromarkExtensions=[]),s=r.fromMarkdownExtensions||(r.fromMarkdownExtensions=[]),o=r.toMarkdownExtensions||(r.toMarkdownExtensions=[]);i.push(mP(n)),s.push(RA()),o.push(zA(n))}const _w=T.createContext({transformPagePoint:e=>e,isStatic:!1,reducedMotion:"never"}),pu=T.createContext({}),mu=T.createContext(null),gu=typeof document<"u",Uh=gu?T.useLayoutEffect:T.useEffect,Cw=T.createContext({strict:!1}),Wh=e=>e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),yP="framerAppearId",Ew="data-"+Wh(yP);function xP(e,t,n,r){const{visualElement:i}=T.useContext(pu),s=T.useContext(Cw),o=T.useContext(mu),a=T.useContext(_w).reducedMotion,l=T.useRef();r=r||s.renderer,!l.current&&r&&(l.current=r(e,{visualState:t,parent:i,props:n,presenceContext:o,blockInitialAnimation:o?o.initial===!1:!1,reducedMotionConfig:a}));const u=l.current;T.useInsertionEffect(()=>{u&&u.update(n,o)});const c=T.useRef(!!(n[Ew]&&!window.HandoffComplete));return Uh(()=>{u&&(u.render(),c.current&&u.animationState&&u.animationState.animateChanges())}),T.useEffect(()=>{u&&(u.updateFeatures(),!c.current&&u.animationState&&u.animationState.animateChanges(),c.current&&(c.current=!1,window.HandoffComplete=!0))}),u}function Ai(e){return e&&typeof e=="object"&&Object.prototype.hasOwnProperty.call(e,"current")}function vP(e,t,n){return T.useCallback(r=>{r&&e.mount&&e.mount(r),t&&(r?t.mount(r):t.unmount()),n&&(typeof n=="function"?n(r):Ai(n)&&(n.current=r))},[t])}function So(e){return typeof e=="string"||Array.isArray(e)}function yu(e){return e!==null&&typeof e=="object"&&typeof e.start=="function"}const Yh=["animate","whileInView","whileFocus","whileHover","whileTap","whileDrag","exit"],qh=["initial",...Yh];function xu(e){return yu(e.animate)||qh.some(t=>So(e[t]))}function Nw(e){return!!(xu(e)||e.variants)}function wP(e,t){if(xu(e)){const{initial:n,animate:r}=e;return{initial:n===!1||So(n)?n:void 0,animate:So(r)?r:void 0}}return e.inherit!==!1?t:{}}function kP(e){const{initial:t,animate:n}=wP(e,T.useContext(pu));return T.useMemo(()=>({initial:t,animate:n}),[Xg(t),Xg(n)])}function Xg(e){return Array.isArray(e)?e.join(" "):e}const Qg={animation:["animate","variants","whileHover","whileTap","exit","whileInView","whileFocus","whileDrag"],exit:["exit"],drag:["drag","dragControls"],focus:["whileFocus"],hover:["whileHover","onHoverStart","onHoverEnd"],tap:["whileTap","onTap","onTapStart","onTapCancel"],pan:["onPan","onPanStart","onPanSessionStart","onPanEnd"],inView:["whileInView","onViewportEnter","onViewportLeave"],layout:["layout","layoutId"]},_o={};for(const e in Qg)_o[e]={isEnabled:t=>Qg[e].some(n=>!!t[n])};function bP(e){for(const t in e)_o[t]={..._o[t],...e[t]}}const Kh=T.createContext({}),Tw=T.createContext({}),SP=Symbol.for("motionComponentSymbol");function _P({preloadedFeatures:e,createVisualElement:t,useRender:n,useVisualState:r,Component:i}){e&&bP(e);function s(a,l){let u;const c={...T.useContext(_w),...a,layoutId:CP(a)},{isStatic:f}=c,d=kP(a),h=r(a,f);if(!f&&gu){d.visualElement=xP(i,h,c,t);const m=T.useContext(Tw),g=T.useContext(Cw).strict;d.visualElement&&(u=d.visualElement.loadFeatures(c,g,e,m))}return T.createElement(pu.Provider,{value:d},u&&d.visualElement?T.createElement(u,{visualElement:d.visualElement,...c}):null,n(i,a,vP(h,d.visualElement,l),h,f,d.visualElement))}const o=T.forwardRef(s);return o[SP]=i,o}function CP({layoutId:e}){const t=T.useContext(Kh).id;return t&&e!==void 0?t+"-"+e:e}function EP(e){function t(r,i={}){return _P(e(r,i))}if(typeof Proxy>"u")return t;const n=new Map;return new Proxy(t,{get:(r,i)=>(n.has(i)||n.set(i,t(i)),n.get(i))})}const NP=["animate","circle","defs","desc","ellipse","g","image","line","filter","marker","mask","metadata","path","pattern","polygon","polyline","rect","stop","switch","symbol","svg","text","tspan","use","view"];function Gh(e){return typeof e!="string"||e.includes("-")?!1:!!(NP.indexOf(e)>-1||/[A-Z]/.test(e))}const El={};function TP(e){Object.assign(El,e)}const Ho=["transformPerspective","x","y","z","translateX","translateY","translateZ","scale","scaleX","scaleY","rotate","rotateX","rotateY","rotateZ","skew","skewX","skewY"],li=new Set(Ho);function Aw(e,{layout:t,layoutId:n}){return li.has(e)||e.startsWith("origin")||(t||n!==void 0)&&(!!El[e]||e==="opacity")}const Ct=e=>!!(e&&e.getVelocity),AP={x:"translateX",y:"translateY",z:"translateZ",transformPerspective:"perspective"},PP=Ho.length;function MP(e,{enableHardwareAcceleration:t=!0,allowTransformNone:n=!0},r,i){let s="";for(let o=0;ot=>typeof t=="string"&&t.startsWith(e),Mw=Pw("--"),td=Pw("var(--"),DP=/var\s*\(\s*--[\w-]+(\s*,\s*(?:(?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)+)?\s*\)/g,IP=(e,t)=>t&&typeof e=="number"?t.transform(e):e,kr=(e,t,n)=>Math.min(Math.max(n,e),t),ui={test:e=>typeof e=="number",parse:parseFloat,transform:e=>e},Zs={...ui,transform:e=>kr(0,1,e)},ya={...ui,default:1},Js=e=>Math.round(e*1e5)/1e5,vu=/(-)?([\d]*\.?[\d])+/g,Dw=/(#[0-9a-f]{3,8}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))/gi,jP=/^(#[0-9a-f]{3,8}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))$/i;function Uo(e){return typeof e=="string"}const Wo=e=>({test:t=>Uo(t)&&t.endsWith(e)&&t.split(" ").length===1,parse:parseFloat,transform:t=>`${t}${e}`}),Qn=Wo("deg"),kn=Wo("%"),te=Wo("px"),LP=Wo("vh"),RP=Wo("vw"),Zg={...kn,parse:e=>kn.parse(e)/100,transform:e=>kn.transform(e*100)},Jg={...ui,transform:Math.round},Iw={borderWidth:te,borderTopWidth:te,borderRightWidth:te,borderBottomWidth:te,borderLeftWidth:te,borderRadius:te,radius:te,borderTopLeftRadius:te,borderTopRightRadius:te,borderBottomRightRadius:te,borderBottomLeftRadius:te,width:te,maxWidth:te,height:te,maxHeight:te,size:te,top:te,right:te,bottom:te,left:te,padding:te,paddingTop:te,paddingRight:te,paddingBottom:te,paddingLeft:te,margin:te,marginTop:te,marginRight:te,marginBottom:te,marginLeft:te,rotate:Qn,rotateX:Qn,rotateY:Qn,rotateZ:Qn,scale:ya,scaleX:ya,scaleY:ya,scaleZ:ya,skew:Qn,skewX:Qn,skewY:Qn,distance:te,translateX:te,translateY:te,translateZ:te,x:te,y:te,z:te,perspective:te,transformPerspective:te,opacity:Zs,originX:Zg,originY:Zg,originZ:te,zIndex:Jg,fillOpacity:Zs,strokeOpacity:Zs,numOctaves:Jg};function Xh(e,t,n,r){const{style:i,vars:s,transform:o,transformOrigin:a}=e;let l=!1,u=!1,c=!0;for(const f in t){const d=t[f];if(Mw(f)){s[f]=d;continue}const h=Iw[f],m=IP(d,h);if(li.has(f)){if(l=!0,o[f]=m,!c)continue;d!==(h.default||0)&&(c=!1)}else f.startsWith("origin")?(u=!0,a[f]=m):i[f]=m}if(t.transform||(l||r?i.transform=MP(e.transform,n,c,r):i.transform&&(i.transform="none")),u){const{originX:f="50%",originY:d="50%",originZ:h=0}=a;i.transformOrigin=`${f} ${d} ${h}`}}const Qh=()=>({style:{},transform:{},transformOrigin:{},vars:{}});function jw(e,t,n){for(const r in t)!Ct(t[r])&&!Aw(r,n)&&(e[r]=t[r])}function zP({transformTemplate:e},t,n){return T.useMemo(()=>{const r=Qh();return Xh(r,t,{enableHardwareAcceleration:!n},e),Object.assign({},r.vars,r.style)},[t])}function FP(e,t,n){const r=e.style||{},i={};return jw(i,r,e),Object.assign(i,zP(e,t,n)),e.transformValues?e.transformValues(i):i}function OP(e,t,n){const r={},i=FP(e,t,n);return e.drag&&e.dragListener!==!1&&(r.draggable=!1,i.userSelect=i.WebkitUserSelect=i.WebkitTouchCallout="none",i.touchAction=e.drag===!0?"none":`pan-${e.drag==="x"?"y":"x"}`),e.tabIndex===void 0&&(e.onTap||e.onTapStart||e.whileTap)&&(r.tabIndex=0),r.style=i,r}const VP=new Set(["animate","exit","variants","initial","style","values","variants","transition","transformTemplate","transformValues","custom","inherit","onBeforeLayoutMeasure","onAnimationStart","onAnimationComplete","onUpdate","onDragStart","onDrag","onDragEnd","onMeasureDragConstraints","onDirectionLock","onDragTransitionEnd","_dragX","_dragY","onHoverStart","onHoverEnd","onViewportEnter","onViewportLeave","globalTapTarget","ignoreStrict","viewport"]);function Nl(e){return e.startsWith("while")||e.startsWith("drag")&&e!=="draggable"||e.startsWith("layout")||e.startsWith("onTap")||e.startsWith("onPan")||e.startsWith("onLayout")||VP.has(e)}let Lw=e=>!Nl(e);function $P(e){e&&(Lw=t=>t.startsWith("on")?!Nl(t):e(t))}try{$P(require("@emotion/is-prop-valid").default)}catch{}function BP(e,t,n){const r={};for(const i in e)i==="values"&&typeof e.values=="object"||(Lw(i)||n===!0&&Nl(i)||!t&&!Nl(i)||e.draggable&&i.startsWith("onDrag"))&&(r[i]=e[i]);return r}function e0(e,t,n){return typeof e=="string"?e:te.transform(t+n*e)}function HP(e,t,n){const r=e0(t,e.x,e.width),i=e0(n,e.y,e.height);return`${r} ${i}`}const UP={offset:"stroke-dashoffset",array:"stroke-dasharray"},WP={offset:"strokeDashoffset",array:"strokeDasharray"};function YP(e,t,n=1,r=0,i=!0){e.pathLength=1;const s=i?UP:WP;e[s.offset]=te.transform(-r);const o=te.transform(t),a=te.transform(n);e[s.array]=`${o} ${a}`}function Zh(e,{attrX:t,attrY:n,attrScale:r,originX:i,originY:s,pathLength:o,pathSpacing:a=1,pathOffset:l=0,...u},c,f,d){if(Xh(e,u,c,d),f){e.style.viewBox&&(e.attrs.viewBox=e.style.viewBox);return}e.attrs=e.style,e.style={};const{attrs:h,style:m,dimensions:g}=e;h.transform&&(g&&(m.transform=h.transform),delete h.transform),g&&(i!==void 0||s!==void 0||m.transform)&&(m.transformOrigin=HP(g,i!==void 0?i:.5,s!==void 0?s:.5)),t!==void 0&&(h.x=t),n!==void 0&&(h.y=n),r!==void 0&&(h.scale=r),o!==void 0&&YP(h,o,a,l,!1)}const Rw=()=>({...Qh(),attrs:{}}),Jh=e=>typeof e=="string"&&e.toLowerCase()==="svg";function qP(e,t,n,r){const i=T.useMemo(()=>{const s=Rw();return Zh(s,t,{enableHardwareAcceleration:!1},Jh(r),e.transformTemplate),{...s.attrs,style:{...s.style}}},[t]);if(e.style){const s={};jw(s,e.style,e),i.style={...s,...i.style}}return i}function KP(e=!1){return(n,r,i,{latestValues:s},o)=>{const l=(Gh(n)?qP:OP)(r,s,o,n),c={...BP(r,typeof n=="string",e),...l,ref:i},{children:f}=r,d=T.useMemo(()=>Ct(f)?f.get():f,[f]);return T.createElement(n,{...c,children:d})}}function zw(e,{style:t,vars:n},r,i){Object.assign(e.style,t,i&&i.getProjectionStyles(r));for(const s in n)e.style.setProperty(s,n[s])}const Fw=new Set(["baseFrequency","diffuseConstant","kernelMatrix","kernelUnitLength","keySplines","keyTimes","limitingConeAngle","markerHeight","markerWidth","numOctaves","targetX","targetY","surfaceScale","specularConstant","specularExponent","stdDeviation","tableValues","viewBox","gradientTransform","pathLength","startOffset","textLength","lengthAdjust"]);function Ow(e,t,n,r){zw(e,t,void 0,r);for(const i in t.attrs)e.setAttribute(Fw.has(i)?i:Wh(i),t.attrs[i])}function ep(e,t){const{style:n}=e,r={};for(const i in n)(Ct(n[i])||t.style&&Ct(t.style[i])||Aw(i,e))&&(r[i]=n[i]);return r}function Vw(e,t){const n=ep(e,t);for(const r in e)if(Ct(e[r])||Ct(t[r])){const i=Ho.indexOf(r)!==-1?"attr"+r.charAt(0).toUpperCase()+r.substring(1):r;n[i]=e[r]}return n}function tp(e,t,n,r={},i={}){return typeof t=="function"&&(t=t(n!==void 0?n:e.custom,r,i)),typeof t=="string"&&(t=e.variants&&e.variants[t]),typeof t=="function"&&(t=t(n!==void 0?n:e.custom,r,i)),t}function $w(e){const t=T.useRef(null);return t.current===null&&(t.current=e()),t.current}const Tl=e=>Array.isArray(e),GP=e=>!!(e&&typeof e=="object"&&e.mix&&e.toValue),XP=e=>Tl(e)?e[e.length-1]||0:e;function Wa(e){const t=Ct(e)?e.get():e;return GP(t)?t.toValue():t}function QP({scrapeMotionValuesFromProps:e,createRenderState:t,onMount:n},r,i,s){const o={latestValues:ZP(r,i,s,e),renderState:t()};return n&&(o.mount=a=>n(r,a,o)),o}const Bw=e=>(t,n)=>{const r=T.useContext(pu),i=T.useContext(mu),s=()=>QP(e,t,r,i);return n?s():$w(s)};function ZP(e,t,n,r){const i={},s=r(e,{});for(const d in s)i[d]=Wa(s[d]);let{initial:o,animate:a}=e;const l=xu(e),u=Nw(e);t&&u&&!l&&e.inherit!==!1&&(o===void 0&&(o=t.initial),a===void 0&&(a=t.animate));let c=n?n.initial===!1:!1;c=c||o===!1;const f=c?a:o;return f&&typeof f!="boolean"&&!yu(f)&&(Array.isArray(f)?f:[f]).forEach(h=>{const m=tp(e,h);if(!m)return;const{transitionEnd:g,transition:w,...p}=m;for(const x in p){let v=p[x];if(Array.isArray(v)){const k=c?v.length-1:0;v=v[k]}v!==null&&(i[x]=v)}for(const x in g)i[x]=g[x]}),i}const Fe=e=>e;class t0{constructor(){this.order=[],this.scheduled=new Set}add(t){if(!this.scheduled.has(t))return this.scheduled.add(t),this.order.push(t),!0}remove(t){const n=this.order.indexOf(t);n!==-1&&(this.order.splice(n,1),this.scheduled.delete(t))}clear(){this.order.length=0,this.scheduled.clear()}}function JP(e){let t=new t0,n=new t0,r=0,i=!1,s=!1;const o=new WeakSet,a={schedule:(l,u=!1,c=!1)=>{const f=c&&i,d=f?t:n;return u&&o.add(l),d.add(l)&&f&&i&&(r=t.order.length),l},cancel:l=>{n.remove(l),o.delete(l)},process:l=>{if(i){s=!0;return}if(i=!0,[t,n]=[n,t],n.clear(),r=t.order.length,r)for(let u=0;u(f[d]=JP(()=>n=!0),f),{}),o=f=>s[f].process(i),a=()=>{const f=performance.now();n=!1,i.delta=r?1e3/60:Math.max(Math.min(f-i.timestamp,eM),1),i.timestamp=f,i.isProcessing=!0,xa.forEach(o),i.isProcessing=!1,n&&t&&(r=!1,e(a))},l=()=>{n=!0,r=!0,i.isProcessing||e(a)};return{schedule:xa.reduce((f,d)=>{const h=s[d];return f[d]=(m,g=!1,w=!1)=>(n||l(),h.schedule(m,g,w)),f},{}),cancel:f=>xa.forEach(d=>s[d].cancel(f)),state:i,steps:s}}const{schedule:be,cancel:Vn,state:rt,steps:Sc}=tM(typeof requestAnimationFrame<"u"?requestAnimationFrame:Fe,!0),nM={useVisualState:Bw({scrapeMotionValuesFromProps:Vw,createRenderState:Rw,onMount:(e,t,{renderState:n,latestValues:r})=>{be.read(()=>{try{n.dimensions=typeof t.getBBox=="function"?t.getBBox():t.getBoundingClientRect()}catch{n.dimensions={x:0,y:0,width:0,height:0}}}),be.render(()=>{Zh(n,r,{enableHardwareAcceleration:!1},Jh(t.tagName),e.transformTemplate),Ow(t,n)})}})},rM={useVisualState:Bw({scrapeMotionValuesFromProps:ep,createRenderState:Qh})};function iM(e,{forwardMotionProps:t=!1},n,r){return{...Gh(e)?nM:rM,preloadedFeatures:n,useRender:KP(t),createVisualElement:r,Component:e}}function Mn(e,t,n,r={passive:!0}){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n)}const Hw=e=>e.pointerType==="mouse"?typeof e.button!="number"||e.button<=0:e.isPrimary!==!1;function wu(e,t="page"){return{point:{x:e[t+"X"],y:e[t+"Y"]}}}const sM=e=>t=>Hw(t)&&e(t,wu(t));function jn(e,t,n,r){return Mn(e,t,sM(n),r)}const oM=(e,t)=>n=>t(e(n)),gr=(...e)=>e.reduce(oM);function Uw(e){let t=null;return()=>{const n=()=>{t=null};return t===null?(t=e,n):!1}}const n0=Uw("dragHorizontal"),r0=Uw("dragVertical");function Ww(e){let t=!1;if(e==="y")t=r0();else if(e==="x")t=n0();else{const n=n0(),r=r0();n&&r?t=()=>{n(),r()}:(n&&n(),r&&r())}return t}function Yw(){const e=Ww(!0);return e?(e(),!1):!0}class Nr{constructor(t){this.isMounted=!1,this.node=t}update(){}}function i0(e,t){const n="pointer"+(t?"enter":"leave"),r="onHover"+(t?"Start":"End"),i=(s,o)=>{if(s.pointerType==="touch"||Yw())return;const a=e.getProps();e.animationState&&a.whileHover&&e.animationState.setActive("whileHover",t),a[r]&&be.update(()=>a[r](s,o))};return jn(e.current,n,i,{passive:!e.getProps()[r]})}class aM extends Nr{mount(){this.unmount=gr(i0(this.node,!0),i0(this.node,!1))}unmount(){}}class lM extends Nr{constructor(){super(...arguments),this.isActive=!1}onFocus(){let t=!1;try{t=this.node.current.matches(":focus-visible")}catch{t=!0}!t||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!0),this.isActive=!0)}onBlur(){!this.isActive||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!1),this.isActive=!1)}mount(){this.unmount=gr(Mn(this.node.current,"focus",()=>this.onFocus()),Mn(this.node.current,"blur",()=>this.onBlur()))}unmount(){}}const qw=(e,t)=>t?e===t?!0:qw(e,t.parentElement):!1;function _c(e,t){if(!t)return;const n=new PointerEvent("pointer"+e);t(n,wu(n))}class uM extends Nr{constructor(){super(...arguments),this.removeStartListeners=Fe,this.removeEndListeners=Fe,this.removeAccessibleListeners=Fe,this.startPointerPress=(t,n)=>{if(this.isPressing)return;this.removeEndListeners();const r=this.node.getProps(),s=jn(window,"pointerup",(a,l)=>{if(!this.checkPressEnd())return;const{onTap:u,onTapCancel:c,globalTapTarget:f}=this.node.getProps();be.update(()=>{!f&&!qw(this.node.current,a.target)?c&&c(a,l):u&&u(a,l)})},{passive:!(r.onTap||r.onPointerUp)}),o=jn(window,"pointercancel",(a,l)=>this.cancelPress(a,l),{passive:!(r.onTapCancel||r.onPointerCancel)});this.removeEndListeners=gr(s,o),this.startPress(t,n)},this.startAccessiblePress=()=>{const t=s=>{if(s.key!=="Enter"||this.isPressing)return;const o=a=>{a.key!=="Enter"||!this.checkPressEnd()||_c("up",(l,u)=>{const{onTap:c}=this.node.getProps();c&&be.update(()=>c(l,u))})};this.removeEndListeners(),this.removeEndListeners=Mn(this.node.current,"keyup",o),_c("down",(a,l)=>{this.startPress(a,l)})},n=Mn(this.node.current,"keydown",t),r=()=>{this.isPressing&&_c("cancel",(s,o)=>this.cancelPress(s,o))},i=Mn(this.node.current,"blur",r);this.removeAccessibleListeners=gr(n,i)}}startPress(t,n){this.isPressing=!0;const{onTapStart:r,whileTap:i}=this.node.getProps();i&&this.node.animationState&&this.node.animationState.setActive("whileTap",!0),r&&be.update(()=>r(t,n))}checkPressEnd(){return this.removeEndListeners(),this.isPressing=!1,this.node.getProps().whileTap&&this.node.animationState&&this.node.animationState.setActive("whileTap",!1),!Yw()}cancelPress(t,n){if(!this.checkPressEnd())return;const{onTapCancel:r}=this.node.getProps();r&&be.update(()=>r(t,n))}mount(){const t=this.node.getProps(),n=jn(t.globalTapTarget?window:this.node.current,"pointerdown",this.startPointerPress,{passive:!(t.onTapStart||t.onPointerStart)}),r=Mn(this.node.current,"focus",this.startAccessiblePress);this.removeStartListeners=gr(n,r)}unmount(){this.removeStartListeners(),this.removeEndListeners(),this.removeAccessibleListeners()}}const nd=new WeakMap,Cc=new WeakMap,cM=e=>{const t=nd.get(e.target);t&&t(e)},fM=e=>{e.forEach(cM)};function dM({root:e,...t}){const n=e||document;Cc.has(n)||Cc.set(n,{});const r=Cc.get(n),i=JSON.stringify(t);return r[i]||(r[i]=new IntersectionObserver(fM,{root:e,...t})),r[i]}function hM(e,t,n){const r=dM(t);return nd.set(e,n),r.observe(e),()=>{nd.delete(e),r.unobserve(e)}}const pM={some:0,all:1};class mM extends Nr{constructor(){super(...arguments),this.hasEnteredView=!1,this.isInView=!1}startObserver(){this.unmount();const{viewport:t={}}=this.node.getProps(),{root:n,margin:r,amount:i="some",once:s}=t,o={root:n?n.current:void 0,rootMargin:r,threshold:typeof i=="number"?i:pM[i]},a=l=>{const{isIntersecting:u}=l;if(this.isInView===u||(this.isInView=u,s&&!u&&this.hasEnteredView))return;u&&(this.hasEnteredView=!0),this.node.animationState&&this.node.animationState.setActive("whileInView",u);const{onViewportEnter:c,onViewportLeave:f}=this.node.getProps(),d=u?c:f;d&&d(l)};return hM(this.node.current,o,a)}mount(){this.startObserver()}update(){if(typeof IntersectionObserver>"u")return;const{props:t,prevProps:n}=this.node;["amount","margin","root"].some(gM(t,n))&&this.startObserver()}unmount(){}}function gM({viewport:e={}},{viewport:t={}}={}){return n=>e[n]!==t[n]}const yM={inView:{Feature:mM},tap:{Feature:uM},focus:{Feature:lM},hover:{Feature:aM}};function Kw(e,t){if(!Array.isArray(t))return!1;const n=t.length;if(n!==e.length)return!1;for(let r=0;rt[r]=n.get()),t}function vM(e){const t={};return e.values.forEach((n,r)=>t[r]=n.getVelocity()),t}function ku(e,t,n){const r=e.getProps();return tp(r,t,n!==void 0?n:r.custom,xM(e),vM(e))}let np=Fe;const Gr=e=>e*1e3,Ln=e=>e/1e3,wM={current:!1},Gw=e=>Array.isArray(e)&&typeof e[0]=="number";function Xw(e){return!!(!e||typeof e=="string"&&Qw[e]||Gw(e)||Array.isArray(e)&&e.every(Xw))}const Ls=([e,t,n,r])=>`cubic-bezier(${e}, ${t}, ${n}, ${r})`,Qw={linear:"linear",ease:"ease",easeIn:"ease-in",easeOut:"ease-out",easeInOut:"ease-in-out",circIn:Ls([0,.65,.55,1]),circOut:Ls([.55,0,1,.45]),backIn:Ls([.31,.01,.66,-.59]),backOut:Ls([.33,1.53,.69,.99])};function Zw(e){if(e)return Gw(e)?Ls(e):Array.isArray(e)?e.map(Zw):Qw[e]}function kM(e,t,n,{delay:r=0,duration:i,repeat:s=0,repeatType:o="loop",ease:a,times:l}={}){const u={[t]:n};l&&(u.offset=l);const c=Zw(a);return Array.isArray(c)&&(u.easing=c),e.animate(u,{delay:r,duration:i,easing:Array.isArray(c)?"linear":c,fill:"both",iterations:s+1,direction:o==="reverse"?"alternate":"normal"})}function bM(e,{repeat:t,repeatType:n="loop"}){const r=t&&n!=="loop"&&t%2===1?0:e.length-1;return e[r]}const Jw=(e,t,n)=>(((1-3*n+3*t)*e+(3*n-6*t))*e+3*t)*e,SM=1e-7,_M=12;function CM(e,t,n,r,i){let s,o,a=0;do o=t+(n-t)/2,s=Jw(o,r,i)-e,s>0?n=o:t=o;while(Math.abs(s)>SM&&++a<_M);return o}function Yo(e,t,n,r){if(e===t&&n===r)return Fe;const i=s=>CM(s,0,1,e,n);return s=>s===0||s===1?s:Jw(i(s),t,r)}const EM=Yo(.42,0,1,1),NM=Yo(0,0,.58,1),ek=Yo(.42,0,.58,1),TM=e=>Array.isArray(e)&&typeof e[0]!="number",tk=e=>t=>t<=.5?e(2*t)/2:(2-e(2*(1-t)))/2,nk=e=>t=>1-e(1-t),rp=e=>1-Math.sin(Math.acos(e)),rk=nk(rp),AM=tk(rp),ik=Yo(.33,1.53,.69,.99),ip=nk(ik),PM=tk(ip),MM=e=>(e*=2)<1?.5*ip(e):.5*(2-Math.pow(2,-10*(e-1))),DM={linear:Fe,easeIn:EM,easeInOut:ek,easeOut:NM,circIn:rp,circInOut:AM,circOut:rk,backIn:ip,backInOut:PM,backOut:ik,anticipate:MM},s0=e=>{if(Array.isArray(e)){np(e.length===4);const[t,n,r,i]=e;return Yo(t,n,r,i)}else if(typeof e=="string")return DM[e];return e},sp=(e,t)=>n=>!!(Uo(n)&&jP.test(n)&&n.startsWith(e)||t&&Object.prototype.hasOwnProperty.call(n,t)),sk=(e,t,n)=>r=>{if(!Uo(r))return r;const[i,s,o,a]=r.match(vu);return{[e]:parseFloat(i),[t]:parseFloat(s),[n]:parseFloat(o),alpha:a!==void 0?parseFloat(a):1}},IM=e=>kr(0,255,e),Ec={...ui,transform:e=>Math.round(IM(e))},Hr={test:sp("rgb","red"),parse:sk("red","green","blue"),transform:({red:e,green:t,blue:n,alpha:r=1})=>"rgba("+Ec.transform(e)+", "+Ec.transform(t)+", "+Ec.transform(n)+", "+Js(Zs.transform(r))+")"};function jM(e){let t="",n="",r="",i="";return e.length>5?(t=e.substring(1,3),n=e.substring(3,5),r=e.substring(5,7),i=e.substring(7,9)):(t=e.substring(1,2),n=e.substring(2,3),r=e.substring(3,4),i=e.substring(4,5),t+=t,n+=n,r+=r,i+=i),{red:parseInt(t,16),green:parseInt(n,16),blue:parseInt(r,16),alpha:i?parseInt(i,16)/255:1}}const rd={test:sp("#"),parse:jM,transform:Hr.transform},Pi={test:sp("hsl","hue"),parse:sk("hue","saturation","lightness"),transform:({hue:e,saturation:t,lightness:n,alpha:r=1})=>"hsla("+Math.round(e)+", "+kn.transform(Js(t))+", "+kn.transform(Js(n))+", "+Js(Zs.transform(r))+")"},ct={test:e=>Hr.test(e)||rd.test(e)||Pi.test(e),parse:e=>Hr.test(e)?Hr.parse(e):Pi.test(e)?Pi.parse(e):rd.parse(e),transform:e=>Uo(e)?e:e.hasOwnProperty("red")?Hr.transform(e):Pi.transform(e)},Pe=(e,t,n)=>-n*e+n*t+e;function Nc(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*6*n:n<1/2?t:n<2/3?e+(t-e)*(2/3-n)*6:e}function LM({hue:e,saturation:t,lightness:n,alpha:r}){e/=360,t/=100,n/=100;let i=0,s=0,o=0;if(!t)i=s=o=n;else{const a=n<.5?n*(1+t):n+t-n*t,l=2*n-a;i=Nc(l,a,e+1/3),s=Nc(l,a,e),o=Nc(l,a,e-1/3)}return{red:Math.round(i*255),green:Math.round(s*255),blue:Math.round(o*255),alpha:r}}const Tc=(e,t,n)=>{const r=e*e;return Math.sqrt(Math.max(0,n*(t*t-r)+r))},RM=[rd,Hr,Pi],zM=e=>RM.find(t=>t.test(e));function o0(e){const t=zM(e);let n=t.parse(e);return t===Pi&&(n=LM(n)),n}const ok=(e,t)=>{const n=o0(e),r=o0(t),i={...n};return s=>(i.red=Tc(n.red,r.red,s),i.green=Tc(n.green,r.green,s),i.blue=Tc(n.blue,r.blue,s),i.alpha=Pe(n.alpha,r.alpha,s),Hr.transform(i))};function FM(e){var t,n;return isNaN(e)&&Uo(e)&&(((t=e.match(vu))===null||t===void 0?void 0:t.length)||0)+(((n=e.match(Dw))===null||n===void 0?void 0:n.length)||0)>0}const ak={regex:DP,countKey:"Vars",token:"${v}",parse:Fe},lk={regex:Dw,countKey:"Colors",token:"${c}",parse:ct.parse},uk={regex:vu,countKey:"Numbers",token:"${n}",parse:ui.parse};function Ac(e,{regex:t,countKey:n,token:r,parse:i}){const s=e.tokenised.match(t);s&&(e["num"+n]=s.length,e.tokenised=e.tokenised.replace(t,r),e.values.push(...s.map(i)))}function Al(e){const t=e.toString(),n={value:t,tokenised:t,values:[],numVars:0,numColors:0,numNumbers:0};return n.value.includes("var(--")&&Ac(n,ak),Ac(n,lk),Ac(n,uk),n}function ck(e){return Al(e).values}function fk(e){const{values:t,numColors:n,numVars:r,tokenised:i}=Al(e),s=t.length;return o=>{let a=i;for(let l=0;ltypeof e=="number"?0:e;function VM(e){const t=ck(e);return fk(e)(t.map(OM))}const br={test:FM,parse:ck,createTransformer:fk,getAnimatableNone:VM},dk=(e,t)=>n=>`${n>0?t:e}`;function hk(e,t){return typeof e=="number"?n=>Pe(e,t,n):ct.test(e)?ok(e,t):e.startsWith("var(")?dk(e,t):mk(e,t)}const pk=(e,t)=>{const n=[...e],r=n.length,i=e.map((s,o)=>hk(s,t[o]));return s=>{for(let o=0;o{const n={...e,...t},r={};for(const i in n)e[i]!==void 0&&t[i]!==void 0&&(r[i]=hk(e[i],t[i]));return i=>{for(const s in r)n[s]=r[s](i);return n}},mk=(e,t)=>{const n=br.createTransformer(t),r=Al(e),i=Al(t);return r.numVars===i.numVars&&r.numColors===i.numColors&&r.numNumbers>=i.numNumbers?gr(pk(r.values,i.values),n):dk(e,t)},Co=(e,t,n)=>{const r=t-e;return r===0?1:(n-e)/r},a0=(e,t)=>n=>Pe(e,t,n);function BM(e){return typeof e=="number"?a0:typeof e=="string"?ct.test(e)?ok:mk:Array.isArray(e)?pk:typeof e=="object"?$M:a0}function HM(e,t,n){const r=[],i=n||BM(e[0]),s=e.length-1;for(let o=0;ot[0];e[0]>e[s-1]&&(e=[...e].reverse(),t=[...t].reverse());const o=HM(t,r,i),a=o.length,l=u=>{let c=0;if(a>1)for(;cl(kr(e[0],e[s-1],u)):l}function UM(e,t){const n=e[e.length-1];for(let r=1;r<=t;r++){const i=Co(0,t,r);e.push(Pe(n,1,i))}}function WM(e){const t=[0];return UM(t,e.length-1),t}function YM(e,t){return e.map(n=>n*t)}function qM(e,t){return e.map(()=>t||ek).splice(0,e.length-1)}function Pl({duration:e=300,keyframes:t,times:n,ease:r="easeInOut"}){const i=TM(r)?r.map(s0):s0(r),s={done:!1,value:t[0]},o=YM(n&&n.length===t.length?n:WM(t),e),a=gk(o,t,{ease:Array.isArray(i)?i:qM(t,i)});return{calculatedDuration:e,next:l=>(s.value=a(l),s.done=l>=e,s)}}function yk(e,t){return t?e*(1e3/t):0}const KM=5;function xk(e,t,n){const r=Math.max(t-KM,0);return yk(n-e(r),t-r)}const Pc=.001,GM=.01,XM=10,QM=.05,ZM=1;function JM({duration:e=800,bounce:t=.25,velocity:n=0,mass:r=1}){let i,s,o=1-t;o=kr(QM,ZM,o),e=kr(GM,XM,Ln(e)),o<1?(i=u=>{const c=u*o,f=c*e,d=c-n,h=id(u,o),m=Math.exp(-f);return Pc-d/h*m},s=u=>{const f=u*o*e,d=f*n+n,h=Math.pow(o,2)*Math.pow(u,2)*e,m=Math.exp(-f),g=id(Math.pow(u,2),o);return(-i(u)+Pc>0?-1:1)*((d-h)*m)/g}):(i=u=>{const c=Math.exp(-u*e),f=(u-n)*e+1;return-Pc+c*f},s=u=>{const c=Math.exp(-u*e),f=(n-u)*(e*e);return c*f});const a=5/e,l=tD(i,s,a);if(e=Gr(e),isNaN(l))return{stiffness:100,damping:10,duration:e};{const u=Math.pow(l,2)*r;return{stiffness:u,damping:o*2*Math.sqrt(r*u),duration:e}}}const eD=12;function tD(e,t,n){let r=n;for(let i=1;ie[n]!==void 0)}function iD(e){let t={velocity:0,stiffness:100,damping:10,mass:1,isResolvedFromDuration:!1,...e};if(!l0(e,rD)&&l0(e,nD)){const n=JM(e);t={...t,...n,mass:1},t.isResolvedFromDuration=!0}return t}function vk({keyframes:e,restDelta:t,restSpeed:n,...r}){const i=e[0],s=e[e.length-1],o={done:!1,value:i},{stiffness:a,damping:l,mass:u,duration:c,velocity:f,isResolvedFromDuration:d}=iD({...r,velocity:-Ln(r.velocity||0)}),h=f||0,m=l/(2*Math.sqrt(a*u)),g=s-i,w=Ln(Math.sqrt(a/u)),p=Math.abs(g)<5;n||(n=p?.01:2),t||(t=p?.005:.5);let x;if(m<1){const v=id(w,m);x=k=>{const N=Math.exp(-m*w*k);return s-N*((h+m*w*g)/v*Math.sin(v*k)+g*Math.cos(v*k))}}else if(m===1)x=v=>s-Math.exp(-w*v)*(g+(h+w*g)*v);else{const v=w*Math.sqrt(m*m-1);x=k=>{const N=Math.exp(-m*w*k),S=Math.min(v*k,300);return s-N*((h+m*w*g)*Math.sinh(S)+v*g*Math.cosh(S))/v}}return{calculatedDuration:d&&c||null,next:v=>{const k=x(v);if(d)o.done=v>=c;else{let N=h;v!==0&&(m<1?N=xk(x,v,k):N=0);const S=Math.abs(N)<=n,A=Math.abs(s-k)<=t;o.done=S&&A}return o.value=o.done?s:k,o}}}function u0({keyframes:e,velocity:t=0,power:n=.8,timeConstant:r=325,bounceDamping:i=10,bounceStiffness:s=500,modifyTarget:o,min:a,max:l,restDelta:u=.5,restSpeed:c}){const f=e[0],d={done:!1,value:f},h=P=>a!==void 0&&Pl,m=P=>a===void 0?l:l===void 0||Math.abs(a-P)-g*Math.exp(-P/r),v=P=>p+x(P),k=P=>{const D=x(P),C=v(P);d.done=Math.abs(D)<=u,d.value=d.done?p:C};let N,S;const A=P=>{h(d.value)&&(N=P,S=vk({keyframes:[d.value,m(d.value)],velocity:xk(v,P,d.value),damping:i,stiffness:s,restDelta:u,restSpeed:c}))};return A(0),{calculatedDuration:null,next:P=>{let D=!1;return!S&&N===void 0&&(D=!0,k(P),A(P)),N!==void 0&&P>N?S.next(P-N):(!D&&k(P),d)}}}const sD=e=>{const t=({timestamp:n})=>e(n);return{start:()=>be.update(t,!0),stop:()=>Vn(t),now:()=>rt.isProcessing?rt.timestamp:performance.now()}},c0=2e4;function f0(e){let t=0;const n=50;let r=e.next(t);for(;!r.done&&t=c0?1/0:t}const oD={decay:u0,inertia:u0,tween:Pl,keyframes:Pl,spring:vk};function Ml({autoplay:e=!0,delay:t=0,driver:n=sD,keyframes:r,type:i="keyframes",repeat:s=0,repeatDelay:o=0,repeatType:a="loop",onPlay:l,onStop:u,onComplete:c,onUpdate:f,...d}){let h=1,m=!1,g,w;const p=()=>{w=new Promise(F=>{g=F})};p();let x;const v=oD[i]||Pl;let k;v!==Pl&&typeof r[0]!="number"&&(k=gk([0,100],r,{clamp:!1}),r=[0,100]);const N=v({...d,keyframes:r});let S;a==="mirror"&&(S=v({...d,keyframes:[...r].reverse(),velocity:-(d.velocity||0)}));let A="idle",P=null,D=null,C=null;N.calculatedDuration===null&&s&&(N.calculatedDuration=f0(N));const{calculatedDuration:j}=N;let z=1/0,H=1/0;j!==null&&(z=j+o,H=z*(s+1)-o);let _=0;const L=F=>{if(D===null)return;h>0&&(D=Math.min(D,F)),h<0&&(D=Math.min(F-H/h,D)),P!==null?_=P:_=Math.round(F-D)*h;const B=_-t*(h>=0?1:-1),E=h>=0?B<0:B>H;_=Math.max(B,0),A==="finished"&&P===null&&(_=H);let q=_,X=N;if(s){const de=Math.min(_,H)/z;let le=Math.floor(de),Ee=de%1;!Ee&&de>=1&&(Ee=1),Ee===1&&le--,le=Math.min(le,s+1),!!(le%2)&&(a==="reverse"?(Ee=1-Ee,o&&(Ee-=o/z)):a==="mirror"&&(X=S)),q=kr(0,1,Ee)*z}const G=E?{done:!1,value:r[0]}:X.next(q);k&&(G.value=k(G.value));let{done:ne}=G;!E&&j!==null&&(ne=h>=0?_>=H:_<=0);const oe=P===null&&(A==="finished"||A==="running"&&ne);return f&&f(G.value),oe&&R(),G},I=()=>{x&&x.stop(),x=void 0},O=()=>{A="idle",I(),g(),p(),D=C=null},R=()=>{A="finished",c&&c(),I(),g()},M=()=>{if(m)return;x||(x=n(L));const F=x.now();l&&l(),P!==null?D=F-P:(!D||A==="finished")&&(D=F),A==="finished"&&p(),C=D,P=null,A="running",x.start()};e&&M();const b={then(F,B){return w.then(F,B)},get time(){return Ln(_)},set time(F){F=Gr(F),_=F,P!==null||!x||h===0?P=F:D=x.now()-F/h},get duration(){const F=N.calculatedDuration===null?f0(N):N.calculatedDuration;return Ln(F)},get speed(){return h},set speed(F){F===h||!x||(h=F,b.time=Ln(_))},get state(){return A},play:M,pause:()=>{A="paused",P=_},stop:()=>{m=!0,A!=="idle"&&(A="idle",u&&u(),O())},cancel:()=>{C!==null&&L(C),O()},complete:()=>{A="finished"},sample:F=>(D=0,L(F))};return b}function aD(e){let t;return()=>(t===void 0&&(t=e()),t)}const lD=aD(()=>Object.hasOwnProperty.call(Element.prototype,"animate")),uD=new Set(["opacity","clipPath","filter","transform","backgroundColor"]),va=10,cD=2e4,fD=(e,t)=>t.type==="spring"||e==="backgroundColor"||!Xw(t.ease);function dD(e,t,{onUpdate:n,onComplete:r,...i}){if(!(lD()&&uD.has(t)&&!i.repeatDelay&&i.repeatType!=="mirror"&&i.damping!==0&&i.type!=="inertia"))return!1;let o=!1,a,l,u=!1;const c=()=>{l=new Promise(v=>{a=v})};c();let{keyframes:f,duration:d=300,ease:h,times:m}=i;if(fD(t,i)){const v=Ml({...i,repeat:0,delay:0});let k={done:!1,value:f[0]};const N=[];let S=0;for(;!k.done&&S{u=!1,g.cancel()},p=()=>{u=!0,be.update(w),a(),c()};return g.onfinish=()=>{u||(e.set(bM(f,i)),r&&r(),p())},{then(v,k){return l.then(v,k)},attachTimeline(v){return g.timeline=v,g.onfinish=null,Fe},get time(){return Ln(g.currentTime||0)},set time(v){g.currentTime=Gr(v)},get speed(){return g.playbackRate},set speed(v){g.playbackRate=v},get duration(){return Ln(d)},play:()=>{o||(g.play(),Vn(w))},pause:()=>g.pause(),stop:()=>{if(o=!0,g.playState==="idle")return;const{currentTime:v}=g;if(v){const k=Ml({...i,autoplay:!1});e.setWithVelocity(k.sample(v-va).value,k.sample(v).value,va)}p()},complete:()=>{u||g.finish()},cancel:p}}function hD({keyframes:e,delay:t,onUpdate:n,onComplete:r}){const i=()=>(n&&n(e[e.length-1]),r&&r(),{time:0,speed:1,duration:0,play:Fe,pause:Fe,stop:Fe,then:s=>(s(),Promise.resolve()),cancel:Fe,complete:Fe});return t?Ml({keyframes:[0,1],duration:0,delay:t,onComplete:i}):i()}const pD={type:"spring",stiffness:500,damping:25,restSpeed:10},mD=e=>({type:"spring",stiffness:550,damping:e===0?2*Math.sqrt(550):30,restSpeed:10}),gD={type:"keyframes",duration:.8},yD={type:"keyframes",ease:[.25,.1,.35,1],duration:.3},xD=(e,{keyframes:t})=>t.length>2?gD:li.has(e)?e.startsWith("scale")?mD(t[1]):pD:yD,sd=(e,t)=>e==="zIndex"?!1:!!(typeof t=="number"||Array.isArray(t)||typeof t=="string"&&(br.test(t)||t==="0")&&!t.startsWith("url(")),vD=new Set(["brightness","contrast","saturate","opacity"]);function wD(e){const[t,n]=e.slice(0,-1).split("(");if(t==="drop-shadow")return e;const[r]=n.match(vu)||[];if(!r)return e;const i=n.replace(r,"");let s=vD.has(t)?1:0;return r!==n&&(s*=100),t+"("+s+i+")"}const kD=/([a-z-]*)\(.*?\)/g,od={...br,getAnimatableNone:e=>{const t=e.match(kD);return t?t.map(wD).join(" "):e}},bD={...Iw,color:ct,backgroundColor:ct,outlineColor:ct,fill:ct,stroke:ct,borderColor:ct,borderTopColor:ct,borderRightColor:ct,borderBottomColor:ct,borderLeftColor:ct,filter:od,WebkitFilter:od},op=e=>bD[e];function wk(e,t){let n=op(e);return n!==od&&(n=br),n.getAnimatableNone?n.getAnimatableNone(t):void 0}const kk=e=>/^0[^.\s]+$/.test(e);function SD(e){if(typeof e=="number")return e===0;if(e!==null)return e==="none"||e==="0"||kk(e)}function _D(e,t,n,r){const i=sd(t,n);let s;Array.isArray(n)?s=[...n]:s=[null,n];const o=r.from!==void 0?r.from:e.get();let a;const l=[];for(let u=0;ui=>{const s=ap(r,e)||{},o=s.delay||r.delay||0;let{elapsed:a=0}=r;a=a-Gr(o);const l=_D(t,e,n,s),u=l[0],c=l[l.length-1],f=sd(e,u),d=sd(e,c);let h={keyframes:l,velocity:t.getVelocity(),ease:"easeOut",...s,delay:-a,onUpdate:m=>{t.set(m),s.onUpdate&&s.onUpdate(m)},onComplete:()=>{i(),s.onComplete&&s.onComplete()}};if(CD(s)||(h={...h,...xD(e,h)}),h.duration&&(h.duration=Gr(h.duration)),h.repeatDelay&&(h.repeatDelay=Gr(h.repeatDelay)),!f||!d||wM.current||s.type===!1||ED.skipAnimations)return hD(h);if(!r.isHandoff&&t.owner&&t.owner.current instanceof HTMLElement&&!t.owner.getProps().onUpdate){const m=dD(t,e,h);if(m)return m}return Ml(h)};function Dl(e){return!!(Ct(e)&&e.add)}const bk=e=>/^\-?\d*\.?\d+$/.test(e);function up(e,t){e.indexOf(t)===-1&&e.push(t)}function cp(e,t){const n=e.indexOf(t);n>-1&&e.splice(n,1)}class fp{constructor(){this.subscriptions=[]}add(t){return up(this.subscriptions,t),()=>cp(this.subscriptions,t)}notify(t,n,r){const i=this.subscriptions.length;if(i)if(i===1)this.subscriptions[0](t,n,r);else for(let s=0;s!isNaN(parseFloat(e));class TD{constructor(t,n={}){this.version="10.18.0",this.timeDelta=0,this.lastUpdated=0,this.canTrackVelocity=!1,this.events={},this.updateAndNotify=(r,i=!0)=>{this.prev=this.current,this.current=r;const{delta:s,timestamp:o}=rt;this.lastUpdated!==o&&(this.timeDelta=s,this.lastUpdated=o,be.postRender(this.scheduleVelocityCheck)),this.prev!==this.current&&this.events.change&&this.events.change.notify(this.current),this.events.velocityChange&&this.events.velocityChange.notify(this.getVelocity()),i&&this.events.renderRequest&&this.events.renderRequest.notify(this.current)},this.scheduleVelocityCheck=()=>be.postRender(this.velocityCheck),this.velocityCheck=({timestamp:r})=>{r!==this.lastUpdated&&(this.prev=this.current,this.events.velocityChange&&this.events.velocityChange.notify(this.getVelocity()))},this.hasAnimated=!1,this.prev=this.current=t,this.canTrackVelocity=ND(this.current),this.owner=n.owner}onChange(t){return this.on("change",t)}on(t,n){this.events[t]||(this.events[t]=new fp);const r=this.events[t].add(n);return t==="change"?()=>{r(),be.read(()=>{this.events.change.getSize()||this.stop()})}:r}clearListeners(){for(const t in this.events)this.events[t].clear()}attach(t,n){this.passiveEffect=t,this.stopPassiveEffect=n}set(t,n=!0){!n||!this.passiveEffect?this.updateAndNotify(t,n):this.passiveEffect(t,this.updateAndNotify)}setWithVelocity(t,n,r){this.set(n),this.prev=t,this.timeDelta=r}jump(t){this.updateAndNotify(t),this.prev=t,this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}get(){return this.current}getPrevious(){return this.prev}getVelocity(){return this.canTrackVelocity?yk(parseFloat(this.current)-parseFloat(this.prev),this.timeDelta):0}start(t){return this.stop(),new Promise(n=>{this.hasAnimated=!0,this.animation=t(n),this.events.animationStart&&this.events.animationStart.notify()}).then(()=>{this.events.animationComplete&&this.events.animationComplete.notify(),this.clearAnimation()})}stop(){this.animation&&(this.animation.stop(),this.events.animationCancel&&this.events.animationCancel.notify()),this.clearAnimation()}isAnimating(){return!!this.animation}clearAnimation(){delete this.animation}destroy(){this.clearListeners(),this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}}function ns(e,t){return new TD(e,t)}const Sk=e=>t=>t.test(e),AD={test:e=>e==="auto",parse:e=>e},_k=[ui,te,kn,Qn,RP,LP,AD],_s=e=>_k.find(Sk(e)),PD=[..._k,ct,br],MD=e=>PD.find(Sk(e));function DD(e,t,n){e.hasValue(t)?e.getValue(t).set(n):e.addValue(t,ns(n))}function ID(e,t){const n=ku(e,t);let{transitionEnd:r={},transition:i={},...s}=n?e.makeTargetAnimatable(n,!1):{};s={...s,...r};for(const o in s){const a=XP(s[o]);DD(e,o,a)}}function jD(e,t,n){var r,i;const s=Object.keys(t).filter(a=>!e.hasValue(a)),o=s.length;if(o)for(let a=0;al.remove(f))),u.push(w)}return o&&Promise.all(u).then(()=>{o&&ID(e,o)}),u}function ad(e,t,n={}){const r=ku(e,t,n.custom);let{transition:i=e.getDefaultTransition()||{}}=r||{};n.transitionOverride&&(i=n.transitionOverride);const s=r?()=>Promise.all(Ck(e,r,n)):()=>Promise.resolve(),o=e.variantChildren&&e.variantChildren.size?(l=0)=>{const{delayChildren:u=0,staggerChildren:c,staggerDirection:f}=i;return OD(e,t,u+l,c,f,n)}:()=>Promise.resolve(),{when:a}=i;if(a){const[l,u]=a==="beforeChildren"?[s,o]:[o,s];return l().then(()=>u())}else return Promise.all([s(),o(n.delay)])}function OD(e,t,n=0,r=0,i=1,s){const o=[],a=(e.variantChildren.size-1)*r,l=i===1?(u=0)=>u*r:(u=0)=>a-u*r;return Array.from(e.variantChildren).sort(VD).forEach((u,c)=>{u.notify("AnimationStart",t),o.push(ad(u,t,{...s,delay:n+l(c)}).then(()=>u.notify("AnimationComplete",t)))}),Promise.all(o)}function VD(e,t){return e.sortNodePosition(t)}function $D(e,t,n={}){e.notify("AnimationStart",t);let r;if(Array.isArray(t)){const i=t.map(s=>ad(e,s,n));r=Promise.all(i)}else if(typeof t=="string")r=ad(e,t,n);else{const i=typeof t=="function"?ku(e,t,n.custom):t;r=Promise.all(Ck(e,i,n))}return r.then(()=>e.notify("AnimationComplete",t))}const BD=[...Yh].reverse(),HD=Yh.length;function UD(e){return t=>Promise.all(t.map(({animation:n,options:r})=>$D(e,n,r)))}function WD(e){let t=UD(e);const n=qD();let r=!0;const i=(l,u)=>{const c=ku(e,u);if(c){const{transition:f,transitionEnd:d,...h}=c;l={...l,...h,...d}}return l};function s(l){t=l(e)}function o(l,u){const c=e.getProps(),f=e.getVariantContext(!0)||{},d=[],h=new Set;let m={},g=1/0;for(let p=0;pg&&N,C=!1;const j=Array.isArray(k)?k:[k];let z=j.reduce(i,{});S===!1&&(z={});const{prevResolvedValues:H={}}=v,_={...H,...z},L=I=>{D=!0,h.has(I)&&(C=!0,h.delete(I)),v.needsAnimating[I]=!0};for(const I in _){const O=z[I],R=H[I];if(m.hasOwnProperty(I))continue;let M=!1;Tl(O)&&Tl(R)?M=!Kw(O,R):M=O!==R,M?O!==void 0?L(I):h.add(I):O!==void 0&&h.has(I)?L(I):v.protectedKeys[I]=!0}v.prevProp=k,v.prevResolvedValues=z,v.isActive&&(m={...m,...z}),r&&e.blockInitialAnimation&&(D=!1),D&&(!A||C)&&d.push(...j.map(I=>({animation:I,options:{type:x,...l}})))}if(h.size){const p={};h.forEach(x=>{const v=e.getBaseTarget(x);v!==void 0&&(p[x]=v)}),d.push({animation:p})}let w=!!d.length;return r&&(c.initial===!1||c.initial===c.animate)&&!e.manuallyAnimateOnMount&&(w=!1),r=!1,w?t(d):Promise.resolve()}function a(l,u,c){var f;if(n[l].isActive===u)return Promise.resolve();(f=e.variantChildren)===null||f===void 0||f.forEach(h=>{var m;return(m=h.animationState)===null||m===void 0?void 0:m.setActive(l,u)}),n[l].isActive=u;const d=o(c,l);for(const h in n)n[h].protectedKeys={};return d}return{animateChanges:o,setActive:a,setAnimateFunction:s,getState:()=>n}}function YD(e,t){return typeof t=="string"?t!==e:Array.isArray(t)?!Kw(t,e):!1}function Mr(e=!1){return{isActive:e,protectedKeys:{},needsAnimating:{},prevResolvedValues:{}}}function qD(){return{animate:Mr(!0),whileInView:Mr(),whileHover:Mr(),whileTap:Mr(),whileDrag:Mr(),whileFocus:Mr(),exit:Mr()}}class KD extends Nr{constructor(t){super(t),t.animationState||(t.animationState=WD(t))}updateAnimationControlsSubscription(){const{animate:t}=this.node.getProps();this.unmount(),yu(t)&&(this.unmount=t.subscribe(this.node))}mount(){this.updateAnimationControlsSubscription()}update(){const{animate:t}=this.node.getProps(),{animate:n}=this.node.prevProps||{};t!==n&&this.updateAnimationControlsSubscription()}unmount(){}}let GD=0;class XD extends Nr{constructor(){super(...arguments),this.id=GD++}update(){if(!this.node.presenceContext)return;const{isPresent:t,onExitComplete:n,custom:r}=this.node.presenceContext,{isPresent:i}=this.node.prevPresenceContext||{};if(!this.node.animationState||t===i)return;const s=this.node.animationState.setActive("exit",!t,{custom:r??this.node.getProps().custom});n&&!t&&s.then(()=>n(this.id))}mount(){const{register:t}=this.node.presenceContext||{};t&&(this.unmount=t(this.id))}unmount(){}}const QD={animation:{Feature:KD},exit:{Feature:XD}},d0=(e,t)=>Math.abs(e-t);function ZD(e,t){const n=d0(e.x,t.x),r=d0(e.y,t.y);return Math.sqrt(n**2+r**2)}class Ek{constructor(t,n,{transformPagePoint:r,contextWindow:i,dragSnapToOrigin:s=!1}={}){if(this.startEvent=null,this.lastMoveEvent=null,this.lastMoveEventInfo=null,this.handlers={},this.contextWindow=window,this.updatePoint=()=>{if(!(this.lastMoveEvent&&this.lastMoveEventInfo))return;const f=Dc(this.lastMoveEventInfo,this.history),d=this.startEvent!==null,h=ZD(f.offset,{x:0,y:0})>=3;if(!d&&!h)return;const{point:m}=f,{timestamp:g}=rt;this.history.push({...m,timestamp:g});const{onStart:w,onMove:p}=this.handlers;d||(w&&w(this.lastMoveEvent,f),this.startEvent=this.lastMoveEvent),p&&p(this.lastMoveEvent,f)},this.handlePointerMove=(f,d)=>{this.lastMoveEvent=f,this.lastMoveEventInfo=Mc(d,this.transformPagePoint),be.update(this.updatePoint,!0)},this.handlePointerUp=(f,d)=>{this.end();const{onEnd:h,onSessionEnd:m,resumeAnimation:g}=this.handlers;if(this.dragSnapToOrigin&&g&&g(),!(this.lastMoveEvent&&this.lastMoveEventInfo))return;const w=Dc(f.type==="pointercancel"?this.lastMoveEventInfo:Mc(d,this.transformPagePoint),this.history);this.startEvent&&h&&h(f,w),m&&m(f,w)},!Hw(t))return;this.dragSnapToOrigin=s,this.handlers=n,this.transformPagePoint=r,this.contextWindow=i||window;const o=wu(t),a=Mc(o,this.transformPagePoint),{point:l}=a,{timestamp:u}=rt;this.history=[{...l,timestamp:u}];const{onSessionStart:c}=n;c&&c(t,Dc(a,this.history)),this.removeListeners=gr(jn(this.contextWindow,"pointermove",this.handlePointerMove),jn(this.contextWindow,"pointerup",this.handlePointerUp),jn(this.contextWindow,"pointercancel",this.handlePointerUp))}updateHandlers(t){this.handlers=t}end(){this.removeListeners&&this.removeListeners(),Vn(this.updatePoint)}}function Mc(e,t){return t?{point:t(e.point)}:e}function h0(e,t){return{x:e.x-t.x,y:e.y-t.y}}function Dc({point:e},t){return{point:e,delta:h0(e,Nk(t)),offset:h0(e,JD(t)),velocity:eI(t,.1)}}function JD(e){return e[0]}function Nk(e){return e[e.length-1]}function eI(e,t){if(e.length<2)return{x:0,y:0};let n=e.length-1,r=null;const i=Nk(e);for(;n>=0&&(r=e[n],!(i.timestamp-r.timestamp>Gr(t)));)n--;if(!r)return{x:0,y:0};const s=Ln(i.timestamp-r.timestamp);if(s===0)return{x:0,y:0};const o={x:(i.x-r.x)/s,y:(i.y-r.y)/s};return o.x===1/0&&(o.x=0),o.y===1/0&&(o.y=0),o}function zt(e){return e.max-e.min}function ld(e,t=0,n=.01){return Math.abs(e-t)<=n}function p0(e,t,n,r=.5){e.origin=r,e.originPoint=Pe(t.min,t.max,e.origin),e.scale=zt(n)/zt(t),(ld(e.scale,1,1e-4)||isNaN(e.scale))&&(e.scale=1),e.translate=Pe(n.min,n.max,e.origin)-e.originPoint,(ld(e.translate)||isNaN(e.translate))&&(e.translate=0)}function eo(e,t,n,r){p0(e.x,t.x,n.x,r?r.originX:void 0),p0(e.y,t.y,n.y,r?r.originY:void 0)}function m0(e,t,n){e.min=n.min+t.min,e.max=e.min+zt(t)}function tI(e,t,n){m0(e.x,t.x,n.x),m0(e.y,t.y,n.y)}function g0(e,t,n){e.min=t.min-n.min,e.max=e.min+zt(t)}function to(e,t,n){g0(e.x,t.x,n.x),g0(e.y,t.y,n.y)}function nI(e,{min:t,max:n},r){return t!==void 0&&en&&(e=r?Pe(n,e,r.max):Math.min(e,n)),e}function y0(e,t,n){return{min:t!==void 0?e.min+t:void 0,max:n!==void 0?e.max+n-(e.max-e.min):void 0}}function rI(e,{top:t,left:n,bottom:r,right:i}){return{x:y0(e.x,n,i),y:y0(e.y,t,r)}}function x0(e,t){let n=t.min-e.min,r=t.max-e.max;return t.max-t.minr?n=Co(t.min,t.max-r,e.min):r>i&&(n=Co(e.min,e.max-i,t.min)),kr(0,1,n)}function oI(e,t){const n={};return t.min!==void 0&&(n.min=t.min-e.min),t.max!==void 0&&(n.max=t.max-e.min),n}const ud=.35;function aI(e=ud){return e===!1?e=0:e===!0&&(e=ud),{x:v0(e,"left","right"),y:v0(e,"top","bottom")}}function v0(e,t,n){return{min:w0(e,t),max:w0(e,n)}}function w0(e,t){return typeof e=="number"?e:e[t]||0}const k0=()=>({translate:0,scale:1,origin:0,originPoint:0}),Mi=()=>({x:k0(),y:k0()}),b0=()=>({min:0,max:0}),Ve=()=>({x:b0(),y:b0()});function Bt(e){return[e("x"),e("y")]}function Tk({top:e,left:t,right:n,bottom:r}){return{x:{min:t,max:n},y:{min:e,max:r}}}function lI({x:e,y:t}){return{top:t.min,right:e.max,bottom:t.max,left:e.min}}function uI(e,t){if(!t)return e;const n=t({x:e.left,y:e.top}),r=t({x:e.right,y:e.bottom});return{top:n.y,left:n.x,bottom:r.y,right:r.x}}function Ic(e){return e===void 0||e===1}function cd({scale:e,scaleX:t,scaleY:n}){return!Ic(e)||!Ic(t)||!Ic(n)}function Lr(e){return cd(e)||Ak(e)||e.z||e.rotate||e.rotateX||e.rotateY}function Ak(e){return S0(e.x)||S0(e.y)}function S0(e){return e&&e!=="0%"}function Il(e,t,n){const r=e-n,i=t*r;return n+i}function _0(e,t,n,r,i){return i!==void 0&&(e=Il(e,i,r)),Il(e,n,r)+t}function fd(e,t=0,n=1,r,i){e.min=_0(e.min,t,n,r,i),e.max=_0(e.max,t,n,r,i)}function Pk(e,{x:t,y:n}){fd(e.x,t.translate,t.scale,t.originPoint),fd(e.y,n.translate,n.scale,n.originPoint)}function cI(e,t,n,r=!1){const i=n.length;if(!i)return;t.x=t.y=1;let s,o;for(let a=0;a1.0000000000001||e<.999999999999?e:1}function er(e,t){e.min=e.min+t,e.max=e.max+t}function E0(e,t,[n,r,i]){const s=t[i]!==void 0?t[i]:.5,o=Pe(e.min,e.max,s);fd(e,t[n],t[r],o,t.scale)}const fI=["x","scaleX","originX"],dI=["y","scaleY","originY"];function Di(e,t){E0(e.x,t,fI),E0(e.y,t,dI)}function Mk(e,t){return Tk(uI(e.getBoundingClientRect(),t))}function hI(e,t,n){const r=Mk(e,n),{scroll:i}=t;return i&&(er(r.x,i.offset.x),er(r.y,i.offset.y)),r}const Dk=({current:e})=>e?e.ownerDocument.defaultView:null,pI=new WeakMap;class mI{constructor(t){this.openGlobalLock=null,this.isDragging=!1,this.currentDirection=null,this.originPoint={x:0,y:0},this.constraints=!1,this.hasMutatedConstraints=!1,this.elastic=Ve(),this.visualElement=t}start(t,{snapToCursor:n=!1}={}){const{presenceContext:r}=this.visualElement;if(r&&r.isPresent===!1)return;const i=c=>{const{dragSnapToOrigin:f}=this.getProps();f?this.pauseAnimation():this.stopAnimation(),n&&this.snapToCursor(wu(c,"page").point)},s=(c,f)=>{const{drag:d,dragPropagation:h,onDragStart:m}=this.getProps();if(d&&!h&&(this.openGlobalLock&&this.openGlobalLock(),this.openGlobalLock=Ww(d),!this.openGlobalLock))return;this.isDragging=!0,this.currentDirection=null,this.resolveConstraints(),this.visualElement.projection&&(this.visualElement.projection.isAnimationBlocked=!0,this.visualElement.projection.target=void 0),Bt(w=>{let p=this.getAxisMotionValue(w).get()||0;if(kn.test(p)){const{projection:x}=this.visualElement;if(x&&x.layout){const v=x.layout.layoutBox[w];v&&(p=zt(v)*(parseFloat(p)/100))}}this.originPoint[w]=p}),m&&be.update(()=>m(c,f),!1,!0);const{animationState:g}=this.visualElement;g&&g.setActive("whileDrag",!0)},o=(c,f)=>{const{dragPropagation:d,dragDirectionLock:h,onDirectionLock:m,onDrag:g}=this.getProps();if(!d&&!this.openGlobalLock)return;const{offset:w}=f;if(h&&this.currentDirection===null){this.currentDirection=gI(w),this.currentDirection!==null&&m&&m(this.currentDirection);return}this.updateAxis("x",f.point,w),this.updateAxis("y",f.point,w),this.visualElement.render(),g&&g(c,f)},a=(c,f)=>this.stop(c,f),l=()=>Bt(c=>{var f;return this.getAnimationState(c)==="paused"&&((f=this.getAxisMotionValue(c).animation)===null||f===void 0?void 0:f.play())}),{dragSnapToOrigin:u}=this.getProps();this.panSession=new Ek(t,{onSessionStart:i,onStart:s,onMove:o,onSessionEnd:a,resumeAnimation:l},{transformPagePoint:this.visualElement.getTransformPagePoint(),dragSnapToOrigin:u,contextWindow:Dk(this.visualElement)})}stop(t,n){const r=this.isDragging;if(this.cancel(),!r)return;const{velocity:i}=n;this.startAnimation(i);const{onDragEnd:s}=this.getProps();s&&be.update(()=>s(t,n))}cancel(){this.isDragging=!1;const{projection:t,animationState:n}=this.visualElement;t&&(t.isAnimationBlocked=!1),this.panSession&&this.panSession.end(),this.panSession=void 0;const{dragPropagation:r}=this.getProps();!r&&this.openGlobalLock&&(this.openGlobalLock(),this.openGlobalLock=null),n&&n.setActive("whileDrag",!1)}updateAxis(t,n,r){const{drag:i}=this.getProps();if(!r||!wa(t,i,this.currentDirection))return;const s=this.getAxisMotionValue(t);let o=this.originPoint[t]+r[t];this.constraints&&this.constraints[t]&&(o=nI(o,this.constraints[t],this.elastic[t])),s.set(o)}resolveConstraints(){var t;const{dragConstraints:n,dragElastic:r}=this.getProps(),i=this.visualElement.projection&&!this.visualElement.projection.layout?this.visualElement.projection.measure(!1):(t=this.visualElement.projection)===null||t===void 0?void 0:t.layout,s=this.constraints;n&&Ai(n)?this.constraints||(this.constraints=this.resolveRefConstraints()):n&&i?this.constraints=rI(i.layoutBox,n):this.constraints=!1,this.elastic=aI(r),s!==this.constraints&&i&&this.constraints&&!this.hasMutatedConstraints&&Bt(o=>{this.getAxisMotionValue(o)&&(this.constraints[o]=oI(i.layoutBox[o],this.constraints[o]))})}resolveRefConstraints(){const{dragConstraints:t,onMeasureDragConstraints:n}=this.getProps();if(!t||!Ai(t))return!1;const r=t.current,{projection:i}=this.visualElement;if(!i||!i.layout)return!1;const s=hI(r,i.root,this.visualElement.getTransformPagePoint());let o=iI(i.layout.layoutBox,s);if(n){const a=n(lI(o));this.hasMutatedConstraints=!!a,a&&(o=Tk(a))}return o}startAnimation(t){const{drag:n,dragMomentum:r,dragElastic:i,dragTransition:s,dragSnapToOrigin:o,onDragTransitionEnd:a}=this.getProps(),l=this.constraints||{},u=Bt(c=>{if(!wa(c,n,this.currentDirection))return;let f=l&&l[c]||{};o&&(f={min:0,max:0});const d=i?200:1e6,h=i?40:1e7,m={type:"inertia",velocity:r?t[c]:0,bounceStiffness:d,bounceDamping:h,timeConstant:750,restDelta:1,restSpeed:10,...s,...f};return this.startAxisValueAnimation(c,m)});return Promise.all(u).then(a)}startAxisValueAnimation(t,n){const r=this.getAxisMotionValue(t);return r.start(lp(t,r,0,n))}stopAnimation(){Bt(t=>this.getAxisMotionValue(t).stop())}pauseAnimation(){Bt(t=>{var n;return(n=this.getAxisMotionValue(t).animation)===null||n===void 0?void 0:n.pause()})}getAnimationState(t){var n;return(n=this.getAxisMotionValue(t).animation)===null||n===void 0?void 0:n.state}getAxisMotionValue(t){const n="_drag"+t.toUpperCase(),r=this.visualElement.getProps(),i=r[n];return i||this.visualElement.getValue(t,(r.initial?r.initial[t]:void 0)||0)}snapToCursor(t){Bt(n=>{const{drag:r}=this.getProps();if(!wa(n,r,this.currentDirection))return;const{projection:i}=this.visualElement,s=this.getAxisMotionValue(n);if(i&&i.layout){const{min:o,max:a}=i.layout.layoutBox[n];s.set(t[n]-Pe(o,a,.5))}})}scalePositionWithinConstraints(){if(!this.visualElement.current)return;const{drag:t,dragConstraints:n}=this.getProps(),{projection:r}=this.visualElement;if(!Ai(n)||!r||!this.constraints)return;this.stopAnimation();const i={x:0,y:0};Bt(o=>{const a=this.getAxisMotionValue(o);if(a){const l=a.get();i[o]=sI({min:l,max:l},this.constraints[o])}});const{transformTemplate:s}=this.visualElement.getProps();this.visualElement.current.style.transform=s?s({},""):"none",r.root&&r.root.updateScroll(),r.updateLayout(),this.resolveConstraints(),Bt(o=>{if(!wa(o,t,null))return;const a=this.getAxisMotionValue(o),{min:l,max:u}=this.constraints[o];a.set(Pe(l,u,i[o]))})}addListeners(){if(!this.visualElement.current)return;pI.set(this.visualElement,this);const t=this.visualElement.current,n=jn(t,"pointerdown",l=>{const{drag:u,dragListener:c=!0}=this.getProps();u&&c&&this.start(l)}),r=()=>{const{dragConstraints:l}=this.getProps();Ai(l)&&(this.constraints=this.resolveRefConstraints())},{projection:i}=this.visualElement,s=i.addEventListener("measure",r);i&&!i.layout&&(i.root&&i.root.updateScroll(),i.updateLayout()),r();const o=Mn(window,"resize",()=>this.scalePositionWithinConstraints()),a=i.addEventListener("didUpdate",({delta:l,hasLayoutChanged:u})=>{this.isDragging&&u&&(Bt(c=>{const f=this.getAxisMotionValue(c);f&&(this.originPoint[c]+=l[c].translate,f.set(f.get()+l[c].translate))}),this.visualElement.render())});return()=>{o(),n(),s(),a&&a()}}getProps(){const t=this.visualElement.getProps(),{drag:n=!1,dragDirectionLock:r=!1,dragPropagation:i=!1,dragConstraints:s=!1,dragElastic:o=ud,dragMomentum:a=!0}=t;return{...t,drag:n,dragDirectionLock:r,dragPropagation:i,dragConstraints:s,dragElastic:o,dragMomentum:a}}}function wa(e,t,n){return(t===!0||t===e)&&(n===null||n===e)}function gI(e,t=10){let n=null;return Math.abs(e.y)>t?n="y":Math.abs(e.x)>t&&(n="x"),n}class yI extends Nr{constructor(t){super(t),this.removeGroupControls=Fe,this.removeListeners=Fe,this.controls=new mI(t)}mount(){const{dragControls:t}=this.node.getProps();t&&(this.removeGroupControls=t.subscribe(this.controls)),this.removeListeners=this.controls.addListeners()||Fe}unmount(){this.removeGroupControls(),this.removeListeners()}}const N0=e=>(t,n)=>{e&&be.update(()=>e(t,n))};class xI extends Nr{constructor(){super(...arguments),this.removePointerDownListener=Fe}onPointerDown(t){this.session=new Ek(t,this.createPanHandlers(),{transformPagePoint:this.node.getTransformPagePoint(),contextWindow:Dk(this.node)})}createPanHandlers(){const{onPanSessionStart:t,onPanStart:n,onPan:r,onPanEnd:i}=this.node.getProps();return{onSessionStart:N0(t),onStart:N0(n),onMove:r,onEnd:(s,o)=>{delete this.session,i&&be.update(()=>i(s,o))}}}mount(){this.removePointerDownListener=jn(this.node.current,"pointerdown",t=>this.onPointerDown(t))}update(){this.session&&this.session.updateHandlers(this.createPanHandlers())}unmount(){this.removePointerDownListener(),this.session&&this.session.end()}}function vI(){const e=T.useContext(mu);if(e===null)return[!0,null];const{isPresent:t,onExitComplete:n,register:r}=e,i=T.useId();return T.useEffect(()=>r(i),[]),!t&&n?[!1,()=>n&&n(i)]:[!0]}const Ya={hasAnimatedSinceResize:!0,hasEverUpdated:!1};function T0(e,t){return t.max===t.min?0:e/(t.max-t.min)*100}const Cs={correct:(e,t)=>{if(!t.target)return e;if(typeof e=="string")if(te.test(e))e=parseFloat(e);else return e;const n=T0(e,t.target.x),r=T0(e,t.target.y);return`${n}% ${r}%`}},wI={correct:(e,{treeScale:t,projectionDelta:n})=>{const r=e,i=br.parse(e);if(i.length>5)return r;const s=br.createTransformer(e),o=typeof i[0]!="number"?1:0,a=n.x.scale*t.x,l=n.y.scale*t.y;i[0+o]/=a,i[1+o]/=l;const u=Pe(a,l,.5);return typeof i[2+o]=="number"&&(i[2+o]/=u),typeof i[3+o]=="number"&&(i[3+o]/=u),s(i)}};class kI extends $.Component{componentDidMount(){const{visualElement:t,layoutGroup:n,switchLayoutGroup:r,layoutId:i}=this.props,{projection:s}=t;TP(bI),s&&(n.group&&n.group.add(s),r&&r.register&&i&&r.register(s),s.root.didUpdate(),s.addEventListener("animationComplete",()=>{this.safeToRemove()}),s.setOptions({...s.options,onExitComplete:()=>this.safeToRemove()})),Ya.hasEverUpdated=!0}getSnapshotBeforeUpdate(t){const{layoutDependency:n,visualElement:r,drag:i,isPresent:s}=this.props,o=r.projection;return o&&(o.isPresent=s,i||t.layoutDependency!==n||n===void 0?o.willUpdate():this.safeToRemove(),t.isPresent!==s&&(s?o.promote():o.relegate()||be.postRender(()=>{const a=o.getStack();(!a||!a.members.length)&&this.safeToRemove()}))),null}componentDidUpdate(){const{projection:t}=this.props.visualElement;t&&(t.root.didUpdate(),queueMicrotask(()=>{!t.currentAnimation&&t.isLead()&&this.safeToRemove()}))}componentWillUnmount(){const{visualElement:t,layoutGroup:n,switchLayoutGroup:r}=this.props,{projection:i}=t;i&&(i.scheduleCheckAfterUnmount(),n&&n.group&&n.group.remove(i),r&&r.deregister&&r.deregister(i))}safeToRemove(){const{safeToRemove:t}=this.props;t&&t()}render(){return null}}function Ik(e){const[t,n]=vI(),r=T.useContext(Kh);return $.createElement(kI,{...e,layoutGroup:r,switchLayoutGroup:T.useContext(Tw),isPresent:t,safeToRemove:n})}const bI={borderRadius:{...Cs,applyTo:["borderTopLeftRadius","borderTopRightRadius","borderBottomLeftRadius","borderBottomRightRadius"]},borderTopLeftRadius:Cs,borderTopRightRadius:Cs,borderBottomLeftRadius:Cs,borderBottomRightRadius:Cs,boxShadow:wI},jk=["TopLeft","TopRight","BottomLeft","BottomRight"],SI=jk.length,A0=e=>typeof e=="string"?parseFloat(e):e,P0=e=>typeof e=="number"||te.test(e);function _I(e,t,n,r,i,s){i?(e.opacity=Pe(0,n.opacity!==void 0?n.opacity:1,CI(r)),e.opacityExit=Pe(t.opacity!==void 0?t.opacity:1,0,EI(r))):s&&(e.opacity=Pe(t.opacity!==void 0?t.opacity:1,n.opacity!==void 0?n.opacity:1,r));for(let o=0;ort?1:n(Co(e,t,r))}function D0(e,t){e.min=t.min,e.max=t.max}function $t(e,t){D0(e.x,t.x),D0(e.y,t.y)}function I0(e,t,n,r,i){return e-=t,e=Il(e,1/n,r),i!==void 0&&(e=Il(e,1/i,r)),e}function NI(e,t=0,n=1,r=.5,i,s=e,o=e){if(kn.test(t)&&(t=parseFloat(t),t=Pe(o.min,o.max,t/100)-o.min),typeof t!="number")return;let a=Pe(s.min,s.max,r);e===s&&(a-=t),e.min=I0(e.min,t,n,a,i),e.max=I0(e.max,t,n,a,i)}function j0(e,t,[n,r,i],s,o){NI(e,t[n],t[r],t[i],t.scale,s,o)}const TI=["x","scaleX","originX"],AI=["y","scaleY","originY"];function L0(e,t,n,r){j0(e.x,t,TI,n?n.x:void 0,r?r.x:void 0),j0(e.y,t,AI,n?n.y:void 0,r?r.y:void 0)}function R0(e){return e.translate===0&&e.scale===1}function Rk(e){return R0(e.x)&&R0(e.y)}function PI(e,t){return e.x.min===t.x.min&&e.x.max===t.x.max&&e.y.min===t.y.min&&e.y.max===t.y.max}function zk(e,t){return Math.round(e.x.min)===Math.round(t.x.min)&&Math.round(e.x.max)===Math.round(t.x.max)&&Math.round(e.y.min)===Math.round(t.y.min)&&Math.round(e.y.max)===Math.round(t.y.max)}function z0(e){return zt(e.x)/zt(e.y)}class MI{constructor(){this.members=[]}add(t){up(this.members,t),t.scheduleRender()}remove(t){if(cp(this.members,t),t===this.prevLead&&(this.prevLead=void 0),t===this.lead){const n=this.members[this.members.length-1];n&&this.promote(n)}}relegate(t){const n=this.members.findIndex(i=>t===i);if(n===0)return!1;let r;for(let i=n;i>=0;i--){const s=this.members[i];if(s.isPresent!==!1){r=s;break}}return r?(this.promote(r),!0):!1}promote(t,n){const r=this.lead;if(t!==r&&(this.prevLead=r,this.lead=t,t.show(),r)){r.instance&&r.scheduleRender(),t.scheduleRender(),t.resumeFrom=r,n&&(t.resumeFrom.preserveOpacity=!0),r.snapshot&&(t.snapshot=r.snapshot,t.snapshot.latestValues=r.animationValues||r.latestValues),t.root&&t.root.isUpdating&&(t.isLayoutDirty=!0);const{crossfade:i}=t.options;i===!1&&r.hide()}}exitAnimationComplete(){this.members.forEach(t=>{const{options:n,resumingFrom:r}=t;n.onExitComplete&&n.onExitComplete(),r&&r.options.onExitComplete&&r.options.onExitComplete()})}scheduleRender(){this.members.forEach(t=>{t.instance&&t.scheduleRender(!1)})}removeLeadSnapshot(){this.lead&&this.lead.snapshot&&(this.lead.snapshot=void 0)}}function F0(e,t,n){let r="";const i=e.x.translate/t.x,s=e.y.translate/t.y;if((i||s)&&(r=`translate3d(${i}px, ${s}px, 0) `),(t.x!==1||t.y!==1)&&(r+=`scale(${1/t.x}, ${1/t.y}) `),n){const{rotate:l,rotateX:u,rotateY:c}=n;l&&(r+=`rotate(${l}deg) `),u&&(r+=`rotateX(${u}deg) `),c&&(r+=`rotateY(${c}deg) `)}const o=e.x.scale*t.x,a=e.y.scale*t.y;return(o!==1||a!==1)&&(r+=`scale(${o}, ${a})`),r||"none"}const DI=(e,t)=>e.depth-t.depth;class II{constructor(){this.children=[],this.isDirty=!1}add(t){up(this.children,t),this.isDirty=!0}remove(t){cp(this.children,t),this.isDirty=!0}forEach(t){this.isDirty&&this.children.sort(DI),this.isDirty=!1,this.children.forEach(t)}}function jI(e,t){const n=performance.now(),r=({timestamp:i})=>{const s=i-n;s>=t&&(Vn(r),e(s-t))};return be.read(r,!0),()=>Vn(r)}function LI(e){window.MotionDebug&&window.MotionDebug.record(e)}function RI(e){return e instanceof SVGElement&&e.tagName!=="svg"}function zI(e,t,n){const r=Ct(e)?e:ns(e);return r.start(lp("",r,t,n)),r.animation}const O0=["","X","Y","Z"],FI={visibility:"hidden"},V0=1e3;let OI=0;const Rr={type:"projectionFrame",totalNodes:0,resolvedTargetDeltas:0,recalculatedProjection:0};function Fk({attachResizeListener:e,defaultParent:t,measureScroll:n,checkIsScrollRoot:r,resetTransform:i}){return class{constructor(o={},a=t==null?void 0:t()){this.id=OI++,this.animationId=0,this.children=new Set,this.options={},this.isTreeAnimating=!1,this.isAnimationBlocked=!1,this.isLayoutDirty=!1,this.isProjectionDirty=!1,this.isSharedProjectionDirty=!1,this.isTransformDirty=!1,this.updateManuallyBlocked=!1,this.updateBlockedByResize=!1,this.isUpdating=!1,this.isSVG=!1,this.needsReset=!1,this.shouldResetTransform=!1,this.treeScale={x:1,y:1},this.eventHandlers=new Map,this.hasTreeAnimated=!1,this.updateScheduled=!1,this.projectionUpdateScheduled=!1,this.checkUpdateFailed=()=>{this.isUpdating&&(this.isUpdating=!1,this.clearAllSnapshots())},this.updateProjection=()=>{this.projectionUpdateScheduled=!1,Rr.totalNodes=Rr.resolvedTargetDeltas=Rr.recalculatedProjection=0,this.nodes.forEach(BI),this.nodes.forEach(qI),this.nodes.forEach(KI),this.nodes.forEach(HI),LI(Rr)},this.hasProjected=!1,this.isVisible=!0,this.animationProgress=0,this.sharedNodes=new Map,this.latestValues=o,this.root=a?a.root||a:this,this.path=a?[...a.path,a]:[],this.parent=a,this.depth=a?a.depth+1:0;for(let l=0;lthis.root.updateBlockedByResize=!1;e(o,()=>{this.root.updateBlockedByResize=!0,f&&f(),f=jI(d,250),Ya.hasAnimatedSinceResize&&(Ya.hasAnimatedSinceResize=!1,this.nodes.forEach(B0))})}l&&this.root.registerSharedNode(l,this),this.options.animate!==!1&&c&&(l||u)&&this.addEventListener("didUpdate",({delta:f,hasLayoutChanged:d,hasRelativeTargetChanged:h,layout:m})=>{if(this.isTreeAnimationBlocked()){this.target=void 0,this.relativeTarget=void 0;return}const g=this.options.transition||c.getDefaultTransition()||JI,{onLayoutAnimationStart:w,onLayoutAnimationComplete:p}=c.getProps(),x=!this.targetLayout||!zk(this.targetLayout,m)||h,v=!d&&h;if(this.options.layoutRoot||this.resumeFrom&&this.resumeFrom.instance||v||d&&(x||!this.currentAnimation)){this.resumeFrom&&(this.resumingFrom=this.resumeFrom,this.resumingFrom.resumingFrom=void 0),this.setAnimationOrigin(f,v);const k={...ap(g,"layout"),onPlay:w,onComplete:p};(c.shouldReduceMotion||this.options.layoutRoot)&&(k.delay=0,k.type=!1),this.startAnimation(k)}else d||B0(this),this.isLead()&&this.options.onExitComplete&&this.options.onExitComplete();this.targetLayout=m})}unmount(){this.options.layoutId&&this.willUpdate(),this.root.nodes.remove(this);const o=this.getStack();o&&o.remove(this),this.parent&&this.parent.children.delete(this),this.instance=void 0,Vn(this.updateProjection)}blockUpdate(){this.updateManuallyBlocked=!0}unblockUpdate(){this.updateManuallyBlocked=!1}isUpdateBlocked(){return this.updateManuallyBlocked||this.updateBlockedByResize}isTreeAnimationBlocked(){return this.isAnimationBlocked||this.parent&&this.parent.isTreeAnimationBlocked()||!1}startUpdate(){this.isUpdateBlocked()||(this.isUpdating=!0,this.nodes&&this.nodes.forEach(GI),this.animationId++)}getTransformTemplate(){const{visualElement:o}=this.options;return o&&o.getProps().transformTemplate}willUpdate(o=!0){if(this.root.hasTreeAnimated=!0,this.root.isUpdateBlocked()){this.options.onExitComplete&&this.options.onExitComplete();return}if(!this.root.isUpdating&&this.root.startUpdate(),this.isLayoutDirty)return;this.isLayoutDirty=!0;for(let c=0;cthis.update()))}clearAllSnapshots(){this.nodes.forEach(UI),this.sharedNodes.forEach(XI)}scheduleUpdateProjection(){this.projectionUpdateScheduled||(this.projectionUpdateScheduled=!0,be.preRender(this.updateProjection,!1,!0))}scheduleCheckAfterUnmount(){be.postRender(()=>{this.isLayoutDirty?this.root.didUpdate():this.root.checkUpdateFailed()})}updateSnapshot(){this.snapshot||!this.instance||(this.snapshot=this.measure())}updateLayout(){if(!this.instance||(this.updateScroll(),!(this.options.alwaysMeasureLayout&&this.isLead())&&!this.isLayoutDirty))return;if(this.resumeFrom&&!this.resumeFrom.instance)for(let l=0;l{const N=k/1e3;H0(f.x,o.x,N),H0(f.y,o.y,N),this.setTargetDelta(f),this.relativeTarget&&this.relativeTargetOrigin&&this.layout&&this.relativeParent&&this.relativeParent.layout&&(to(d,this.layout.layoutBox,this.relativeParent.layout.layoutBox),QI(this.relativeTarget,this.relativeTargetOrigin,d,N),v&&PI(this.relativeTarget,v)&&(this.isProjectionDirty=!1),v||(v=Ve()),$t(v,this.relativeTarget)),g&&(this.animationValues=c,_I(c,u,this.latestValues,N,x,p)),this.root.scheduleUpdateProjection(),this.scheduleRender(),this.animationProgress=N},this.mixTargetDelta(this.options.layoutRoot?1e3:0)}startAnimation(o){this.notifyListeners("animationStart"),this.currentAnimation&&this.currentAnimation.stop(),this.resumingFrom&&this.resumingFrom.currentAnimation&&this.resumingFrom.currentAnimation.stop(),this.pendingAnimation&&(Vn(this.pendingAnimation),this.pendingAnimation=void 0),this.pendingAnimation=be.update(()=>{Ya.hasAnimatedSinceResize=!0,this.currentAnimation=zI(0,V0,{...o,onUpdate:a=>{this.mixTargetDelta(a),o.onUpdate&&o.onUpdate(a)},onComplete:()=>{o.onComplete&&o.onComplete(),this.completeAnimation()}}),this.resumingFrom&&(this.resumingFrom.currentAnimation=this.currentAnimation),this.pendingAnimation=void 0})}completeAnimation(){this.resumingFrom&&(this.resumingFrom.currentAnimation=void 0,this.resumingFrom.preserveOpacity=void 0);const o=this.getStack();o&&o.exitAnimationComplete(),this.resumingFrom=this.currentAnimation=this.animationValues=void 0,this.notifyListeners("animationComplete")}finishAnimation(){this.currentAnimation&&(this.mixTargetDelta&&this.mixTargetDelta(V0),this.currentAnimation.stop()),this.completeAnimation()}applyTransformsToTarget(){const o=this.getLead();let{targetWithTransforms:a,target:l,layout:u,latestValues:c}=o;if(!(!a||!l||!u)){if(this!==o&&this.layout&&u&&Ok(this.options.animationType,this.layout.layoutBox,u.layoutBox)){l=this.target||Ve();const f=zt(this.layout.layoutBox.x);l.x.min=o.target.x.min,l.x.max=l.x.min+f;const d=zt(this.layout.layoutBox.y);l.y.min=o.target.y.min,l.y.max=l.y.min+d}$t(a,l),Di(a,c),eo(this.projectionDeltaWithTransform,this.layoutCorrected,a,c)}}registerSharedNode(o,a){this.sharedNodes.has(o)||this.sharedNodes.set(o,new MI),this.sharedNodes.get(o).add(a);const u=a.options.initialPromotionConfig;a.promote({transition:u?u.transition:void 0,preserveFollowOpacity:u&&u.shouldPreserveFollowOpacity?u.shouldPreserveFollowOpacity(a):void 0})}isLead(){const o=this.getStack();return o?o.lead===this:!0}getLead(){var o;const{layoutId:a}=this.options;return a?((o=this.getStack())===null||o===void 0?void 0:o.lead)||this:this}getPrevLead(){var o;const{layoutId:a}=this.options;return a?(o=this.getStack())===null||o===void 0?void 0:o.prevLead:void 0}getStack(){const{layoutId:o}=this.options;if(o)return this.root.sharedNodes.get(o)}promote({needsReset:o,transition:a,preserveFollowOpacity:l}={}){const u=this.getStack();u&&u.promote(this,l),o&&(this.projectionDelta=void 0,this.needsReset=!0),a&&this.setOptions({transition:a})}relegate(){const o=this.getStack();return o?o.relegate(this):!1}resetRotation(){const{visualElement:o}=this.options;if(!o)return;let a=!1;const{latestValues:l}=o;if((l.rotate||l.rotateX||l.rotateY||l.rotateZ)&&(a=!0),!a)return;const u={};for(let c=0;c{var a;return(a=o.currentAnimation)===null||a===void 0?void 0:a.stop()}),this.root.nodes.forEach($0),this.root.sharedNodes.clear()}}}function VI(e){e.updateLayout()}function $I(e){var t;const n=((t=e.resumeFrom)===null||t===void 0?void 0:t.snapshot)||e.snapshot;if(e.isLead()&&e.layout&&n&&e.hasListeners("didUpdate")){const{layoutBox:r,measuredBox:i}=e.layout,{animationType:s}=e.options,o=n.source!==e.layout.source;s==="size"?Bt(f=>{const d=o?n.measuredBox[f]:n.layoutBox[f],h=zt(d);d.min=r[f].min,d.max=d.min+h}):Ok(s,n.layoutBox,r)&&Bt(f=>{const d=o?n.measuredBox[f]:n.layoutBox[f],h=zt(r[f]);d.max=d.min+h,e.relativeTarget&&!e.currentAnimation&&(e.isProjectionDirty=!0,e.relativeTarget[f].max=e.relativeTarget[f].min+h)});const a=Mi();eo(a,r,n.layoutBox);const l=Mi();o?eo(l,e.applyTransform(i,!0),n.measuredBox):eo(l,r,n.layoutBox);const u=!Rk(a);let c=!1;if(!e.resumeFrom){const f=e.getClosestProjectingParent();if(f&&!f.resumeFrom){const{snapshot:d,layout:h}=f;if(d&&h){const m=Ve();to(m,n.layoutBox,d.layoutBox);const g=Ve();to(g,r,h.layoutBox),zk(m,g)||(c=!0),f.options.layoutRoot&&(e.relativeTarget=g,e.relativeTargetOrigin=m,e.relativeParent=f)}}}e.notifyListeners("didUpdate",{layout:r,snapshot:n,delta:l,layoutDelta:a,hasLayoutChanged:u,hasRelativeTargetChanged:c})}else if(e.isLead()){const{onExitComplete:r}=e.options;r&&r()}e.options.transition=void 0}function BI(e){Rr.totalNodes++,e.parent&&(e.isProjecting()||(e.isProjectionDirty=e.parent.isProjectionDirty),e.isSharedProjectionDirty||(e.isSharedProjectionDirty=!!(e.isProjectionDirty||e.parent.isProjectionDirty||e.parent.isSharedProjectionDirty)),e.isTransformDirty||(e.isTransformDirty=e.parent.isTransformDirty))}function HI(e){e.isProjectionDirty=e.isSharedProjectionDirty=e.isTransformDirty=!1}function UI(e){e.clearSnapshot()}function $0(e){e.clearMeasurements()}function WI(e){e.isLayoutDirty=!1}function YI(e){const{visualElement:t}=e.options;t&&t.getProps().onBeforeLayoutMeasure&&t.notify("BeforeLayoutMeasure"),e.resetTransform()}function B0(e){e.finishAnimation(),e.targetDelta=e.relativeTarget=e.target=void 0,e.isProjectionDirty=!0}function qI(e){e.resolveTargetDelta()}function KI(e){e.calcProjection()}function GI(e){e.resetRotation()}function XI(e){e.removeLeadSnapshot()}function H0(e,t,n){e.translate=Pe(t.translate,0,n),e.scale=Pe(t.scale,1,n),e.origin=t.origin,e.originPoint=t.originPoint}function U0(e,t,n,r){e.min=Pe(t.min,n.min,r),e.max=Pe(t.max,n.max,r)}function QI(e,t,n,r){U0(e.x,t.x,n.x,r),U0(e.y,t.y,n.y,r)}function ZI(e){return e.animationValues&&e.animationValues.opacityExit!==void 0}const JI={duration:.45,ease:[.4,0,.1,1]},W0=e=>typeof navigator<"u"&&navigator.userAgent.toLowerCase().includes(e),Y0=W0("applewebkit/")&&!W0("chrome/")?Math.round:Fe;function q0(e){e.min=Y0(e.min),e.max=Y0(e.max)}function ej(e){q0(e.x),q0(e.y)}function Ok(e,t,n){return e==="position"||e==="preserve-aspect"&&!ld(z0(t),z0(n),.2)}const tj=Fk({attachResizeListener:(e,t)=>Mn(e,"resize",t),measureScroll:()=>({x:document.documentElement.scrollLeft||document.body.scrollLeft,y:document.documentElement.scrollTop||document.body.scrollTop}),checkIsScrollRoot:()=>!0}),jc={current:void 0},Vk=Fk({measureScroll:e=>({x:e.scrollLeft,y:e.scrollTop}),defaultParent:()=>{if(!jc.current){const e=new tj({});e.mount(window),e.setOptions({layoutScroll:!0}),jc.current=e}return jc.current},resetTransform:(e,t)=>{e.style.transform=t!==void 0?t:"none"},checkIsScrollRoot:e=>window.getComputedStyle(e).position==="fixed"}),nj={pan:{Feature:xI},drag:{Feature:yI,ProjectionNode:Vk,MeasureLayout:Ik}},rj=/var\((--[a-zA-Z0-9-_]+),? ?([a-zA-Z0-9 ()%#.,-]+)?\)/;function ij(e){const t=rj.exec(e);if(!t)return[,];const[,n,r]=t;return[n,r]}function dd(e,t,n=1){const[r,i]=ij(e);if(!r)return;const s=window.getComputedStyle(t).getPropertyValue(r);if(s){const o=s.trim();return bk(o)?parseFloat(o):o}else return td(i)?dd(i,t,n+1):i}function sj(e,{...t},n){const r=e.current;if(!(r instanceof Element))return{target:t,transitionEnd:n};n&&(n={...n}),e.values.forEach(i=>{const s=i.get();if(!td(s))return;const o=dd(s,r);o&&i.set(o)});for(const i in t){const s=t[i];if(!td(s))continue;const o=dd(s,r);o&&(t[i]=o,n||(n={}),n[i]===void 0&&(n[i]=s))}return{target:t,transitionEnd:n}}const oj=new Set(["width","height","top","left","right","bottom","x","y","translateX","translateY"]),$k=e=>oj.has(e),aj=e=>Object.keys(e).some($k),K0=e=>e===ui||e===te,G0=(e,t)=>parseFloat(e.split(", ")[t]),X0=(e,t)=>(n,{transform:r})=>{if(r==="none"||!r)return 0;const i=r.match(/^matrix3d\((.+)\)$/);if(i)return G0(i[1],t);{const s=r.match(/^matrix\((.+)\)$/);return s?G0(s[1],e):0}},lj=new Set(["x","y","z"]),uj=Ho.filter(e=>!lj.has(e));function cj(e){const t=[];return uj.forEach(n=>{const r=e.getValue(n);r!==void 0&&(t.push([n,r.get()]),r.set(n.startsWith("scale")?1:0))}),t.length&&e.render(),t}const rs={width:({x:e},{paddingLeft:t="0",paddingRight:n="0"})=>e.max-e.min-parseFloat(t)-parseFloat(n),height:({y:e},{paddingTop:t="0",paddingBottom:n="0"})=>e.max-e.min-parseFloat(t)-parseFloat(n),top:(e,{top:t})=>parseFloat(t),left:(e,{left:t})=>parseFloat(t),bottom:({y:e},{top:t})=>parseFloat(t)+(e.max-e.min),right:({x:e},{left:t})=>parseFloat(t)+(e.max-e.min),x:X0(4,13),y:X0(5,14)};rs.translateX=rs.x;rs.translateY=rs.y;const fj=(e,t,n)=>{const r=t.measureViewportBox(),i=t.current,s=getComputedStyle(i),{display:o}=s,a={};o==="none"&&t.setStaticValue("display",e.display||"block"),n.forEach(u=>{a[u]=rs[u](r,s)}),t.render();const l=t.measureViewportBox();return n.forEach(u=>{const c=t.getValue(u);c&&c.jump(a[u]),e[u]=rs[u](l,s)}),e},dj=(e,t,n={},r={})=>{t={...t},r={...r};const i=Object.keys(t).filter($k);let s=[],o=!1;const a=[];if(i.forEach(l=>{const u=e.getValue(l);if(!e.hasValue(l))return;let c=n[l],f=_s(c);const d=t[l];let h;if(Tl(d)){const m=d.length,g=d[0]===null?1:0;c=d[g],f=_s(c);for(let w=g;w=0?window.pageYOffset:null,u=fj(t,e,a);return s.length&&s.forEach(([c,f])=>{e.getValue(c).set(f)}),e.render(),gu&&l!==null&&window.scrollTo({top:l}),{target:u,transitionEnd:r}}else return{target:t,transitionEnd:r}};function hj(e,t,n,r){return aj(t)?dj(e,t,n,r):{target:t,transitionEnd:r}}const pj=(e,t,n,r)=>{const i=sj(e,t,r);return t=i.target,r=i.transitionEnd,hj(e,t,n,r)},hd={current:null},Bk={current:!1};function mj(){if(Bk.current=!0,!!gu)if(window.matchMedia){const e=window.matchMedia("(prefers-reduced-motion)"),t=()=>hd.current=e.matches;e.addListener(t),t()}else hd.current=!1}function gj(e,t,n){const{willChange:r}=t;for(const i in t){const s=t[i],o=n[i];if(Ct(s))e.addValue(i,s),Dl(r)&&r.add(i);else if(Ct(o))e.addValue(i,ns(s,{owner:e})),Dl(r)&&r.remove(i);else if(o!==s)if(e.hasValue(i)){const a=e.getValue(i);!a.hasAnimated&&a.set(s)}else{const a=e.getStaticValue(i);e.addValue(i,ns(a!==void 0?a:s,{owner:e}))}}for(const i in n)t[i]===void 0&&e.removeValue(i);return t}const Q0=new WeakMap,Hk=Object.keys(_o),yj=Hk.length,Z0=["AnimationStart","AnimationComplete","Update","BeforeLayoutMeasure","LayoutMeasure","LayoutAnimationStart","LayoutAnimationComplete"],xj=qh.length;class vj{constructor({parent:t,props:n,presenceContext:r,reducedMotionConfig:i,visualState:s},o={}){this.current=null,this.children=new Set,this.isVariantNode=!1,this.isControllingVariants=!1,this.shouldReduceMotion=null,this.values=new Map,this.features={},this.valueSubscriptions=new Map,this.prevMotionValues={},this.events={},this.propEventSubscriptions={},this.notifyUpdate=()=>this.notify("Update",this.latestValues),this.render=()=>{this.current&&(this.triggerBuild(),this.renderInstance(this.current,this.renderState,this.props.style,this.projection))},this.scheduleRender=()=>be.render(this.render,!1,!0);const{latestValues:a,renderState:l}=s;this.latestValues=a,this.baseTarget={...a},this.initialValues=n.initial?{...a}:{},this.renderState=l,this.parent=t,this.props=n,this.presenceContext=r,this.depth=t?t.depth+1:0,this.reducedMotionConfig=i,this.options=o,this.isControllingVariants=xu(n),this.isVariantNode=Nw(n),this.isVariantNode&&(this.variantChildren=new Set),this.manuallyAnimateOnMount=!!(t&&t.current);const{willChange:u,...c}=this.scrapeMotionValuesFromProps(n,{});for(const f in c){const d=c[f];a[f]!==void 0&&Ct(d)&&(d.set(a[f],!1),Dl(u)&&u.add(f))}}scrapeMotionValuesFromProps(t,n){return{}}mount(t){this.current=t,Q0.set(t,this),this.projection&&!this.projection.instance&&this.projection.mount(t),this.parent&&this.isVariantNode&&!this.isControllingVariants&&(this.removeFromVariantTree=this.parent.addVariantChild(this)),this.values.forEach((n,r)=>this.bindToMotionValue(r,n)),Bk.current||mj(),this.shouldReduceMotion=this.reducedMotionConfig==="never"?!1:this.reducedMotionConfig==="always"?!0:hd.current,this.parent&&this.parent.children.add(this),this.update(this.props,this.presenceContext)}unmount(){Q0.delete(this.current),this.projection&&this.projection.unmount(),Vn(this.notifyUpdate),Vn(this.render),this.valueSubscriptions.forEach(t=>t()),this.removeFromVariantTree&&this.removeFromVariantTree(),this.parent&&this.parent.children.delete(this);for(const t in this.events)this.events[t].clear();for(const t in this.features)this.features[t].unmount();this.current=null}bindToMotionValue(t,n){const r=li.has(t),i=n.on("change",o=>{this.latestValues[t]=o,this.props.onUpdate&&be.update(this.notifyUpdate,!1,!0),r&&this.projection&&(this.projection.isTransformDirty=!0)}),s=n.on("renderRequest",this.scheduleRender);this.valueSubscriptions.set(t,()=>{i(),s()})}sortNodePosition(t){return!this.current||!this.sortInstanceNodePosition||this.type!==t.type?0:this.sortInstanceNodePosition(this.current,t.current)}loadFeatures({children:t,...n},r,i,s){let o,a;for(let l=0;lthis.scheduleRender(),animationType:typeof u=="string"?u:"both",initialPromotionConfig:s,layoutScroll:d,layoutRoot:h})}return a}updateFeatures(){for(const t in this.features){const n=this.features[t];n.isMounted?n.update():(n.mount(),n.isMounted=!0)}}triggerBuild(){this.build(this.renderState,this.latestValues,this.options,this.props)}measureViewportBox(){return this.current?this.measureInstanceViewportBox(this.current,this.props):Ve()}getStaticValue(t){return this.latestValues[t]}setStaticValue(t,n){this.latestValues[t]=n}makeTargetAnimatable(t,n=!0){return this.makeTargetAnimatableFromInstance(t,this.props,n)}update(t,n){(t.transformTemplate||this.props.transformTemplate)&&this.scheduleRender(),this.prevProps=this.props,this.props=t,this.prevPresenceContext=this.presenceContext,this.presenceContext=n;for(let r=0;rn.variantChildren.delete(t)}addValue(t,n){n!==this.values.get(t)&&(this.removeValue(t),this.bindToMotionValue(t,n)),this.values.set(t,n),this.latestValues[t]=n.get()}removeValue(t){this.values.delete(t);const n=this.valueSubscriptions.get(t);n&&(n(),this.valueSubscriptions.delete(t)),delete this.latestValues[t],this.removeValueFromRenderState(t,this.renderState)}hasValue(t){return this.values.has(t)}getValue(t,n){if(this.props.values&&this.props.values[t])return this.props.values[t];let r=this.values.get(t);return r===void 0&&n!==void 0&&(r=ns(n,{owner:this}),this.addValue(t,r)),r}readValue(t){var n;return this.latestValues[t]!==void 0||!this.current?this.latestValues[t]:(n=this.getBaseTargetFromProps(this.props,t))!==null&&n!==void 0?n:this.readValueFromInstance(this.current,t,this.options)}setBaseTarget(t,n){this.baseTarget[t]=n}getBaseTarget(t){var n;const{initial:r}=this.props,i=typeof r=="string"||typeof r=="object"?(n=tp(this.props,r))===null||n===void 0?void 0:n[t]:void 0;if(r&&i!==void 0)return i;const s=this.getBaseTargetFromProps(this.props,t);return s!==void 0&&!Ct(s)?s:this.initialValues[t]!==void 0&&i===void 0?void 0:this.baseTarget[t]}on(t,n){return this.events[t]||(this.events[t]=new fp),this.events[t].add(n)}notify(t,...n){this.events[t]&&this.events[t].notify(...n)}}class Uk extends vj{sortInstanceNodePosition(t,n){return t.compareDocumentPosition(n)&2?1:-1}getBaseTargetFromProps(t,n){return t.style?t.style[n]:void 0}removeValueFromRenderState(t,{vars:n,style:r}){delete n[t],delete r[t]}makeTargetAnimatableFromInstance({transition:t,transitionEnd:n,...r},{transformValues:i},s){let o=RD(r,t||{},this);if(i&&(n&&(n=i(n)),r&&(r=i(r)),o&&(o=i(o))),s){jD(this,r,o);const a=pj(this,r,o,n);n=a.transitionEnd,r=a.target}return{transition:t,transitionEnd:n,...r}}}function wj(e){return window.getComputedStyle(e)}class kj extends Uk{constructor(){super(...arguments),this.type="html"}readValueFromInstance(t,n){if(li.has(n)){const r=op(n);return r&&r.default||0}else{const r=wj(t),i=(Mw(n)?r.getPropertyValue(n):r[n])||0;return typeof i=="string"?i.trim():i}}measureInstanceViewportBox(t,{transformPagePoint:n}){return Mk(t,n)}build(t,n,r,i){Xh(t,n,r,i.transformTemplate)}scrapeMotionValuesFromProps(t,n){return ep(t,n)}handleChildMotionValue(){this.childSubscription&&(this.childSubscription(),delete this.childSubscription);const{children:t}=this.props;Ct(t)&&(this.childSubscription=t.on("change",n=>{this.current&&(this.current.textContent=`${n}`)}))}renderInstance(t,n,r,i){zw(t,n,r,i)}}class bj extends Uk{constructor(){super(...arguments),this.type="svg",this.isSVGTag=!1}getBaseTargetFromProps(t,n){return t[n]}readValueFromInstance(t,n){if(li.has(n)){const r=op(n);return r&&r.default||0}return n=Fw.has(n)?n:Wh(n),t.getAttribute(n)}measureInstanceViewportBox(){return Ve()}scrapeMotionValuesFromProps(t,n){return Vw(t,n)}build(t,n,r,i){Zh(t,n,r,this.isSVGTag,i.transformTemplate)}renderInstance(t,n,r,i){Ow(t,n,r,i)}mount(t){this.isSVGTag=Jh(t.tagName),super.mount(t)}}const Sj=(e,t)=>Gh(e)?new bj(t,{enableHardwareAcceleration:!1}):new kj(t,{enableHardwareAcceleration:!0}),_j={layout:{ProjectionNode:Vk,MeasureLayout:Ik}},Cj={...QD,...yM,...nj,..._j},Wk=EP((e,t)=>iM(e,t,Cj,Sj));function Yk(){const e=T.useRef(!1);return Uh(()=>(e.current=!0,()=>{e.current=!1}),[]),e}function Ej(){const e=Yk(),[t,n]=T.useState(0),r=T.useCallback(()=>{e.current&&n(t+1)},[t]);return[T.useCallback(()=>be.postRender(r),[r]),t]}class Nj extends T.Component{getSnapshotBeforeUpdate(t){const n=this.props.childRef.current;if(n&&t.isPresent&&!this.props.isPresent){const r=this.props.sizeRef.current;r.height=n.offsetHeight||0,r.width=n.offsetWidth||0,r.top=n.offsetTop,r.left=n.offsetLeft}return null}componentDidUpdate(){}render(){return this.props.children}}function Tj({children:e,isPresent:t}){const n=T.useId(),r=T.useRef(null),i=T.useRef({width:0,height:0,top:0,left:0});return T.useInsertionEffect(()=>{const{width:s,height:o,top:a,left:l}=i.current;if(t||!r.current||!s||!o)return;r.current.dataset.motionPopId=n;const u=document.createElement("style");return document.head.appendChild(u),u.sheet&&u.sheet.insertRule(` - [data-motion-pop-id="${n}"] { - position: absolute !important; - width: ${s}px !important; - height: ${o}px !important; - top: ${a}px !important; - left: ${l}px !important; - } - `),()=>{document.head.removeChild(u)}},[t]),T.createElement(Nj,{isPresent:t,childRef:r,sizeRef:i},T.cloneElement(e,{ref:r}))}const Lc=({children:e,initial:t,isPresent:n,onExitComplete:r,custom:i,presenceAffectsLayout:s,mode:o})=>{const a=$w(Aj),l=T.useId(),u=T.useMemo(()=>({id:l,initial:t,isPresent:n,custom:i,onExitComplete:c=>{a.set(c,!0);for(const f of a.values())if(!f)return;r&&r()},register:c=>(a.set(c,!1),()=>a.delete(c))}),s?void 0:[n]);return T.useMemo(()=>{a.forEach((c,f)=>a.set(f,!1))},[n]),T.useEffect(()=>{!n&&!a.size&&r&&r()},[n]),o==="popLayout"&&(e=T.createElement(Tj,{isPresent:n},e)),T.createElement(mu.Provider,{value:u},e)};function Aj(){return new Map}function Pj(e){return T.useEffect(()=>()=>e(),[])}const zr=e=>e.key||"";function Mj(e,t){e.forEach(n=>{const r=zr(n);t.set(r,n)})}function Dj(e){const t=[];return T.Children.forEach(e,n=>{T.isValidElement(n)&&t.push(n)}),t}const qk=({children:e,custom:t,initial:n=!0,onExitComplete:r,exitBeforeEnter:i,presenceAffectsLayout:s=!0,mode:o="sync"})=>{const a=T.useContext(Kh).forceRender||Ej()[0],l=Yk(),u=Dj(e);let c=u;const f=T.useRef(new Map).current,d=T.useRef(c),h=T.useRef(new Map).current,m=T.useRef(!0);if(Uh(()=>{m.current=!1,Mj(u,h),d.current=c}),Pj(()=>{m.current=!0,h.clear(),f.clear()}),m.current)return T.createElement(T.Fragment,null,c.map(x=>T.createElement(Lc,{key:zr(x),isPresent:!0,initial:n?void 0:!1,presenceAffectsLayout:s,mode:o},x)));c=[...c];const g=d.current.map(zr),w=u.map(zr),p=g.length;for(let x=0;x{if(w.indexOf(v)!==-1)return;const k=h.get(v);if(!k)return;const N=g.indexOf(v);let S=x;if(!S){const A=()=>{f.delete(v);const P=Array.from(h.keys()).filter(D=>!w.includes(D));if(P.forEach(D=>h.delete(D)),d.current=u.filter(D=>{const C=zr(D);return C===v||P.includes(C)}),!f.size){if(l.current===!1)return;a(),r&&r()}};S=T.createElement(Lc,{key:zr(k),isPresent:!1,onExitComplete:A,custom:t,presenceAffectsLayout:s,mode:o},k),f.set(v,S)}c.splice(N,0,S)}),c=c.map(x=>{const v=x.key;return f.has(v)?x:T.createElement(Lc,{key:zr(x),isPresent:!0,presenceAffectsLayout:s,mode:o},x)}),T.createElement(T.Fragment,null,f.size?c:c.map(x=>T.cloneElement(x)))},Ij=e=>{try{return new Intl.DateTimeFormat("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit"}).format(e)}catch{return""}},jj=e=>{if(!e)return"bg-slate-500/30 text-slate-200 border border-white/10";const t=e.toLowerCase();return["finish","completed","success","ready"].some(n=>t.includes(n))?"bg-emerald-500/20 text-emerald-200 border border-emerald-400/30":["fail","error"].some(n=>t.includes(n))?"bg-rose-500/20 text-rose-200 border border-rose-400/30":["continue","running","in_progress"].some(n=>t.includes(n))?"bg-amber-500/20 text-amber-100 border border-amber-400/30":"bg-slate-500/30 text-slate-200 border border-white/10"},Dr=({title:e,icon:t,children:n})=>y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4 text-sm text-slate-200",children:[y.jsxs("div",{className:"mb-2 flex items-center gap-2 text-[12px] uppercase tracking-[0.18em] text-slate-400",children:[y.jsx("span",{className:"inline-flex h-7 w-7 items-center justify-center rounded-full bg-white/10 text-slate-200 shadow-[0_0_12px_rgba(33,240,255,0.25)]",children:t}),e]}),y.jsx("div",{className:"space-y-2 whitespace-pre-wrap text-sm leading-relaxed text-slate-200",children:n})]}),Lj=e=>{const t=e==null?void 0:e.function,n=(e==null?void 0:e.arguments)||{};if(!t)return(e==null?void 0:e.action)||(e==null?void 0:e.command)||"Unknown Action";switch(t){case"add_task":{const r=n.task_id||"?",i=n.name||"";return i?`Add Task: '${r}' (${i})`:`Add Task: '${r}'`}case"remove_task":return`Remove Task: '${n.task_id||"?"}'`;case"update_task":{const r=n.task_id||"?",i=Object.keys(n).filter(o=>o!=="task_id"&&n[o]!==null&&n[o]!==void 0),s=i.length>0?i.join(", "):"fields";return`Update Task: '${r}' (${s})`}case"add_dependency":{const r=n.dependency_id||"?",i=n.from_task_id||"?",s=n.to_task_id||"?";return`Add Dependency (ID ${r}): ${i} → ${s}`}case"remove_dependency":return`Remove Dependency: '${n.dependency_id||"?"}'`;case"update_dependency":return`Update Dependency: '${n.dependency_id||"?"}'`;case"build_constellation":{const r=n.config||{};if(n.task_count!==void 0||n.dependency_count!==void 0){const i=n.task_count||0,s=n.dependency_count||0;return`Build Constellation (${i} tasks, ${s} dependencies)`}if(typeof r=="object"&&r!==null){const i=Array.isArray(r.tasks)?r.tasks.length:0,s=Array.isArray(r.dependencies)?r.dependencies.length:0;return`Build Constellation (${i} tasks, ${s} dependencies)`}return"Build Constellation"}case"clear_constellation":return"Clear Constellation (remove all tasks)";case"load_constellation":{const r=n.file_path||"?";return`Load Constellation from '${r.split(/[/\\]/).pop()||r}'`}case"save_constellation":{const r=n.file_path||"?";return`Save Constellation to '${r.split(/[/\\]/).pop()||r}'`}default:{const r=Object.entries(n).slice(0,2);if(r.length>0){const i=r.map(([s,o])=>`${s}=${o}`).join(", ");return`${t}(${i})`}return t}}},Rj=e=>{if(!e)return y.jsx(ic,{className:"h-3.5 w-3.5"});const t=e.toLowerCase();return["finish","completed","success","ready"].some(n=>t.includes(n))?y.jsx(Kr,{className:"h-3.5 w-3.5"}):["fail","error"].some(n=>t.includes(n))?y.jsx(Oo,{className:"h-3.5 w-3.5"}):["continue","running","in_progress"].some(n=>t.includes(n))?y.jsx(ic,{className:"h-3.5 w-3.5"}):y.jsx(ic,{className:"h-3.5 w-3.5"})},zj=({action:e,isLast:t,isExpanded:n,onToggle:r})=>{var u,c,f,d;const i=((u=e==null?void 0:e.result)==null?void 0:u.status)||(e==null?void 0:e.status)||((c=e==null?void 0:e.arguments)==null?void 0:c.status),s=((f=e==null?void 0:e.result)==null?void 0:f.error)||((d=e==null?void 0:e.result)==null?void 0:d.message),o=i&&String(i).toLowerCase()==="continue",a=Lj(e),l=()=>{if(!i)return"text-slate-400";const h=i.toLowerCase();return["finish","completed","success","ready"].some(m=>h.includes(m))?"text-emerald-400":["fail","error"].some(m=>h.includes(m))?"text-rose-400":["continue","running","in_progress"].some(m=>h.includes(m))?"text-amber-400":"text-slate-400"};return y.jsxs("div",{className:"relative",children:[y.jsxs("div",{className:"absolute left-0 top-0 flex h-full w-6",children:[y.jsx("div",{className:"w-px bg-white/10"}),!t&&y.jsx("div",{className:"absolute left-0 top-7 h-[calc(100%-1.75rem)] w-px bg-white/10"})]}),y.jsx("div",{className:"ml-6 pb-3",children:y.jsxs("div",{className:"flex items-start gap-2",children:[y.jsx("div",{className:"mt-3 h-px w-3 flex-shrink-0 bg-white/10"}),y.jsxs("div",{className:"flex-1 min-w-0",children:[y.jsxs("button",{onClick:r,className:"group flex w-full items-center gap-2 rounded-lg border border-white/5 bg-white/5 px-3 py-2 text-left text-sm transition hover:border-white/20 hover:bg-white/10",children:[y.jsx("span",{className:we("flex-shrink-0",l()),children:Rj(i)}),y.jsx("span",{className:"flex-1 truncate font-medium text-slate-200",children:a}),!o&&(e.arguments||s)&&y.jsx(Vf,{className:we("h-3.5 w-3.5 flex-shrink-0 text-slate-400 transition-transform",n&&"rotate-180")})]}),n&&!o&&y.jsxs("div",{className:"mt-2 space-y-2 rounded-lg border border-white/5 bg-black/20 p-3",children:[i&&y.jsxs("div",{children:[y.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-slate-400",children:"Status"}),y.jsx("div",{className:we("text-sm font-medium",l()),children:String(i).toUpperCase()})]}),e.arguments&&y.jsxs("div",{children:[y.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-slate-400",children:"Arguments"}),y.jsx("pre",{className:"whitespace-pre-wrap rounded-lg border border-white/5 bg-black/30 p-2 text-xs text-slate-300",children:JSON.stringify(e.arguments,null,2)})]}),y.jsxs("div",{children:[y.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-slate-400",children:"Full Action Object (Debug)"}),y.jsx("pre",{className:"whitespace-pre-wrap rounded-lg border border-white/5 bg-black/30 p-2 text-xs text-slate-300",children:JSON.stringify(e,null,2)})]}),s&&y.jsxs("div",{className:"rounded-lg border border-rose-400/30 bg-rose-500/10 p-2",children:[y.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-rose-300",children:"Error"}),y.jsx("div",{className:"text-xs text-rose-100",children:String(s)})]})]})]})]})})]})},Fj=({message:e,nextMessage:t,stepNumber:n})=>{const[r,i]=T.useState(!1),[s,o]=T.useState(!1),[a,l]=T.useState(new Set),u=e.role==="user",c=e.kind==="action",f=e.kind==="response"?e.payload:void 0,d=!!e.payload&&(c||e.kind==="system"),h=T.useMemo(()=>Ij(e.timestamp),[e.timestamp]),m=T.useMemo(()=>u?"You":e.agentName?e.agentName.toLowerCase().includes("constellation")?"UFO":e.agentName:"UFO",[u,e.agentName]),g=f==null?void 0:f.status,w=e.kind==="response"&&(t==null?void 0:t.kind)==="action",p=w?t==null?void 0:t.payload:void 0;if(c)return null;const x=()=>{e.payload&&sn().send({type:"replay_action",timestamp:Date.now(),payload:e.payload})};return y.jsxs("div",{className:we("flex w-full flex-col gap-2 transition-all",{"items-end":u,"items-start":!u}),children:[y.jsxs("div",{className:we("w-[88%] rounded-3xl border px-6 py-5 shadow-xl sm:w-[74%]",u?"rounded-br-xl border-galaxy-blue/50 bg-gradient-to-br from-galaxy-blue/25 via-galaxy-purple/25 to-galaxy-blue/15 text-slate-50 shadow-[0_0_30px_rgba(15,123,255,0.2),inset_0_1px_0_rgba(147,197,253,0.15)]":"rounded-bl-xl border-[rgba(10,186,181,0.35)] bg-gradient-to-br from-[rgba(10,186,181,0.12)] via-[rgba(12,50,65,0.8)] to-[rgba(11,30,45,0.85)] text-slate-100 shadow-[0_0_25px_rgba(10,186,181,0.18),inset_0_1px_0_rgba(10,186,181,0.12)]"),children:[!u&&y.jsxs("div",{className:"mb-4 flex items-center justify-between gap-3",children:[y.jsxs("div",{className:"flex items-center gap-3",children:[y.jsx("div",{className:"flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-cyan-500/20 to-blue-500/20 border border-cyan-400/30 shadow-lg",children:y.jsx(XC,{className:"h-5 w-5 text-cyan-300","aria-hidden":!0})}),y.jsxs("div",{className:"flex flex-col gap-0.5",children:[y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsx("span",{className:"font-bold text-base text-slate-100",children:m}),n!==void 0&&y.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-gradient-to-r from-cyan-500/20 to-blue-500/20 border border-cyan-400/30 px-2 py-0.5 text-[10px] font-semibold text-cyan-300",children:[y.jsx("span",{className:"opacity-70",children:"STEP"}),y.jsx("span",{children:n})]})]}),y.jsx("span",{className:"text-[10px] text-slate-400",children:h})]})]}),y.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[y.jsxs("span",{className:"inline-flex items-center gap-1.5 rounded-lg border border-white/10 bg-white/5 px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider text-slate-300",children:[y.jsx($f,{className:"h-3 w-3","aria-hidden":!0}),e.kind]}),g&&y.jsxs("span",{className:we("inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider",jj(g)),children:[y.jsx(Kr,{className:"h-3 w-3","aria-hidden":!0}),String(g).toUpperCase()]})]})]}),u&&y.jsx("div",{className:"mb-4 flex items-center justify-between gap-3",children:y.jsxs("div",{className:"flex items-center gap-3",children:[y.jsx("div",{className:"flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-purple-500/20 to-pink-500/20 border border-purple-400/30 shadow-lg",children:y.jsx(qC,{className:"h-5 w-5 text-purple-300","aria-hidden":!0})}),y.jsxs("div",{className:"flex flex-col gap-0.5",children:[y.jsx("span",{className:"font-bold text-base text-slate-100",children:m}),y.jsx("span",{className:"text-[10px] text-slate-400",children:h})]})]})}),e.kind==="response"&&f?y.jsxs("div",{className:"space-y-4",children:[f.thought&&y.jsx(Dr,{title:"Thought",icon:y.jsx(TC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:(()=>{const v=String(f.thought),k=100;if(!(v.length>k))return y.jsx("p",{children:v});let S=k;const A=[". ",`. -`,"! ",`! -`,"? ",`? -`];for(const P of A){const D=v.lastIndexOf(P,k);if(D>k*.7){S=D+P.length;break}}return y.jsxs("div",{children:[y.jsx("p",{children:s?v:v.substring(0,S).trim()+"..."}),y.jsx("button",{onClick:()=>o(!s),className:"mt-2 inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs text-slate-300 transition hover:border-white/30 hover:bg-white/10",children:s?y.jsxs(y.Fragment,{children:[y.jsx(Ym,{className:"h-3 w-3","aria-hidden":!0}),"Show less"]}):y.jsxs(y.Fragment,{children:[y.jsx(Vf,{className:"h-3 w-3","aria-hidden":!0}),"Show more (",v.length," chars)"]})})]})})()}),f.plan&&y.jsx(Dr,{title:"Plan",icon:y.jsx(RC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:Array.isArray(f.plan)?y.jsx("ul",{className:"space-y-1 text-sm",children:f.plan.map((v,k)=>y.jsxs("li",{className:"flex items-start gap-2 text-slate-200",children:[y.jsx("span",{className:"mt-[2px] h-2 w-2 rounded-full bg-galaxy-blue","aria-hidden":!0}),y.jsx("span",{children:v})]},k))}):y.jsx("p",{children:f.plan})}),f.decomposition_strategy&&y.jsx(Dr,{title:"Decomposition",icon:y.jsx(PC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("p",{children:f.decomposition_strategy})}),f.ask_details&&y.jsx(Dr,{title:"Ask Details",icon:y.jsx(uv,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("pre",{className:"whitespace-pre-wrap text-xs text-slate-200/90",children:JSON.stringify(f.ask_details,null,2)})}),f.actions_summary&&y.jsx(Dr,{title:"Action Summary",icon:y.jsx($f,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("p",{children:f.actions_summary})}),(f.response||f.final_response)&&y.jsx(Dr,{title:"Response",icon:y.jsx(Kr,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("p",{children:f.final_response||f.response})}),f.validation&&y.jsx(Dr,{title:"Validation",icon:y.jsx(CC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("pre",{className:"whitespace-pre-wrap text-xs text-slate-200/90",children:JSON.stringify(f.validation,null,2)})}),!(f.thought||f.plan||f.actions_summary||f.response||f.final_response)&&y.jsx("div",{className:"prose prose-invert max-w-none text-sm leading-relaxed prose-headings:text-slate-100 prose-p:mb-3 prose-p:text-slate-200 prose-pre:bg-slate-900/80 prose-strong:text-slate-100",children:y.jsx($g,{remarkPlugins:[Gg],children:e.content})}),f.results&&g&&String(g).toLowerCase()!=="continue"&&y.jsxs("div",{className:we("mt-6 rounded-2xl border-2 p-6 shadow-xl",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"border-rose-500/50 bg-gradient-to-br from-rose-500/15 to-rose-600/8":"border-emerald-500/50 bg-gradient-to-br from-emerald-500/15 to-emerald-600/8"),children:[y.jsxs("div",{className:"mb-4 flex items-center gap-3",children:[y.jsx("div",{className:we("flex h-10 w-10 items-center justify-center rounded-xl shadow-lg",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"bg-gradient-to-br from-rose-500/35 to-rose-600/25 border border-rose-400/40":"bg-gradient-to-br from-emerald-500/35 to-emerald-600/25 border border-emerald-400/40"),children:String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?y.jsx(Oo,{className:"h-5 w-5 text-rose-300","aria-hidden":!0}):y.jsx(Kr,{className:"h-5 w-5 text-emerald-300","aria-hidden":!0})}),y.jsxs("div",{children:[y.jsx("h3",{className:we("text-base font-bold uppercase tracking-wider",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"text-rose-200":"text-emerald-200"),children:"Final Results"}),y.jsxs("p",{className:"text-xs text-slate-400 mt-0.5",children:["Status: ",String(g).toUpperCase()]})]})]}),y.jsx("div",{className:we("rounded-xl border p-4",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"border-rose-400/20 bg-rose-950/30":"border-emerald-400/20 bg-emerald-950/30"),children:typeof f.results=="string"?y.jsx("div",{className:we("whitespace-pre-wrap text-sm leading-relaxed",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"text-rose-100/90":"text-emerald-100/90"),children:f.results}):y.jsx("pre",{className:we("whitespace-pre-wrap text-sm leading-relaxed",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"text-rose-100/90":"text-emerald-100/90"),children:JSON.stringify(f.results,null,2)})})]})]}):y.jsx("div",{className:"prose prose-invert max-w-none text-sm leading-relaxed prose-headings:text-slate-100 prose-p:mb-3 prose-p:text-slate-200 prose-pre:bg-slate-900/80 prose-strong:text-slate-100",children:y.jsx($g,{remarkPlugins:[Gg],children:e.content})}),(d||c)&&y.jsxs("div",{className:"mt-5 flex items-center gap-3 text-xs text-slate-300",children:[c&&y.jsxs("button",{type:"button",onClick:x,className:"inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1 transition hover:border-white/30 hover:bg-white/10",children:[y.jsx(lv,{className:"h-3 w-3","aria-hidden":!0}),"Replay"]}),d&&y.jsxs("button",{type:"button",onClick:()=>i(v=>!v),className:"inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-3 py-1 transition hover:border-white/30 hover:bg-white/10",children:[r?"Hide JSON":"View JSON",r?y.jsx(Ym,{className:"h-3 w-3","aria-hidden":!0}):y.jsx(Vf,{className:"h-3 w-3","aria-hidden":!0})]})]}),y.jsx(qk,{initial:!1,children:d&&r&&y.jsx(Wk.pre,{initial:{height:0,opacity:0},animate:{height:"auto",opacity:1},exit:{height:0,opacity:0},transition:{duration:.2},className:"mt-3 max-h-80 overflow-auto rounded-xl border border-white/10 bg-black/40 p-4 text-xs text-cyan-100",children:JSON.stringify(e.payload,null,2)})})]}),w&&p&&Array.isArray(p.actions)&&p.actions.length>0&&y.jsx("div",{className:"ml-12 w-[calc(88%-3rem)] sm:w-[calc(74%-3rem)]",children:p.actions.map((v,k)=>y.jsx(zj,{action:v,index:k,isLast:k===p.actions.length-1,isExpanded:a.has(k),onToggle:()=>{const N=new Set(a);N.has(k)?N.delete(k):N.add(k),l(N)}},k))})]})},Oj=[{label:"/reset",description:"Reset the current session state."},{label:"/replay",description:"Start next session and replay last request."}],Vj=()=>{const[e,t]=T.useState(""),[n,r]=T.useState(!1),{connected:i,session:s,ui:o,toggleComposerShortcuts:a,resetSessionState:l,messages:u,setTaskRunning:c,stopCurrentTask:f}=Ce(g=>({connected:g.connected,session:g.session,ui:g.ui,toggleComposerShortcuts:g.toggleComposerShortcuts,resetSessionState:g.resetSessionState,messages:g.messages,setTaskRunning:g.setTaskRunning,stopCurrentTask:g.stopCurrentTask})),d=T.useCallback(g=>{switch(g){case"/reset":return sn().sendReset(),l(),!0;case"/replay":{const w=[...u].reverse().find(p=>p.role==="user");return w?(sn().send({type:"next_session",timestamp:Date.now()}),l(),setTimeout(()=>{sn().sendRequest(w.content);const p=Ce.getState(),x=p.ensureSession(s.id,s.displayName),v=es();p.addMessage({id:v,sessionId:x,role:"user",kind:"user",author:"You",content:w.content,timestamp:Date.now(),status:"sent"})},500),!0):(console.warn("No previous user message to replay"),!0)}default:return!1}},[l,u,s.id,s.displayName]),h=T.useCallback(async()=>{const g=e.trim();if(!g||!i)return;if(g.startsWith("/")&&d(g.toLowerCase())){t("");return}const w=Ce.getState(),p=w.ensureSession(s.id,s.displayName),x=es();if(w.addMessage({id:x,sessionId:p,role:"user",kind:"user",author:"You",content:g,timestamp:Date.now(),status:"sent"}),Object.keys(w.constellations).length>0){const k=`temp-${Date.now()}`;w.upsertConstellation({id:k,name:"Loading...",status:"pending",description:"Waiting for constellation to be created...",taskIds:[],dag:{nodes:[],edges:[]},statistics:{total:0,pending:0,running:0,completed:0,failed:0},createdAt:Date.now()}),w.setActiveConstellation(k),console.log("📊 Created temporary constellation for new request")}r(!0),c(!0);try{sn().sendRequest(g)}catch(k){console.error("Failed to send request",k),w.updateMessage(x,{status:"error"}),c(!1)}finally{t(""),r(!1)}},[i,e,d,s.displayName,s.id,c]),m=g=>{if(o.isTaskRunning){g.key==="Enter"&&g.preventDefault();return}g.key==="Enter"&&!g.shiftKey&&(g.preventDefault(),h())};return y.jsx("div",{className:"relative rounded-[30px] border border-white/10 bg-gradient-to-br from-[rgba(11,24,44,0.82)] to-[rgba(8,15,28,0.75)] p-4 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(15,123,255,0.12),inset_0_1px_1px_rgba(255,255,255,0.06)] ring-1 ring-inset ring-white/5",children:y.jsxs("div",{className:"relative",children:[y.jsx("textarea",{value:e,onChange:g=>t(g.target.value),onKeyDown:m,placeholder:i?"Ask Galaxy to orchestrate a new mission…":"Waiting for connection…",rows:3,className:"w-full resize-none rounded-3xl border border-white/5 bg-black/40 px-5 py-4 text-sm text-slate-100 placeholder:text-slate-500 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus:border-white/15 focus:outline-none focus:ring-1 focus:ring-white/10 focus:shadow-[0_0_8px_rgba(15,123,255,0.08),inset_0_2px_8px_rgba(0,0,0,0.3)]",disabled:!i||n||o.isTaskRunning}),y.jsxs("div",{className:"mt-3 flex items-center justify-between gap-2 text-xs text-slate-400",children:[y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsxs("button",{type:"button",onClick:()=>a(),className:"inline-flex items-center gap-2 rounded-full border border-white/10 px-3 py-1 hover:border-white/30",children:[y.jsx(KC,{className:"h-3 w-3","aria-hidden":!0}),"Shortcuts"]}),o.showComposerShortcuts&&y.jsx(y.Fragment,{children:Oj.map(g=>y.jsx("button",{type:"button",onClick:()=>{t(g.label),a()},title:g.description,className:"rounded-full border border-white/10 bg-black/30 px-3 py-1 text-xs font-medium text-slate-200 transition hover:border-white/30 hover:bg-black/40",children:g.label},g.label))})]}),y.jsx("button",{type:"button",onClick:o.isTaskRunning?f:h,disabled:!i||!o.isTaskRunning&&e.trim().length===0||n,className:we("inline-flex items-center gap-2 rounded-full px-4 py-2 text-sm font-semibold text-white transition-all duration-300",o.isTaskRunning?"bg-gradient-to-br from-[rgba(80,20,30,0.75)] via-[rgba(100,25,35,0.70)] to-[rgba(80,20,30,0.75)] hover:from-[rgba(100,25,35,0.85)] hover:via-[rgba(120,30,40,0.80)] hover:to-[rgba(100,25,35,0.85)] border border-rose-900/40 hover:border-rose-800/50 shadow-[0_0_16px_rgba(139,0,0,0.25),0_4px_12px_rgba(0,0,0,0.4),inset_0_1px_1px_rgba(255,255,255,0.08)]":"bg-gradient-to-br from-[rgba(6,182,212,0.85)] via-[rgba(147,51,234,0.80)] to-[rgba(236,72,153,0.85)] hover:from-[rgba(6,182,212,0.95)] hover:via-[rgba(147,51,234,0.90)] hover:to-[rgba(236,72,153,0.95)] border border-cyan-400/30 hover:border-purple-400/40 shadow-[0_0_20px_rgba(6,182,212,0.3),0_0_30px_rgba(147,51,234,0.2),0_4px_16px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(255,255,255,0.15),inset_0_-1px_2px_rgba(0,0,0,0.2)] active:scale-95 active:shadow-[0_0_15px_rgba(6,182,212,0.4),0_2px_8px_rgba(0,0,0,0.4)]",(!i||!o.isTaskRunning&&e.trim().length===0||n)&&"opacity-50 grayscale"),children:n?y.jsxs(y.Fragment,{children:[y.jsx(ou,{className:"h-4 w-4 animate-spin","aria-hidden":!0}),"Sending"]}):o.isTaskRunning?y.jsxs(y.Fragment,{children:[y.jsx(UC,{className:"h-4 w-4","aria-hidden":!0}),"Stop"]}):y.jsxs(y.Fragment,{children:[y.jsx(VC,{className:"h-4 w-4","aria-hidden":!0}),"Launch"]})})]})]})})},$j=(e,t,n)=>{const r=t.toLowerCase().trim();return e.filter(i=>n==="all"||i.kind===n?r?[i.content,i.agentName,i.role].filter(Boolean).map(a=>String(a).toLowerCase()).join(" ").includes(r):!0:!1)},Bj=()=>{const{messages:e,searchQuery:t,messageKind:n,isTaskStopped:r}=Ce(l=>({messages:l.messages,searchQuery:l.ui.searchQuery,messageKind:l.ui.messageKindFilter,isTaskStopped:l.ui.isTaskStopped}),Oe),i=T.useRef(null),s=T.useMemo(()=>$j(e,t,n),[e,n,t]),o=T.useMemo(()=>{const l=new Map;let u=0;return s.forEach(c=>{c.role==="user"?u=0:c.kind!=="action"&&(u++,l.set(c.id,u))}),l},[s]),a=T.useMemo(()=>{var u,c,f;if(e.length===0)return!1;const l=e[e.length-1];if(l.role==="user"||l.role==="assistant"&&l.kind==="action")return!0;if(l.role==="assistant"&&l.kind==="response"){const d=String(((u=l.payload)==null?void 0:u.status)||((f=(c=l.payload)==null?void 0:c.result)==null?void 0:f.status)||"").toLowerCase();if(d==="continue"||d==="running"||d==="pending"||d==="")return!0}return!1},[e]);return T.useEffect(()=>{i.current&&i.current.scrollTo({top:i.current.scrollHeight,behavior:"smooth"})},[s.length]),y.jsxs("div",{className:"flex h-full min-h-0 flex-col gap-4",children:[y.jsx(NE,{}),y.jsx("div",{ref:i,className:"flex-1 overflow-y-auto rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-6 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(15,123,255,0.15),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:y.jsx("div",{className:"flex flex-col gap-5",children:s.length===0?y.jsxs("div",{className:"flex h-full flex-col items-center justify-center gap-3 text-center text-slate-400",children:[y.jsx("span",{className:"text-3xl",children:"✨"}),y.jsx("p",{className:"max-w-sm text-sm",children:"Ready to launch. Describe a mission for the Galaxy Agent, or use quick commands below to explore diagnostics."})]}):y.jsxs(y.Fragment,{children:[s.map((l,u)=>y.jsx(Fj,{message:l,nextMessage:s[u+1],stepNumber:o.get(l.id)},l.id)),a&&!r&&y.jsxs("div",{className:"ml-14 flex items-center gap-2 rounded-xl border border-cyan-500/30 bg-gradient-to-r from-cyan-950/30 to-blue-950/20 px-4 py-2.5 shadow-[0_0_20px_rgba(6,182,212,0.15)]",children:[y.jsx(ou,{className:"h-3.5 w-3.5 animate-spin text-cyan-400"}),y.jsx("span",{className:"text-xs font-medium text-cyan-300/90",children:"UFO is thinking..."})]}),r&&y.jsxs("div",{className:"ml-14 flex items-center gap-2 rounded-xl border border-purple-400/20 bg-gradient-to-r from-purple-950/20 to-indigo-950/15 px-4 py-2.5 shadow-[0_0_16px_rgba(147,51,234,0.08)]",children:[y.jsx("div",{className:"h-2 w-2 rounded-full bg-purple-300/80 animate-pulse"}),y.jsx("span",{className:"text-xs font-medium text-purple-200/80",children:"Task stopped by user. Ready for new mission."})]})]})})}),y.jsx(Vj,{})]})},Hj=()=>{const{session:e,resetSessionState:t}=Ce(i=>({session:i.session,resetSessionState:i.resetSessionState})),n=()=>{sn().sendReset(),t()},r=()=>{sn().send({type:"next_session",timestamp:Date.now()}),t()};return y.jsxs("div",{className:"flex flex-col gap-4 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-5 text-sm text-slate-100 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(6,182,212,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:[y.jsx("div",{className:"flex items-start justify-start",children:y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsx($f,{className:"h-5 w-5 text-cyan-400 drop-shadow-[0_0_8px_rgba(6,182,212,0.5)]","aria-hidden":!0}),y.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:e.displayName})]})}),y.jsxs("div",{className:"grid grid-cols-1 gap-3",children:[y.jsxs("button",{type:"button",onClick:n,className:"flex items-center gap-3 rounded-2xl border border-[rgba(10,186,181,0.4)] bg-gradient-to-r from-[rgba(10,186,181,0.15)] to-[rgba(6,182,212,0.15)] px-4 py-3 shadow-[0_4px_16px_rgba(0,0,0,0.25),0_0_15px_rgba(10,186,181,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)] transition-all duration-200 hover:border-[rgba(10,186,181,0.6)] hover:from-[rgba(10,186,181,0.25)] hover:to-[rgba(6,182,212,0.25)] hover:shadow-[0_8px_24px_rgba(0,0,0,0.3),0_0_25px_rgba(10,186,181,0.3)]",children:[y.jsx(lv,{className:"h-4 w-4 text-[rgb(10,186,181)]","aria-hidden":!0}),y.jsxs("div",{className:"text-left",children:[y.jsx("div",{className:"text-sm font-medium text-white",children:"Reset Session"}),y.jsx("div",{className:"text-xs text-slate-400",children:"Clear chat, tasks, and devices"})]})]}),y.jsxs("button",{type:"button",onClick:r,className:"flex items-center gap-3 rounded-2xl border border-emerald-400/40 bg-gradient-to-r from-emerald-500/15 to-cyan-500/15 px-4 py-3 shadow-[0_4px_16px_rgba(0,0,0,0.25),0_0_15px_rgba(16,185,129,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)] transition-all duration-200 hover:border-emerald-400/60 hover:from-emerald-500/25 hover:to-cyan-500/25 hover:shadow-[0_8px_24px_rgba(0,0,0,0.3),0_0_25px_rgba(16,185,129,0.3)]",children:[y.jsx(uv,{className:"h-4 w-4 text-emerald-300","aria-hidden":!0}),y.jsxs("div",{className:"text-left",children:[y.jsx("div",{className:"text-sm font-medium text-white",children:"Next Session"}),y.jsx("div",{className:"text-xs text-slate-400",children:"Launch with a fresh constellation"})]})]})]})]})},J0={connected:{label:"Connected",dot:"bg-emerald-400",text:"text-emerald-300"},idle:{label:"Idle",dot:"bg-cyan-400",text:"text-cyan-200"},busy:{label:"Busy",dot:"bg-amber-400",text:"text-amber-200"},connecting:{label:"Connecting",dot:"bg-blue-400",text:"text-blue-200"},failed:{label:"Failed",dot:"bg-rose-500",text:"text-rose-200"},disconnected:{label:"Disconnected",dot:"bg-slate-500",text:"text-slate-300"},offline:{label:"Offline",dot:"bg-slate-600",text:"text-slate-400"},unknown:{label:"Unknown",dot:"bg-slate-600",text:"text-slate-400"}},Uj=e=>{if(!e)return"No heartbeat yet";const t=Date.parse(e);if(Number.isNaN(t))return e;const n=Date.now()-t;if(n<6e4)return"Just now";const r=Math.round(n/6e4);return r<60?`${r} min ago`:`${Math.round(r/60)} hr ago`},Wj=({device:e})=>{const t=J0[e.status]||J0.unknown,n=e.highlightUntil&&e.highlightUntil>Date.now();return y.jsxs("div",{className:we("group rounded-2xl border bg-gradient-to-br p-4 text-xs transition-all duration-300","border-white/20 from-[rgba(25,40,60,0.75)] via-[rgba(20,35,52,0.7)] to-[rgba(15,28,45,0.75)]","shadow-[0_4px_16px_rgba(0,0,0,0.3),0_0_8px_rgba(15,123,255,0.1),inset_0_1px_2px_rgba(255,255,255,0.1),inset_0_0_20px_rgba(15,123,255,0.03)]","hover:border-white/35 hover:from-[rgba(28,45,65,0.85)] hover:via-[rgba(23,38,56,0.8)] hover:to-[rgba(18,30,48,0.85)]","hover:shadow-[0_8px_24px_rgba(0,0,0,0.35),0_0_20px_rgba(15,123,255,0.2),0_0_30px_rgba(6,182,212,0.15),inset_0_1px_2px_rgba(255,255,255,0.15),inset_0_0_30px_rgba(15,123,255,0.06)]","hover:translate-y-[-2px]",n&&"border-cyan-400/50 from-[rgba(6,182,212,0.2)] via-[rgba(15,123,255,0.15)] to-[rgba(15,28,45,0.8)] shadow-[0_0_30px_rgba(6,182,212,0.4),0_0_40px_rgba(6,182,212,0.25),0_4px_16px_rgba(0,0,0,0.3),inset_0_0_30px_rgba(6,182,212,0.1)]"),children:[y.jsxs("div",{className:"flex items-start justify-between gap-3",children:[y.jsxs("div",{children:[y.jsx("div",{className:"font-mono text-sm text-white drop-shadow-[0_1px_4px_rgba(0,0,0,0.5)]",children:e.name}),y.jsxs("div",{className:"mt-1 flex items-center gap-2",children:[y.jsx("span",{className:we("h-2 w-2 rounded-full shadow-[0_0_6px_currentColor]",t.dot),"aria-hidden":!0}),y.jsx("span",{className:we("text-[11px] uppercase tracking-[0.2em]",t.text),children:t.label}),e.os&&y.jsxs(y.Fragment,{children:[y.jsx("span",{className:"text-slate-600",children:"|"}),y.jsx("span",{className:"rounded-full border border-indigo-400/30 bg-indigo-500/20 px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.15em] text-indigo-300 shadow-[0_0_8px_rgba(99,102,241,0.2),inset_0_1px_1px_rgba(255,255,255,0.1)]",children:e.os})]})]})]}),y.jsx(MC,{className:"h-4 w-4 text-slate-400 transition-all group-hover:text-cyan-400 group-hover:drop-shadow-[0_0_6px_rgba(6,182,212,0.5)]","aria-hidden":!0})]}),y.jsxs("div",{className:"mt-3 grid gap-2 text-[11px] text-slate-300",children:[e.capabilities&&e.capabilities.length>0&&y.jsxs("div",{children:["Capabilities: ",e.capabilities.join(", ")]}),y.jsxs("div",{className:"flex items-center gap-2 text-slate-400",children:[y.jsx(su,{className:"h-3 w-3","aria-hidden":!0}),Uj(e.lastHeartbeat)]}),e.metadata&&e.metadata.region&&y.jsxs("div",{children:["Region: ",e.metadata.region]})]})]})},Yj=()=>{const{devices:e}=Ce(o=>({devices:o.devices}),Oe),[t,n]=T.useState(""),r=T.useMemo(()=>{const o=Object.values(e);if(!t)return o;const a=t.toLowerCase();return o.filter(l=>{var u;return[l.name,l.id,l.os,(u=l.metadata)==null?void 0:u.region].filter(Boolean).map(c=>String(c).toLowerCase()).some(c=>c.includes(a))})},[e,t]),i=r.length,s=r.filter(o=>o.status==="connected"||o.status==="idle"||o.status==="busy").length;return y.jsxs("div",{className:"flex h-full flex-col gap-4 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-5 text-sm text-slate-100 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(16,185,129,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:[y.jsx("div",{className:"flex items-center justify-between",children:y.jsxs("div",{className:"flex items-center gap-3",children:[y.jsx(NC,{className:"h-5 w-5 text-emerald-400 drop-shadow-[0_0_8px_rgba(16,185,129,0.5)]","aria-hidden":!0}),y.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:"Device Agent"}),y.jsxs("div",{className:"mt-0.5 rounded-lg border border-emerald-400/40 bg-gradient-to-r from-emerald-500/15 to-emerald-600/10 px-2.5 py-1 text-xs font-medium text-emerald-200 shadow-[0_0_15px_rgba(16,185,129,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)]",children:[s,"/",i," online"]})]})}),y.jsxs("div",{className:"flex items-center gap-2 rounded-xl border border-white/5 bg-gradient-to-r from-black/30 to-black/20 px-3 py-2.5 text-xs text-slate-300 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus-within:border-white/15 focus-within:shadow-[0_0_8px_rgba(16,185,129,0.08),inset_0_2px_8px_rgba(0,0,0,0.3)]",children:[y.jsx(cv,{className:"h-3.5 w-3.5","aria-hidden":!0}),y.jsx("input",{type:"search",value:t,onChange:o=>n(o.target.value),placeholder:"Filter by id, region, or OS",className:"w-full bg-transparent focus:outline-none"})]}),y.jsx("div",{className:"flex-1 space-y-3 overflow-y-auto",children:r.length===0?y.jsxs("div",{className:"flex flex-col items-center gap-2 rounded-2xl border border-dashed border-white/10 bg-white/5 p-6 text-center text-xs text-slate-400",children:[y.jsx(GC,{className:"h-5 w-5","aria-hidden":!0}),"No devices reported yet."]}):r.map(o=>y.jsx(Wj,{device:o},o.id))})]})},ey=()=>y.jsxs("div",{className:"flex h-full w-full flex-col gap-4 overflow-hidden",children:[y.jsx(Hj,{}),y.jsx("div",{className:"flex-1 overflow-y-auto space-y-4 pr-1",children:y.jsx(Yj,{})})]}),qj={info:{icon:y.jsx(jC,{className:"h-4 w-4","aria-hidden":!0}),className:"border-cyan-400/40 bg-cyan-500/20 text-cyan-100"},success:{icon:y.jsx(Kr,{className:"h-4 w-4","aria-hidden":!0}),className:"border-emerald-400/40 bg-emerald-500/20 text-emerald-100"},warning:{icon:y.jsx(Wm,{className:"h-4 w-4","aria-hidden":!0}),className:"border-amber-400/40 bg-amber-500/20 text-amber-100"},error:{icon:y.jsx(Wm,{className:"h-4 w-4","aria-hidden":!0}),className:"border-rose-400/40 bg-rose-500/20 text-rose-100"}},Kj=5e3,Gj=()=>{const{notifications:e,dismissNotification:t,markNotificationRead:n}=Ce(r=>({notifications:r.notifications,dismissNotification:r.dismissNotification,markNotificationRead:r.markNotificationRead}));return T.useEffect(()=>{const r=[];return e.forEach(i=>{const s=setTimeout(()=>{t(i.id)},Kj);r.push(s)}),()=>{r.forEach(i=>clearTimeout(i))}},[e,t]),y.jsx("div",{className:"pointer-events-none fixed bottom-6 left-6 z-50 flex w-80 flex-col gap-3",children:y.jsx(qk,{children:e.map(r=>{const i=qj[r.severity];return y.jsxs(Wk.div,{initial:{y:20,opacity:0},animate:{y:0,opacity:1},exit:{y:10,opacity:0},transition:{duration:.2},className:we("pointer-events-auto relative rounded-2xl border px-4 py-3 shadow-lg",i.className),onMouseEnter:()=>n(r.id),children:[y.jsx("button",{type:"button",className:"absolute right-2 top-2 rounded-full border border-white/20 p-1 text-slate-200 transition hover:bg-white/10",onClick:()=>t(r.id),children:y.jsx(Bf,{className:"h-3 w-3","aria-hidden":!0})}),y.jsxs("div",{className:"flex items-start gap-3 pr-6",children:[y.jsx("div",{className:"mt-1 flex-shrink-0",children:i.icon}),y.jsxs("div",{className:"flex-1 min-w-0 text-xs",children:[y.jsx("div",{className:"font-semibold text-white break-words",children:r.title}),r.description&&y.jsx("div",{className:"mt-1 text-[11px] text-slate-200/80 break-words",children:r.description}),y.jsxs("div",{className:"mt-2 flex items-center justify-between text-[10px] uppercase tracking-[0.18em] text-slate-300/70",children:[y.jsx("span",{className:"truncate",children:r.source||"system"}),y.jsx("span",{className:"flex-shrink-0 ml-2",children:new Date(r.timestamp).toLocaleTimeString()})]})]})]})]},r.id)})})})};function gt(e){if(typeof e=="string"||typeof e=="number")return""+e;let t="";if(Array.isArray(e))for(let n=0,r;ne;function Kk(e,t=Zj,n){const r=Qj(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return Xj(r),r}const ty=(e,t)=>{const n=dv(e),r=(i,s=t)=>Kk(n,i,s);return Object.assign(r,n),r},Jj=(e,t)=>e?ty(e,t):ty;var eL={value:()=>{}};function bu(){for(var e=0,t=arguments.length,n={},r;e=0&&(r=n.slice(i+1),n=n.slice(0,i)),n&&!t.hasOwnProperty(n))throw new Error("unknown type: "+n);return{type:n,name:r}})}qa.prototype=bu.prototype={constructor:qa,on:function(e,t){var n=this._,r=tL(e+"",n),i,s=-1,o=r.length;if(arguments.length<2){for(;++s0)for(var n=new Array(i),r=0,i,s;r=0&&(t=e.slice(0,n))!=="xmlns"&&(e=e.slice(n+1)),ry.hasOwnProperty(t)?{space:ry[t],local:e}:e}function rL(e){return function(){var t=this.ownerDocument,n=this.namespaceURI;return n===pd&&t.documentElement.namespaceURI===pd?t.createElement(e):t.createElementNS(n,e)}}function iL(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}function Gk(e){var t=Su(e);return(t.local?iL:rL)(t)}function sL(){}function dp(e){return e==null?sL:function(){return this.querySelector(e)}}function oL(e){typeof e!="function"&&(e=dp(e));for(var t=this._groups,n=t.length,r=new Array(n),i=0;i=v&&(v=x+1);!(N=w[v])&&++v=0;)(o=r[i])&&(s&&o.compareDocumentPosition(s)^4&&s.parentNode.insertBefore(o,s),s=o);return this}function ML(e){e||(e=DL);function t(f,d){return f&&d?e(f.__data__,d.__data__):!f-!d}for(var n=this._groups,r=n.length,i=new Array(r),s=0;st?1:e>=t?0:NaN}function IL(){var e=arguments[0];return arguments[0]=this,e.apply(null,arguments),this}function jL(){return Array.from(this)}function LL(){for(var e=this._groups,t=0,n=e.length;t1?this.each((t==null?YL:typeof t=="function"?KL:qL)(e,t,n??"")):is(this.node(),e)}function is(e,t){return e.style.getPropertyValue(t)||e2(e).getComputedStyle(e,null).getPropertyValue(t)}function XL(e){return function(){delete this[e]}}function QL(e,t){return function(){this[e]=t}}function ZL(e,t){return function(){var n=t.apply(this,arguments);n==null?delete this[e]:this[e]=n}}function JL(e,t){return arguments.length>1?this.each((t==null?XL:typeof t=="function"?ZL:QL)(e,t)):this.node()[e]}function t2(e){return e.trim().split(/^|\s+/)}function hp(e){return e.classList||new n2(e)}function n2(e){this._node=e,this._names=t2(e.getAttribute("class")||"")}n2.prototype={add:function(e){var t=this._names.indexOf(e);t<0&&(this._names.push(e),this._node.setAttribute("class",this._names.join(" ")))},remove:function(e){var t=this._names.indexOf(e);t>=0&&(this._names.splice(t,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(e){return this._names.indexOf(e)>=0}};function r2(e,t){for(var n=hp(e),r=-1,i=t.length;++r=0&&(n=t.slice(r+1),t=t.slice(0,r)),{type:t,name:n}})}function NR(e){return function(){var t=this.__on;if(t){for(var n=0,r=-1,i=t.length,s;n()=>e;function md(e,{sourceEvent:t,subject:n,target:r,identifier:i,active:s,x:o,y:a,dx:l,dy:u,dispatch:c}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},subject:{value:n,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:s,enumerable:!0,configurable:!0},x:{value:o,enumerable:!0,configurable:!0},y:{value:a,enumerable:!0,configurable:!0},dx:{value:l,enumerable:!0,configurable:!0},dy:{value:u,enumerable:!0,configurable:!0},_:{value:c}})}md.prototype.on=function(){var e=this._.on.apply(this._,arguments);return e===this._?this:e};function zR(e){return!e.ctrlKey&&!e.button}function FR(){return this.parentNode}function OR(e,t){return t??{x:e.x,y:e.y}}function VR(){return navigator.maxTouchPoints||"ontouchstart"in this}function $R(){var e=zR,t=FR,n=OR,r=VR,i={},s=bu("start","drag","end"),o=0,a,l,u,c,f=0;function d(k){k.on("mousedown.drag",h).filter(r).on("touchstart.drag",w).on("touchmove.drag",p,RR).on("touchend.drag touchcancel.drag",x).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function h(k,N){if(!(c||!e.call(this,k,N))){var S=v(this,t.call(this,k,N),k,N,"mouse");S&&(nn(k.view).on("mousemove.drag",m,Eo).on("mouseup.drag",g,Eo),a2(k.view),Rc(k),u=!1,a=k.clientX,l=k.clientY,S("start",k))}}function m(k){if($i(k),!u){var N=k.clientX-a,S=k.clientY-l;u=N*N+S*S>f}i.mouse("drag",k)}function g(k){nn(k.view).on("mousemove.drag mouseup.drag",null),l2(k.view,u),$i(k),i.mouse("end",k)}function w(k,N){if(e.call(this,k,N)){var S=k.changedTouches,A=t.call(this,k,N),P=S.length,D,C;for(D=0;D>8&15|t>>4&240,t>>4&15|t&240,(t&15)<<4|t&15,1):n===8?ba(t>>24&255,t>>16&255,t>>8&255,(t&255)/255):n===4?ba(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|t&240,((t&15)<<4|t&15)/255):null):(t=HR.exec(e))?new kt(t[1],t[2],t[3],1):(t=UR.exec(e))?new kt(t[1]*255/100,t[2]*255/100,t[3]*255/100,1):(t=WR.exec(e))?ba(t[1],t[2],t[3],t[4]):(t=YR.exec(e))?ba(t[1]*255/100,t[2]*255/100,t[3]*255/100,t[4]):(t=qR.exec(e))?cy(t[1],t[2]/100,t[3]/100,1):(t=KR.exec(e))?cy(t[1],t[2]/100,t[3]/100,t[4]):iy.hasOwnProperty(e)?ay(iy[e]):e==="transparent"?new kt(NaN,NaN,NaN,0):null}function ay(e){return new kt(e>>16&255,e>>8&255,e&255,1)}function ba(e,t,n,r){return r<=0&&(e=t=n=NaN),new kt(e,t,n,r)}function QR(e){return e instanceof Ko||(e=Ao(e)),e?(e=e.rgb(),new kt(e.r,e.g,e.b,e.opacity)):new kt}function gd(e,t,n,r){return arguments.length===1?QR(e):new kt(e,t,n,r??1)}function kt(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}pp(kt,gd,u2(Ko,{brighter(e){return e=e==null?Ll:Math.pow(Ll,e),new kt(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?No:Math.pow(No,e),new kt(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new kt(Xr(this.r),Xr(this.g),Xr(this.b),Rl(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:ly,formatHex:ly,formatHex8:ZR,formatRgb:uy,toString:uy}));function ly(){return`#${Ur(this.r)}${Ur(this.g)}${Ur(this.b)}`}function ZR(){return`#${Ur(this.r)}${Ur(this.g)}${Ur(this.b)}${Ur((isNaN(this.opacity)?1:this.opacity)*255)}`}function uy(){const e=Rl(this.opacity);return`${e===1?"rgb(":"rgba("}${Xr(this.r)}, ${Xr(this.g)}, ${Xr(this.b)}${e===1?")":`, ${e})`}`}function Rl(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function Xr(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function Ur(e){return e=Xr(e),(e<16?"0":"")+e.toString(16)}function cy(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new rn(e,t,n,r)}function c2(e){if(e instanceof rn)return new rn(e.h,e.s,e.l,e.opacity);if(e instanceof Ko||(e=Ao(e)),!e)return new rn;if(e instanceof rn)return e;e=e.rgb();var t=e.r/255,n=e.g/255,r=e.b/255,i=Math.min(t,n,r),s=Math.max(t,n,r),o=NaN,a=s-i,l=(s+i)/2;return a?(t===s?o=(n-r)/a+(n0&&l<1?0:o,new rn(o,a,l,e.opacity)}function JR(e,t,n,r){return arguments.length===1?c2(e):new rn(e,t,n,r??1)}function rn(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}pp(rn,JR,u2(Ko,{brighter(e){return e=e==null?Ll:Math.pow(Ll,e),new rn(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=e==null?No:Math.pow(No,e),new rn(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,t=isNaN(e)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*t,i=2*n-r;return new kt(zc(e>=240?e-240:e+120,i,r),zc(e,i,r),zc(e<120?e+240:e-120,i,r),this.opacity)},clamp(){return new rn(fy(this.h),Sa(this.s),Sa(this.l),Rl(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const e=Rl(this.opacity);return`${e===1?"hsl(":"hsla("}${fy(this.h)}, ${Sa(this.s)*100}%, ${Sa(this.l)*100}%${e===1?")":`, ${e})`}`}}));function fy(e){return e=(e||0)%360,e<0?e+360:e}function Sa(e){return Math.max(0,Math.min(1,e||0))}function zc(e,t,n){return(e<60?t+(n-t)*e/60:e<180?n:e<240?t+(n-t)*(240-e)/60:t)*255}const f2=e=>()=>e;function ez(e,t){return function(n){return e+n*t}}function tz(e,t,n){return e=Math.pow(e,n),t=Math.pow(t,n)-e,n=1/n,function(r){return Math.pow(e+r*t,n)}}function nz(e){return(e=+e)==1?d2:function(t,n){return n-t?tz(t,n,e):f2(isNaN(t)?n:t)}}function d2(e,t){var n=t-e;return n?ez(e,n):f2(isNaN(e)?t:e)}const dy=function e(t){var n=nz(t);function r(i,s){var o=n((i=gd(i)).r,(s=gd(s)).r),a=n(i.g,s.g),l=n(i.b,s.b),u=d2(i.opacity,s.opacity);return function(c){return i.r=o(c),i.g=a(c),i.b=l(c),i.opacity=u(c),i+""}}return r.gamma=e,r}(1);function tr(e,t){return e=+e,t=+t,function(n){return e*(1-n)+t*n}}var yd=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,Fc=new RegExp(yd.source,"g");function rz(e){return function(){return e}}function iz(e){return function(t){return e(t)+""}}function sz(e,t){var n=yd.lastIndex=Fc.lastIndex=0,r,i,s,o=-1,a=[],l=[];for(e=e+"",t=t+"";(r=yd.exec(e))&&(i=Fc.exec(t));)(s=i.index)>n&&(s=t.slice(n,s),a[o]?a[o]+=s:a[++o]=s),(r=r[0])===(i=i[0])?a[o]?a[o]+=i:a[++o]=i:(a[++o]=null,l.push({i:o,x:tr(r,i)})),n=Fc.lastIndex;return n180?c+=360:c-u>180&&(u+=360),d.push({i:f.push(i(f)+"rotate(",null,r)-2,x:tr(u,c)})):c&&f.push(i(f)+"rotate("+c+r)}function a(u,c,f,d){u!==c?d.push({i:f.push(i(f)+"skewX(",null,r)-2,x:tr(u,c)}):c&&f.push(i(f)+"skewX("+c+r)}function l(u,c,f,d,h,m){if(u!==f||c!==d){var g=h.push(i(h)+"scale(",null,",",null,")");m.push({i:g-4,x:tr(u,f)},{i:g-2,x:tr(c,d)})}else(f!==1||d!==1)&&h.push(i(h)+"scale("+f+","+d+")")}return function(u,c){var f=[],d=[];return u=e(u),c=e(c),s(u.translateX,u.translateY,c.translateX,c.translateY,f,d),o(u.rotate,c.rotate,f,d),a(u.skewX,c.skewX,f,d),l(u.scaleX,u.scaleY,c.scaleX,c.scaleY,f,d),u=c=null,function(h){for(var m=-1,g=d.length,w;++m=0&&e._call.call(void 0,t),e=e._next;--ss}function my(){ri=(Fl=Po.now())+_u,ss=Rs=0;try{mz()}finally{ss=0,yz(),ri=0}}function gz(){var e=Po.now(),t=e-Fl;t>m2&&(_u-=t,Fl=e)}function yz(){for(var e,t=zl,n,r=1/0;t;)t._call?(r>t._time&&(r=t._time),e=t,t=t._next):(n=t._next,t._next=null,t=e?e._next=n:zl=n);zs=e,vd(r)}function vd(e){if(!ss){Rs&&(Rs=clearTimeout(Rs));var t=e-ri;t>24?(e<1/0&&(Rs=setTimeout(my,e-Po.now()-_u)),Es&&(Es=clearInterval(Es))):(Es||(Fl=Po.now(),Es=setInterval(gz,m2)),ss=1,g2(my))}}function gy(e,t,n){var r=new Ol;return t=t==null?0:+t,r.restart(i=>{r.stop(),e(i+t)},t,n),r}var xz=bu("start","end","cancel","interrupt"),vz=[],x2=0,yy=1,wd=2,Ka=3,xy=4,kd=5,Ga=6;function Cu(e,t,n,r,i,s){var o=e.__transition;if(!o)e.__transition={};else if(n in o)return;wz(e,n,{name:t,index:r,group:i,on:xz,tween:vz,time:s.time,delay:s.delay,duration:s.duration,ease:s.ease,timer:null,state:x2})}function gp(e,t){var n=fn(e,t);if(n.state>x2)throw new Error("too late; already scheduled");return n}function _n(e,t){var n=fn(e,t);if(n.state>Ka)throw new Error("too late; already running");return n}function fn(e,t){var n=e.__transition;if(!n||!(n=n[t]))throw new Error("transition not found");return n}function wz(e,t,n){var r=e.__transition,i;r[t]=n,n.timer=y2(s,0,n.time);function s(u){n.state=yy,n.timer.restart(o,n.delay,n.time),n.delay<=u&&o(u-n.delay)}function o(u){var c,f,d,h;if(n.state!==yy)return l();for(c in r)if(h=r[c],h.name===n.name){if(h.state===Ka)return gy(o);h.state===xy?(h.state=Ga,h.timer.stop(),h.on.call("interrupt",e,e.__data__,h.index,h.group),delete r[c]):+cwd&&r.state=0&&(t=t.slice(0,n)),!t||t==="start"})}function Xz(e,t,n){var r,i,s=Gz(t)?gp:_n;return function(){var o=s(this,e),a=o.on;a!==r&&(i=(r=a).copy()).on(t,n),o.on=i}}function Qz(e,t){var n=this._id;return arguments.length<2?fn(this.node(),n).on.on(e):this.each(Xz(n,e,t))}function Zz(e){return function(){var t=this.parentNode;for(var n in this.__transition)if(+n!==e)return;t&&t.removeChild(this)}}function Jz(){return this.on("end.remove",Zz(this._id))}function e8(e){var t=this._name,n=this._id;typeof e!="function"&&(e=dp(e));for(var r=this._groups,i=r.length,s=new Array(i),o=0;o()=>e;function C8(e,{sourceEvent:t,target:n,transform:r,dispatch:i}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},transform:{value:r,enumerable:!0,configurable:!0},_:{value:i}})}function Dn(e,t,n){this.k=e,this.x=t,this.y=n}Dn.prototype={constructor:Dn,scale:function(e){return e===1?this:new Dn(this.k*e,this.x,this.y)},translate:function(e,t){return e===0&t===0?this:new Dn(this.k,this.x+this.k*e,this.y+this.k*t)},apply:function(e){return[e[0]*this.k+this.x,e[1]*this.k+this.y]},applyX:function(e){return e*this.k+this.x},applyY:function(e){return e*this.k+this.y},invert:function(e){return[(e[0]-this.x)/this.k,(e[1]-this.y)/this.k]},invertX:function(e){return(e-this.x)/this.k},invertY:function(e){return(e-this.y)/this.k},rescaleX:function(e){return e.copy().domain(e.range().map(this.invertX,this).map(e.invert,e))},rescaleY:function(e){return e.copy().domain(e.range().map(this.invertY,this).map(e.invert,e))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var yr=new Dn(1,0,0);Dn.prototype;function Oc(e){e.stopImmediatePropagation()}function Ns(e){e.preventDefault(),e.stopImmediatePropagation()}function E8(e){return(!e.ctrlKey||e.type==="wheel")&&!e.button}function N8(){var e=this;return e instanceof SVGElement?(e=e.ownerSVGElement||e,e.hasAttribute("viewBox")?(e=e.viewBox.baseVal,[[e.x,e.y],[e.x+e.width,e.y+e.height]]):[[0,0],[e.width.baseVal.value,e.height.baseVal.value]]):[[0,0],[e.clientWidth,e.clientHeight]]}function vy(){return this.__zoom||yr}function T8(e){return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*(e.ctrlKey?10:1)}function A8(){return navigator.maxTouchPoints||"ontouchstart"in this}function P8(e,t,n){var r=e.invertX(t[0][0])-n[0][0],i=e.invertX(t[1][0])-n[1][0],s=e.invertY(t[0][1])-n[0][1],o=e.invertY(t[1][1])-n[1][1];return e.translate(i>r?(r+i)/2:Math.min(0,r)||Math.max(0,i),o>s?(s+o)/2:Math.min(0,s)||Math.max(0,o))}function M8(){var e=E8,t=N8,n=P8,r=T8,i=A8,s=[0,1/0],o=[[-1/0,-1/0],[1/0,1/0]],a=250,l=hz,u=bu("start","zoom","end"),c,f,d,h=500,m=150,g=0,w=10;function p(_){_.property("__zoom",vy).on("wheel.zoom",P,{passive:!1}).on("mousedown.zoom",D).on("dblclick.zoom",C).filter(i).on("touchstart.zoom",j).on("touchmove.zoom",z).on("touchend.zoom touchcancel.zoom",H).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}p.transform=function(_,L,I,O){var R=_.selection?_.selection():_;R.property("__zoom",vy),_!==R?N(_,L,I,O):R.interrupt().each(function(){S(this,arguments).event(O).start().zoom(null,typeof L=="function"?L.apply(this,arguments):L).end()})},p.scaleBy=function(_,L,I,O){p.scaleTo(_,function(){var R=this.__zoom.k,M=typeof L=="function"?L.apply(this,arguments):L;return R*M},I,O)},p.scaleTo=function(_,L,I,O){p.transform(_,function(){var R=t.apply(this,arguments),M=this.__zoom,b=I==null?k(R):typeof I=="function"?I.apply(this,arguments):I,F=M.invert(b),B=typeof L=="function"?L.apply(this,arguments):L;return n(v(x(M,B),b,F),R,o)},I,O)},p.translateBy=function(_,L,I,O){p.transform(_,function(){return n(this.__zoom.translate(typeof L=="function"?L.apply(this,arguments):L,typeof I=="function"?I.apply(this,arguments):I),t.apply(this,arguments),o)},null,O)},p.translateTo=function(_,L,I,O,R){p.transform(_,function(){var M=t.apply(this,arguments),b=this.__zoom,F=O==null?k(M):typeof O=="function"?O.apply(this,arguments):O;return n(yr.translate(F[0],F[1]).scale(b.k).translate(typeof L=="function"?-L.apply(this,arguments):-L,typeof I=="function"?-I.apply(this,arguments):-I),M,o)},O,R)};function x(_,L){return L=Math.max(s[0],Math.min(s[1],L)),L===_.k?_:new Dn(L,_.x,_.y)}function v(_,L,I){var O=L[0]-I[0]*_.k,R=L[1]-I[1]*_.k;return O===_.x&&R===_.y?_:new Dn(_.k,O,R)}function k(_){return[(+_[0][0]+ +_[1][0])/2,(+_[0][1]+ +_[1][1])/2]}function N(_,L,I,O){_.on("start.zoom",function(){S(this,arguments).event(O).start()}).on("interrupt.zoom end.zoom",function(){S(this,arguments).event(O).end()}).tween("zoom",function(){var R=this,M=arguments,b=S(R,M).event(O),F=t.apply(R,M),B=I==null?k(F):typeof I=="function"?I.apply(R,M):I,E=Math.max(F[1][0]-F[0][0],F[1][1]-F[0][1]),q=R.__zoom,X=typeof L=="function"?L.apply(R,M):L,G=l(q.invert(B).concat(E/q.k),X.invert(B).concat(E/X.k));return function(ne){if(ne===1)ne=X;else{var oe=G(ne),de=E/oe[2];ne=new Dn(de,B[0]-oe[0]*de,B[1]-oe[1]*de)}b.zoom(null,ne)}})}function S(_,L,I){return!I&&_.__zooming||new A(_,L)}function A(_,L){this.that=_,this.args=L,this.active=0,this.sourceEvent=null,this.extent=t.apply(_,L),this.taps=0}A.prototype={event:function(_){return _&&(this.sourceEvent=_),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(_,L){return this.mouse&&_!=="mouse"&&(this.mouse[1]=L.invert(this.mouse[0])),this.touch0&&_!=="touch"&&(this.touch0[1]=L.invert(this.touch0[0])),this.touch1&&_!=="touch"&&(this.touch1[1]=L.invert(this.touch1[0])),this.that.__zoom=L,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(_){var L=nn(this.that).datum();u.call(_,this.that,new C8(_,{sourceEvent:this.sourceEvent,target:p,transform:this.that.__zoom,dispatch:u}),L)}};function P(_,...L){if(!e.apply(this,arguments))return;var I=S(this,L).event(_),O=this.__zoom,R=Math.max(s[0],Math.min(s[1],O.k*Math.pow(2,r.apply(this,arguments)))),M=yn(_);if(I.wheel)(I.mouse[0][0]!==M[0]||I.mouse[0][1]!==M[1])&&(I.mouse[1]=O.invert(I.mouse[0]=M)),clearTimeout(I.wheel);else{if(O.k===R)return;I.mouse=[M,O.invert(M)],Xa(this),I.start()}Ns(_),I.wheel=setTimeout(b,m),I.zoom("mouse",n(v(x(O,R),I.mouse[0],I.mouse[1]),I.extent,o));function b(){I.wheel=null,I.end()}}function D(_,...L){if(d||!e.apply(this,arguments))return;var I=_.currentTarget,O=S(this,L,!0).event(_),R=nn(_.view).on("mousemove.zoom",B,!0).on("mouseup.zoom",E,!0),M=yn(_,I),b=_.clientX,F=_.clientY;a2(_.view),Oc(_),O.mouse=[M,this.__zoom.invert(M)],Xa(this),O.start();function B(q){if(Ns(q),!O.moved){var X=q.clientX-b,G=q.clientY-F;O.moved=X*X+G*G>g}O.event(q).zoom("mouse",n(v(O.that.__zoom,O.mouse[0]=yn(q,I),O.mouse[1]),O.extent,o))}function E(q){R.on("mousemove.zoom mouseup.zoom",null),l2(q.view,O.moved),Ns(q),O.event(q).end()}}function C(_,...L){if(e.apply(this,arguments)){var I=this.__zoom,O=yn(_.changedTouches?_.changedTouches[0]:_,this),R=I.invert(O),M=I.k*(_.shiftKey?.5:2),b=n(v(x(I,M),O,R),t.apply(this,L),o);Ns(_),a>0?nn(this).transition().duration(a).call(N,b,O,_):nn(this).call(p.transform,b,O,_)}}function j(_,...L){if(e.apply(this,arguments)){var I=_.touches,O=I.length,R=S(this,L,_.changedTouches.length===O).event(_),M,b,F,B;for(Oc(_),b=0;b"[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#001",error002:()=>"It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",error003:e=>`Node type "${e}" not found. Using fallback type "default".`,error004:()=>"The React Flow parent container needs a width and a height to render the graph.",error005:()=>"Only child nodes can use a parent extent.",error006:()=>"Can't create edge. An edge needs a source and a target.",error007:e=>`The old edge with id=${e} does not exist.`,error009:e=>`Marker type "${e}" doesn't exist.`,error008:(e,t)=>`Couldn't create edge for ${e?"target":"source"} handle id: "${e?t.targetHandle:t.sourceHandle}", edge id: ${t.id}.`,error010:()=>"Handle: No node id found. Make sure to only use a Handle inside a custom Node.",error011:e=>`Edge type "${e}" not found. Using fallback type "default".`,error012:e=>`Node with id "${e}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`},b2=Bn.error001();function Ie(e,t){const n=T.useContext(Eu);if(n===null)throw new Error(b2);return Kk(n,e,t)}const Ke=()=>{const e=T.useContext(Eu);if(e===null)throw new Error(b2);return T.useMemo(()=>({getState:e.getState,setState:e.setState,subscribe:e.subscribe,destroy:e.destroy}),[e])},I8=e=>e.userSelectionActive?"none":"all";function S2({position:e,children:t,className:n,style:r,...i}){const s=Ie(I8),o=`${e}`.split("-");return $.createElement("div",{className:gt(["react-flow__panel",n,...o]),style:{...r,pointerEvents:s},...i},t)}function j8({proOptions:e,position:t="bottom-right"}){return e!=null&&e.hideAttribution?null:$.createElement(S2,{position:t,className:"react-flow__attribution","data-message":"Please only hide this attribution when you are subscribed to React Flow Pro: https://reactflow.dev/pro"},$.createElement("a",{href:"https://reactflow.dev",target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution"},"React Flow"))}const L8=({x:e,y:t,label:n,labelStyle:r={},labelShowBg:i=!0,labelBgStyle:s={},labelBgPadding:o=[2,4],labelBgBorderRadius:a=2,children:l,className:u,...c})=>{const f=T.useRef(null),[d,h]=T.useState({x:0,y:0,width:0,height:0}),m=gt(["react-flow__edge-textwrapper",u]);return T.useEffect(()=>{if(f.current){const g=f.current.getBBox();h({x:g.x,y:g.y,width:g.width,height:g.height})}},[n]),typeof n>"u"||!n?null:$.createElement("g",{transform:`translate(${e-d.width/2} ${t-d.height/2})`,className:m,visibility:d.width?"visible":"hidden",...c},i&&$.createElement("rect",{width:d.width+2*o[0],x:-o[0],y:-o[1],height:d.height+2*o[1],className:"react-flow__edge-textbg",style:s,rx:a,ry:a}),$.createElement("text",{className:"react-flow__edge-text",y:d.height/2,dy:"0.3em",ref:f,style:r},n),l)};var R8=T.memo(L8);const xp=e=>({width:e.offsetWidth,height:e.offsetHeight}),os=(e,t=0,n=1)=>Math.min(Math.max(e,t),n),vp=(e={x:0,y:0},t)=>({x:os(e.x,t[0][0],t[1][0]),y:os(e.y,t[0][1],t[1][1])}),wy=(e,t,n)=>en?-os(Math.abs(e-n),1,50)/50:0,_2=(e,t)=>{const n=wy(e.x,35,t.width-35)*20,r=wy(e.y,35,t.height-35)*20;return[n,r]},C2=e=>{var t;return((t=e.getRootNode)==null?void 0:t.call(e))||(window==null?void 0:window.document)},z8=(e,t)=>({x:Math.min(e.x,t.x),y:Math.min(e.y,t.y),x2:Math.max(e.x2,t.x2),y2:Math.max(e.y2,t.y2)}),wp=({x:e,y:t,width:n,height:r})=>({x:e,y:t,x2:e+n,y2:t+r}),F8=({x:e,y:t,x2:n,y2:r})=>({x:e,y:t,width:n-e,height:r-t}),ky=e=>({...e.positionAbsolute||{x:0,y:0},width:e.width||0,height:e.height||0}),bd=(e,t)=>{const n=Math.max(0,Math.min(e.x+e.width,t.x+t.width)-Math.max(e.x,t.x)),r=Math.max(0,Math.min(e.y+e.height,t.y+t.height)-Math.max(e.y,t.y));return Math.ceil(n*r)},O8=e=>qt(e.width)&&qt(e.height)&&qt(e.x)&&qt(e.y),qt=e=>!isNaN(e)&&isFinite(e),Re=Symbol.for("internals"),E2=["Enter"," ","Escape"],V8=(e,t)=>{},$8=e=>"nativeEvent"in e;function Sd(e){var i,s;const t=$8(e)?e.nativeEvent:e,n=((s=(i=t.composedPath)==null?void 0:i.call(t))==null?void 0:s[0])||e.target;return["INPUT","SELECT","TEXTAREA"].includes(n==null?void 0:n.nodeName)||(n==null?void 0:n.hasAttribute("contenteditable"))||!!(n!=null&&n.closest(".nokey"))}const N2=e=>"clientX"in e,xr=(e,t)=>{var s,o;const n=N2(e),r=n?e.clientX:(s=e.touches)==null?void 0:s[0].clientX,i=n?e.clientY:(o=e.touches)==null?void 0:o[0].clientY;return{x:r-((t==null?void 0:t.left)??0),y:i-((t==null?void 0:t.top)??0)}},Vl=()=>{var e;return typeof navigator<"u"&&((e=navigator==null?void 0:navigator.userAgent)==null?void 0:e.indexOf("Mac"))>=0},Go=({id:e,path:t,labelX:n,labelY:r,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u,style:c,markerEnd:f,markerStart:d,interactionWidth:h=20})=>$.createElement($.Fragment,null,$.createElement("path",{id:e,style:c,d:t,fill:"none",className:"react-flow__edge-path",markerEnd:f,markerStart:d}),h&&$.createElement("path",{d:t,fill:"none",strokeOpacity:0,strokeWidth:h,className:"react-flow__edge-interaction"}),i&&qt(n)&&qt(r)?$.createElement(R8,{x:n,y:r,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u}):null);Go.displayName="BaseEdge";function Ts(e,t,n){return n===void 0?n:r=>{const i=t().edges.find(s=>s.id===e);i&&n(r,{...i})}}function T2({sourceX:e,sourceY:t,targetX:n,targetY:r}){const i=Math.abs(n-e)/2,s=n{const[w,p,x]=P2({sourceX:e,sourceY:t,sourcePosition:i,targetX:n,targetY:r,targetPosition:s});return $.createElement(Go,{path:w,labelX:p,labelY:x,label:o,labelStyle:a,labelShowBg:l,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:f,style:d,markerEnd:h,markerStart:m,interactionWidth:g})});kp.displayName="SimpleBezierEdge";const Sy={[Q.Left]:{x:-1,y:0},[Q.Right]:{x:1,y:0},[Q.Top]:{x:0,y:-1},[Q.Bottom]:{x:0,y:1}},B8=({source:e,sourcePosition:t=Q.Bottom,target:n})=>t===Q.Left||t===Q.Right?e.xMath.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2));function H8({source:e,sourcePosition:t=Q.Bottom,target:n,targetPosition:r=Q.Top,center:i,offset:s}){const o=Sy[t],a=Sy[r],l={x:e.x+o.x*s,y:e.y+o.y*s},u={x:n.x+a.x*s,y:n.y+a.y*s},c=B8({source:l,sourcePosition:t,target:u}),f=c.x!==0?"x":"y",d=c[f];let h=[],m,g;const w={x:0,y:0},p={x:0,y:0},[x,v,k,N]=T2({sourceX:e.x,sourceY:e.y,targetX:n.x,targetY:n.y});if(o[f]*a[f]===-1){m=i.x??x,g=i.y??v;const A=[{x:m,y:l.y},{x:m,y:u.y}],P=[{x:l.x,y:g},{x:u.x,y:g}];o[f]===d?h=f==="x"?A:P:h=f==="x"?P:A}else{const A=[{x:l.x,y:u.y}],P=[{x:u.x,y:l.y}];if(f==="x"?h=o.x===d?P:A:h=o.y===d?A:P,t===r){const H=Math.abs(e[f]-n[f]);if(H<=s){const _=Math.min(s-1,s-H);o[f]===d?w[f]=(l[f]>e[f]?-1:1)*_:p[f]=(u[f]>n[f]?-1:1)*_}}if(t!==r){const H=f==="x"?"y":"x",_=o[f]===a[H],L=l[H]>u[H],I=l[H]=z?(m=(D.x+C.x)/2,g=h[0].y):(m=h[0].x,g=(D.y+C.y)/2)}return[[e,{x:l.x+w.x,y:l.y+w.y},...h,{x:u.x+p.x,y:u.y+p.y},n],m,g,k,N]}function U8(e,t,n,r){const i=Math.min(_y(e,t)/2,_y(t,n)/2,r),{x:s,y:o}=t;if(e.x===s&&s===n.x||e.y===o&&o===n.y)return`L${s} ${o}`;if(e.y===o){const u=e.x{let v="";return x>0&&x{const[p,x,v]=_d({sourceX:e,sourceY:t,sourcePosition:f,targetX:n,targetY:r,targetPosition:d,borderRadius:g==null?void 0:g.borderRadius,offset:g==null?void 0:g.offset});return $.createElement(Go,{path:p,labelX:x,labelY:v,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u,style:c,markerEnd:h,markerStart:m,interactionWidth:w})});Nu.displayName="SmoothStepEdge";const bp=T.memo(e=>{var t;return $.createElement(Nu,{...e,pathOptions:T.useMemo(()=>{var n;return{borderRadius:0,offset:(n=e.pathOptions)==null?void 0:n.offset}},[(t=e.pathOptions)==null?void 0:t.offset])})});bp.displayName="StepEdge";function W8({sourceX:e,sourceY:t,targetX:n,targetY:r}){const[i,s,o,a]=T2({sourceX:e,sourceY:t,targetX:n,targetY:r});return[`M ${e},${t}L ${n},${r}`,i,s,o,a]}const Sp=T.memo(({sourceX:e,sourceY:t,targetX:n,targetY:r,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u,style:c,markerEnd:f,markerStart:d,interactionWidth:h})=>{const[m,g,w]=W8({sourceX:e,sourceY:t,targetX:n,targetY:r});return $.createElement(Go,{path:m,labelX:g,labelY:w,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u,style:c,markerEnd:f,markerStart:d,interactionWidth:h})});Sp.displayName="StraightEdge";function Ea(e,t){return e>=0?.5*e:t*25*Math.sqrt(-e)}function Cy({pos:e,x1:t,y1:n,x2:r,y2:i,c:s}){switch(e){case Q.Left:return[t-Ea(t-r,s),n];case Q.Right:return[t+Ea(r-t,s),n];case Q.Top:return[t,n-Ea(n-i,s)];case Q.Bottom:return[t,n+Ea(i-n,s)]}}function M2({sourceX:e,sourceY:t,sourcePosition:n=Q.Bottom,targetX:r,targetY:i,targetPosition:s=Q.Top,curvature:o=.25}){const[a,l]=Cy({pos:n,x1:e,y1:t,x2:r,y2:i,c:o}),[u,c]=Cy({pos:s,x1:r,y1:i,x2:e,y2:t,c:o}),[f,d,h,m]=A2({sourceX:e,sourceY:t,targetX:r,targetY:i,sourceControlX:a,sourceControlY:l,targetControlX:u,targetControlY:c});return[`M${e},${t} C${a},${l} ${u},${c} ${r},${i}`,f,d,h,m]}const $l=T.memo(({sourceX:e,sourceY:t,targetX:n,targetY:r,sourcePosition:i=Q.Bottom,targetPosition:s=Q.Top,label:o,labelStyle:a,labelShowBg:l,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:f,style:d,markerEnd:h,markerStart:m,pathOptions:g,interactionWidth:w})=>{const[p,x,v]=M2({sourceX:e,sourceY:t,sourcePosition:i,targetX:n,targetY:r,targetPosition:s,curvature:g==null?void 0:g.curvature});return $.createElement(Go,{path:p,labelX:x,labelY:v,label:o,labelStyle:a,labelShowBg:l,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:f,style:d,markerEnd:h,markerStart:m,interactionWidth:w})});$l.displayName="BezierEdge";const _p=T.createContext(null),Y8=_p.Provider;_p.Consumer;const q8=()=>T.useContext(_p),K8=e=>"id"in e&&"source"in e&&"target"in e,G8=({source:e,sourceHandle:t,target:n,targetHandle:r})=>`reactflow__edge-${e}${t||""}-${n}${r||""}`,Cd=(e,t)=>typeof e>"u"?"":typeof e=="string"?e:`${t?`${t}__`:""}${Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("&")}`,X8=(e,t)=>t.some(n=>n.source===e.source&&n.target===e.target&&(n.sourceHandle===e.sourceHandle||!n.sourceHandle&&!e.sourceHandle)&&(n.targetHandle===e.targetHandle||!n.targetHandle&&!e.targetHandle)),Q8=(e,t)=>{if(!e.source||!e.target)return t;let n;return K8(e)?n={...e}:n={...e,id:G8(e)},X8(n,t)?t:t.concat(n)},Ed=({x:e,y:t},[n,r,i],s,[o,a])=>{const l={x:(e-n)/i,y:(t-r)/i};return s?{x:o*Math.round(l.x/o),y:a*Math.round(l.y/a)}:l},D2=({x:e,y:t},[n,r,i])=>({x:e*i+n,y:t*i+r}),Hi=(e,t=[0,0])=>{if(!e)return{x:0,y:0,positionAbsolute:{x:0,y:0}};const n=(e.width??0)*t[0],r=(e.height??0)*t[1],i={x:e.position.x-n,y:e.position.y-r};return{...i,positionAbsolute:e.positionAbsolute?{x:e.positionAbsolute.x-n,y:e.positionAbsolute.y-r}:i}},Cp=(e,t=[0,0])=>{if(e.length===0)return{x:0,y:0,width:0,height:0};const n=e.reduce((r,i)=>{const{x:s,y:o}=Hi(i,t).positionAbsolute;return z8(r,wp({x:s,y:o,width:i.width||0,height:i.height||0}))},{x:1/0,y:1/0,x2:-1/0,y2:-1/0});return F8(n)},I2=(e,t,[n,r,i]=[0,0,1],s=!1,o=!1,a=[0,0])=>{const l={x:(t.x-n)/i,y:(t.y-r)/i,width:t.width/i,height:t.height/i},u=[];return e.forEach(c=>{const{width:f,height:d,selectable:h=!0,hidden:m=!1}=c;if(o&&!h||m)return!1;const{positionAbsolute:g}=Hi(c,a),w={x:g.x,y:g.y,width:f||0,height:d||0},p=bd(l,w),x=typeof f>"u"||typeof d>"u"||f===null||d===null,v=s&&p>0,k=(f||0)*(d||0);(x||v||p>=k||c.dragging)&&u.push(c)}),u},j2=(e,t)=>{const n=e.map(r=>r.id);return t.filter(r=>n.includes(r.source)||n.includes(r.target))},L2=(e,t,n,r,i,s=.1)=>{const o=t/(e.width*(1+s)),a=n/(e.height*(1+s)),l=Math.min(o,a),u=os(l,r,i),c=e.x+e.width/2,f=e.y+e.height/2,d=t/2-c*u,h=n/2-f*u;return{x:d,y:h,zoom:u}},Fr=(e,t=0)=>e.transition().duration(t);function Ey(e,t,n,r){return(t[n]||[]).reduce((i,s)=>{var o,a;return`${e.id}-${s.id}-${n}`!==r&&i.push({id:s.id||null,type:n,nodeId:e.id,x:(((o=e.positionAbsolute)==null?void 0:o.x)??0)+s.x+s.width/2,y:(((a=e.positionAbsolute)==null?void 0:a.y)??0)+s.y+s.height/2}),i},[])}function Z8(e,t,n,r,i,s){const{x:o,y:a}=xr(e),u=t.elementsFromPoint(o,a).find(m=>m.classList.contains("react-flow__handle"));if(u){const m=u.getAttribute("data-nodeid");if(m){const g=Ep(void 0,u),w=u.getAttribute("data-handleid"),p=s({nodeId:m,id:w,type:g});if(p){const x=i.find(v=>v.nodeId===m&&v.type===g&&v.id===w);return{handle:{id:w,type:g,nodeId:m,x:(x==null?void 0:x.x)||n.x,y:(x==null?void 0:x.y)||n.y},validHandleResult:p}}}}let c=[],f=1/0;if(i.forEach(m=>{const g=Math.sqrt((m.x-n.x)**2+(m.y-n.y)**2);if(g<=r){const w=s(m);g<=f&&(gm.isValid),h=c.some(({handle:m})=>m.type==="target");return c.find(({handle:m,validHandleResult:g})=>h?m.type==="target":d?g.isValid:!0)||c[0]}const J8={source:null,target:null,sourceHandle:null,targetHandle:null},R2=()=>({handleDomNode:null,isValid:!1,connection:J8,endHandle:null});function z2(e,t,n,r,i,s,o){const a=i==="target",l=o.querySelector(`.react-flow__handle[data-id="${e==null?void 0:e.nodeId}-${e==null?void 0:e.id}-${e==null?void 0:e.type}"]`),u={...R2(),handleDomNode:l};if(l){const c=Ep(void 0,l),f=l.getAttribute("data-nodeid"),d=l.getAttribute("data-handleid"),h=l.classList.contains("connectable"),m=l.classList.contains("connectableend"),g={source:a?f:n,sourceHandle:a?d:r,target:a?n:f,targetHandle:a?r:d};u.connection=g,h&&m&&(t===ii.Strict?a&&c==="source"||!a&&c==="target":f!==n||d!==r)&&(u.endHandle={nodeId:f,handleId:d,type:c},u.isValid=s(g))}return u}function e6({nodes:e,nodeId:t,handleId:n,handleType:r}){return e.reduce((i,s)=>{if(s[Re]){const{handleBounds:o}=s[Re];let a=[],l=[];o&&(a=Ey(s,o,"source",`${t}-${n}-${r}`),l=Ey(s,o,"target",`${t}-${n}-${r}`)),i.push(...a,...l)}return i},[])}function Ep(e,t){return e||(t!=null&&t.classList.contains("target")?"target":t!=null&&t.classList.contains("source")?"source":null)}function Vc(e){e==null||e.classList.remove("valid","connecting","react-flow__handle-valid","react-flow__handle-connecting")}function t6(e,t){let n=null;return t?n="valid":e&&!t&&(n="invalid"),n}function F2({event:e,handleId:t,nodeId:n,onConnect:r,isTarget:i,getState:s,setState:o,isValidConnection:a,edgeUpdaterType:l,onReconnectEnd:u}){const c=C2(e.target),{connectionMode:f,domNode:d,autoPanOnConnect:h,connectionRadius:m,onConnectStart:g,panBy:w,getNodes:p,cancelConnection:x}=s();let v=0,k;const{x:N,y:S}=xr(e),A=c==null?void 0:c.elementFromPoint(N,S),P=Ep(l,A),D=d==null?void 0:d.getBoundingClientRect();if(!D||!P)return;let C,j=xr(e,D),z=!1,H=null,_=!1,L=null;const I=e6({nodes:p(),nodeId:n,handleId:t,handleType:P}),O=()=>{if(!h)return;const[b,F]=_2(j,D);w({x:b,y:F}),v=requestAnimationFrame(O)};o({connectionPosition:j,connectionStatus:null,connectionNodeId:n,connectionHandleId:t,connectionHandleType:P,connectionStartHandle:{nodeId:n,handleId:t,type:P},connectionEndHandle:null}),g==null||g(e,{nodeId:n,handleId:t,handleType:P});function R(b){const{transform:F}=s();j=xr(b,D);const{handle:B,validHandleResult:E}=Z8(b,c,Ed(j,F,!1,[1,1]),m,I,q=>z2(q,f,n,t,i?"target":"source",a,c));if(k=B,z||(O(),z=!0),L=E.handleDomNode,H=E.connection,_=E.isValid,o({connectionPosition:k&&_?D2({x:k.x,y:k.y},F):j,connectionStatus:t6(!!k,_),connectionEndHandle:E.endHandle}),!k&&!_&&!L)return Vc(C);H.source!==H.target&&L&&(Vc(C),C=L,L.classList.add("connecting","react-flow__handle-connecting"),L.classList.toggle("valid",_),L.classList.toggle("react-flow__handle-valid",_))}function M(b){var F,B;(k||L)&&H&&_&&(r==null||r(H)),(B=(F=s()).onConnectEnd)==null||B.call(F,b),l&&(u==null||u(b)),Vc(C),x(),cancelAnimationFrame(v),z=!1,_=!1,H=null,L=null,c.removeEventListener("mousemove",R),c.removeEventListener("mouseup",M),c.removeEventListener("touchmove",R),c.removeEventListener("touchend",M)}c.addEventListener("mousemove",R),c.addEventListener("mouseup",M),c.addEventListener("touchmove",R),c.addEventListener("touchend",M)}const Ny=()=>!0,n6=e=>({connectionStartHandle:e.connectionStartHandle,connectOnClick:e.connectOnClick,noPanClassName:e.noPanClassName}),r6=(e,t,n)=>r=>{const{connectionStartHandle:i,connectionEndHandle:s,connectionClickStartHandle:o}=r;return{connecting:(i==null?void 0:i.nodeId)===e&&(i==null?void 0:i.handleId)===t&&(i==null?void 0:i.type)===n||(s==null?void 0:s.nodeId)===e&&(s==null?void 0:s.handleId)===t&&(s==null?void 0:s.type)===n,clickConnecting:(o==null?void 0:o.nodeId)===e&&(o==null?void 0:o.handleId)===t&&(o==null?void 0:o.type)===n}},O2=T.forwardRef(({type:e="source",position:t=Q.Top,isValidConnection:n,isConnectable:r=!0,isConnectableStart:i=!0,isConnectableEnd:s=!0,id:o,onConnect:a,children:l,className:u,onMouseDown:c,onTouchStart:f,...d},h)=>{var D,C;const m=o||null,g=e==="target",w=Ke(),p=q8(),{connectOnClick:x,noPanClassName:v}=Ie(n6,Oe),{connecting:k,clickConnecting:N}=Ie(r6(p,m,e),Oe);p||(C=(D=w.getState()).onError)==null||C.call(D,"010",Bn.error010());const S=j=>{const{defaultEdgeOptions:z,onConnect:H,hasDefaultEdges:_}=w.getState(),L={...z,...j};if(_){const{edges:I,setEdges:O}=w.getState();O(Q8(L,I))}H==null||H(L),a==null||a(L)},A=j=>{if(!p)return;const z=N2(j);i&&(z&&j.button===0||!z)&&F2({event:j,handleId:m,nodeId:p,onConnect:S,isTarget:g,getState:w.getState,setState:w.setState,isValidConnection:n||w.getState().isValidConnection||Ny}),z?c==null||c(j):f==null||f(j)},P=j=>{const{onClickConnectStart:z,onClickConnectEnd:H,connectionClickStartHandle:_,connectionMode:L,isValidConnection:I}=w.getState();if(!p||!_&&!i)return;if(!_){z==null||z(j,{nodeId:p,handleId:m,handleType:e}),w.setState({connectionClickStartHandle:{nodeId:p,type:e,handleId:m}});return}const O=C2(j.target),R=n||I||Ny,{connection:M,isValid:b}=z2({nodeId:p,id:m,type:e},L,_.nodeId,_.handleId||null,_.type,R,O);b&&S(M),H==null||H(j),w.setState({connectionClickStartHandle:null})};return $.createElement("div",{"data-handleid":m,"data-nodeid":p,"data-handlepos":t,"data-id":`${p}-${m}-${e}`,className:gt(["react-flow__handle",`react-flow__handle-${t}`,"nodrag",v,u,{source:!g,target:g,connectable:r,connectablestart:i,connectableend:s,connecting:N,connectionindicator:r&&(i&&!k||s&&k)}]),onMouseDown:A,onTouchStart:A,onClick:x?P:void 0,ref:h,...d},l)});O2.displayName="Handle";var as=T.memo(O2);const V2=({data:e,isConnectable:t,targetPosition:n=Q.Top,sourcePosition:r=Q.Bottom})=>$.createElement($.Fragment,null,$.createElement(as,{type:"target",position:n,isConnectable:t}),e==null?void 0:e.label,$.createElement(as,{type:"source",position:r,isConnectable:t}));V2.displayName="DefaultNode";var Nd=T.memo(V2);const $2=({data:e,isConnectable:t,sourcePosition:n=Q.Bottom})=>$.createElement($.Fragment,null,e==null?void 0:e.label,$.createElement(as,{type:"source",position:n,isConnectable:t}));$2.displayName="InputNode";var B2=T.memo($2);const H2=({data:e,isConnectable:t,targetPosition:n=Q.Top})=>$.createElement($.Fragment,null,$.createElement(as,{type:"target",position:n,isConnectable:t}),e==null?void 0:e.label);H2.displayName="OutputNode";var U2=T.memo(H2);const Np=()=>null;Np.displayName="GroupNode";const i6=e=>({selectedNodes:e.getNodes().filter(t=>t.selected),selectedEdges:e.edges.filter(t=>t.selected).map(t=>({...t}))}),Na=e=>e.id;function s6(e,t){return Oe(e.selectedNodes.map(Na),t.selectedNodes.map(Na))&&Oe(e.selectedEdges.map(Na),t.selectedEdges.map(Na))}const W2=T.memo(({onSelectionChange:e})=>{const t=Ke(),{selectedNodes:n,selectedEdges:r}=Ie(i6,s6);return T.useEffect(()=>{const i={nodes:n,edges:r};e==null||e(i),t.getState().onSelectionChange.forEach(s=>s(i))},[n,r,e]),null});W2.displayName="SelectionListener";const o6=e=>!!e.onSelectionChange;function a6({onSelectionChange:e}){const t=Ie(o6);return e||t?$.createElement(W2,{onSelectionChange:e}):null}const l6=e=>({setNodes:e.setNodes,setEdges:e.setEdges,setDefaultNodesAndEdges:e.setDefaultNodesAndEdges,setMinZoom:e.setMinZoom,setMaxZoom:e.setMaxZoom,setTranslateExtent:e.setTranslateExtent,setNodeExtent:e.setNodeExtent,reset:e.reset});function di(e,t){T.useEffect(()=>{typeof e<"u"&&t(e)},[e])}function ae(e,t,n){T.useEffect(()=>{typeof t<"u"&&n({[e]:t})},[t])}const u6=({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,onConnect:i,onConnectStart:s,onConnectEnd:o,onClickConnectStart:a,onClickConnectEnd:l,nodesDraggable:u,nodesConnectable:c,nodesFocusable:f,edgesFocusable:d,edgesUpdatable:h,elevateNodesOnSelect:m,minZoom:g,maxZoom:w,nodeExtent:p,onNodesChange:x,onEdgesChange:v,elementsSelectable:k,connectionMode:N,snapGrid:S,snapToGrid:A,translateExtent:P,connectOnClick:D,defaultEdgeOptions:C,fitView:j,fitViewOptions:z,onNodesDelete:H,onEdgesDelete:_,onNodeDrag:L,onNodeDragStart:I,onNodeDragStop:O,onSelectionDrag:R,onSelectionDragStart:M,onSelectionDragStop:b,noPanClassName:F,nodeOrigin:B,rfId:E,autoPanOnConnect:q,autoPanOnNodeDrag:X,onError:G,connectionRadius:ne,isValidConnection:oe,nodeDragThreshold:de})=>{const{setNodes:le,setEdges:Ee,setDefaultNodesAndEdges:lt,setMinZoom:Nt,setMaxZoom:yt,setTranslateExtent:je,setNodeExtent:Tt,reset:ge}=Ie(l6,Oe),Z=Ke();return T.useEffect(()=>{const et=r==null?void 0:r.map(dn=>({...dn,...C}));return lt(n,et),()=>{ge()}},[]),ae("defaultEdgeOptions",C,Z.setState),ae("connectionMode",N,Z.setState),ae("onConnect",i,Z.setState),ae("onConnectStart",s,Z.setState),ae("onConnectEnd",o,Z.setState),ae("onClickConnectStart",a,Z.setState),ae("onClickConnectEnd",l,Z.setState),ae("nodesDraggable",u,Z.setState),ae("nodesConnectable",c,Z.setState),ae("nodesFocusable",f,Z.setState),ae("edgesFocusable",d,Z.setState),ae("edgesUpdatable",h,Z.setState),ae("elementsSelectable",k,Z.setState),ae("elevateNodesOnSelect",m,Z.setState),ae("snapToGrid",A,Z.setState),ae("snapGrid",S,Z.setState),ae("onNodesChange",x,Z.setState),ae("onEdgesChange",v,Z.setState),ae("connectOnClick",D,Z.setState),ae("fitViewOnInit",j,Z.setState),ae("fitViewOnInitOptions",z,Z.setState),ae("onNodesDelete",H,Z.setState),ae("onEdgesDelete",_,Z.setState),ae("onNodeDrag",L,Z.setState),ae("onNodeDragStart",I,Z.setState),ae("onNodeDragStop",O,Z.setState),ae("onSelectionDrag",R,Z.setState),ae("onSelectionDragStart",M,Z.setState),ae("onSelectionDragStop",b,Z.setState),ae("noPanClassName",F,Z.setState),ae("nodeOrigin",B,Z.setState),ae("rfId",E,Z.setState),ae("autoPanOnConnect",q,Z.setState),ae("autoPanOnNodeDrag",X,Z.setState),ae("onError",G,Z.setState),ae("connectionRadius",ne,Z.setState),ae("isValidConnection",oe,Z.setState),ae("nodeDragThreshold",de,Z.setState),di(e,le),di(t,Ee),di(g,Nt),di(w,yt),di(P,je),di(p,Tt),null},Ty={display:"none"},c6={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},Y2="react-flow__node-desc",q2="react-flow__edge-desc",f6="react-flow__aria-live",d6=e=>e.ariaLiveMessage;function h6({rfId:e}){const t=Ie(d6);return $.createElement("div",{id:`${f6}-${e}`,"aria-live":"assertive","aria-atomic":"true",style:c6},t)}function p6({rfId:e,disableKeyboardA11y:t}){return $.createElement($.Fragment,null,$.createElement("div",{id:`${Y2}-${e}`,style:Ty},"Press enter or space to select a node.",!t&&"You can then use the arrow keys to move the node around."," Press delete to remove it and escape to cancel."," "),$.createElement("div",{id:`${q2}-${e}`,style:Ty},"Press enter or space to select an edge. You can then press delete to remove it or escape to cancel."),!t&&$.createElement(h6,{rfId:e}))}var Io=(e=null,t={actInsideInputWithModifier:!0})=>{const[n,r]=T.useState(!1),i=T.useRef(!1),s=T.useRef(new Set([])),[o,a]=T.useMemo(()=>{if(e!==null){const u=(Array.isArray(e)?e:[e]).filter(f=>typeof f=="string").map(f=>f.split("+")),c=u.reduce((f,d)=>f.concat(...d),[]);return[u,c]}return[[],[]]},[e]);return T.useEffect(()=>{const l=typeof document<"u"?document:null,u=(t==null?void 0:t.target)||l;if(e!==null){const c=h=>{if(i.current=h.ctrlKey||h.metaKey||h.shiftKey,(!i.current||i.current&&!t.actInsideInputWithModifier)&&Sd(h))return!1;const g=Py(h.code,a);s.current.add(h[g]),Ay(o,s.current,!1)&&(h.preventDefault(),r(!0))},f=h=>{if((!i.current||i.current&&!t.actInsideInputWithModifier)&&Sd(h))return!1;const g=Py(h.code,a);Ay(o,s.current,!0)?(r(!1),s.current.clear()):s.current.delete(h[g]),h.key==="Meta"&&s.current.clear(),i.current=!1},d=()=>{s.current.clear(),r(!1)};return u==null||u.addEventListener("keydown",c),u==null||u.addEventListener("keyup",f),window.addEventListener("blur",d),()=>{u==null||u.removeEventListener("keydown",c),u==null||u.removeEventListener("keyup",f),window.removeEventListener("blur",d)}}},[e,r]),n};function Ay(e,t,n){return e.filter(r=>n||r.length===t.size).some(r=>r.every(i=>t.has(i)))}function Py(e,t){return t.includes(e)?"code":"key"}function K2(e,t,n,r){var a,l;const i=e.parentNode||e.parentId;if(!i)return n;const s=t.get(i),o=Hi(s,r);return K2(s,t,{x:(n.x??0)+o.x,y:(n.y??0)+o.y,z:(((a=s[Re])==null?void 0:a.z)??0)>(n.z??0)?((l=s[Re])==null?void 0:l.z)??0:n.z??0},r)}function G2(e,t,n){e.forEach(r=>{var s;const i=r.parentNode||r.parentId;if(i&&!e.has(i))throw new Error(`Parent node ${i} not found`);if(i||n!=null&&n[r.id]){const{x:o,y:a,z:l}=K2(r,e,{...r.position,z:((s=r[Re])==null?void 0:s.z)??0},t);r.positionAbsolute={x:o,y:a},r[Re].z=l,n!=null&&n[r.id]&&(r[Re].isParent=!0)}})}function $c(e,t,n,r){const i=new Map,s={},o=r?1e3:0;return e.forEach(a=>{var h;const l=(qt(a.zIndex)?a.zIndex:0)+(a.selected?o:0),u=t.get(a.id),c={...a,positionAbsolute:{x:a.position.x,y:a.position.y}},f=a.parentNode||a.parentId;f&&(s[f]=!0);const d=(u==null?void 0:u.type)&&(u==null?void 0:u.type)!==a.type;Object.defineProperty(c,Re,{enumerable:!1,value:{handleBounds:d||(h=u==null?void 0:u[Re])==null?void 0:h.handleBounds,z:l}}),i.set(a.id,c)}),G2(i,n,s),i}function X2(e,t={}){const{getNodes:n,width:r,height:i,minZoom:s,maxZoom:o,d3Zoom:a,d3Selection:l,fitViewOnInitDone:u,fitViewOnInit:c,nodeOrigin:f}=e(),d=t.initial&&!u&&c;if(a&&l&&(d||!t.initial)){const m=n().filter(w=>{var x;const p=t.includeHiddenNodes?w.width&&w.height:!w.hidden;return(x=t.nodes)!=null&&x.length?p&&t.nodes.some(v=>v.id===w.id):p}),g=m.every(w=>w.width&&w.height);if(m.length>0&&g){const w=Cp(m,f),{x:p,y:x,zoom:v}=L2(w,r,i,t.minZoom??s,t.maxZoom??o,t.padding??.1),k=yr.translate(p,x).scale(v);return typeof t.duration=="number"&&t.duration>0?a.transform(Fr(l,t.duration),k):a.transform(l,k),!0}}return!1}function m6(e,t){return e.forEach(n=>{const r=t.get(n.id);r&&t.set(r.id,{...r,[Re]:r[Re],selected:n.selected})}),new Map(t)}function g6(e,t){return t.map(n=>{const r=e.find(i=>i.id===n.id);return r&&(n.selected=r.selected),n})}function Ta({changedNodes:e,changedEdges:t,get:n,set:r}){const{nodeInternals:i,edges:s,onNodesChange:o,onEdgesChange:a,hasDefaultNodes:l,hasDefaultEdges:u}=n();e!=null&&e.length&&(l&&r({nodeInternals:m6(e,i)}),o==null||o(e)),t!=null&&t.length&&(u&&r({edges:g6(t,s)}),a==null||a(t))}const hi=()=>{},y6={zoomIn:hi,zoomOut:hi,zoomTo:hi,getZoom:()=>1,setViewport:hi,getViewport:()=>({x:0,y:0,zoom:1}),fitView:()=>!1,setCenter:hi,fitBounds:hi,project:e=>e,screenToFlowPosition:e=>e,flowToScreenPosition:e=>e,viewportInitialized:!1},x6=e=>({d3Zoom:e.d3Zoom,d3Selection:e.d3Selection}),v6=()=>{const e=Ke(),{d3Zoom:t,d3Selection:n}=Ie(x6,Oe);return T.useMemo(()=>n&&t?{zoomIn:i=>t.scaleBy(Fr(n,i==null?void 0:i.duration),1.2),zoomOut:i=>t.scaleBy(Fr(n,i==null?void 0:i.duration),1/1.2),zoomTo:(i,s)=>t.scaleTo(Fr(n,s==null?void 0:s.duration),i),getZoom:()=>e.getState().transform[2],setViewport:(i,s)=>{const[o,a,l]=e.getState().transform,u=yr.translate(i.x??o,i.y??a).scale(i.zoom??l);t.transform(Fr(n,s==null?void 0:s.duration),u)},getViewport:()=>{const[i,s,o]=e.getState().transform;return{x:i,y:s,zoom:o}},fitView:i=>X2(e.getState,i),setCenter:(i,s,o)=>{const{width:a,height:l,maxZoom:u}=e.getState(),c=typeof(o==null?void 0:o.zoom)<"u"?o.zoom:u,f=a/2-i*c,d=l/2-s*c,h=yr.translate(f,d).scale(c);t.transform(Fr(n,o==null?void 0:o.duration),h)},fitBounds:(i,s)=>{const{width:o,height:a,minZoom:l,maxZoom:u}=e.getState(),{x:c,y:f,zoom:d}=L2(i,o,a,l,u,(s==null?void 0:s.padding)??.1),h=yr.translate(c,f).scale(d);t.transform(Fr(n,s==null?void 0:s.duration),h)},project:i=>{const{transform:s,snapToGrid:o,snapGrid:a}=e.getState();return console.warn("[DEPRECATED] `project` is deprecated. Instead use `screenToFlowPosition`. There is no need to subtract the react flow bounds anymore! https://reactflow.dev/api-reference/types/react-flow-instance#screen-to-flow-position"),Ed(i,s,o,a)},screenToFlowPosition:i=>{const{transform:s,snapToGrid:o,snapGrid:a,domNode:l}=e.getState();if(!l)return i;const{x:u,y:c}=l.getBoundingClientRect(),f={x:i.x-u,y:i.y-c};return Ed(f,s,o,a)},flowToScreenPosition:i=>{const{transform:s,domNode:o}=e.getState();if(!o)return i;const{x:a,y:l}=o.getBoundingClientRect(),u=D2(i,s);return{x:u.x+a,y:u.y+l}},viewportInitialized:!0}:y6,[t,n])};function Tu(){const e=v6(),t=Ke(),n=T.useCallback(()=>t.getState().getNodes().map(g=>({...g})),[]),r=T.useCallback(g=>t.getState().nodeInternals.get(g),[]),i=T.useCallback(()=>{const{edges:g=[]}=t.getState();return g.map(w=>({...w}))},[]),s=T.useCallback(g=>{const{edges:w=[]}=t.getState();return w.find(p=>p.id===g)},[]),o=T.useCallback(g=>{const{getNodes:w,setNodes:p,hasDefaultNodes:x,onNodesChange:v}=t.getState(),k=w(),N=typeof g=="function"?g(k):g;if(x)p(N);else if(v){const S=N.length===0?k.map(A=>({type:"remove",id:A.id})):N.map(A=>({item:A,type:"reset"}));v(S)}},[]),a=T.useCallback(g=>{const{edges:w=[],setEdges:p,hasDefaultEdges:x,onEdgesChange:v}=t.getState(),k=typeof g=="function"?g(w):g;if(x)p(k);else if(v){const N=k.length===0?w.map(S=>({type:"remove",id:S.id})):k.map(S=>({item:S,type:"reset"}));v(N)}},[]),l=T.useCallback(g=>{const w=Array.isArray(g)?g:[g],{getNodes:p,setNodes:x,hasDefaultNodes:v,onNodesChange:k}=t.getState();if(v){const S=[...p(),...w];x(S)}else if(k){const N=w.map(S=>({item:S,type:"add"}));k(N)}},[]),u=T.useCallback(g=>{const w=Array.isArray(g)?g:[g],{edges:p=[],setEdges:x,hasDefaultEdges:v,onEdgesChange:k}=t.getState();if(v)x([...p,...w]);else if(k){const N=w.map(S=>({item:S,type:"add"}));k(N)}},[]),c=T.useCallback(()=>{const{getNodes:g,edges:w=[],transform:p}=t.getState(),[x,v,k]=p;return{nodes:g().map(N=>({...N})),edges:w.map(N=>({...N})),viewport:{x,y:v,zoom:k}}},[]),f=T.useCallback(({nodes:g,edges:w})=>{const{nodeInternals:p,getNodes:x,edges:v,hasDefaultNodes:k,hasDefaultEdges:N,onNodesDelete:S,onEdgesDelete:A,onNodesChange:P,onEdgesChange:D}=t.getState(),C=(g||[]).map(L=>L.id),j=(w||[]).map(L=>L.id),z=x().reduce((L,I)=>{const O=I.parentNode||I.parentId,R=!C.includes(I.id)&&O&&L.find(b=>b.id===O);return(typeof I.deletable=="boolean"?I.deletable:!0)&&(C.includes(I.id)||R)&&L.push(I),L},[]),H=v.filter(L=>typeof L.deletable=="boolean"?L.deletable:!0),_=H.filter(L=>j.includes(L.id));if(z||_){const L=j2(z,H),I=[..._,...L],O=I.reduce((R,M)=>(R.includes(M.id)||R.push(M.id),R),[]);if((N||k)&&(N&&t.setState({edges:v.filter(R=>!O.includes(R.id))}),k&&(z.forEach(R=>{p.delete(R.id)}),t.setState({nodeInternals:new Map(p)}))),O.length>0&&(A==null||A(I),D&&D(O.map(R=>({id:R,type:"remove"})))),z.length>0&&(S==null||S(z),P)){const R=z.map(M=>({id:M.id,type:"remove"}));P(R)}}},[]),d=T.useCallback(g=>{const w=O8(g),p=w?null:t.getState().nodeInternals.get(g.id);return!w&&!p?[null,null,w]:[w?g:ky(p),p,w]},[]),h=T.useCallback((g,w=!0,p)=>{const[x,v,k]=d(g);return x?(p||t.getState().getNodes()).filter(N=>{if(!k&&(N.id===v.id||!N.positionAbsolute))return!1;const S=ky(N),A=bd(S,x);return w&&A>0||A>=x.width*x.height}):[]},[]),m=T.useCallback((g,w,p=!0)=>{const[x]=d(g);if(!x)return!1;const v=bd(x,w);return p&&v>0||v>=x.width*x.height},[]);return T.useMemo(()=>({...e,getNodes:n,getNode:r,getEdges:i,getEdge:s,setNodes:o,setEdges:a,addNodes:l,addEdges:u,toObject:c,deleteElements:f,getIntersectingNodes:h,isNodeIntersecting:m}),[e,n,r,i,s,o,a,l,u,c,f,h,m])}const w6={actInsideInputWithModifier:!1};var k6=({deleteKeyCode:e,multiSelectionKeyCode:t})=>{const n=Ke(),{deleteElements:r}=Tu(),i=Io(e,w6),s=Io(t);T.useEffect(()=>{if(i){const{edges:o,getNodes:a}=n.getState(),l=a().filter(c=>c.selected),u=o.filter(c=>c.selected);r({nodes:l,edges:u}),n.setState({nodesSelectionActive:!1})}},[i]),T.useEffect(()=>{n.setState({multiSelectionActive:s})},[s])};function b6(e){const t=Ke();T.useEffect(()=>{let n;const r=()=>{var s,o;if(!e.current)return;const i=xp(e.current);(i.height===0||i.width===0)&&((o=(s=t.getState()).onError)==null||o.call(s,"004",Bn.error004())),t.setState({width:i.width||500,height:i.height||500})};return r(),window.addEventListener("resize",r),e.current&&(n=new ResizeObserver(()=>r()),n.observe(e.current)),()=>{window.removeEventListener("resize",r),n&&e.current&&n.unobserve(e.current)}},[])}const Tp={position:"absolute",width:"100%",height:"100%",top:0,left:0},S6=(e,t)=>e.x!==t.x||e.y!==t.y||e.zoom!==t.k,Aa=e=>({x:e.x,y:e.y,zoom:e.k}),pi=(e,t)=>e.target.closest(`.${t}`),My=(e,t)=>t===2&&Array.isArray(e)&&e.includes(2),Dy=e=>{const t=e.ctrlKey&&Vl()?10:1;return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*t},_6=e=>({d3Zoom:e.d3Zoom,d3Selection:e.d3Selection,d3ZoomHandler:e.d3ZoomHandler,userSelectionActive:e.userSelectionActive}),C6=({onMove:e,onMoveStart:t,onMoveEnd:n,onPaneContextMenu:r,zoomOnScroll:i=!0,zoomOnPinch:s=!0,panOnScroll:o=!1,panOnScrollSpeed:a=.5,panOnScrollMode:l=Wr.Free,zoomOnDoubleClick:u=!0,elementsSelectable:c,panOnDrag:f=!0,defaultViewport:d,translateExtent:h,minZoom:m,maxZoom:g,zoomActivationKeyCode:w,preventScrolling:p=!0,children:x,noWheelClassName:v,noPanClassName:k})=>{const N=T.useRef(),S=Ke(),A=T.useRef(!1),P=T.useRef(!1),D=T.useRef(null),C=T.useRef({x:0,y:0,zoom:0}),{d3Zoom:j,d3Selection:z,d3ZoomHandler:H,userSelectionActive:_}=Ie(_6,Oe),L=Io(w),I=T.useRef(0),O=T.useRef(!1),R=T.useRef();return b6(D),T.useEffect(()=>{if(D.current){const M=D.current.getBoundingClientRect(),b=M8().scaleExtent([m,g]).translateExtent(h),F=nn(D.current).call(b),B=yr.translate(d.x,d.y).scale(os(d.zoom,m,g)),E=[[0,0],[M.width,M.height]],q=b.constrain()(B,E,h);b.transform(F,q),b.wheelDelta(Dy),S.setState({d3Zoom:b,d3Selection:F,d3ZoomHandler:F.on("wheel.zoom"),transform:[q.x,q.y,q.k],domNode:D.current.closest(".react-flow")})}},[]),T.useEffect(()=>{z&&j&&(o&&!L&&!_?z.on("wheel.zoom",M=>{if(pi(M,v))return!1;M.preventDefault(),M.stopImmediatePropagation();const b=z.property("__zoom").k||1;if(M.ctrlKey&&s){const oe=yn(M),de=Dy(M),le=b*Math.pow(2,de);j.scaleTo(z,le,oe,M);return}const F=M.deltaMode===1?20:1;let B=l===Wr.Vertical?0:M.deltaX*F,E=l===Wr.Horizontal?0:M.deltaY*F;!Vl()&&M.shiftKey&&l!==Wr.Vertical&&(B=M.deltaY*F,E=0),j.translateBy(z,-(B/b)*a,-(E/b)*a,{internal:!0});const q=Aa(z.property("__zoom")),{onViewportChangeStart:X,onViewportChange:G,onViewportChangeEnd:ne}=S.getState();clearTimeout(R.current),O.current||(O.current=!0,t==null||t(M,q),X==null||X(q)),O.current&&(e==null||e(M,q),G==null||G(q),R.current=setTimeout(()=>{n==null||n(M,q),ne==null||ne(q),O.current=!1},150))},{passive:!1}):typeof H<"u"&&z.on("wheel.zoom",function(M,b){if(!p&&M.type==="wheel"&&!M.ctrlKey||pi(M,v))return null;M.preventDefault(),H.call(this,M,b)},{passive:!1}))},[_,o,l,z,j,H,L,s,p,v,t,e,n]),T.useEffect(()=>{j&&j.on("start",M=>{var B,E;if(!M.sourceEvent||M.sourceEvent.internal)return null;I.current=(B=M.sourceEvent)==null?void 0:B.button;const{onViewportChangeStart:b}=S.getState(),F=Aa(M.transform);A.current=!0,C.current=F,((E=M.sourceEvent)==null?void 0:E.type)==="mousedown"&&S.setState({paneDragging:!0}),b==null||b(F),t==null||t(M.sourceEvent,F)})},[j,t]),T.useEffect(()=>{j&&(_&&!A.current?j.on("zoom",null):_||j.on("zoom",M=>{var F;const{onViewportChange:b}=S.getState();if(S.setState({transform:[M.transform.x,M.transform.y,M.transform.k]}),P.current=!!(r&&My(f,I.current??0)),(e||b)&&!((F=M.sourceEvent)!=null&&F.internal)){const B=Aa(M.transform);b==null||b(B),e==null||e(M.sourceEvent,B)}}))},[_,j,e,f,r]),T.useEffect(()=>{j&&j.on("end",M=>{if(!M.sourceEvent||M.sourceEvent.internal)return null;const{onViewportChangeEnd:b}=S.getState();if(A.current=!1,S.setState({paneDragging:!1}),r&&My(f,I.current??0)&&!P.current&&r(M.sourceEvent),P.current=!1,(n||b)&&S6(C.current,M.transform)){const F=Aa(M.transform);C.current=F,clearTimeout(N.current),N.current=setTimeout(()=>{b==null||b(F),n==null||n(M.sourceEvent,F)},o?150:0)}})},[j,o,f,n,r]),T.useEffect(()=>{j&&j.filter(M=>{const b=L||i,F=s&&M.ctrlKey;if((f===!0||Array.isArray(f)&&f.includes(1))&&M.button===1&&M.type==="mousedown"&&(pi(M,"react-flow__node")||pi(M,"react-flow__edge")))return!0;if(!f&&!b&&!o&&!u&&!s||_||!u&&M.type==="dblclick"||pi(M,v)&&M.type==="wheel"||pi(M,k)&&(M.type!=="wheel"||o&&M.type==="wheel"&&!L)||!s&&M.ctrlKey&&M.type==="wheel"||!b&&!o&&!F&&M.type==="wheel"||!f&&(M.type==="mousedown"||M.type==="touchstart")||Array.isArray(f)&&!f.includes(M.button)&&M.type==="mousedown")return!1;const B=Array.isArray(f)&&f.includes(M.button)||!M.button||M.button<=1;return(!M.ctrlKey||M.type==="wheel")&&B})},[_,j,i,s,o,u,f,c,L]),$.createElement("div",{className:"react-flow__renderer",ref:D,style:Tp},x)},E6=e=>({userSelectionActive:e.userSelectionActive,userSelectionRect:e.userSelectionRect});function N6(){const{userSelectionActive:e,userSelectionRect:t}=Ie(E6,Oe);return e&&t?$.createElement("div",{className:"react-flow__selection react-flow__container",style:{width:t.width,height:t.height,transform:`translate(${t.x}px, ${t.y}px)`}}):null}function Iy(e,t){const n=t.parentNode||t.parentId,r=e.find(i=>i.id===n);if(r){const i=t.position.x+t.width-r.width,s=t.position.y+t.height-r.height;if(i>0||s>0||t.position.x<0||t.position.y<0){if(r.style={...r.style},r.style.width=r.style.width??r.width,r.style.height=r.style.height??r.height,i>0&&(r.style.width+=i),s>0&&(r.style.height+=s),t.position.x<0){const o=Math.abs(t.position.x);r.position.x=r.position.x-o,r.style.width+=o,t.position.x=0}if(t.position.y<0){const o=Math.abs(t.position.y);r.position.y=r.position.y-o,r.style.height+=o,t.position.y=0}r.width=r.style.width,r.height=r.style.height}}}function Q2(e,t){if(e.some(r=>r.type==="reset"))return e.filter(r=>r.type==="reset").map(r=>r.item);const n=e.filter(r=>r.type==="add").map(r=>r.item);return t.reduce((r,i)=>{const s=e.filter(a=>a.id===i.id);if(s.length===0)return r.push(i),r;const o={...i};for(const a of s)if(a)switch(a.type){case"select":{o.selected=a.selected;break}case"position":{typeof a.position<"u"&&(o.position=a.position),typeof a.positionAbsolute<"u"&&(o.positionAbsolute=a.positionAbsolute),typeof a.dragging<"u"&&(o.dragging=a.dragging),o.expandParent&&Iy(r,o);break}case"dimensions":{typeof a.dimensions<"u"&&(o.width=a.dimensions.width,o.height=a.dimensions.height),typeof a.updateStyle<"u"&&(o.style={...o.style||{},...a.dimensions}),typeof a.resizing=="boolean"&&(o.resizing=a.resizing),o.expandParent&&Iy(r,o);break}case"remove":return r}return r.push(o),r},n)}function Z2(e,t){return Q2(e,t)}function T6(e,t){return Q2(e,t)}const nr=(e,t)=>({id:e,type:"select",selected:t});function Ii(e,t){return e.reduce((n,r)=>{const i=t.includes(r.id);return!r.selected&&i?(r.selected=!0,n.push(nr(r.id,!0))):r.selected&&!i&&(r.selected=!1,n.push(nr(r.id,!1))),n},[])}const Bc=(e,t)=>n=>{n.target===t.current&&(e==null||e(n))},A6=e=>({userSelectionActive:e.userSelectionActive,elementsSelectable:e.elementsSelectable,dragging:e.paneDragging}),J2=T.memo(({isSelecting:e,selectionMode:t=Mo.Full,panOnDrag:n,onSelectionStart:r,onSelectionEnd:i,onPaneClick:s,onPaneContextMenu:o,onPaneScroll:a,onPaneMouseEnter:l,onPaneMouseMove:u,onPaneMouseLeave:c,children:f})=>{const d=T.useRef(null),h=Ke(),m=T.useRef(0),g=T.useRef(0),w=T.useRef(),{userSelectionActive:p,elementsSelectable:x,dragging:v}=Ie(A6,Oe),k=()=>{h.setState({userSelectionActive:!1,userSelectionRect:null}),m.current=0,g.current=0},N=H=>{s==null||s(H),h.getState().resetSelectedElements(),h.setState({nodesSelectionActive:!1})},S=H=>{if(Array.isArray(n)&&(n!=null&&n.includes(2))){H.preventDefault();return}o==null||o(H)},A=a?H=>a(H):void 0,P=H=>{const{resetSelectedElements:_,domNode:L}=h.getState();if(w.current=L==null?void 0:L.getBoundingClientRect(),!x||!e||H.button!==0||H.target!==d.current||!w.current)return;const{x:I,y:O}=xr(H,w.current);_(),h.setState({userSelectionRect:{width:0,height:0,startX:I,startY:O,x:I,y:O}}),r==null||r(H)},D=H=>{const{userSelectionRect:_,nodeInternals:L,edges:I,transform:O,onNodesChange:R,onEdgesChange:M,nodeOrigin:b,getNodes:F}=h.getState();if(!e||!w.current||!_)return;h.setState({userSelectionActive:!0,nodesSelectionActive:!1});const B=xr(H,w.current),E=_.startX??0,q=_.startY??0,X={..._,x:B.xle.id),de=ne.map(le=>le.id);if(m.current!==de.length){m.current=de.length;const le=Ii(G,de);le.length&&(R==null||R(le))}if(g.current!==oe.length){g.current=oe.length;const le=Ii(I,oe);le.length&&(M==null||M(le))}h.setState({userSelectionRect:X})},C=H=>{if(H.button!==0)return;const{userSelectionRect:_}=h.getState();!p&&_&&H.target===d.current&&(N==null||N(H)),h.setState({nodesSelectionActive:m.current>0}),k(),i==null||i(H)},j=H=>{p&&(h.setState({nodesSelectionActive:m.current>0}),i==null||i(H)),k()},z=x&&(e||p);return $.createElement("div",{className:gt(["react-flow__pane",{dragging:v,selection:e}]),onClick:z?void 0:Bc(N,d),onContextMenu:Bc(S,d),onWheel:Bc(A,d),onMouseEnter:z?void 0:l,onMouseDown:z?P:void 0,onMouseMove:z?D:u,onMouseUp:z?C:void 0,onMouseLeave:z?j:c,ref:d,style:Tp},f,$.createElement(N6,null))});J2.displayName="Pane";function eb(e,t){const n=e.parentNode||e.parentId;if(!n)return!1;const r=t.get(n);return r?r.selected?!0:eb(r,t):!1}function jy(e,t,n){let r=e;do{if(r!=null&&r.matches(t))return!0;if(r===n.current)return!1;r=r.parentElement}while(r);return!1}function P6(e,t,n,r){return Array.from(e.values()).filter(i=>(i.selected||i.id===r)&&(!i.parentNode||i.parentId||!eb(i,e))&&(i.draggable||t&&typeof i.draggable>"u")).map(i=>{var s,o;return{id:i.id,position:i.position||{x:0,y:0},positionAbsolute:i.positionAbsolute||{x:0,y:0},distance:{x:n.x-(((s=i.positionAbsolute)==null?void 0:s.x)??0),y:n.y-(((o=i.positionAbsolute)==null?void 0:o.y)??0)},delta:{x:0,y:0},extent:i.extent,parentNode:i.parentNode||i.parentId,parentId:i.parentNode||i.parentId,width:i.width,height:i.height,expandParent:i.expandParent}})}function M6(e,t){return!t||t==="parent"?t:[t[0],[t[1][0]-(e.width||0),t[1][1]-(e.height||0)]]}function tb(e,t,n,r,i=[0,0],s){const o=M6(e,e.extent||r);let a=o;const l=e.parentNode||e.parentId;if(e.extent==="parent"&&!e.expandParent)if(l&&e.width&&e.height){const f=n.get(l),{x:d,y:h}=Hi(f,i).positionAbsolute;a=f&&qt(d)&&qt(h)&&qt(f.width)&&qt(f.height)?[[d+e.width*i[0],h+e.height*i[1]],[d+f.width-e.width+e.width*i[0],h+f.height-e.height+e.height*i[1]]]:a}else s==null||s("005",Bn.error005()),a=o;else if(e.extent&&l&&e.extent!=="parent"){const f=n.get(l),{x:d,y:h}=Hi(f,i).positionAbsolute;a=[[e.extent[0][0]+d,e.extent[0][1]+h],[e.extent[1][0]+d,e.extent[1][1]+h]]}let u={x:0,y:0};if(l){const f=n.get(l);u=Hi(f,i).positionAbsolute}const c=a&&a!=="parent"?vp(t,a):t;return{position:{x:c.x-u.x,y:c.y-u.y},positionAbsolute:c}}function Hc({nodeId:e,dragItems:t,nodeInternals:n}){const r=t.map(i=>({...n.get(i.id),position:i.position,positionAbsolute:i.positionAbsolute}));return[e?r.find(i=>i.id===e):r[0],r]}const Ly=(e,t,n,r)=>{const i=t.querySelectorAll(e);if(!i||!i.length)return null;const s=Array.from(i),o=t.getBoundingClientRect(),a={x:o.width*r[0],y:o.height*r[1]};return s.map(l=>{const u=l.getBoundingClientRect();return{id:l.getAttribute("data-handleid"),position:l.getAttribute("data-handlepos"),x:(u.left-o.left-a.x)/n,y:(u.top-o.top-a.y)/n,...xp(l)}})};function As(e,t,n){return n===void 0?n:r=>{const i=t().nodeInternals.get(e);i&&n(r,{...i})}}function Td({id:e,store:t,unselect:n=!1,nodeRef:r}){const{addSelectedNodes:i,unselectNodesAndEdges:s,multiSelectionActive:o,nodeInternals:a,onError:l}=t.getState(),u=a.get(e);if(!u){l==null||l("012",Bn.error012(e));return}t.setState({nodesSelectionActive:!1}),u.selected?(n||u.selected&&o)&&(s({nodes:[u],edges:[]}),requestAnimationFrame(()=>{var c;return(c=r==null?void 0:r.current)==null?void 0:c.blur()})):i([e])}function D6(){const e=Ke();return T.useCallback(({sourceEvent:n})=>{const{transform:r,snapGrid:i,snapToGrid:s}=e.getState(),o=n.touches?n.touches[0].clientX:n.clientX,a=n.touches?n.touches[0].clientY:n.clientY,l={x:(o-r[0])/r[2],y:(a-r[1])/r[2]};return{xSnapped:s?i[0]*Math.round(l.x/i[0]):l.x,ySnapped:s?i[1]*Math.round(l.y/i[1]):l.y,...l}},[])}function Uc(e){return(t,n,r)=>e==null?void 0:e(t,r)}function nb({nodeRef:e,disabled:t=!1,noDragClassName:n,handleSelector:r,nodeId:i,isSelectable:s,selectNodesOnDrag:o}){const a=Ke(),[l,u]=T.useState(!1),c=T.useRef([]),f=T.useRef({x:null,y:null}),d=T.useRef(0),h=T.useRef(null),m=T.useRef({x:0,y:0}),g=T.useRef(null),w=T.useRef(!1),p=T.useRef(!1),x=T.useRef(!1),v=D6();return T.useEffect(()=>{if(e!=null&&e.current){const k=nn(e.current),N=({x:P,y:D})=>{const{nodeInternals:C,onNodeDrag:j,onSelectionDrag:z,updateNodePositions:H,nodeExtent:_,snapGrid:L,snapToGrid:I,nodeOrigin:O,onError:R}=a.getState();f.current={x:P,y:D};let M=!1,b={x:0,y:0,x2:0,y2:0};if(c.current.length>1&&_){const B=Cp(c.current,O);b=wp(B)}if(c.current=c.current.map(B=>{const E={x:P-B.distance.x,y:D-B.distance.y};I&&(E.x=L[0]*Math.round(E.x/L[0]),E.y=L[1]*Math.round(E.y/L[1]));const q=[[_[0][0],_[0][1]],[_[1][0],_[1][1]]];c.current.length>1&&_&&!B.extent&&(q[0][0]=B.positionAbsolute.x-b.x+_[0][0],q[1][0]=B.positionAbsolute.x+(B.width??0)-b.x2+_[1][0],q[0][1]=B.positionAbsolute.y-b.y+_[0][1],q[1][1]=B.positionAbsolute.y+(B.height??0)-b.y2+_[1][1]);const X=tb(B,E,C,q,O,R);return M=M||B.position.x!==X.position.x||B.position.y!==X.position.y,B.position=X.position,B.positionAbsolute=X.positionAbsolute,B}),!M)return;H(c.current,!0,!0),u(!0);const F=i?j:Uc(z);if(F&&g.current){const[B,E]=Hc({nodeId:i,dragItems:c.current,nodeInternals:C});F(g.current,B,E)}},S=()=>{if(!h.current)return;const[P,D]=_2(m.current,h.current);if(P!==0||D!==0){const{transform:C,panBy:j}=a.getState();f.current.x=(f.current.x??0)-P/C[2],f.current.y=(f.current.y??0)-D/C[2],j({x:P,y:D})&&N(f.current)}d.current=requestAnimationFrame(S)},A=P=>{var O;const{nodeInternals:D,multiSelectionActive:C,nodesDraggable:j,unselectNodesAndEdges:z,onNodeDragStart:H,onSelectionDragStart:_}=a.getState();p.current=!0;const L=i?H:Uc(_);(!o||!s)&&!C&&i&&((O=D.get(i))!=null&&O.selected||z()),i&&s&&o&&Td({id:i,store:a,nodeRef:e});const I=v(P);if(f.current=I,c.current=P6(D,j,I,i),L&&c.current){const[R,M]=Hc({nodeId:i,dragItems:c.current,nodeInternals:D});L(P.sourceEvent,R,M)}};if(t)k.on(".drag",null);else{const P=$R().on("start",D=>{const{domNode:C,nodeDragThreshold:j}=a.getState();j===0&&A(D),x.current=!1;const z=v(D);f.current=z,h.current=(C==null?void 0:C.getBoundingClientRect())||null,m.current=xr(D.sourceEvent,h.current)}).on("drag",D=>{var H,_;const C=v(D),{autoPanOnNodeDrag:j,nodeDragThreshold:z}=a.getState();if(D.sourceEvent.type==="touchmove"&&D.sourceEvent.touches.length>1&&(x.current=!0),!x.current){if(!w.current&&p.current&&j&&(w.current=!0,S()),!p.current){const L=C.xSnapped-(((H=f==null?void 0:f.current)==null?void 0:H.x)??0),I=C.ySnapped-(((_=f==null?void 0:f.current)==null?void 0:_.y)??0);Math.sqrt(L*L+I*I)>z&&A(D)}(f.current.x!==C.xSnapped||f.current.y!==C.ySnapped)&&c.current&&p.current&&(g.current=D.sourceEvent,m.current=xr(D.sourceEvent,h.current),N(C))}}).on("end",D=>{if(!(!p.current||x.current)&&(u(!1),w.current=!1,p.current=!1,cancelAnimationFrame(d.current),c.current)){const{updateNodePositions:C,nodeInternals:j,onNodeDragStop:z,onSelectionDragStop:H}=a.getState(),_=i?z:Uc(H);if(C(c.current,!1,!1),_){const[L,I]=Hc({nodeId:i,dragItems:c.current,nodeInternals:j});_(D.sourceEvent,L,I)}}}).filter(D=>{const C=D.target;return!D.button&&(!n||!jy(C,`.${n}`,e))&&(!r||jy(C,r,e))});return k.call(P),()=>{k.on(".drag",null)}}}},[e,t,n,r,s,a,i,o,v]),l}function rb(){const e=Ke();return T.useCallback(n=>{const{nodeInternals:r,nodeExtent:i,updateNodePositions:s,getNodes:o,snapToGrid:a,snapGrid:l,onError:u,nodesDraggable:c}=e.getState(),f=o().filter(x=>x.selected&&(x.draggable||c&&typeof x.draggable>"u")),d=a?l[0]:5,h=a?l[1]:5,m=n.isShiftPressed?4:1,g=n.x*d*m,w=n.y*h*m,p=f.map(x=>{if(x.positionAbsolute){const v={x:x.positionAbsolute.x+g,y:x.positionAbsolute.y+w};a&&(v.x=l[0]*Math.round(v.x/l[0]),v.y=l[1]*Math.round(v.y/l[1]));const{positionAbsolute:k,position:N}=tb(x,v,r,i,void 0,u);x.position=N,x.positionAbsolute=k}return x});s(p,!0,!1)},[])}const Ui={ArrowUp:{x:0,y:-1},ArrowDown:{x:0,y:1},ArrowLeft:{x:-1,y:0},ArrowRight:{x:1,y:0}};var Ps=e=>{const t=({id:n,type:r,data:i,xPos:s,yPos:o,xPosOrigin:a,yPosOrigin:l,selected:u,onClick:c,onMouseEnter:f,onMouseMove:d,onMouseLeave:h,onContextMenu:m,onDoubleClick:g,style:w,className:p,isDraggable:x,isSelectable:v,isConnectable:k,isFocusable:N,selectNodesOnDrag:S,sourcePosition:A,targetPosition:P,hidden:D,resizeObserver:C,dragHandle:j,zIndex:z,isParent:H,noDragClassName:_,noPanClassName:L,initialized:I,disableKeyboardA11y:O,ariaLabel:R,rfId:M,hasHandleBounds:b})=>{const F=Ke(),B=T.useRef(null),E=T.useRef(null),q=T.useRef(A),X=T.useRef(P),G=T.useRef(r),ne=v||x||c||f||d||h,oe=rb(),de=As(n,F.getState,f),le=As(n,F.getState,d),Ee=As(n,F.getState,h),lt=As(n,F.getState,m),Nt=As(n,F.getState,g),yt=ge=>{const{nodeDragThreshold:Z}=F.getState();if(v&&(!S||!x||Z>0)&&Td({id:n,store:F,nodeRef:B}),c){const et=F.getState().nodeInternals.get(n);et&&c(ge,{...et})}},je=ge=>{if(!Sd(ge)&&!O)if(E2.includes(ge.key)&&v){const Z=ge.key==="Escape";Td({id:n,store:F,unselect:Z,nodeRef:B})}else x&&u&&Object.prototype.hasOwnProperty.call(Ui,ge.key)&&(F.setState({ariaLiveMessage:`Moved selected node ${ge.key.replace("Arrow","").toLowerCase()}. New position, x: ${~~s}, y: ${~~o}`}),oe({x:Ui[ge.key].x,y:Ui[ge.key].y,isShiftPressed:ge.shiftKey}))};T.useEffect(()=>()=>{E.current&&(C==null||C.unobserve(E.current),E.current=null)},[]),T.useEffect(()=>{if(B.current&&!D){const ge=B.current;(!I||!b||E.current!==ge)&&(E.current&&(C==null||C.unobserve(E.current)),C==null||C.observe(ge),E.current=ge)}},[D,I,b]),T.useEffect(()=>{const ge=G.current!==r,Z=q.current!==A,et=X.current!==P;B.current&&(ge||Z||et)&&(ge&&(G.current=r),Z&&(q.current=A),et&&(X.current=P),F.getState().updateNodeDimensions([{id:n,nodeElement:B.current,forceUpdate:!0}]))},[n,r,A,P]);const Tt=nb({nodeRef:B,disabled:D||!x,noDragClassName:_,handleSelector:j,nodeId:n,isSelectable:v,selectNodesOnDrag:S});return D?null:$.createElement("div",{className:gt(["react-flow__node",`react-flow__node-${r}`,{[L]:x},p,{selected:u,selectable:v,parent:H,dragging:Tt}]),ref:B,style:{zIndex:z,transform:`translate(${a}px,${l}px)`,pointerEvents:ne?"all":"none",visibility:I?"visible":"hidden",...w},"data-id":n,"data-testid":`rf__node-${n}`,onMouseEnter:de,onMouseMove:le,onMouseLeave:Ee,onContextMenu:lt,onClick:yt,onDoubleClick:Nt,onKeyDown:N?je:void 0,tabIndex:N?0:void 0,role:N?"button":void 0,"aria-describedby":O?void 0:`${Y2}-${M}`,"aria-label":R},$.createElement(Y8,{value:n},$.createElement(e,{id:n,data:i,type:r,xPos:s,yPos:o,selected:u,isConnectable:k,sourcePosition:A,targetPosition:P,dragging:Tt,dragHandle:j,zIndex:z})))};return t.displayName="NodeWrapper",T.memo(t)};const I6=e=>{const t=e.getNodes().filter(n=>n.selected);return{...Cp(t,e.nodeOrigin),transformString:`translate(${e.transform[0]}px,${e.transform[1]}px) scale(${e.transform[2]})`,userSelectionActive:e.userSelectionActive}};function j6({onSelectionContextMenu:e,noPanClassName:t,disableKeyboardA11y:n}){const r=Ke(),{width:i,height:s,x:o,y:a,transformString:l,userSelectionActive:u}=Ie(I6,Oe),c=rb(),f=T.useRef(null);if(T.useEffect(()=>{var m;n||(m=f.current)==null||m.focus({preventScroll:!0})},[n]),nb({nodeRef:f}),u||!i||!s)return null;const d=e?m=>{const g=r.getState().getNodes().filter(w=>w.selected);e(m,g)}:void 0,h=m=>{Object.prototype.hasOwnProperty.call(Ui,m.key)&&c({x:Ui[m.key].x,y:Ui[m.key].y,isShiftPressed:m.shiftKey})};return $.createElement("div",{className:gt(["react-flow__nodesselection","react-flow__container",t]),style:{transform:l}},$.createElement("div",{ref:f,className:"react-flow__nodesselection-rect",onContextMenu:d,tabIndex:n?void 0:-1,onKeyDown:n?void 0:h,style:{width:i,height:s,top:a,left:o}}))}var L6=T.memo(j6);const R6=e=>e.nodesSelectionActive,ib=({children:e,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:i,onPaneContextMenu:s,onPaneScroll:o,deleteKeyCode:a,onMove:l,onMoveStart:u,onMoveEnd:c,selectionKeyCode:f,selectionOnDrag:d,selectionMode:h,onSelectionStart:m,onSelectionEnd:g,multiSelectionKeyCode:w,panActivationKeyCode:p,zoomActivationKeyCode:x,elementsSelectable:v,zoomOnScroll:k,zoomOnPinch:N,panOnScroll:S,panOnScrollSpeed:A,panOnScrollMode:P,zoomOnDoubleClick:D,panOnDrag:C,defaultViewport:j,translateExtent:z,minZoom:H,maxZoom:_,preventScrolling:L,onSelectionContextMenu:I,noWheelClassName:O,noPanClassName:R,disableKeyboardA11y:M})=>{const b=Ie(R6),F=Io(f),B=Io(p),E=B||C,q=B||S,X=F||d&&E!==!0;return k6({deleteKeyCode:a,multiSelectionKeyCode:w}),$.createElement(C6,{onMove:l,onMoveStart:u,onMoveEnd:c,onPaneContextMenu:s,elementsSelectable:v,zoomOnScroll:k,zoomOnPinch:N,panOnScroll:q,panOnScrollSpeed:A,panOnScrollMode:P,zoomOnDoubleClick:D,panOnDrag:!F&&E,defaultViewport:j,translateExtent:z,minZoom:H,maxZoom:_,zoomActivationKeyCode:x,preventScrolling:L,noWheelClassName:O,noPanClassName:R},$.createElement(J2,{onSelectionStart:m,onSelectionEnd:g,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:i,onPaneContextMenu:s,onPaneScroll:o,panOnDrag:E,isSelecting:!!X,selectionMode:h},e,b&&$.createElement(L6,{onSelectionContextMenu:I,noPanClassName:R,disableKeyboardA11y:M})))};ib.displayName="FlowRenderer";var z6=T.memo(ib);function F6(e){return Ie(T.useCallback(n=>e?I2(n.nodeInternals,{x:0,y:0,width:n.width,height:n.height},n.transform,!0):n.getNodes(),[e]))}function O6(e){const t={input:Ps(e.input||B2),default:Ps(e.default||Nd),output:Ps(e.output||U2),group:Ps(e.group||Np)},n={},r=Object.keys(e).filter(i=>!["input","default","output","group"].includes(i)).reduce((i,s)=>(i[s]=Ps(e[s]||Nd),i),n);return{...t,...r}}const V6=({x:e,y:t,width:n,height:r,origin:i})=>!n||!r?{x:e,y:t}:i[0]<0||i[1]<0||i[0]>1||i[1]>1?{x:e,y:t}:{x:e-n*i[0],y:t-r*i[1]},$6=e=>({nodesDraggable:e.nodesDraggable,nodesConnectable:e.nodesConnectable,nodesFocusable:e.nodesFocusable,elementsSelectable:e.elementsSelectable,updateNodeDimensions:e.updateNodeDimensions,onError:e.onError}),sb=e=>{const{nodesDraggable:t,nodesConnectable:n,nodesFocusable:r,elementsSelectable:i,updateNodeDimensions:s,onError:o}=Ie($6,Oe),a=F6(e.onlyRenderVisibleElements),l=T.useRef(),u=T.useMemo(()=>{if(typeof ResizeObserver>"u")return null;const c=new ResizeObserver(f=>{const d=f.map(h=>({id:h.target.getAttribute("data-id"),nodeElement:h.target,forceUpdate:!0}));s(d)});return l.current=c,c},[]);return T.useEffect(()=>()=>{var c;(c=l==null?void 0:l.current)==null||c.disconnect()},[]),$.createElement("div",{className:"react-flow__nodes",style:Tp},a.map(c=>{var N,S,A;let f=c.type||"default";e.nodeTypes[f]||(o==null||o("003",Bn.error003(f)),f="default");const d=e.nodeTypes[f]||e.nodeTypes.default,h=!!(c.draggable||t&&typeof c.draggable>"u"),m=!!(c.selectable||i&&typeof c.selectable>"u"),g=!!(c.connectable||n&&typeof c.connectable>"u"),w=!!(c.focusable||r&&typeof c.focusable>"u"),p=e.nodeExtent?vp(c.positionAbsolute,e.nodeExtent):c.positionAbsolute,x=(p==null?void 0:p.x)??0,v=(p==null?void 0:p.y)??0,k=V6({x,y:v,width:c.width??0,height:c.height??0,origin:e.nodeOrigin});return $.createElement(d,{key:c.id,id:c.id,className:c.className,style:c.style,type:f,data:c.data,sourcePosition:c.sourcePosition||Q.Bottom,targetPosition:c.targetPosition||Q.Top,hidden:c.hidden,xPos:x,yPos:v,xPosOrigin:k.x,yPosOrigin:k.y,selectNodesOnDrag:e.selectNodesOnDrag,onClick:e.onNodeClick,onMouseEnter:e.onNodeMouseEnter,onMouseMove:e.onNodeMouseMove,onMouseLeave:e.onNodeMouseLeave,onContextMenu:e.onNodeContextMenu,onDoubleClick:e.onNodeDoubleClick,selected:!!c.selected,isDraggable:h,isSelectable:m,isConnectable:g,isFocusable:w,resizeObserver:u,dragHandle:c.dragHandle,zIndex:((N=c[Re])==null?void 0:N.z)??0,isParent:!!((S=c[Re])!=null&&S.isParent),noDragClassName:e.noDragClassName,noPanClassName:e.noPanClassName,initialized:!!c.width&&!!c.height,rfId:e.rfId,disableKeyboardA11y:e.disableKeyboardA11y,ariaLabel:c.ariaLabel,hasHandleBounds:!!((A=c[Re])!=null&&A.handleBounds)})}))};sb.displayName="NodeRenderer";var B6=T.memo(sb);const H6=(e,t,n)=>n===Q.Left?e-t:n===Q.Right?e+t:e,U6=(e,t,n)=>n===Q.Top?e-t:n===Q.Bottom?e+t:e,Ry="react-flow__edgeupdater",zy=({position:e,centerX:t,centerY:n,radius:r=10,onMouseDown:i,onMouseEnter:s,onMouseOut:o,type:a})=>$.createElement("circle",{onMouseDown:i,onMouseEnter:s,onMouseOut:o,className:gt([Ry,`${Ry}-${a}`]),cx:H6(t,r,e),cy:U6(n,r,e),r,stroke:"transparent",fill:"transparent"}),W6=()=>!0;var mi=e=>{const t=({id:n,className:r,type:i,data:s,onClick:o,onEdgeDoubleClick:a,selected:l,animated:u,label:c,labelStyle:f,labelShowBg:d,labelBgStyle:h,labelBgPadding:m,labelBgBorderRadius:g,style:w,source:p,target:x,sourceX:v,sourceY:k,targetX:N,targetY:S,sourcePosition:A,targetPosition:P,elementsSelectable:D,hidden:C,sourceHandleId:j,targetHandleId:z,onContextMenu:H,onMouseEnter:_,onMouseMove:L,onMouseLeave:I,reconnectRadius:O,onReconnect:R,onReconnectStart:M,onReconnectEnd:b,markerEnd:F,markerStart:B,rfId:E,ariaLabel:q,isFocusable:X,isReconnectable:G,pathOptions:ne,interactionWidth:oe,disableKeyboardA11y:de})=>{const le=T.useRef(null),[Ee,lt]=T.useState(!1),[Nt,yt]=T.useState(!1),je=Ke(),Tt=T.useMemo(()=>`url('#${Cd(B,E)}')`,[B,E]),ge=T.useMemo(()=>`url('#${Cd(F,E)}')`,[F,E]);if(C)return null;const Z=ye=>{var Xe;const{edges:He,addSelectedEdges:Ge,unselectNodesAndEdges:At,multiSelectionActive:Pt}=je.getState(),Te=He.find(ut=>ut.id===n);Te&&(D&&(je.setState({nodesSelectionActive:!1}),Te.selected&&Pt?(At({nodes:[],edges:[Te]}),(Xe=le.current)==null||Xe.blur()):Ge([n])),o&&o(ye,Te))},et=Ts(n,je.getState,a),dn=Ts(n,je.getState,H),Tr=Ts(n,je.getState,_),Wn=Ts(n,je.getState,L),Yn=Ts(n,je.getState,I),Qt=(ye,He)=>{if(ye.button!==0)return;const{edges:Ge,isValidConnection:At}=je.getState(),Pt=He?x:p,Te=(He?z:j)||null,Xe=He?"target":"source",ut=At||W6,Au=He,hs=Ge.find(Ar=>Ar.id===n);yt(!0),M==null||M(ye,hs,Xe);const Pu=Ar=>{yt(!1),b==null||b(Ar,hs,Xe)};F2({event:ye,handleId:Te,nodeId:Pt,onConnect:Ar=>R==null?void 0:R(hs,Ar),isTarget:Au,getState:je.getState,setState:je.setState,isValidConnection:ut,edgeUpdaterType:Xe,onReconnectEnd:Pu})},qn=ye=>Qt(ye,!0),V=ye=>Qt(ye,!1),Y=()=>lt(!0),ee=()=>lt(!1),re=!D&&!o,he=ye=>{var He;if(!de&&E2.includes(ye.key)&&D){const{unselectNodesAndEdges:Ge,addSelectedEdges:At,edges:Pt}=je.getState();ye.key==="Escape"?((He=le.current)==null||He.blur(),Ge({edges:[Pt.find(Xe=>Xe.id===n)]})):At([n])}};return $.createElement("g",{className:gt(["react-flow__edge",`react-flow__edge-${i}`,r,{selected:l,animated:u,inactive:re,updating:Ee}]),onClick:Z,onDoubleClick:et,onContextMenu:dn,onMouseEnter:Tr,onMouseMove:Wn,onMouseLeave:Yn,onKeyDown:X?he:void 0,tabIndex:X?0:void 0,role:X?"button":"img","data-testid":`rf__edge-${n}`,"aria-label":q===null?void 0:q||`Edge from ${p} to ${x}`,"aria-describedby":X?`${q2}-${E}`:void 0,ref:le},!Nt&&$.createElement(e,{id:n,source:p,target:x,selected:l,animated:u,label:c,labelStyle:f,labelShowBg:d,labelBgStyle:h,labelBgPadding:m,labelBgBorderRadius:g,data:s,style:w,sourceX:v,sourceY:k,targetX:N,targetY:S,sourcePosition:A,targetPosition:P,sourceHandleId:j,targetHandleId:z,markerStart:Tt,markerEnd:ge,pathOptions:ne,interactionWidth:oe}),G&&$.createElement($.Fragment,null,(G==="source"||G===!0)&&$.createElement(zy,{position:A,centerX:v,centerY:k,radius:O,onMouseDown:qn,onMouseEnter:Y,onMouseOut:ee,type:"source"}),(G==="target"||G===!0)&&$.createElement(zy,{position:P,centerX:N,centerY:S,radius:O,onMouseDown:V,onMouseEnter:Y,onMouseOut:ee,type:"target"})))};return t.displayName="EdgeWrapper",T.memo(t)};function Y6(e){const t={default:mi(e.default||$l),straight:mi(e.bezier||Sp),step:mi(e.step||bp),smoothstep:mi(e.step||Nu),simplebezier:mi(e.simplebezier||kp)},n={},r=Object.keys(e).filter(i=>!["default","bezier"].includes(i)).reduce((i,s)=>(i[s]=mi(e[s]||$l),i),n);return{...t,...r}}function Fy(e,t,n=null){const r=((n==null?void 0:n.x)||0)+t.x,i=((n==null?void 0:n.y)||0)+t.y,s=(n==null?void 0:n.width)||t.width,o=(n==null?void 0:n.height)||t.height;switch(e){case Q.Top:return{x:r+s/2,y:i};case Q.Right:return{x:r+s,y:i+o/2};case Q.Bottom:return{x:r+s/2,y:i+o};case Q.Left:return{x:r,y:i+o/2}}}function Oy(e,t){return e?e.length===1||!t?e[0]:t&&e.find(n=>n.id===t)||null:null}const q6=(e,t,n,r,i,s)=>{const o=Fy(n,e,t),a=Fy(s,r,i);return{sourceX:o.x,sourceY:o.y,targetX:a.x,targetY:a.y}};function K6({sourcePos:e,targetPos:t,sourceWidth:n,sourceHeight:r,targetWidth:i,targetHeight:s,width:o,height:a,transform:l}){const u={x:Math.min(e.x,t.x),y:Math.min(e.y,t.y),x2:Math.max(e.x+n,t.x+i),y2:Math.max(e.y+r,t.y+s)};u.x===u.x2&&(u.x2+=1),u.y===u.y2&&(u.y2+=1);const c=wp({x:(0-l[0])/l[2],y:(0-l[1])/l[2],width:o/l[2],height:a/l[2]}),f=Math.max(0,Math.min(c.x2,u.x2)-Math.max(c.x,u.x)),d=Math.max(0,Math.min(c.y2,u.y2)-Math.max(c.y,u.y));return Math.ceil(f*d)>0}function Vy(e){var r,i,s,o,a;const t=((r=e==null?void 0:e[Re])==null?void 0:r.handleBounds)||null,n=t&&(e==null?void 0:e.width)&&(e==null?void 0:e.height)&&typeof((i=e==null?void 0:e.positionAbsolute)==null?void 0:i.x)<"u"&&typeof((s=e==null?void 0:e.positionAbsolute)==null?void 0:s.y)<"u";return[{x:((o=e==null?void 0:e.positionAbsolute)==null?void 0:o.x)||0,y:((a=e==null?void 0:e.positionAbsolute)==null?void 0:a.y)||0,width:(e==null?void 0:e.width)||0,height:(e==null?void 0:e.height)||0},t,!!n]}const G6=[{level:0,isMaxLevel:!0,edges:[]}];function X6(e,t,n=!1){let r=-1;const i=e.reduce((o,a)=>{var c,f;const l=qt(a.zIndex);let u=l?a.zIndex:0;if(n){const d=t.get(a.target),h=t.get(a.source),m=a.selected||(d==null?void 0:d.selected)||(h==null?void 0:h.selected),g=Math.max(((c=h==null?void 0:h[Re])==null?void 0:c.z)||0,((f=d==null?void 0:d[Re])==null?void 0:f.z)||0,1e3);u=(l?a.zIndex:0)+(m?g:0)}return o[u]?o[u].push(a):o[u]=[a],r=u>r?u:r,o},{}),s=Object.entries(i).map(([o,a])=>{const l=+o;return{edges:a,level:l,isMaxLevel:l===r}});return s.length===0?G6:s}function Q6(e,t,n){const r=Ie(T.useCallback(i=>e?i.edges.filter(s=>{const o=t.get(s.source),a=t.get(s.target);return(o==null?void 0:o.width)&&(o==null?void 0:o.height)&&(a==null?void 0:a.width)&&(a==null?void 0:a.height)&&K6({sourcePos:o.positionAbsolute||{x:0,y:0},targetPos:a.positionAbsolute||{x:0,y:0},sourceWidth:o.width,sourceHeight:o.height,targetWidth:a.width,targetHeight:a.height,width:i.width,height:i.height,transform:i.transform})}):i.edges,[e,t]));return X6(r,t,n)}const Z6=({color:e="none",strokeWidth:t=1})=>$.createElement("polyline",{style:{stroke:e,strokeWidth:t},strokeLinecap:"round",strokeLinejoin:"round",fill:"none",points:"-5,-4 0,0 -5,4"}),J6=({color:e="none",strokeWidth:t=1})=>$.createElement("polyline",{style:{stroke:e,fill:e,strokeWidth:t},strokeLinecap:"round",strokeLinejoin:"round",points:"-5,-4 0,0 -5,4 -5,-4"}),$y={[Do.Arrow]:Z6,[Do.ArrowClosed]:J6};function eF(e){const t=Ke();return T.useMemo(()=>{var i,s;return Object.prototype.hasOwnProperty.call($y,e)?$y[e]:((s=(i=t.getState()).onError)==null||s.call(i,"009",Bn.error009(e)),null)},[e])}const tF=({id:e,type:t,color:n,width:r=12.5,height:i=12.5,markerUnits:s="strokeWidth",strokeWidth:o,orient:a="auto-start-reverse"})=>{const l=eF(t);return l?$.createElement("marker",{className:"react-flow__arrowhead",id:e,markerWidth:`${r}`,markerHeight:`${i}`,viewBox:"-10 -10 20 20",markerUnits:s,orient:a,refX:"0",refY:"0"},$.createElement(l,{color:n,strokeWidth:o})):null},nF=({defaultColor:e,rfId:t})=>n=>{const r=[];return n.edges.reduce((i,s)=>([s.markerStart,s.markerEnd].forEach(o=>{if(o&&typeof o=="object"){const a=Cd(o,t);r.includes(a)||(i.push({id:a,color:o.color||e,...o}),r.push(a))}}),i),[]).sort((i,s)=>i.id.localeCompare(s.id))},ob=({defaultColor:e,rfId:t})=>{const n=Ie(T.useCallback(nF({defaultColor:e,rfId:t}),[e,t]),(r,i)=>!(r.length!==i.length||r.some((s,o)=>s.id!==i[o].id)));return $.createElement("defs",null,n.map(r=>$.createElement(tF,{id:r.id,key:r.id,type:r.type,color:r.color,width:r.width,height:r.height,markerUnits:r.markerUnits,strokeWidth:r.strokeWidth,orient:r.orient})))};ob.displayName="MarkerDefinitions";var rF=T.memo(ob);const iF=e=>({nodesConnectable:e.nodesConnectable,edgesFocusable:e.edgesFocusable,edgesUpdatable:e.edgesUpdatable,elementsSelectable:e.elementsSelectable,width:e.width,height:e.height,connectionMode:e.connectionMode,nodeInternals:e.nodeInternals,onError:e.onError}),ab=({defaultMarkerColor:e,onlyRenderVisibleElements:t,elevateEdgesOnSelect:n,rfId:r,edgeTypes:i,noPanClassName:s,onEdgeContextMenu:o,onEdgeMouseEnter:a,onEdgeMouseMove:l,onEdgeMouseLeave:u,onEdgeClick:c,onEdgeDoubleClick:f,onReconnect:d,onReconnectStart:h,onReconnectEnd:m,reconnectRadius:g,children:w,disableKeyboardA11y:p})=>{const{edgesFocusable:x,edgesUpdatable:v,elementsSelectable:k,width:N,height:S,connectionMode:A,nodeInternals:P,onError:D}=Ie(iF,Oe),C=Q6(t,P,n);return N?$.createElement($.Fragment,null,C.map(({level:j,edges:z,isMaxLevel:H})=>$.createElement("svg",{key:j,style:{zIndex:j},width:N,height:S,className:"react-flow__edges react-flow__container"},H&&$.createElement(rF,{defaultColor:e,rfId:r}),$.createElement("g",null,z.map(_=>{const[L,I,O]=Vy(P.get(_.source)),[R,M,b]=Vy(P.get(_.target));if(!O||!b)return null;let F=_.type||"default";i[F]||(D==null||D("011",Bn.error011(F)),F="default");const B=i[F]||i.default,E=A===ii.Strict?M.target:(M.target??[]).concat(M.source??[]),q=Oy(I.source,_.sourceHandle),X=Oy(E,_.targetHandle),G=(q==null?void 0:q.position)||Q.Bottom,ne=(X==null?void 0:X.position)||Q.Top,oe=!!(_.focusable||x&&typeof _.focusable>"u"),de=_.reconnectable||_.updatable,le=typeof d<"u"&&(de||v&&typeof de>"u");if(!q||!X)return D==null||D("008",Bn.error008(q,_)),null;const{sourceX:Ee,sourceY:lt,targetX:Nt,targetY:yt}=q6(L,q,G,R,X,ne);return $.createElement(B,{key:_.id,id:_.id,className:gt([_.className,s]),type:F,data:_.data,selected:!!_.selected,animated:!!_.animated,hidden:!!_.hidden,label:_.label,labelStyle:_.labelStyle,labelShowBg:_.labelShowBg,labelBgStyle:_.labelBgStyle,labelBgPadding:_.labelBgPadding,labelBgBorderRadius:_.labelBgBorderRadius,style:_.style,source:_.source,target:_.target,sourceHandleId:_.sourceHandle,targetHandleId:_.targetHandle,markerEnd:_.markerEnd,markerStart:_.markerStart,sourceX:Ee,sourceY:lt,targetX:Nt,targetY:yt,sourcePosition:G,targetPosition:ne,elementsSelectable:k,onContextMenu:o,onMouseEnter:a,onMouseMove:l,onMouseLeave:u,onClick:c,onEdgeDoubleClick:f,onReconnect:d,onReconnectStart:h,onReconnectEnd:m,reconnectRadius:g,rfId:r,ariaLabel:_.ariaLabel,isFocusable:oe,isReconnectable:le,pathOptions:"pathOptions"in _?_.pathOptions:void 0,interactionWidth:_.interactionWidth,disableKeyboardA11y:p})})))),w):null};ab.displayName="EdgeRenderer";var sF=T.memo(ab);const oF=e=>`translate(${e.transform[0]}px,${e.transform[1]}px) scale(${e.transform[2]})`;function aF({children:e}){const t=Ie(oF);return $.createElement("div",{className:"react-flow__viewport react-flow__container",style:{transform:t}},e)}function lF(e){const t=Tu(),n=T.useRef(!1);T.useEffect(()=>{!n.current&&t.viewportInitialized&&e&&(setTimeout(()=>e(t),1),n.current=!0)},[e,t.viewportInitialized])}const uF={[Q.Left]:Q.Right,[Q.Right]:Q.Left,[Q.Top]:Q.Bottom,[Q.Bottom]:Q.Top},lb=({nodeId:e,handleType:t,style:n,type:r=sr.Bezier,CustomComponent:i,connectionStatus:s})=>{var S,A,P;const{fromNode:o,handleId:a,toX:l,toY:u,connectionMode:c}=Ie(T.useCallback(D=>({fromNode:D.nodeInternals.get(e),handleId:D.connectionHandleId,toX:(D.connectionPosition.x-D.transform[0])/D.transform[2],toY:(D.connectionPosition.y-D.transform[1])/D.transform[2],connectionMode:D.connectionMode}),[e]),Oe),f=(S=o==null?void 0:o[Re])==null?void 0:S.handleBounds;let d=f==null?void 0:f[t];if(c===ii.Loose&&(d=d||(f==null?void 0:f[t==="source"?"target":"source"])),!o||!d)return null;const h=a?d.find(D=>D.id===a):d[0],m=h?h.x+h.width/2:(o.width??0)/2,g=h?h.y+h.height/2:o.height??0,w=(((A=o.positionAbsolute)==null?void 0:A.x)??0)+m,p=(((P=o.positionAbsolute)==null?void 0:P.y)??0)+g,x=h==null?void 0:h.position,v=x?uF[x]:null;if(!x||!v)return null;if(i)return $.createElement(i,{connectionLineType:r,connectionLineStyle:n,fromNode:o,fromHandle:h,fromX:w,fromY:p,toX:l,toY:u,fromPosition:x,toPosition:v,connectionStatus:s});let k="";const N={sourceX:w,sourceY:p,sourcePosition:x,targetX:l,targetY:u,targetPosition:v};return r===sr.Bezier?[k]=M2(N):r===sr.Step?[k]=_d({...N,borderRadius:0}):r===sr.SmoothStep?[k]=_d(N):r===sr.SimpleBezier?[k]=P2(N):k=`M${w},${p} ${l},${u}`,$.createElement("path",{d:k,fill:"none",className:"react-flow__connection-path",style:n})};lb.displayName="ConnectionLine";const cF=e=>({nodeId:e.connectionNodeId,handleType:e.connectionHandleType,nodesConnectable:e.nodesConnectable,connectionStatus:e.connectionStatus,width:e.width,height:e.height});function fF({containerStyle:e,style:t,type:n,component:r}){const{nodeId:i,handleType:s,nodesConnectable:o,width:a,height:l,connectionStatus:u}=Ie(cF,Oe);return!(i&&s&&a&&o)?null:$.createElement("svg",{style:e,width:a,height:l,className:"react-flow__edges react-flow__connectionline react-flow__container"},$.createElement("g",{className:gt(["react-flow__connection",u])},$.createElement(lb,{nodeId:i,handleType:s,style:t,type:n,CustomComponent:r,connectionStatus:u})))}function By(e,t){return T.useRef(null),Ke(),T.useMemo(()=>t(e),[e])}const ub=({nodeTypes:e,edgeTypes:t,onMove:n,onMoveStart:r,onMoveEnd:i,onInit:s,onNodeClick:o,onEdgeClick:a,onNodeDoubleClick:l,onEdgeDoubleClick:u,onNodeMouseEnter:c,onNodeMouseMove:f,onNodeMouseLeave:d,onNodeContextMenu:h,onSelectionContextMenu:m,onSelectionStart:g,onSelectionEnd:w,connectionLineType:p,connectionLineStyle:x,connectionLineComponent:v,connectionLineContainerStyle:k,selectionKeyCode:N,selectionOnDrag:S,selectionMode:A,multiSelectionKeyCode:P,panActivationKeyCode:D,zoomActivationKeyCode:C,deleteKeyCode:j,onlyRenderVisibleElements:z,elementsSelectable:H,selectNodesOnDrag:_,defaultViewport:L,translateExtent:I,minZoom:O,maxZoom:R,preventScrolling:M,defaultMarkerColor:b,zoomOnScroll:F,zoomOnPinch:B,panOnScroll:E,panOnScrollSpeed:q,panOnScrollMode:X,zoomOnDoubleClick:G,panOnDrag:ne,onPaneClick:oe,onPaneMouseEnter:de,onPaneMouseMove:le,onPaneMouseLeave:Ee,onPaneScroll:lt,onPaneContextMenu:Nt,onEdgeContextMenu:yt,onEdgeMouseEnter:je,onEdgeMouseMove:Tt,onEdgeMouseLeave:ge,onReconnect:Z,onReconnectStart:et,onReconnectEnd:dn,reconnectRadius:Tr,noDragClassName:Wn,noWheelClassName:Yn,noPanClassName:Qt,elevateEdgesOnSelect:qn,disableKeyboardA11y:V,nodeOrigin:Y,nodeExtent:ee,rfId:re})=>{const he=By(e,O6),ye=By(t,Y6);return lF(s),$.createElement(z6,{onPaneClick:oe,onPaneMouseEnter:de,onPaneMouseMove:le,onPaneMouseLeave:Ee,onPaneContextMenu:Nt,onPaneScroll:lt,deleteKeyCode:j,selectionKeyCode:N,selectionOnDrag:S,selectionMode:A,onSelectionStart:g,onSelectionEnd:w,multiSelectionKeyCode:P,panActivationKeyCode:D,zoomActivationKeyCode:C,elementsSelectable:H,onMove:n,onMoveStart:r,onMoveEnd:i,zoomOnScroll:F,zoomOnPinch:B,zoomOnDoubleClick:G,panOnScroll:E,panOnScrollSpeed:q,panOnScrollMode:X,panOnDrag:ne,defaultViewport:L,translateExtent:I,minZoom:O,maxZoom:R,onSelectionContextMenu:m,preventScrolling:M,noDragClassName:Wn,noWheelClassName:Yn,noPanClassName:Qt,disableKeyboardA11y:V},$.createElement(aF,null,$.createElement(sF,{edgeTypes:ye,onEdgeClick:a,onEdgeDoubleClick:u,onlyRenderVisibleElements:z,onEdgeContextMenu:yt,onEdgeMouseEnter:je,onEdgeMouseMove:Tt,onEdgeMouseLeave:ge,onReconnect:Z,onReconnectStart:et,onReconnectEnd:dn,reconnectRadius:Tr,defaultMarkerColor:b,noPanClassName:Qt,elevateEdgesOnSelect:!!qn,disableKeyboardA11y:V,rfId:re},$.createElement(fF,{style:x,type:p,component:v,containerStyle:k})),$.createElement("div",{className:"react-flow__edgelabel-renderer"}),$.createElement(B6,{nodeTypes:he,onNodeClick:o,onNodeDoubleClick:l,onNodeMouseEnter:c,onNodeMouseMove:f,onNodeMouseLeave:d,onNodeContextMenu:h,selectNodesOnDrag:_,onlyRenderVisibleElements:z,noPanClassName:Qt,noDragClassName:Wn,disableKeyboardA11y:V,nodeOrigin:Y,nodeExtent:ee,rfId:re})))};ub.displayName="GraphView";var dF=T.memo(ub);const Ad=[[Number.NEGATIVE_INFINITY,Number.NEGATIVE_INFINITY],[Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY]],Xn={rfId:"1",width:0,height:0,transform:[0,0,1],nodeInternals:new Map,edges:[],onNodesChange:null,onEdgesChange:null,hasDefaultNodes:!1,hasDefaultEdges:!1,d3Zoom:null,d3Selection:null,d3ZoomHandler:void 0,minZoom:.5,maxZoom:2,translateExtent:Ad,nodeExtent:Ad,nodesSelectionActive:!1,userSelectionActive:!1,userSelectionRect:null,connectionNodeId:null,connectionHandleId:null,connectionHandleType:"source",connectionPosition:{x:0,y:0},connectionStatus:null,connectionMode:ii.Strict,domNode:null,paneDragging:!1,noPanClassName:"nopan",nodeOrigin:[0,0],nodeDragThreshold:0,snapGrid:[15,15],snapToGrid:!1,nodesDraggable:!0,nodesConnectable:!0,nodesFocusable:!0,edgesFocusable:!0,edgesUpdatable:!0,elementsSelectable:!0,elevateNodesOnSelect:!0,fitViewOnInit:!1,fitViewOnInitDone:!1,fitViewOnInitOptions:void 0,onSelectionChange:[],multiSelectionActive:!1,connectionStartHandle:null,connectionEndHandle:null,connectionClickStartHandle:null,connectOnClick:!0,ariaLiveMessage:"",autoPanOnConnect:!0,autoPanOnNodeDrag:!0,connectionRadius:20,onError:V8,isValidConnection:void 0},hF=()=>Jj((e,t)=>({...Xn,setNodes:n=>{const{nodeInternals:r,nodeOrigin:i,elevateNodesOnSelect:s}=t();e({nodeInternals:$c(n,r,i,s)})},getNodes:()=>Array.from(t().nodeInternals.values()),setEdges:n=>{const{defaultEdgeOptions:r={}}=t();e({edges:n.map(i=>({...r,...i}))})},setDefaultNodesAndEdges:(n,r)=>{const i=typeof n<"u",s=typeof r<"u",o=i?$c(n,new Map,t().nodeOrigin,t().elevateNodesOnSelect):new Map;e({nodeInternals:o,edges:s?r:[],hasDefaultNodes:i,hasDefaultEdges:s})},updateNodeDimensions:n=>{const{onNodesChange:r,nodeInternals:i,fitViewOnInit:s,fitViewOnInitDone:o,fitViewOnInitOptions:a,domNode:l,nodeOrigin:u}=t(),c=l==null?void 0:l.querySelector(".react-flow__viewport");if(!c)return;const f=window.getComputedStyle(c),{m22:d}=new window.DOMMatrixReadOnly(f.transform),h=n.reduce((g,w)=>{const p=i.get(w.id);if(p!=null&&p.hidden)i.set(p.id,{...p,[Re]:{...p[Re],handleBounds:void 0}});else if(p){const x=xp(w.nodeElement);!!(x.width&&x.height&&(p.width!==x.width||p.height!==x.height||w.forceUpdate))&&(i.set(p.id,{...p,[Re]:{...p[Re],handleBounds:{source:Ly(".source",w.nodeElement,d,u),target:Ly(".target",w.nodeElement,d,u)}},...x}),g.push({id:p.id,type:"dimensions",dimensions:x}))}return g},[]);G2(i,u);const m=o||s&&!o&&X2(t,{initial:!0,...a});e({nodeInternals:new Map(i),fitViewOnInitDone:m}),(h==null?void 0:h.length)>0&&(r==null||r(h))},updateNodePositions:(n,r=!0,i=!1)=>{const{triggerNodeChanges:s}=t(),o=n.map(a=>{const l={id:a.id,type:"position",dragging:i};return r&&(l.positionAbsolute=a.positionAbsolute,l.position=a.position),l});s(o)},triggerNodeChanges:n=>{const{onNodesChange:r,nodeInternals:i,hasDefaultNodes:s,nodeOrigin:o,getNodes:a,elevateNodesOnSelect:l}=t();if(n!=null&&n.length){if(s){const u=Z2(n,a()),c=$c(u,i,o,l);e({nodeInternals:c})}r==null||r(n)}},addSelectedNodes:n=>{const{multiSelectionActive:r,edges:i,getNodes:s}=t();let o,a=null;r?o=n.map(l=>nr(l,!0)):(o=Ii(s(),n),a=Ii(i,[])),Ta({changedNodes:o,changedEdges:a,get:t,set:e})},addSelectedEdges:n=>{const{multiSelectionActive:r,edges:i,getNodes:s}=t();let o,a=null;r?o=n.map(l=>nr(l,!0)):(o=Ii(i,n),a=Ii(s(),[])),Ta({changedNodes:a,changedEdges:o,get:t,set:e})},unselectNodesAndEdges:({nodes:n,edges:r}={})=>{const{edges:i,getNodes:s}=t(),o=n||s(),a=r||i,l=o.map(c=>(c.selected=!1,nr(c.id,!1))),u=a.map(c=>nr(c.id,!1));Ta({changedNodes:l,changedEdges:u,get:t,set:e})},setMinZoom:n=>{const{d3Zoom:r,maxZoom:i}=t();r==null||r.scaleExtent([n,i]),e({minZoom:n})},setMaxZoom:n=>{const{d3Zoom:r,minZoom:i}=t();r==null||r.scaleExtent([i,n]),e({maxZoom:n})},setTranslateExtent:n=>{var r;(r=t().d3Zoom)==null||r.translateExtent(n),e({translateExtent:n})},resetSelectedElements:()=>{const{edges:n,getNodes:r}=t(),s=r().filter(a=>a.selected).map(a=>nr(a.id,!1)),o=n.filter(a=>a.selected).map(a=>nr(a.id,!1));Ta({changedNodes:s,changedEdges:o,get:t,set:e})},setNodeExtent:n=>{const{nodeInternals:r}=t();r.forEach(i=>{i.positionAbsolute=vp(i.position,n)}),e({nodeExtent:n,nodeInternals:new Map(r)})},panBy:n=>{const{transform:r,width:i,height:s,d3Zoom:o,d3Selection:a,translateExtent:l}=t();if(!o||!a||!n.x&&!n.y)return!1;const u=yr.translate(r[0]+n.x,r[1]+n.y).scale(r[2]),c=[[0,0],[i,s]],f=o==null?void 0:o.constrain()(u,c,l);return o.transform(a,f),r[0]!==f.x||r[1]!==f.y||r[2]!==f.k},cancelConnection:()=>e({connectionNodeId:Xn.connectionNodeId,connectionHandleId:Xn.connectionHandleId,connectionHandleType:Xn.connectionHandleType,connectionStatus:Xn.connectionStatus,connectionStartHandle:Xn.connectionStartHandle,connectionEndHandle:Xn.connectionEndHandle}),reset:()=>e({...Xn})}),Object.is),Ap=({children:e})=>{const t=T.useRef(null);return t.current||(t.current=hF()),$.createElement(D8,{value:t.current},e)};Ap.displayName="ReactFlowProvider";const cb=({children:e})=>T.useContext(Eu)?$.createElement($.Fragment,null,e):$.createElement(Ap,null,e);cb.displayName="ReactFlowWrapper";const pF={input:B2,default:Nd,output:U2,group:Np},mF={default:$l,straight:Sp,step:bp,smoothstep:Nu,simplebezier:kp},gF=[0,0],yF=[15,15],xF={x:0,y:0,zoom:1},vF={width:"100%",height:"100%",overflow:"hidden",position:"relative",zIndex:0},fb=T.forwardRef(({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,className:i,nodeTypes:s=pF,edgeTypes:o=mF,onNodeClick:a,onEdgeClick:l,onInit:u,onMove:c,onMoveStart:f,onMoveEnd:d,onConnect:h,onConnectStart:m,onConnectEnd:g,onClickConnectStart:w,onClickConnectEnd:p,onNodeMouseEnter:x,onNodeMouseMove:v,onNodeMouseLeave:k,onNodeContextMenu:N,onNodeDoubleClick:S,onNodeDragStart:A,onNodeDrag:P,onNodeDragStop:D,onNodesDelete:C,onEdgesDelete:j,onSelectionChange:z,onSelectionDragStart:H,onSelectionDrag:_,onSelectionDragStop:L,onSelectionContextMenu:I,onSelectionStart:O,onSelectionEnd:R,connectionMode:M=ii.Strict,connectionLineType:b=sr.Bezier,connectionLineStyle:F,connectionLineComponent:B,connectionLineContainerStyle:E,deleteKeyCode:q="Backspace",selectionKeyCode:X="Shift",selectionOnDrag:G=!1,selectionMode:ne=Mo.Full,panActivationKeyCode:oe="Space",multiSelectionKeyCode:de=Vl()?"Meta":"Control",zoomActivationKeyCode:le=Vl()?"Meta":"Control",snapToGrid:Ee=!1,snapGrid:lt=yF,onlyRenderVisibleElements:Nt=!1,selectNodesOnDrag:yt=!0,nodesDraggable:je,nodesConnectable:Tt,nodesFocusable:ge,nodeOrigin:Z=gF,edgesFocusable:et,edgesUpdatable:dn,elementsSelectable:Tr,defaultViewport:Wn=xF,minZoom:Yn=.5,maxZoom:Qt=2,translateExtent:qn=Ad,preventScrolling:V=!0,nodeExtent:Y,defaultMarkerColor:ee="#b1b1b7",zoomOnScroll:re=!0,zoomOnPinch:he=!0,panOnScroll:ye=!1,panOnScrollSpeed:He=.5,panOnScrollMode:Ge=Wr.Free,zoomOnDoubleClick:At=!0,panOnDrag:Pt=!0,onPaneClick:Te,onPaneMouseEnter:Xe,onPaneMouseMove:ut,onPaneMouseLeave:Au,onPaneScroll:hs,onPaneContextMenu:Pu,children:Dp,onEdgeContextMenu:Ar,onEdgeDoubleClick:gb,onEdgeMouseEnter:yb,onEdgeMouseMove:xb,onEdgeMouseLeave:vb,onEdgeUpdate:wb,onEdgeUpdateStart:kb,onEdgeUpdateEnd:bb,onReconnect:Sb,onReconnectStart:_b,onReconnectEnd:Cb,reconnectRadius:Eb=10,edgeUpdaterRadius:Nb=10,onNodesChange:Tb,onEdgesChange:Ab,noDragClassName:Pb="nodrag",noWheelClassName:Mb="nowheel",noPanClassName:Ip="nopan",fitView:Db=!1,fitViewOptions:Ib,connectOnClick:jb=!0,attributionPosition:Lb,proOptions:Rb,defaultEdgeOptions:zb,elevateNodesOnSelect:Fb=!0,elevateEdgesOnSelect:Ob=!1,disableKeyboardA11y:jp=!1,autoPanOnConnect:Vb=!0,autoPanOnNodeDrag:$b=!0,connectionRadius:Bb=20,isValidConnection:Hb,onError:Ub,style:Wb,id:Lp,nodeDragThreshold:Yb,...qb},Kb)=>{const Mu=Lp||"1";return $.createElement("div",{...qb,style:{...Wb,...vF},ref:Kb,className:gt(["react-flow",i]),"data-testid":"rf__wrapper",id:Lp},$.createElement(cb,null,$.createElement(dF,{onInit:u,onMove:c,onMoveStart:f,onMoveEnd:d,onNodeClick:a,onEdgeClick:l,onNodeMouseEnter:x,onNodeMouseMove:v,onNodeMouseLeave:k,onNodeContextMenu:N,onNodeDoubleClick:S,nodeTypes:s,edgeTypes:o,connectionLineType:b,connectionLineStyle:F,connectionLineComponent:B,connectionLineContainerStyle:E,selectionKeyCode:X,selectionOnDrag:G,selectionMode:ne,deleteKeyCode:q,multiSelectionKeyCode:de,panActivationKeyCode:oe,zoomActivationKeyCode:le,onlyRenderVisibleElements:Nt,selectNodesOnDrag:yt,defaultViewport:Wn,translateExtent:qn,minZoom:Yn,maxZoom:Qt,preventScrolling:V,zoomOnScroll:re,zoomOnPinch:he,zoomOnDoubleClick:At,panOnScroll:ye,panOnScrollSpeed:He,panOnScrollMode:Ge,panOnDrag:Pt,onPaneClick:Te,onPaneMouseEnter:Xe,onPaneMouseMove:ut,onPaneMouseLeave:Au,onPaneScroll:hs,onPaneContextMenu:Pu,onSelectionContextMenu:I,onSelectionStart:O,onSelectionEnd:R,onEdgeContextMenu:Ar,onEdgeDoubleClick:gb,onEdgeMouseEnter:yb,onEdgeMouseMove:xb,onEdgeMouseLeave:vb,onReconnect:Sb??wb,onReconnectStart:_b??kb,onReconnectEnd:Cb??bb,reconnectRadius:Eb??Nb,defaultMarkerColor:ee,noDragClassName:Pb,noWheelClassName:Mb,noPanClassName:Ip,elevateEdgesOnSelect:Ob,rfId:Mu,disableKeyboardA11y:jp,nodeOrigin:Z,nodeExtent:Y}),$.createElement(u6,{nodes:e,edges:t,defaultNodes:n,defaultEdges:r,onConnect:h,onConnectStart:m,onConnectEnd:g,onClickConnectStart:w,onClickConnectEnd:p,nodesDraggable:je,nodesConnectable:Tt,nodesFocusable:ge,edgesFocusable:et,edgesUpdatable:dn,elementsSelectable:Tr,elevateNodesOnSelect:Fb,minZoom:Yn,maxZoom:Qt,nodeExtent:Y,onNodesChange:Tb,onEdgesChange:Ab,snapToGrid:Ee,snapGrid:lt,connectionMode:M,translateExtent:qn,connectOnClick:jb,defaultEdgeOptions:zb,fitView:Db,fitViewOptions:Ib,onNodesDelete:C,onEdgesDelete:j,onNodeDragStart:A,onNodeDrag:P,onNodeDragStop:D,onSelectionDrag:_,onSelectionDragStart:H,onSelectionDragStop:L,noPanClassName:Ip,nodeOrigin:Z,rfId:Mu,autoPanOnConnect:Vb,autoPanOnNodeDrag:$b,onError:Ub,connectionRadius:Bb,isValidConnection:Hb,nodeDragThreshold:Yb}),$.createElement(a6,{onSelectionChange:z}),Dp,$.createElement(j8,{proOptions:Rb,position:Lb}),$.createElement(p6,{rfId:Mu,disableKeyboardA11y:jp})))});fb.displayName="ReactFlow";function db(e){return t=>{const[n,r]=T.useState(t),i=T.useCallback(s=>r(o=>e(s,o)),[]);return[n,r,i]}}const wF=db(Z2),kF=db(T6);function bF(){return $.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"},$.createElement("path",{d:"M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z"}))}function SF(){return $.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 5"},$.createElement("path",{d:"M0 0h32v4.2H0z"}))}function _F(){return $.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 30"},$.createElement("path",{d:"M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z"}))}function CF(){return $.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32"},$.createElement("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z"}))}function EF(){return $.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32"},$.createElement("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z"}))}const Fs=({children:e,className:t,...n})=>$.createElement("button",{type:"button",className:gt(["react-flow__controls-button",t]),...n},e);Fs.displayName="ControlButton";const NF=e=>({isInteractive:e.nodesDraggable||e.nodesConnectable||e.elementsSelectable,minZoomReached:e.transform[2]<=e.minZoom,maxZoomReached:e.transform[2]>=e.maxZoom}),hb=({style:e,showZoom:t=!0,showFitView:n=!0,showInteractive:r=!0,fitViewOptions:i,onZoomIn:s,onZoomOut:o,onFitView:a,onInteractiveChange:l,className:u,children:c,position:f="bottom-left"})=>{const d=Ke(),[h,m]=T.useState(!1),{isInteractive:g,minZoomReached:w,maxZoomReached:p}=Ie(NF,Oe),{zoomIn:x,zoomOut:v,fitView:k}=Tu();if(T.useEffect(()=>{m(!0)},[]),!h)return null;const N=()=>{x(),s==null||s()},S=()=>{v(),o==null||o()},A=()=>{k(i),a==null||a()},P=()=>{d.setState({nodesDraggable:!g,nodesConnectable:!g,elementsSelectable:!g}),l==null||l(!g)};return $.createElement(S2,{className:gt(["react-flow__controls",u]),position:f,style:e,"data-testid":"rf__controls"},t&&$.createElement($.Fragment,null,$.createElement(Fs,{onClick:N,className:"react-flow__controls-zoomin",title:"zoom in","aria-label":"zoom in",disabled:p},$.createElement(bF,null)),$.createElement(Fs,{onClick:S,className:"react-flow__controls-zoomout",title:"zoom out","aria-label":"zoom out",disabled:w},$.createElement(SF,null))),n&&$.createElement(Fs,{className:"react-flow__controls-fitview",onClick:A,title:"fit view","aria-label":"fit view"},$.createElement(_F,null)),r&&$.createElement(Fs,{className:"react-flow__controls-interactive",onClick:P,title:"toggle interactivity","aria-label":"toggle interactivity"},g?$.createElement(EF,null):$.createElement(CF,null)),c)};hb.displayName="Controls";var TF=T.memo(hb),un;(function(e){e.Lines="lines",e.Dots="dots",e.Cross="cross"})(un||(un={}));function AF({color:e,dimensions:t,lineWidth:n}){return $.createElement("path",{stroke:e,strokeWidth:n,d:`M${t[0]/2} 0 V${t[1]} M0 ${t[1]/2} H${t[0]}`})}function PF({color:e,radius:t}){return $.createElement("circle",{cx:t,cy:t,r:t,fill:e})}const MF={[un.Dots]:"#91919a",[un.Lines]:"#eee",[un.Cross]:"#e2e2e2"},DF={[un.Dots]:1,[un.Lines]:1,[un.Cross]:6},IF=e=>({transform:e.transform,patternId:`pattern-${e.rfId}`});function pb({id:e,variant:t=un.Dots,gap:n=20,size:r,lineWidth:i=1,offset:s=2,color:o,style:a,className:l}){const u=T.useRef(null),{transform:c,patternId:f}=Ie(IF,Oe),d=o||MF[t],h=r||DF[t],m=t===un.Dots,g=t===un.Cross,w=Array.isArray(n)?n:[n,n],p=[w[0]*c[2]||1,w[1]*c[2]||1],x=h*c[2],v=g?[x,x]:p,k=m?[x/s,x/s]:[v[0]/s,v[1]/s];return $.createElement("svg",{className:gt(["react-flow__background",l]),style:{...a,position:"absolute",width:"100%",height:"100%",top:0,left:0},ref:u,"data-testid":"rf__background"},$.createElement("pattern",{id:f+e,x:c[0]%p[0],y:c[1]%p[1],width:p[0],height:p[1],patternUnits:"userSpaceOnUse",patternTransform:`translate(-${k[0]},-${k[1]})`},m?$.createElement(PF,{color:d,radius:x/s}):$.createElement(AF,{dimensions:v,color:d,lineWidth:i})),$.createElement("rect",{x:"0",y:"0",width:"100%",height:"100%",fill:`url(#${f+e})`}))}pb.displayName="Background";var jF=T.memo(pb);const Hy={pending:{bg:"linear-gradient(135deg, rgba(30,41,59,0.9) 0%, rgba(51,65,85,0.85) 100%)",border:"rgba(148, 163, 184, 0.4)",text:"#cbd5e1",shadow:"rgba(148, 163, 184, 0.5)",glow:"0 0 20px rgba(148, 163, 184, 0.3), 0 0 30px rgba(148, 163, 184, 0.15)"},running:{bg:"linear-gradient(135deg, rgba(6,182,212,0.2) 0%, rgba(14,165,233,0.15) 50%, rgba(12,74,110,0.85) 100%)",border:"rgba(56, 189, 248, 0.6)",text:"#bae6fd",shadow:"rgba(56, 189, 248, 0.7)",glow:"0 0 25px rgba(56, 189, 248, 0.4), 0 0 35px rgba(6, 182, 212, 0.25), inset 0 0 20px rgba(56, 189, 248, 0.08)"},completed:{bg:"linear-gradient(135deg, rgba(16,185,129,0.2) 0%, rgba(74,222,128,0.12) 50%, rgba(20,83,45,0.85) 100%)",border:"rgba(74, 222, 128, 0.6)",text:"#bbf7d0",shadow:"rgba(74, 222, 128, 0.7)",glow:"0 0 25px rgba(74, 222, 128, 0.4), 0 0 35px rgba(16, 185, 129, 0.25), inset 0 0 20px rgba(74, 222, 128, 0.08)"},failed:{bg:"linear-gradient(135deg, rgba(239,68,68,0.2) 0%, rgba(248,113,113,0.12) 50%, rgba(127,29,29,0.85) 100%)",border:"rgba(248, 113, 113, 0.6)",text:"#fecaca",shadow:"rgba(248, 113, 113, 0.7)",glow:"0 0 25px rgba(248, 113, 113, 0.4), 0 0 35px rgba(239, 68, 68, 0.25), inset 0 0 20px rgba(248, 113, 113, 0.08)"},skipped:{bg:"linear-gradient(135deg, rgba(250,204,21,0.2) 0%, rgba(253,224,71,0.12) 50%, rgba(113,63,18,0.85) 100%)",border:"rgba(250, 204, 21, 0.6)",text:"#fef3c7",shadow:"rgba(250, 204, 21, 0.7)",glow:"0 0 25px rgba(250, 204, 21, 0.4), 0 0 35px rgba(250, 204, 21, 0.25), inset 0 0 20px rgba(250, 204, 21, 0.08)"}},LF=e=>{if(!e)return y.jsx(Ks,{className:"h-4 w-4"});const t=e.toLowerCase();return t==="running"||t==="in_progress"?y.jsx(ou,{className:"h-4 w-4 animate-spin"}):t==="completed"||t==="success"||t==="finish"?y.jsx(Kr,{className:"h-4 w-4"}):t==="failed"||t==="error"?y.jsx(Oo,{className:"h-4 w-4"}):t==="pending"||t==="waiting"?y.jsx(su,{className:"h-4 w-4 animate-pulse"}):t==="skipped"?y.jsx(Ks,{className:"h-4 w-4"}):y.jsx(Ks,{className:"h-4 w-4"})},RF={star:({data:e})=>{const t=Hy[e.status??"pending"]??Hy.pending,n=LF(e.status);return y.jsxs("div",{className:"relative w-[280px]",children:[y.jsx(as,{type:"target",position:Q.Left,style:{opacity:0}}),y.jsx(as,{type:"source",position:Q.Right,style:{opacity:0}}),y.jsxs("div",{className:"rounded-2xl border-2 px-5 py-4 text-left shadow-2xl backdrop-blur-sm transition-all duration-300 hover:scale-105",style:{background:t.bg,borderColor:t.border,boxShadow:`${t.glow}, 0 8px 32px rgba(0,0,0,0.4), inset 0 1px 2px rgba(255,255,255,0.1)`},children:[y.jsx("div",{className:"absolute -top-2 -right-2 flex items-center justify-center rounded-full border-2 p-1.5 shadow-lg transition-all duration-300",style:{background:t.bg,borderColor:t.border,color:t.text,boxShadow:`0 0 15px ${t.shadow}, 0 0 8px ${t.border}`},children:n}),y.jsx("div",{className:"absolute top-0 left-0 right-0 h-[1px] opacity-50",style:{background:`linear-gradient(90deg, transparent 0%, ${t.border} 50%, transparent 100%)`}}),y.jsx("div",{className:"text-xl font-semibold uppercase tracking-wider mb-2 drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]",style:{color:t.text,opacity:.85},children:e.taskId}),y.jsx("div",{className:"text-2xl font-bold leading-snug drop-shadow-[0_2px_8px_rgba(0,0,0,0.6)]",style:{color:t.text},children:e.label}),y.jsx("div",{className:"absolute bottom-0 left-0 right-0 h-[1px] opacity-30",style:{background:`linear-gradient(90deg, transparent 0%, ${t.border} 50%, transparent 100%)`}})]})]})}},zF=(e,t)=>{const n=new Set(e.map(g=>g.id)),r=new Map,i=new Map,s=new Map,o=new Map;e.forEach(g=>{r.set(g.id,0),i.set(g.id,0),s.set(g.id,[]),o.set(g.id,[])}),t.forEach(g=>{var w,p;!n.has(g.source)||!n.has(g.target)||(r.set(g.target,(r.get(g.target)??0)+1),i.set(g.source,(i.get(g.source)??0)+1),(w=s.get(g.source))==null||w.push(g.target),(p=o.get(g.target))==null||p.push(g.source))});const a=[],l=new Map;r.forEach((g,w)=>{g===0&&(a.push(w),l.set(w,0))});const u=new Map(r);for(;a.length>0;){const g=a.shift(),w=l.get(g)??0;(s.get(g)??[]).forEach(p=>{const x=Math.max(l.get(p)??0,w+1);l.set(p,x);const v=(u.get(p)??0)-1;u.set(p,v),v===0&&a.push(p)})}e.forEach(g=>{l.has(g.id)||l.set(g.id,0)});const c=new Map;e.forEach(g=>{const w=l.get(g.id)??0;c.has(w)||c.set(w,[]),c.get(w).push(g)});const f=500,d=200,h=-100,m=new Map;return Array.from(c.entries()).sort(([g],[w])=>g-w).forEach(([g,w])=>{const p=w.sort((k,N)=>{const S=o.get(k.id)??[],A=o.get(N.id)??[];if(S.length>0&&A.length>0){const P=S.reduce((C,j)=>{const z=m.get(j);return C+((z==null?void 0:z.y)??0)},0)/S.length,D=A.reduce((C,j)=>{const z=m.get(j);return C+((z==null?void 0:z.y)??0)},0)/A.length;return P-D}return S.length>0?S.reduce((D,C)=>{const j=m.get(C);return D+((j==null?void 0:j.y)??0)},0)/S.length:A.length>0?-(A.reduce((D,C)=>{const j=m.get(C);return D+((j==null?void 0:j.y)??0)},0)/A.length):k.label.localeCompare(N.label)}),x=p.length,v=d+Math.min(x*15,150);if(g===0){const k=(x-1)*v,N=k>0?-(k/2):0;p.forEach((S,A)=>{m.set(S.id,{x:h+g*f,y:N+A*v})})}else{const k=new Map;p.forEach(N=>{const S=o.get(N.id)??[],A=S.length>0?S.reduce((D,C)=>{const j=m.get(C);return D+((j==null?void 0:j.y)??0)},0)/S.length:0,P=Math.round(A/10)*10;k.has(P)||k.set(P,[]),k.get(P).push(N)}),k.forEach((N,S)=>{const A=N.length;if(A===1)m.set(N[0].id,{x:h+g*f,y:S});else{const P=(A-1)*v,D=S-P/2;N.forEach((C,j)=>{m.set(C.id,{x:h+g*f,y:D+j*v})})}})}}),m},Uy=(e,t)=>{const n=zF(e,t);return e.map(r=>{const i=n.get(r.id)??{x:0,y:0};return{id:r.id,type:"star",data:{label:r.label,status:r.status,taskId:r.id},position:i,draggable:!1,connectable:!1,sourcePosition:Q.Right,targetPosition:Q.Left}})},Wy=e=>e.map(t=>{const n=t.isSatisfied===!1?{color:"rgba(248, 113, 113, 0.7)",glowColor:"rgba(239, 68, 68, 0.5)",markerColor:"rgba(248, 113, 113, 0.9)"}:t.isSatisfied===!0?{color:"rgba(74, 222, 128, 0.7)",glowColor:"rgba(16, 185, 129, 0.5)",markerColor:"rgba(74, 222, 128, 0.9)"}:{color:"rgba(56, 189, 248, 0.7)",glowColor:"rgba(6, 182, 212, 0.5)",markerColor:"rgba(56, 189, 248, 0.9)"};return{id:t.id,source:t.source,target:t.target,type:"default",animated:t.isSatisfied===!1,style:{stroke:n.color,strokeWidth:2.5,filter:`drop-shadow(0 0 2px ${n.glowColor})`},markerEnd:{type:Do.Arrow,color:n.markerColor,width:20,height:20,strokeWidth:2}}}),FF=({nodes:e,edges:t,onSelectNode:n})=>{const[r,i,s]=wF(Uy(e,t)),[o,a,l]=kF(Wy(t)),{setViewport:u}=Tu(),c=T.useRef(!1);return T.useEffect(()=>{i(Uy(e,t)),a(Wy(t))},[t,e,a,i]),T.useEffect(()=>{r.length>0&&!c.current&&setTimeout(()=>{const f=Math.min(...r.map(D=>D.position.x)),d=Math.max(...r.map(D=>D.position.x)),h=Math.min(...r.map(D=>D.position.y)),m=Math.max(...r.map(D=>D.position.y)),g=d-f+280,w=m-h+180,p=document.querySelector(".react-flow"),x=(p==null?void 0:p.clientWidth)||800,v=(p==null?void 0:p.clientHeight)||600,k=x*.95/g,N=v*.9/w,S=Math.max(Math.min(k,N,1.5),.45),A=-f*S+30,P=(v-w*S)/2-h*S;u({x:A,y:P,zoom:S}),c.current=!0},150)},[r,u]),y.jsxs(fb,{nodes:r,edges:o,nodeTypes:RF,onNodesChange:s,onEdgesChange:l,fitView:!1,defaultViewport:{x:-50,y:0,zoom:.6},minZoom:.1,maxZoom:2,onNodeClick:(f,d)=>n==null?void 0:n(d.id),panOnScroll:!0,zoomOnScroll:!0,nodesDraggable:!1,nodesConnectable:!1,edgesFocusable:!1,elementsSelectable:!0,proOptions:{hideAttribution:!0},className:"rounded-2xl border border-white/5 bg-black/40",style:{height:"100%",minHeight:260},defaultEdgeOptions:{type:"default",animated:!1,style:{strokeWidth:2.5}},children:[y.jsx(TF,{showInteractive:!1,position:"bottom-left"}),y.jsx(jF,{gap:28,size:1.8,color:"rgba(100, 116, 139, 0.2)"})]})},OF=e=>y.jsx(Ap,{children:y.jsx(FF,{...e})}),VF=({constellation:e,onBack:t})=>{var S;const n=((S=e.metadata)==null?void 0:S.statistics)||{},r=n.task_status_counts||{},i=n.total_tasks||e.statistics.total,s=n.total_dependencies||0,o=r.completed||0,a=r.failed||0,l=r.running||0,u=r.pending||0,c=r.ready||0,f=o+a,d=f>0?o/f*100:0,h=n.execution_duration,m=h!=null?`${h.toFixed(2)}s`:"N/A",g=n.critical_path_length,w=n.total_work,p=n.parallelism_ratio,x=A=>{if(!A)return"N/A";try{const P=new Date(A);return new Intl.DateTimeFormat("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit"}).format(P)}catch{return"N/A"}},v=x(n.created_at),k=x(n.execution_start_time),N=x(n.execution_end_time);return y.jsxs("div",{className:"flex h-full flex-col gap-4 overflow-y-auto p-1",children:[y.jsxs("div",{className:"flex items-center gap-3",children:[y.jsxs("button",{onClick:t,className:"flex items-center gap-2 rounded-full border border-white/10 bg-black/30 px-3 py-2 text-xs text-slate-200 transition hover:border-white/30 hover:bg-black/40",children:[y.jsx(av,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Back to DAG"]}),y.jsx("div",{className:"text-sm font-semibold text-white",children:"Execution Summary"})]}),y.jsx("div",{className:"rounded-2xl border border-emerald-400/30 bg-gradient-to-br from-emerald-500/10 to-cyan-500/10 p-4",children:y.jsxs("div",{className:"flex items-center justify-between",children:[y.jsxs("div",{children:[y.jsx("div",{className:"text-xs uppercase tracking-[0.2em] text-slate-400",children:"Success Rate"}),y.jsx("div",{className:"mt-1 text-3xl font-bold text-emerald-300",children:f>0?`${d.toFixed(1)}%`:"N/A"}),y.jsxs("div",{className:"mt-1 text-xs text-slate-400",children:[o," of ",f," completed tasks"]})]}),y.jsx(YC,{className:"h-10 w-10 text-emerald-400/40","aria-hidden":!0})]})}),y.jsxs("div",{className:"grid grid-cols-4 gap-2 text-center",children:[y.jsxs("div",{className:"rounded-xl border border-white/10 bg-white/5 px-2 py-2",children:[y.jsx("div",{className:"text-[9px] uppercase tracking-[0.2em] text-slate-400",children:"Total"}),y.jsx("div",{className:"mt-0.5 text-lg font-bold text-white",children:i})]}),y.jsxs("div",{className:"rounded-xl border border-white/10 bg-white/5 px-2 py-2",children:[y.jsx("div",{className:"text-[9px] uppercase tracking-[0.2em] text-slate-400",children:"Pending"}),y.jsx("div",{className:"mt-0.5 text-lg font-bold text-slate-300",children:u})]}),y.jsxs("div",{className:"rounded-xl border border-white/10 bg-white/5 px-2 py-2",children:[y.jsx("div",{className:"text-[9px] uppercase tracking-[0.2em] text-slate-400",children:"Running"}),y.jsx("div",{className:"mt-0.5 text-lg font-bold text-cyan-300",children:l})]}),y.jsxs("div",{className:"rounded-xl border border-white/10 bg-white/5 px-2 py-2",children:[y.jsx("div",{className:"text-[9px] uppercase tracking-[0.2em] text-slate-400",children:"Done"}),y.jsx("div",{className:"mt-0.5 text-lg font-bold text-emerald-300",children:o})]})]}),y.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[y.jsxs("div",{className:"flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-slate-400",children:[y.jsx(Kr,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Completed"]}),y.jsx("div",{className:"mt-2 text-2xl font-bold text-emerald-300",children:o}),y.jsxs("div",{className:"mt-1 text-xs text-slate-500",children:[i>0?`${(o/i*100).toFixed(0)}%`:"0%"," of total"]})]}),y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[y.jsxs("div",{className:"flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-slate-400",children:[y.jsx(Oo,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Failed"]}),y.jsx("div",{className:"mt-2 text-2xl font-bold text-rose-300",children:a}),y.jsxs("div",{className:"mt-1 text-xs text-slate-500",children:[i>0?`${(a/i*100).toFixed(0)}%`:"0%"," of total"]})]}),y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[y.jsxs("div",{className:"flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-slate-400",children:[y.jsx(su,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Running"]}),y.jsx("div",{className:"mt-2 text-2xl font-bold text-cyan-300",children:l}),y.jsx("div",{className:"mt-1 text-xs text-slate-500",children:"Active execution"})]}),y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[y.jsxs("div",{className:"flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-slate-400",children:[y.jsx(IC,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Pending"]}),y.jsx("div",{className:"mt-2 text-2xl font-bold text-slate-300",children:u}),y.jsx("div",{className:"mt-1 text-xs text-slate-500",children:"Awaiting execution"})]})]}),(s>0||c>0)&&y.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c>0&&y.jsxs("div",{className:"rounded-2xl border border-yellow-400/30 bg-yellow-500/10 p-4",children:[y.jsx("div",{className:"text-xs uppercase tracking-[0.2em] text-slate-400",children:"Ready"}),y.jsx("div",{className:"mt-2 text-2xl font-bold text-yellow-300",children:c}),y.jsx("div",{className:"mt-1 text-xs text-slate-500",children:"Can be executed"})]}),s>0&&y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[y.jsx("div",{className:"text-xs uppercase tracking-[0.2em] text-slate-400",children:"Dependencies"}),y.jsx("div",{className:"mt-2 text-2xl font-bold text-slate-300",children:s}),y.jsx("div",{className:"mt-1 text-xs text-slate-500",children:"Total links"})]})]}),p!=null&&y.jsxs("div",{className:"rounded-2xl border border-purple-400/30 bg-gradient-to-br from-purple-500/10 to-blue-500/10 p-4",children:[y.jsx("div",{className:"text-xs font-semibold uppercase tracking-[0.2em] text-slate-400 mb-3",children:"Parallelism Analysis"}),y.jsxs("div",{className:"grid grid-cols-3 gap-4 text-center",children:[y.jsxs("div",{children:[y.jsx("div",{className:"text-xs text-slate-400",children:"Critical Path"}),y.jsx("div",{className:"mt-1 text-xl font-bold text-purple-300",children:g!=null?Number(g).toFixed(2):"N/A"})]}),y.jsxs("div",{children:[y.jsx("div",{className:"text-xs text-slate-400",children:"Total Work"}),y.jsx("div",{className:"mt-1 text-xl font-bold text-blue-300",children:w!=null?Number(w).toFixed(2):"N/A"})]}),y.jsxs("div",{children:[y.jsx("div",{className:"text-xs text-slate-400",children:"Ratio"}),y.jsx("div",{className:"mt-1 text-xl font-bold text-cyan-300",children:p?`${p.toFixed(2)}x`:"N/A"})]})]})]}),y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[y.jsx("div",{className:"text-xs font-semibold uppercase tracking-[0.2em] text-slate-400 mb-3",children:"Execution Timeline"}),y.jsxs("div",{className:"space-y-2 text-xs",children:[y.jsxs("div",{className:"flex justify-between",children:[y.jsx("span",{className:"text-slate-400",children:"Created:"}),y.jsx("span",{className:"font-mono text-slate-200",children:v})]}),y.jsxs("div",{className:"flex justify-between",children:[y.jsx("span",{className:"text-slate-400",children:"Started:"}),y.jsx("span",{className:"font-mono text-slate-200",children:k})]}),e.status==="completed"&&y.jsxs("div",{className:"flex justify-between",children:[y.jsx("span",{className:"text-slate-400",children:"Ended:"}),y.jsx("span",{className:"font-mono text-slate-200",children:N})]}),y.jsxs("div",{className:"flex justify-between border-t border-white/10 pt-2 mt-2",children:[y.jsx("span",{className:"text-slate-400 font-semibold",children:"Duration:"}),y.jsx("span",{className:"font-mono text-emerald-300 font-semibold",children:m})]})]})]}),e.metadata&&Object.keys(e.metadata).length>0&&y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[y.jsx("div",{className:"text-xs font-semibold uppercase tracking-[0.2em] text-slate-400 mb-3",children:"Additional Information"}),y.jsxs("div",{className:"space-y-2 text-xs",children:[e.description&&y.jsxs("div",{children:[y.jsx("span",{className:"text-slate-400",children:"Description:"}),y.jsx("div",{className:"mt-1 text-slate-200",children:e.description})]}),e.metadata.display_name&&y.jsxs("div",{className:"flex justify-between",children:[y.jsx("span",{className:"text-slate-400",children:"Name:"}),y.jsx("span",{className:"text-slate-200",children:e.metadata.display_name})]})]})]})]})},$F={pending:"text-slate-300",running:"text-cyan-300",completed:"text-emerald-300",failed:"text-rose-300"},BF=({constellation:e,onSelectTask:t,variant:n="standalone"})=>{const[r,i]=T.useState(!1);if(!e)return y.jsxs("div",{className:we("flex h-full flex-col items-center justify-center gap-3 rounded-3xl p-8 text-center text-sm text-slate-300",n==="standalone"?"glass-card":"border border-white/5 bg-black/30"),children:[y.jsx($C,{className:"h-6 w-6","aria-hidden":!0}),y.jsx("div",{children:"No active constellation yet."}),y.jsx("div",{className:"text-xs text-slate-500",children:"Launch a request to generate a TaskConstellation."})]});const s=$F[e.status]||"text-slate-300",o=we("flex h-full flex-col gap-4 rounded-3xl p-5",n==="standalone"?"glass-card":"border border-white/5 bg-black/30",n==="embedded"&&"max-h-[420px]"),a=we("flex-1 overflow-hidden rounded-3xl border border-white/5 bg-black/30",n==="embedded"?"h-[260px]":"h-[320px]"),l=e.status==="completed"||e.status==="failed";return y.jsxs("div",{className:o,children:[y.jsxs("div",{className:"flex items-center justify-between gap-4",children:[y.jsxs("div",{className:"flex items-center gap-2 text-xs text-slate-400",children:[y.jsx(WC,{className:"h-3 w-3","aria-hidden":!0}),y.jsxs("span",{children:[e.taskIds.length," tasks"]}),y.jsx("span",{className:"mx-1",children:"•"}),y.jsx("span",{className:s,children:e.status})]}),l&&y.jsxs("button",{onClick:()=>i(!r),className:we("flex items-center gap-2 rounded-full border border-white/10 px-3 py-1.5 text-xs transition",r?"bg-emerald-500/20 border-emerald-400/40 text-emerald-300":"bg-black/30 text-slate-300 hover:border-white/30 hover:bg-black/40"),title:"View execution summary",children:[y.jsx(EC,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Stats"]})]}),y.jsx("div",{className:a,children:r?y.jsx(VF,{constellation:e,onBack:()=>i(!1)}):y.jsx(OF,{nodes:e.dag.nodes,edges:e.dag.edges,onSelectNode:t})})]})},HF=e=>{const t=e.toLowerCase();return t==="running"||t==="in_progress"?y.jsx(ou,{className:"h-3.5 w-3.5 animate-spin text-cyan-300","aria-hidden":!0}):t==="completed"||t==="success"||t==="finish"?y.jsx(AC,{className:"h-3.5 w-3.5 text-emerald-300","aria-hidden":!0}):t==="failed"||t==="error"?y.jsx(Oo,{className:"h-3.5 w-3.5 text-rose-400","aria-hidden":!0}):t==="pending"||t==="waiting"?y.jsx(su,{className:"h-3.5 w-3.5 animate-pulse text-slate-300","aria-hidden":!0}):t==="skipped"?y.jsx(Ks,{className:"h-3.5 w-3.5 text-amber-300","aria-hidden":!0}):y.jsx(Ks,{className:"h-3.5 w-3.5 text-slate-300","aria-hidden":!0})},UF=["all","pending","running","completed","failed"],WF={all:"All",pending:"Pending",running:"Running",completed:"Completed",failed:"Failed"},YF=({tasks:e,activeTaskId:t,onSelectTask:n})=>{const[r,i]=T.useState("all"),s=T.useMemo(()=>{const o={running:0,pending:1,failed:2,completed:3,skipped:4};return e.filter(a=>r==="all"||a.status===r).sort((a,l)=>{const u=(o[a.status]??99)-(o[l.status]??99);return u!==0?u:(a.name||a.id).localeCompare(l.name||l.id)})},[r,e]);return y.jsxs("div",{className:"flex h-full flex-col gap-3 text-xs text-slate-200",children:[y.jsx("div",{className:"flex items-center justify-between",children:y.jsx("div",{className:"flex items-center gap-1 rounded-full border border-white/10 bg-black/30 px-2 py-1",children:UF.map(o=>y.jsx("button",{type:"button",onClick:()=>i(o),className:we("rounded-full px-2 py-1 text-[10px] uppercase tracking-[0.18em]",r===o?"bg-gradient-to-r from-galaxy-blue/40 to-galaxy-purple/40 text-white":"text-slate-400"),children:WF[o]},o))})}),y.jsx("div",{className:"flex-1 space-y-2 overflow-y-auto",children:s.length===0?y.jsx("div",{className:"flex flex-col items-center gap-2 rounded-2xl border border-dashed border-white/10 bg-white/5 p-6 text-center text-xs text-slate-400",children:"No tasks match this filter yet."}):s.map(o=>{const a=HF(o.status);return y.jsxs("button",{type:"button",onClick:()=>n(o.id),className:we("w-full rounded-2xl border px-3 py-3 text-left transition",t===o.id?"border-galaxy-blue/60 bg-galaxy-blue/15 shadow-glow":"border-white/10 bg-white/5 hover:border-white/25 hover:bg-white/10"),children:[y.jsxs("div",{className:"flex items-center justify-between gap-3 text-xs text-slate-200",children:[y.jsxs("div",{className:"flex items-center gap-2",children:[a,y.jsx("span",{className:"font-medium text-white",children:o.name||o.id})]}),y.jsx("div",{className:"text-[10px] uppercase tracking-[0.18em] text-slate-400",children:o.status})]}),y.jsx("div",{className:"mt-1 text-[11px] text-slate-400",children:o.deviceId?`device: ${o.deviceId}`:"No device assigned"})]},o.id)})})]})},Pa=e=>{if(!e)return"∅";try{return JSON.stringify(e,null,2)}catch{return String(e)}},qF=e=>e.length?y.jsx("ul",{className:"space-y-2 text-xs",children:e.map(t=>y.jsxs("li",{className:we("rounded-xl border px-3 py-2",t.level==="error"?"border-rose-400/40 bg-rose-500/10 text-rose-100":"border-white/10 bg-white/5 text-slate-200"),children:[y.jsxs("div",{className:"flex items-center justify-between text-[10px] uppercase tracking-[0.18em] text-slate-400",children:[y.jsx("span",{children:t.level}),y.jsx("span",{children:new Date(t.timestamp).toLocaleTimeString()})]}),y.jsx("div",{className:"mt-1 text-xs",children:t.message})]},t.id))}):y.jsx("div",{className:"text-xs text-slate-400",children:"No logs streamed yet."}),KF=({task:e,onBack:t})=>{const n=Ce(o=>o.setActiveTask);if(!e)return y.jsx("div",{className:"flex h-full flex-col items-center justify-center gap-3 text-center text-sm text-slate-300",children:"Select a task to inspect details."});const r=()=>{sn().send({type:"task_retry",taskId:e.id,constellationId:e.constellationId,timestamp:Date.now()})},i=()=>{sn().send({type:"task_skip",taskId:e.id,constellationId:e.constellationId,timestamp:Date.now()})},s=()=>{t?t():n(null)};return y.jsxs("div",{className:"flex h-full flex-col gap-4 text-xs text-slate-200",children:[y.jsxs("div",{className:"flex items-center justify-between",children:[y.jsxs("div",{className:"flex items-center gap-3",children:[y.jsxs("button",{type:"button",onClick:s,className:"inline-flex items-center gap-1 rounded-full border border-white/10 bg-gradient-to-r from-white/8 to-white/5 px-3 py-2 text-xs text-slate-100 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.08)] transition-all hover:border-white/25 hover:shadow-[0_4px_12px_rgba(0,0,0,0.25),0_0_15px_rgba(15,123,255,0.15)]",title:"Back to task list",children:[y.jsx(av,{className:"h-3 w-3","aria-hidden":!0}),"Back"]}),y.jsxs("div",{children:[y.jsx("div",{className:"text-xs uppercase tracking-[0.25em] text-slate-400",children:"Task Detail"}),y.jsx("div",{className:"text-lg font-semibold text-white drop-shadow-[0_0_8px_rgba(255,255,255,0.3)]",children:e.name||e.id}),y.jsxs("div",{className:"text-[11px] text-slate-400",children:["Status: ",e.status]})]})]}),y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsxs("button",{type:"button",onClick:r,className:"inline-flex items-center gap-2 rounded-full border border-white/10 bg-gradient-to-r from-white/8 to-white/5 px-3 py-2 text-xs text-slate-100 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.08)] transition-all hover:border-emerald-400/40 hover:shadow-[0_4px_12px_rgba(0,0,0,0.25),0_0_15px_rgba(16,185,129,0.2)]",children:[y.jsx(OC,{className:"h-3 w-3","aria-hidden":!0}),"Retry"]}),y.jsxs("button",{type:"button",onClick:i,className:"inline-flex items-center gap-2 rounded-full border border-white/10 bg-gradient-to-r from-white/8 to-white/5 px-3 py-2 text-xs text-slate-100 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.08)] transition-all hover:border-amber-400/40 hover:shadow-[0_4px_12px_rgba(0,0,0,0.25),0_0_15px_rgba(245,158,11,0.2)]",children:[y.jsx(BC,{className:"h-3 w-3","aria-hidden":!0}),"Skip"]})]})]}),y.jsxs("div",{className:"grid gap-3 text-[11px] text-slate-300",children:[y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-gradient-to-br from-white/8 to-white/4 px-3 py-2 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.06)]",children:[y.jsxs("div",{children:["Device: ",e.deviceId||"not assigned"]}),y.jsxs("div",{children:["Started: ",e.startedAt?new Date(e.startedAt).toLocaleTimeString():"—"]}),y.jsxs("div",{children:["Completed: ",e.completedAt?new Date(e.completedAt).toLocaleTimeString():"—"]})]}),y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-gradient-to-br from-white/8 to-white/4 px-3 py-2 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.06)]",children:[y.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-[0.18em] text-slate-400",children:"Dependencies"}),y.jsx("div",{children:e.dependencies.length?e.dependencies.join(", "):"None"})]}),e.error&&y.jsxs("div",{className:"rounded-2xl border border-rose-400/40 bg-gradient-to-r from-rose-500/15 to-rose-600/10 px-3 py-2 text-rose-100 shadow-[0_0_20px_rgba(244,63,94,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)]",children:["Error: ",e.error]})]}),y.jsxs("div",{className:"grid grid-cols-1 gap-3",children:[y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-gradient-to-br from-black/50 to-black/30 shadow-[0_4px_16px_rgba(0,0,0,0.3),inset_0_1px_1px_rgba(255,255,255,0.05)]",children:[y.jsxs("div",{className:"flex items-center justify-between border-b border-white/10 bg-white/5 px-3 py-2 text-[10px] uppercase tracking-[0.18em] text-slate-400",children:["Input",y.jsxs("button",{type:"button",className:"inline-flex items-center gap-1 text-[10px] text-slate-400 transition hover:text-slate-200",onClick:()=>{navigator!=null&&navigator.clipboard&&navigator.clipboard.writeText(Pa(e.input))},children:[y.jsx(qm,{className:"h-3 w-3","aria-hidden":!0})," Copy"]})]}),y.jsx("pre",{className:"max-h-60 overflow-auto px-3 py-3 text-[11px] text-slate-200",children:Pa(e.input)})]}),y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-gradient-to-br from-black/50 to-black/30 shadow-[0_4px_16px_rgba(0,0,0,0.3),inset_0_1px_1px_rgba(255,255,255,0.05)]",children:[y.jsxs("div",{className:"flex items-center justify-between border-b border-white/10 bg-white/5 px-3 py-2 text-[10px] uppercase tracking-[0.18em] text-slate-400",children:["Output",y.jsxs("button",{type:"button",className:"inline-flex items-center gap-1 text-[10px] text-slate-400 transition hover:text-slate-200",onClick:()=>{navigator!=null&&navigator.clipboard&&navigator.clipboard.writeText(Pa(e.output||e.result))},children:[y.jsx(qm,{className:"h-3 w-3","aria-hidden":!0})," Copy"]})]}),y.jsx("pre",{className:"max-h-60 overflow-auto px-3 py-3 text-[11px] text-slate-200",children:Pa(e.output||e.result)})]})]}),y.jsxs("div",{className:"flex-1 overflow-y-auto rounded-2xl border border-white/10 bg-gradient-to-br from-black/50 to-black/30 px-3 py-3 shadow-[0_4px_16px_rgba(0,0,0,0.3),inset_0_1px_1px_rgba(255,255,255,0.05)]",children:[y.jsx("div",{className:"mb-2 text-[10px] uppercase tracking-[0.18em] text-slate-400",children:"Logs"}),qF(e.logs)]})]})},Yy={pending:"bg-slate-500/20 text-slate-300 border-slate-400/30",running:"bg-cyan-500/20 text-cyan-300 border-cyan-400/40",executing:"bg-cyan-500/20 text-cyan-300 border-cyan-400/40",completed:"bg-emerald-500/20 text-emerald-300 border-emerald-400/40",failed:"bg-rose-500/20 text-rose-300 border-rose-400/40"},qy=()=>{const{constellations:e,tasks:t,ui:n,setActiveConstellation:r,setActiveTask:i}=Ce(f=>({constellations:f.constellations,tasks:f.tasks,ui:f.ui,setActiveConstellation:f.setActiveConstellation,setActiveTask:f.setActiveTask}),Oe),s=T.useMemo(()=>Object.values(e).sort((f,d)=>(d.updatedAt??0)-(f.updatedAt??0)),[e]),o=T.useMemo(()=>{const f=Object.values(e).sort((h,m)=>(h.createdAt??0)-(m.createdAt??0)),d={};return f.forEach((h,m)=>{d[h.id]=m+1}),d},[e]);T.useEffect(()=>{!n.activeConstellationId&&s.length>0&&r(s[0].id)},[s,r,n.activeConstellationId]);const a=n.activeConstellationId?e[n.activeConstellationId]:s[0],l=T.useMemo(()=>a?a.taskIds.map(f=>t[f]).filter(f=>!!f):[],[a,t]),u=n.activeTaskId?t[n.activeTaskId]:void 0,c=f=>{const d=f.target.value;r(d||null)};return y.jsxs("div",{className:"flex h-full w-full flex-col gap-3",children:[y.jsxs("div",{className:"flex flex-1 min-h-0 flex-col gap-3 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-4 overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(147,51,234,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:[y.jsxs("div",{className:"flex items-center justify-between flex-shrink-0",children:[y.jsxs("div",{className:"flex items-center gap-3",children:[y.jsx(zC,{className:"h-5 w-5 text-purple-400 drop-shadow-[0_0_8px_rgba(147,51,234,0.5)]","aria-hidden":!0}),y.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:"Constellation Overview"}),a&&y.jsx("span",{className:we("rounded-full border px-3 py-1.5 text-xs font-semibold uppercase tracking-wider shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.1)]",Yy[a.status]||Yy.pending),children:a.status})]}),y.jsxs("select",{value:(a==null?void 0:a.id)||"",onChange:c,className:"rounded-full border border-white/5 bg-gradient-to-r from-black/30 to-black/20 px-3 py-1.5 text-xs text-slate-200 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus:border-white/15 focus:outline-none focus:ring-1 focus:ring-white/10",children:[s.map(f=>y.jsxs("option",{value:f.id,children:["Request ",o[f.id]||"?"]},f.id)),s.length===0&&y.jsx("option",{value:"",children:"No constellations"})]})]}),y.jsx("div",{className:"flex-1 min-h-0 overflow-hidden",children:y.jsx(BF,{constellation:a,onSelectTask:f=>i(f),variant:"embedded"})})]}),y.jsx("div",{className:"flex flex-1 min-h-0 flex-col gap-3 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-4 overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(6,182,212,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:u?y.jsx(KF,{task:u,onBack:()=>i(null)}):y.jsxs(y.Fragment,{children:[y.jsx("div",{className:"flex items-center justify-between flex-shrink-0",children:y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsx(HC,{className:"h-5 w-5 text-cyan-400 drop-shadow-[0_0_8px_rgba(6,182,212,0.5)]","aria-hidden":!0}),y.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:"TaskStar List"})]})}),y.jsx("div",{className:"flex-1 min-h-0 overflow-hidden",children:y.jsx(YF,{tasks:l,activeTaskId:n.activeTaskId,onSelectTask:f=>i(f)})})]})})]})},GF=e=>{const t=["white","blue","yellow","orange","red"],n=[.35,.3,.2,.1,.05];return Array.from({length:e},(r,i)=>{const s=Math.random();let o=0,a="white";for(let l=0;lArray.from({length:e},(t,n)=>({id:`shooting-${n}`,top:Math.random()*60+10,left:Math.random()*80,width:Math.random()*100+120,opacity:Math.random()*.3+.3})),QF=()=>{const e=T.useMemo(()=>GF(40),[]),t=T.useMemo(()=>XF(3),[]);return y.jsxs("div",{className:"absolute inset-0 overflow-hidden pointer-events-none",children:[e.map(n=>y.jsx("span",{className:"star-static","data-color":n.color,style:{left:`${n.left}%`,top:`${n.top}%`,width:`${n.size}rem`,height:`${n.size}rem`,opacity:n.opacity},"aria-hidden":!0},n.id)),t.map(n=>y.jsx("span",{className:"shooting-star-static",style:{top:`${n.top}%`,left:`${n.left}%`,width:`${n.width}px`,opacity:n.opacity},"aria-hidden":!0},n.id))]})},Ky={connecting:{label:"Connecting",color:"text-cyan-300"},connected:{label:"Connected",color:"text-emerald-300"},reconnecting:{label:"Reconnecting",color:"text-amber-300"},disconnected:{label:"Disconnected",color:"text-rose-300"},idle:{label:"Idle",color:"text-slate-400"}},ZF=()=>{const{session:e,connectionStatus:t,ui:n,toggleLeftDrawer:r,toggleRightDrawer:i}=Ce(o=>({session:o.session,connectionStatus:o.connectionStatus,ui:o.ui,toggleLeftDrawer:o.toggleLeftDrawer,toggleRightDrawer:o.toggleRightDrawer}),Oe);T.useEffect(()=>{const o=document.documentElement,a=document.body;e.highContrast?(o.classList.add("high-contrast"),a.classList.add("high-contrast")):(o.classList.remove("high-contrast"),a.classList.remove("high-contrast"))},[e.highContrast]);const s=Ky[t]??Ky.idle;return y.jsxs("div",{className:"relative min-h-screen w-full text-white galaxy-bg",children:[y.jsx("div",{className:"pointer-events-none absolute inset-0",children:y.jsx(QF,{})}),y.jsx("header",{className:"relative z-20 border-b border-white/5 bg-transparent",children:y.jsxs("div",{className:"mx-auto flex max-w-[2560px] items-center justify-between gap-4 px-4 sm:px-6 lg:px-8 py-3",children:[y.jsxs("div",{className:"flex items-center gap-2 lg:hidden",children:[y.jsx("button",{onClick:()=>r(),className:"rounded-lg border border-white/10 bg-white/5 p-2 text-slate-300 transition hover:bg-white/10 hover:text-white","aria-label":"Toggle left sidebar",children:y.jsx(FC,{className:"h-5 w-5"})}),y.jsx("button",{onClick:()=>i(),className:"rounded-lg border border-white/10 bg-white/5 p-2 text-slate-300 transition hover:bg-white/10 hover:text-white","aria-label":"Toggle right sidebar",children:y.jsx(LC,{className:"h-5 w-5"})})]}),y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsx("div",{className:"relative",children:y.jsx("img",{src:"/logo3.png",alt:"UFO3 logo",className:"relative h-12 w-12 sm:h-16 sm:w-16 lg:h-20 lg:w-20 drop-shadow-[0_0_20px_rgba(6,182,212,0.3)]"})}),y.jsx("div",{className:"hidden sm:block",children:y.jsxs("h1",{className:"font-heading text-xl sm:text-2xl lg:text-3xl font-bold tracking-tighter drop-shadow-[0_2px_12px_rgba(0,0,0,0.5)]",children:[y.jsx("span",{className:"text-transparent bg-clip-text bg-gradient-to-r from-cyan-300 via-white to-purple-300",children:"UFO"}),y.jsx("sup",{className:"text-sm sm:text-base lg:text-lg font-semibold text-transparent bg-clip-text bg-gradient-to-r from-cyan-300 via-white to-purple-300 ml-0.5",children:"3"}),y.jsx("span",{className:"ml-2 lg:ml-3 text-base sm:text-lg lg:text-xl font-normal tracking-wide text-transparent bg-clip-text bg-gradient-to-r from-cyan-200 via-purple-200 to-cyan-200 hidden md:inline",children:"Weaving the Digital Agent Galaxy"})]})})]}),y.jsxs("div",{className:"flex items-center gap-3 sm:gap-4 rounded-full border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] to-[rgba(8,15,28,0.85)] px-3 sm:px-5 py-2 sm:py-2.5 shadow-[0_4px_16px_rgba(0,0,0,0.3),0_1px_4px_rgba(15,123,255,0.1),inset_0_1px_1px_rgba(255,255,255,0.06)] ring-1 ring-inset ring-white/5",children:[y.jsx("span",{className:`h-2 w-2 sm:h-2.5 sm:w-2.5 rounded-full shadow-neon ${t==="connected"?"bg-emerald-400 animate-pulse":t==="reconnecting"?"bg-amber-400 animate-pulse":"bg-rose-400"}`}),y.jsxs("div",{className:"flex flex-col leading-tight",children:[y.jsx("span",{className:`text-[10px] sm:text-xs font-medium uppercase tracking-[0.2em] ${s.color}`,children:s.label}),y.jsx("span",{className:"text-[9px] sm:text-[11px] text-slate-400/80",children:e.displayName})]})]})]})}),y.jsxs("main",{className:"relative z-10 mx-auto flex h-[calc(100vh-94px)] max-w-[2560px] gap-4 px-4 sm:px-6 lg:px-8 pb-6 pt-1",children:[n.showLeftDrawer&&y.jsxs("div",{className:"fixed inset-0 z-50 lg:hidden",children:[y.jsx("div",{className:"absolute inset-0 bg-black/60 backdrop-blur-sm",onClick:()=>r(!1)}),y.jsxs("div",{className:"absolute left-0 top-0 h-full w-80 max-w-[85vw] bg-[#0a0e1a] shadow-2xl animate-slide-in-left",children:[y.jsxs("div",{className:"flex items-center justify-between border-b border-white/10 p-4",children:[y.jsx("h2",{className:"text-lg font-semibold text-white",children:"Devices"}),y.jsx("button",{onClick:()=>r(!1),className:"rounded-lg p-1.5 text-slate-400 transition hover:bg-white/5 hover:text-white",children:y.jsx(Bf,{className:"h-5 w-5"})})]}),y.jsx("div",{className:"h-[calc(100%-64px)] overflow-y-auto",children:y.jsx(ey,{})})]})]}),n.showRightDrawer&&y.jsxs("div",{className:"fixed inset-0 z-50 lg:hidden",children:[y.jsx("div",{className:"absolute inset-0 bg-black/60 backdrop-blur-sm",onClick:()=>i(!1)}),y.jsxs("div",{className:"absolute right-0 top-0 h-full w-96 max-w-[90vw] bg-[#0a0e1a] shadow-2xl animate-slide-in-right",children:[y.jsxs("div",{className:"flex items-center justify-between border-b border-white/10 p-4",children:[y.jsx("h2",{className:"text-lg font-semibold text-white",children:"Constellation"}),y.jsx("button",{onClick:()=>i(!1),className:"rounded-lg p-1.5 text-slate-400 transition hover:bg-white/5 hover:text-white",children:y.jsx(Bf,{className:"h-5 w-5"})})]}),y.jsx("div",{className:"h-[calc(100%-64px)] overflow-y-auto",children:y.jsx(qy,{})})]})]}),y.jsx("div",{className:"hidden xl:flex xl:w-72 2xl:w-80",children:y.jsx(ey,{})}),y.jsx("div",{className:"flex min-w-0 flex-1 flex-col",children:y.jsx(Bj,{})}),y.jsx("div",{className:"hidden lg:flex lg:w-[520px] xl:w-[560px] 2xl:w-[640px]",children:y.jsx(qy,{})})]}),y.jsx(Gj,{})]})},Pp=sn();Pp.onStatusChange(e=>{const t=Ce.getState();switch(e){case"connected":t.setConnectionStatus("connected");break;case"connecting":t.setConnectionStatus("connecting");break;case"reconnecting":t.setConnectionStatus("reconnecting");break;case"disconnected":t.setConnectionStatus("disconnected");break}});const no=e=>e!=null&&e.timestamp?Math.round(e.timestamp*1e3):Date.now(),Wc=e=>{if(!e)return;const t=Date.parse(e);return Number.isNaN(t)?void 0:t},Wi=e=>{try{const t={...e};if(t.thought&&typeof t.thought=="string"&&t.thought.length>100){let r=100;const i=[". ",`. -`,"! ",`! -`,"? ",`? -`];for(const s of i){const o=t.thought.lastIndexOf(s,100);if(o>100*.7){r=o+s.length;break}}t.thought=t.thought.substring(0,r).trim()+`... [Truncated: ${t.thought.length} chars total]`}return JSON.stringify(t,null,2)}catch(t){return console.error("Failed to stringify payload",t),String(e)}},JF=e=>e.map(t=>`- ${typeof t=="string"?t:Wi(t)}`).join(` -`),eO=e=>{if(!e)return"Agent responded.";if(typeof e=="string"){if(e.length>100){let r=100;const i=[". ",`. -`,"! ",`! -`,"? ",`? -`];for(const o of i){const a=e.lastIndexOf(o,100);if(a>100*.7){r=a+o.length;break}}return`${e.substring(0,r).trim()}... - -_[Truncated: ${e.length} chars total]_`}return e}const t=[];if(e.thought){const n=String(e.thought),r=100;if(n.length>r){let i=r;const s=[". ",`. -`,"! ",`! -`,"? ",`? -`];for(const a of s){const l=n.lastIndexOf(a,r);if(l>r*.7){i=l+a.length;break}}const o=n.substring(0,i).trim();t.push(`**💭 Thought** -${o}... - -_[Truncated: ${n.length} chars total]_`)}else t.push(`**💭 Thought** -${n}`)}if(e.plan){const n=Array.isArray(e.plan)?JF(e.plan):e.plan;t.push(`**📋 Plan** -${n}`)}return e.actions_summary&&t.push(`**⚡ Actions Summary** -${e.actions_summary}`),e.response&&t.push(`${e.response}`),e.final_response&&t.push(`${e.final_response}`),t.length===0&&e.message&&t.push(String(e.message)),t.length===0&&t.push(Wi(e)),t.join(` - -`)},tO=e=>e?Array.isArray(e.actions)?e.actions.map((n,r)=>{const i=n.description||n.name||`Action ${r+1}`,s=n.target_device_id?` _(device: ${n.target_device_id})_`:"";return`**${i}**${s} -${Wi(n.parameters??n)}`}).join(` - -`):e.action_type||e.name?`**${e.action_type||e.name}** -${Wi(e)}`:Wi(e):"Action executed.",mb=e=>{var n;const t=e.data||{};return t.constellation||t.updated_constellation||t.new_constellation||((n=e.output_data)==null?void 0:n.constellation)||null},Mp=e=>{var l,u;const t=mb(e);if(!t)return;const n=Ce.getState(),r=t.constellation_id||e.constellation_id||n.ensureSession(),i=t.dependencies||{},s=[];t.tasks&&Object.entries(t.tasks).forEach(([c,f])=>{const d=f,h=d.task_id||c;s.push({id:h,constellationId:r,name:d.name||h,description:d.description,status:d.status,deviceId:d.target_device_id||d.device_id,input:d.input,output:d.output,result:d.result,error:d.error,startedAt:Wc(d.started_at),completedAt:Wc(d.completed_at),logs:Array.isArray(d.logs)?d.logs.map((m,g)=>({id:`${h}-log-${g}`,timestamp:Date.now(),level:m.level||"info",message:m.message||Wi(m),payload:m.payload})):[]})}),n.bulkUpsertTasks(r,s,i);const o=s.map(c=>({id:c.id,label:c.name||c.id,status:c.status,deviceId:c.deviceId})),a=Object.entries(i).flatMap(([c,f])=>Array.isArray(f)?f.map(d=>({id:`${d}->${c}`,source:d,target:c})):[]);n.upsertConstellation({id:r,name:t.name||r,status:t.state||e.constellation_state||"running",description:t.description,metadata:{...t.metadata||{},statistics:t.statistics,execution_start_time:(l=t.metadata)==null?void 0:l.execution_start_time,execution_end_time:(u=t.metadata)==null?void 0:u.execution_end_time},createdAt:Wc(t.created_at),taskIds:s.map(c=>c.id),dag:{nodes:o,edges:a}})},Pd=e=>{Ce.getState().pushNotification({id:es(),timestamp:Date.now(),read:!1,...e})},nO=e=>{var s,o,a;const t=Ce.getState();if(t.ui.isTaskStopped){console.log("⚠️ Ignoring agent response - task was stopped by user");return}const n=t.ensureSession(((s=e.data)==null?void 0:s.session_id)||null),r=eO(e.output_data);t.addMessage({id:es(),sessionId:n,role:"assistant",kind:"response",author:e.agent_name||"Galaxy Agent",content:r,payload:e.output_data,timestamp:no(e),agentName:e.agent_name}),Mp(e);const i=(a=(o=e.output_data)==null?void 0:o.status)==null?void 0:a.toLowerCase();(i==="finish"||i==="fail")&&t.setTaskRunning(!1)},rO=e=>{var i,s;const t=Ce.getState();if(t.ui.isTaskStopped){console.log("⚠️ Ignoring agent action - task was stopped by user");return}const n=t.ensureSession(((i=e.data)==null?void 0:i.session_id)||null),r=tO(e.output_data);t.addMessage({id:es(),sessionId:n,role:"assistant",kind:"action",author:e.agent_name||"Galaxy Agent",content:r,payload:e.output_data,timestamp:no(e),agentName:e.agent_name,actionType:(s=e.output_data)==null?void 0:s.action_type})},iO=e=>{var i,s,o,a,l,u,c,f,d,h;const t=Ce.getState(),n=e.constellation_id||((i=e.data)==null?void 0:i.constellation_id)||((s=mb(e))==null?void 0:s.constellation_id);if(!e.task_id||!n)return;const r={status:e.status,result:e.result??((o=e.data)==null?void 0:o.result),error:e.error??((a=e.data)==null?void 0:a.error)??null,deviceId:((l=e.data)==null?void 0:l.device_id)??((u=e.data)==null?void 0:u.deviceId)};if(e.event_type==="task_completed"&&(r.completedAt=no(e)),e.event_type==="task_started"&&(r.startedAt=no(e)),t.updateTask(e.task_id,r),(c=e.data)!=null&&c.log_entry){const m=e.data.log_entry;t.appendTaskLog(e.task_id,m)}else(f=e.data)!=null&&f.message&&t.appendTaskLog(e.task_id,{id:`${e.task_id}-${e.event_type}-${Date.now()}`,timestamp:no(e),level:e.event_type==="task_failed"?"error":"info",message:e.data.message,payload:e.data});e.event_type==="task_failed"&&Pd({severity:"error",title:`Task ${e.task_id} failed`,description:((d=e.error)==null?void 0:d.toString())||"A task reported a failure.",source:n}),(e.event_type==="task_completed"||e.event_type==="task_failed")&&((h=e.data)!=null&&h.constellation)&&(Mp(e),console.log(`🔄 Updated constellation from ${e.event_type} event`))},sO=e=>{if(Mp(e),e.event_type==="constellation_started"){const t=Ce.getState(),n=e.constellation_id;n&&(Object.keys(t.constellations).forEach(r=>{r.startsWith("temp-")&&(t.removeConstellation(r),console.log(`🗑️ Removed temporary constellation: ${r}`))}),t.setActiveConstellation(n),console.log(`🌟 Auto-switched to new constellation: ${n}`))}e.event_type==="constellation_completed"&&Pd({severity:"success",title:"Constellation completed",description:`Constellation ${e.constellation_id||""} finished execution successfully.`,source:e.constellation_id}),e.event_type==="constellation_failed"&&Pd({severity:"error",title:"Constellation failed",description:`Constellation ${e.constellation_id||""} reported a failure.`,source:e.constellation_id})},oO=e=>{var a,l,u,c,f;console.log("📱 Device event received:",{event_type:e.event_type,device_id:e.device_id,device_status:e.device_status,device_info_status:(a=e.device_info)==null?void 0:a.status,full_event:e});const t=Ce.getState(),n=e.all_devices||((l=e.data)==null?void 0:l.all_devices);n&&e.event_type==="device_snapshot"&&t.setDevicesFromSnapshot(n);const r=e.device_info||((u=e.data)==null?void 0:u.device_info)||{},i=e.device_id||r.device_id||((c=e.data)==null?void 0:c.device_id)||null;if(!i)return;const{statusChanged:s,previousStatus:o}=t.upsertDevice({id:i,name:r.device_id||i,status:e.device_status||r.status,os:r.os,serverUrl:r.server_url,capabilities:r.capabilities,metadata:r.metadata,lastHeartbeat:r.last_heartbeat,connectionAttempts:r.connection_attempts,maxRetries:r.max_retries,currentTaskId:r.current_task_id,tags:(f=r.metadata)==null?void 0:f.tags,metrics:r.metrics});console.log("📱 Device upserted:",{deviceId:i,statusChanged:s,previousStatus:o,newStatus:e.device_status||r.status}),window.setTimeout(()=>{Ce.getState().clearDeviceHighlight(i)},4e3)},aO=e=>{var n;const t=e.type||e.event_type;if(t==="reset_acknowledged"){console.log("✅ Session reset acknowledged:",e),Ce.getState().pushNotification({id:`reset-${Date.now()}`,title:"Session Reset",description:e.message||"Session has been reset successfully",severity:"success",timestamp:Date.now(),read:!1});return}if(t==="next_session_acknowledged"){console.log("✅ Next session acknowledged:",e),Ce.getState().pushNotification({id:`next-session-${Date.now()}`,title:"New Session",description:e.message||"New session created successfully",severity:"success",timestamp:Date.now(),read:!1});return}if(t==="stop_acknowledged"){console.log("✅ Task stop acknowledged:",e),Ce.getState().pushNotification({id:`stop-task-${Date.now()}`,title:"Task Stopped",description:e.message||"Task stopped and new session created",severity:"info",timestamp:Date.now(),read:!1});return}if((n=e.event_type)!=null&&n.startsWith("device_")){oO(e);return}switch(e.event_type){case"agent_response":nO(e);break;case"agent_action":rO(e);break;case"constellation_started":case"constellation_modified":case"constellation_completed":case"constellation_failed":sO(e);break;case"task_started":case"task_completed":case"task_failed":iO(e);break}};Pp.connect().catch(e=>{console.error("❌ Failed to connect to Galaxy WebSocket server:",e),Ce.getState().setConnectionStatus("disconnected")});Pp.onEvent(e=>{Ce.getState().addEventToLog(e),aO(e)});Yc.createRoot(document.getElementById("root")).render(y.jsx($.StrictMode,{children:y.jsx(ZF,{})})); diff --git a/galaxy/webui/frontend/dist/assets/index-Bz7234yJ.css b/galaxy/webui/frontend/dist/assets/index-Bz7234yJ.css new file mode 100644 index 000000000..8c72b1da6 --- /dev/null +++ b/galaxy/webui/frontend/dist/assets/index-Bz7234yJ.css @@ -0,0 +1 @@ +.react-flow{direction:ltr}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;cursor:grab}.react-flow__pane.selection{cursor:pointer}.react-flow__pane.dragging{cursor:grabbing}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow .react-flow__edges{pointer-events:none;overflow:visible}.react-flow__edge-path,.react-flow__connection-path{stroke:#b1b1b7;stroke-width:1;fill:none}.react-flow__edge{pointer-events:visibleStroke;cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge:focus .react-flow__edge-path,.react-flow__edge:focus-visible .react-flow__edge-path{stroke:#555}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge-textbg{fill:#fff}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__connectionline{z-index:1001}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:grab}.react-flow__node.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background:#1a192b;border:1px solid white;border-radius:100%}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:-4px;transform:translate(-50%)}.react-flow__handle-top{left:50%;top:-4px;transform:translate(-50%)}.react-flow__handle-left{top:50%;left:-4px;transform:translateY(-50%)}.react-flow__handle-right{right:-4px;top:50%;transform:translateY(-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.center{left:50%;transform:translate(-50%)}.react-flow__attribution{font-size:10px;background:#ffffff80;padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-default,.react-flow__node-input,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:3px;width:150px;font-size:12px;color:#222;text-align:center;border-width:1px;border-style:solid;border-color:#1a192b;background-color:#fff}.react-flow__node-default.selectable:hover,.react-flow__node-input.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:0 1px 4px 1px #00000014}.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:0 0 0 .5px #1a192b}.react-flow__node-group{background-color:#f0f0f040}.react-flow__nodesselection-rect,.react-flow__selection{background:#0059dc14;border:1px dotted rgba(0,89,220,.8)}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls{box-shadow:0 0 2px 1px #00000014}.react-flow__controls-button{border:none;background:#fefefe;border-bottom:1px solid #eee;box-sizing:content-box;display:flex;justify-content:center;align-items:center;width:16px;height:16px;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;padding:5px}.react-flow__controls-button:hover{background:#f4f4f4}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__minimap{background-color:#fff}.react-flow__minimap svg{display:block}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:4px;height:4px;border:1px solid #fff;border-radius:1px;background-color:#3367d9;transform:translate(-50%,-50%)}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:#3367d9;border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}*,: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:Inter,IBM Plex Sans,system-ui,sans-serif;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,Menlo,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}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.-right-2{right:-.5rem}.-top-2{top:-.5rem}.bottom-0{bottom:0}.bottom-6{bottom:1.5rem}.left-0{left:0}.left-6{left:1.5rem}.right-0{right:0}.right-2{right:.5rem}.top-0{top:0}.top-2{top:.5rem}.top-7{top:1.75rem}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-0\.5{margin-left:.125rem}.ml-12{margin-left:3rem}.ml-14{margin-left:3.5rem}.ml-2{margin-left:.5rem}.ml-6{margin-left:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-\[2px\]{margin-top:2px}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-9{height:2.25rem}.h-\[1px\]{height:1px}.h-\[260px\]{height:260px}.h-\[320px\]{height:320px}.h-\[calc\(100\%-1\.75rem\)\]{height:calc(100% - 1.75rem)}.h-\[calc\(100\%-64px\)\]{height:calc(100% - 64px)}.h-\[calc\(100vh-94px\)\]{height:calc(100vh - 94px)}.h-full{height:100%}.h-px{height:1px}.max-h-60{max-height:15rem}.max-h-80{max-height:20rem}.max-h-\[420px\]{max-height:420px}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:0px}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-12{width:3rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-96{width:24rem}.w-\[280px\]{width:280px}.w-\[88\%\]{width:88%}.w-\[calc\(88\%-3rem\)\]{width:calc(88% - 3rem)}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.max-w-2xl{max-width:42rem}.max-w-\[2560px\]{max-width:2560px}.max-w-\[85vw\]{max-width:85vw}.max-w-\[90vw\]{max-width:90vw}.max-w-none{max-width:none}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.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))}@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-none{resize:none}.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-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.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-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * 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-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * 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-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-\[24px\]{border-radius:24px}.rounded-\[28px\]{border-radius:28px}.rounded-\[30px\]{border-radius:30px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded-bl-xl{border-bottom-left-radius:.75rem}.rounded-br-xl{border-bottom-right-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[rgba\(10\,186\,181\,0\.35\)\]{border-color:#0abab559}.border-\[rgba\(10\,186\,181\,0\.4\)\]{border-color:#0abab566}.border-amber-400\/30{border-color:#fbbf244d}.border-amber-400\/40{border-color:#fbbf2466}.border-cyan-400\/30{border-color:#22d3ee4d}.border-cyan-400\/40{border-color:#22d3ee66}.border-cyan-400\/50{border-color:#22d3ee80}.border-cyan-500\/30{border-color:#06b6d44d}.border-emerald-400\/20{border-color:#34d39933}.border-emerald-400\/30{border-color:#34d3994d}.border-emerald-400\/40{border-color:#34d39966}.border-emerald-500\/50{border-color:#10b98180}.border-galaxy-blue\/50{border-color:#0f7bff80}.border-galaxy-blue\/60{border-color:#0f7bff99}.border-indigo-400\/30{border-color:#818cf84d}.border-indigo-400\/40{border-color:#818cf866}.border-purple-400\/20{border-color:#c084fc33}.border-purple-400\/30{border-color:#c084fc4d}.border-rose-400\/20{border-color:#fb718533}.border-rose-400\/30{border-color:#fb71854d}.border-rose-400\/40{border-color:#fb718566}.border-rose-500\/40{border-color:#f43f5e66}.border-rose-500\/50{border-color:#f43f5e80}.border-rose-900\/40{border-color:#88133766}.border-slate-400\/30{border-color:#94a3b84d}.border-slate-700{--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity, 1))}.border-white\/10{border-color:#ffffff1a}.border-white\/20{border-color:#fff3}.border-white\/5{border-color:#ffffff0d}.border-yellow-400\/30{border-color:#facc154d}.bg-\[\#0a0e1a\]{--tw-bg-opacity: 1;background-color:rgb(10 14 26 / var(--tw-bg-opacity, 1))}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-500\/20{background-color:#f59e0b33}.bg-black\/20{background-color:#0003}.bg-black\/30{background-color:#0000004d}.bg-black\/40{background-color:#0006}.bg-black\/60{background-color:#0009}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-cyan-400{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity, 1))}.bg-cyan-500\/20{background-color:#06b6d433}.bg-cyan-500\/30{background-color:#06b6d44d}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-emerald-500\/20{background-color:#10b98133}.bg-emerald-950\/30{background-color:#022c224d}.bg-galaxy-blue{--tw-bg-opacity: 1;background-color:rgb(15 123 255 / var(--tw-bg-opacity, 1))}.bg-galaxy-blue\/15{background-color:#0f7bff26}.bg-indigo-500\/20{background-color:#6366f133}.bg-purple-300\/80{background-color:#d8b4fecc}.bg-rose-400{--tw-bg-opacity: 1;background-color:rgb(251 113 133 / var(--tw-bg-opacity, 1))}.bg-rose-500{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity, 1))}.bg-rose-500\/10{background-color:#f43f5e1a}.bg-rose-500\/20{background-color:#f43f5e33}.bg-rose-950\/30{background-color:#4c05194d}.bg-slate-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity, 1))}.bg-slate-500\/20{background-color:#64748b33}.bg-slate-500\/30{background-color:#64748b4d}.bg-slate-600{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity, 1))}.bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.bg-slate-800\/60{background-color:#1e293b99}.bg-slate-800\/80{background-color:#1e293bcc}.bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.bg-slate-900\/95{background-color:#0f172af2}.bg-slate-950\/95{background-color:#020617f2}.bg-transparent{background-color:transparent}.bg-white\/10{background-color:#ffffff1a}.bg-white\/5{background-color:#ffffff0d}.bg-yellow-500\/10{background-color:#eab3081a}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.bg-starfield{background-image:radial-gradient(circle at 10% 20%,rgba(33,240,255,.18),transparent 45%),radial-gradient(circle at 80% 10%,rgba(147,51,234,.22),transparent 50%),radial-gradient(circle at 50% 80%,rgba(14,116,144,.3),transparent 55%)}.from-\[rgba\(10\,186\,181\,0\.12\)\]{--tw-gradient-from: rgba(10,186,181,.12) var(--tw-gradient-from-position);--tw-gradient-to: rgba(10, 186, 181, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(10\,186\,181\,0\.15\)\]{--tw-gradient-from: rgba(10,186,181,.15) var(--tw-gradient-from-position);--tw-gradient-to: rgba(10, 186, 181, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(11\,24\,44\,0\.82\)\]{--tw-gradient-from: rgba(11,24,44,.82) var(--tw-gradient-from-position);--tw-gradient-to: rgba(11, 24, 44, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(11\,30\,45\,0\.85\)\]{--tw-gradient-from: rgba(11,30,45,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(11, 30, 45, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(11\,30\,45\,0\.88\)\]{--tw-gradient-from: rgba(11,30,45,.88) var(--tw-gradient-from-position);--tw-gradient-to: rgba(11, 30, 45, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(25\,40\,60\,0\.75\)\]{--tw-gradient-from: rgba(25,40,60,.75) var(--tw-gradient-from-position);--tw-gradient-to: rgba(25, 40, 60, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(6\,182\,212\,0\.2\)\]{--tw-gradient-from: rgba(6,182,212,.2) var(--tw-gradient-from-position);--tw-gradient-to: rgba(6, 182, 212, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(6\,182\,212\,0\.85\)\]{--tw-gradient-from: rgba(6,182,212,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(6, 182, 212, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(80\,20\,30\,0\.75\)\]{--tw-gradient-from: rgba(80,20,30,.75) var(--tw-gradient-from-position);--tw-gradient-to: rgba(80, 20, 30, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-black\/30{--tw-gradient-from: rgb(0 0 0 / .3) var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-black\/50{--tw-gradient-from: rgb(0 0 0 / .5) var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-200{--tw-gradient-from: #a5f3fc var(--tw-gradient-from-position);--tw-gradient-to: rgb(165 243 252 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-300{--tw-gradient-from: #67e8f9 var(--tw-gradient-from-position);--tw-gradient-to: rgb(103 232 249 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-500\/20{--tw-gradient-from: rgb(6 182 212 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(6 182 212 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-950\/30{--tw-gradient-from: rgb(8 51 68 / .3) var(--tw-gradient-from-position);--tw-gradient-to: rgb(8 51 68 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500\/10{--tw-gradient-from: rgb(16 185 129 / .1) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500\/15{--tw-gradient-from: rgb(16 185 129 / .15) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500\/35{--tw-gradient-from: rgb(16 185 129 / .35) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-galaxy-blue{--tw-gradient-from: #0F7BFF var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 123 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-galaxy-blue\/25{--tw-gradient-from: rgb(15 123 255 / .25) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 123 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-galaxy-blue\/40{--tw-gradient-from: rgb(15 123 255 / .4) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 123 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-500\/10{--tw-gradient-from: rgb(168 85 247 / .1) var(--tw-gradient-from-position);--tw-gradient-to: rgb(168 85 247 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-500\/20{--tw-gradient-from: rgb(168 85 247 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(168 85 247 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-950\/20{--tw-gradient-from: rgb(59 7 100 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(59 7 100 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-rose-500\/15{--tw-gradient-from: rgb(244 63 94 / .15) var(--tw-gradient-from-position);--tw-gradient-to: rgb(244 63 94 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-rose-500\/35{--tw-gradient-from: rgb(244 63 94 / .35) var(--tw-gradient-from-position);--tw-gradient-to: rgb(244 63 94 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-\[rgba\(100\,25\,35\,0\.70\)\]{--tw-gradient-to: rgba(100, 25, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(100,25,35,.7) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(12\,50\,65\,0\.8\)\]{--tw-gradient-to: rgba(12, 50, 65, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(12,50,65,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(147\,51\,234\,0\.80\)\]{--tw-gradient-to: rgba(147, 51, 234, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(147,51,234,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(15\,123\,255\,0\.15\)\]{--tw-gradient-to: rgba(15, 123, 255, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(15,123,255,.15) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(20\,35\,52\,0\.7\)\]{--tw-gradient-to: rgba(20, 35, 52, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(20,35,52,.7) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(8\,20\,35\,0\.82\)\]{--tw-gradient-to: rgba(8, 20, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(8,20,35,.82) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(8\,20\,35\,0\.85\)\]{--tw-gradient-to: rgba(8, 20, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(8,20,35,.85) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-galaxy-purple\/25{--tw-gradient-to: rgb(123 44 191 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgb(123 44 191 / .25) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-purple-200{--tw-gradient-to: rgb(233 213 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #e9d5ff var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-white{--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #fff var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-\[rgba\(11\,30\,45\,0\.85\)\]{--tw-gradient-to: rgba(11,30,45,.85) var(--tw-gradient-to-position)}.to-\[rgba\(15\,28\,45\,0\.75\)\]{--tw-gradient-to: rgba(15,28,45,.75) var(--tw-gradient-to-position)}.to-\[rgba\(15\,28\,45\,0\.8\)\]{--tw-gradient-to: rgba(15,28,45,.8) var(--tw-gradient-to-position)}.to-\[rgba\(236\,72\,153\,0\.85\)\]{--tw-gradient-to: rgba(236,72,153,.85) var(--tw-gradient-to-position)}.to-\[rgba\(6\,15\,28\,0\.85\)\]{--tw-gradient-to: rgba(6,15,28,.85) var(--tw-gradient-to-position)}.to-\[rgba\(6\,15\,28\,0\.88\)\]{--tw-gradient-to: rgba(6,15,28,.88) var(--tw-gradient-to-position)}.to-\[rgba\(6\,182\,212\,0\.15\)\]{--tw-gradient-to: rgba(6,182,212,.15) var(--tw-gradient-to-position)}.to-\[rgba\(8\,15\,28\,0\.75\)\]{--tw-gradient-to: rgba(8,15,28,.75) var(--tw-gradient-to-position)}.to-\[rgba\(8\,15\,28\,0\.85\)\]{--tw-gradient-to: rgba(8,15,28,.85) var(--tw-gradient-to-position)}.to-\[rgba\(80\,20\,30\,0\.75\)\]{--tw-gradient-to: rgba(80,20,30,.75) var(--tw-gradient-to-position)}.to-black\/20{--tw-gradient-to: rgb(0 0 0 / .2) var(--tw-gradient-to-position)}.to-black\/30{--tw-gradient-to: rgb(0 0 0 / .3) var(--tw-gradient-to-position)}.to-blue-500\/10{--tw-gradient-to: rgb(59 130 246 / .1) var(--tw-gradient-to-position)}.to-blue-500\/20{--tw-gradient-to: rgb(59 130 246 / .2) var(--tw-gradient-to-position)}.to-blue-600\/15{--tw-gradient-to: rgb(37 99 235 / .15) var(--tw-gradient-to-position)}.to-blue-950\/20{--tw-gradient-to: rgb(23 37 84 / .2) var(--tw-gradient-to-position)}.to-cyan-200{--tw-gradient-to: #a5f3fc var(--tw-gradient-to-position)}.to-cyan-500\/10{--tw-gradient-to: rgb(6 182 212 / .1) var(--tw-gradient-to-position)}.to-cyan-500\/15{--tw-gradient-to: rgb(6 182 212 / .15) var(--tw-gradient-to-position)}.to-emerald-600\/10{--tw-gradient-to: rgb(5 150 105 / .1) var(--tw-gradient-to-position)}.to-emerald-600\/25{--tw-gradient-to: rgb(5 150 105 / .25) var(--tw-gradient-to-position)}.to-galaxy-blue\/15{--tw-gradient-to: rgb(15 123 255 / .15) var(--tw-gradient-to-position)}.to-galaxy-purple{--tw-gradient-to: #7b2cbf var(--tw-gradient-to-position)}.to-galaxy-purple\/40{--tw-gradient-to: rgb(123 44 191 / .4) var(--tw-gradient-to-position)}.to-indigo-950\/15{--tw-gradient-to: rgb(30 27 75 / .15) var(--tw-gradient-to-position)}.to-pink-500\/20{--tw-gradient-to: rgb(236 72 153 / .2) var(--tw-gradient-to-position)}.to-purple-300{--tw-gradient-to: #d8b4fe var(--tw-gradient-to-position)}.to-purple-500\/20{--tw-gradient-to: rgb(168 85 247 / .2) var(--tw-gradient-to-position)}.to-rose-600\/10{--tw-gradient-to: rgb(225 29 72 / .1) var(--tw-gradient-to-position)}.to-rose-600\/25{--tw-gradient-to: rgb(225 29 72 / .25) var(--tw-gradient-to-position)}.to-white\/5{--tw-gradient-to: rgb(255 255 255 / .05) var(--tw-gradient-to-position)}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.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-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-3{padding-bottom:.75rem}.pb-6{padding-bottom:1.5rem}.pr-1{padding-right:.25rem}.pr-6{padding-right:1.5rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-heading{font-family:IBM Plex Sans,Inter,system-ui,sans-serif}.font-mono{font-family:JetBrains Mono,Menlo,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.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-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.15em\]{letter-spacing:.15em}.tracking-\[0\.18em\]{letter-spacing:.18em}.tracking-\[0\.25em\]{letter-spacing:.25em}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-tight{letter-spacing:-.025em}.tracking-tighter{letter-spacing:-.05em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[rgb\(10\,186\,181\)\]{--tw-text-opacity: 1;color:rgb(10 186 181 / var(--tw-text-opacity, 1))}.text-amber-100{--tw-text-opacity: 1;color:rgb(254 243 199 / var(--tw-text-opacity, 1))}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-cyan-100{--tw-text-opacity: 1;color:rgb(207 250 254 / var(--tw-text-opacity, 1))}.text-cyan-200{--tw-text-opacity: 1;color:rgb(165 243 252 / var(--tw-text-opacity, 1))}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity, 1))}.text-cyan-300\/90{color:#67e8f9e6}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.text-cyan-500{--tw-text-opacity: 1;color:rgb(6 182 212 / var(--tw-text-opacity, 1))}.text-emerald-100{--tw-text-opacity: 1;color:rgb(209 250 229 / var(--tw-text-opacity, 1))}.text-emerald-100\/90{color:#d1fae5e6}.text-emerald-200{--tw-text-opacity: 1;color:rgb(167 243 208 / var(--tw-text-opacity, 1))}.text-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-emerald-400\/40{color:#34d39966}.text-indigo-300{--tw-text-opacity: 1;color:rgb(165 180 252 / var(--tw-text-opacity, 1))}.text-purple-200\/80{color:#e9d5ffcc}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-rose-100{--tw-text-opacity: 1;color:rgb(255 228 230 / var(--tw-text-opacity, 1))}.text-rose-100\/90{color:#ffe4e6e6}.text-rose-200{--tw-text-opacity: 1;color:rgb(254 205 211 / var(--tw-text-opacity, 1))}.text-rose-300{--tw-text-opacity: 1;color:rgb(253 164 175 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.text-slate-200\/80{color:#e2e8f0cc}.text-slate-200\/90{color:#e2e8f0e6}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.text-slate-300\/70{color:#cbd5e1b3}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-400\/80{color:#94a3b8cc}.text-slate-50{--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.text-transparent{color:transparent}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.placeholder-slate-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(100 116 139 / var(--tw-placeholder-opacity, 1))}.placeholder-slate-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(100 116 139 / var(--tw-placeholder-opacity, 1))}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px 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-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px 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-\[0_0_12px_rgba\(33\,240\,255\,0\.25\)\]{--tw-shadow: 0 0 12px rgba(33,240,255,.25);--tw-shadow-colored: 0 0 12px 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-\[0_0_15px_rgba\(16\,185\,129\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 0 15px rgba(16,185,129,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 0 15px var(--tw-shadow-color), inset 0 1px 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)}.shadow-\[0_0_15px_rgba\(6\,182\,212\,0\.2\)\]{--tw-shadow: 0 0 15px rgba(6,182,212,.2);--tw-shadow-colored: 0 0 15px 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-\[0_0_16px_rgba\(139\,0\,0\,0\.25\)\,0_4px_12px_rgba\(0\,0\,0\,0\.4\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 0 16px rgba(139,0,0,.25),0 4px 12px rgba(0,0,0,.4),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 0 16px var(--tw-shadow-color), 0 4px 12px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_0_16px_rgba\(147\,51\,234\,0\.08\)\]{--tw-shadow: 0 0 16px rgba(147,51,234,.08);--tw-shadow-colored: 0 0 16px 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-\[0_0_20px_rgba\(15\,123\,255\,0\.4\)\,0_2px_8px_rgba\(123\,44\,191\,0\.3\)\]{--tw-shadow: 0 0 20px rgba(15,123,255,.4),0 2px 8px rgba(123,44,191,.3);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color), 0 2px 8px 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-\[0_0_20px_rgba\(244\,63\,94\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 0 20px rgba(244,63,94,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color), inset 0 1px 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)}.shadow-\[0_0_20px_rgba\(6\,182\,212\,0\.15\)\]{--tw-shadow: 0 0 20px rgba(6,182,212,.15);--tw-shadow-colored: 0 0 20px 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-\[0_0_20px_rgba\(6\,182\,212\,0\.3\)\,0_0_30px_rgba\(147\,51\,234\,0\.2\)\,0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.15\)\,inset_0_-1px_2px_rgba\(0\,0\,0\,0\.2\)\]{--tw-shadow: 0 0 20px rgba(6,182,212,.3),0 0 30px rgba(147,51,234,.2),0 4px 16px rgba(0,0,0,.3),inset 0 1px 2px rgba(255,255,255,.15),inset 0 -1px 2px rgba(0,0,0,.2);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color), 0 0 30px var(--tw-shadow-color), 0 4px 16px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color), inset 0 -1px 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)}.shadow-\[0_0_25px_rgba\(10\,186\,181\,0\.18\)\,inset_0_1px_0_rgba\(10\,186\,181\,0\.12\)\]{--tw-shadow: 0 0 25px rgba(10,186,181,.18),inset 0 1px 0 rgba(10,186,181,.12);--tw-shadow-colored: 0 0 25px var(--tw-shadow-color), inset 0 1px 0 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-\[0_0_30px_rgba\(15\,123\,255\,0\.2\)\,inset_0_1px_0_rgba\(147\,197\,253\,0\.15\)\]{--tw-shadow: 0 0 30px rgba(15,123,255,.2),inset 0 1px 0 rgba(147,197,253,.15);--tw-shadow-colored: 0 0 30px var(--tw-shadow-color), inset 0 1px 0 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-\[0_0_30px_rgba\(6\,182\,212\,0\.4\)\,0_0_40px_rgba\(6\,182\,212\,0\.25\)\,0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,inset_0_0_30px_rgba\(6\,182\,212\,0\.1\)\]{--tw-shadow: 0 0 30px rgba(6,182,212,.4),0 0 40px rgba(6,182,212,.25),0 4px 16px rgba(0,0,0,.3),inset 0 0 30px rgba(6,182,212,.1);--tw-shadow-colored: 0 0 30px var(--tw-shadow-color), 0 0 40px var(--tw-shadow-color), 0 4px 16px var(--tw-shadow-color), inset 0 0 30px 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-\[0_0_6px_currentColor\]{--tw-shadow: 0 0 6px currentColor;--tw-shadow-colored: 0 0 6px 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-\[0_0_8px_rgba\(99\,102\,241\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 0 8px rgba(99,102,241,.2),inset 0 1px 1px rgba(255,255,255,.1);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_2px_8px_rgba\(0\,0\,0\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 2px 8px rgba(0,0,0,.2),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_2px_8px_rgba\(0\,0\,0\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 2px 8px rgba(0,0,0,.2),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_2px_8px_rgba\(0\,0\,0\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 2px 8px rgba(0,0,0,.2),inset 0 1px 1px rgba(255,255,255,.1);--tw-shadow-colored: 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_4px_16px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(10\,186\,181\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.25),0 0 15px rgba(10,186,181,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color), inset 0 1px 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)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(16\,185\,129\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.25),0 0 15px rgba(16,185,129,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color), inset 0 1px 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)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,0_0_8px_rgba\(15\,123\,255\,0\.1\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\,inset_0_0_20px_rgba\(15\,123\,255\,0\.03\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.3),0 0 8px rgba(15,123,255,.1),inset 0 1px 2px rgba(255,255,255,.1),inset 0 0 20px rgba(15,123,255,.03);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 0 8px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color), inset 0 0 20px 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-\[0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,0_1px_4px_rgba\(15\,123\,255\,0\.1\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.3),0 1px 4px rgba(15,123,255,.1),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 1px 4px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.05\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.3),inset 0 1px 1px rgba(255,255,255,.05);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.35\)\,0_2px_8px_rgba\(15\,123\,255\,0\.1\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.35),0 2px 8px rgba(15,123,255,.1),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(147\,51\,234\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(147,51,234,.12),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(15\,123\,255\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(15,123,255,.12),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(15\,123\,255\,0\.15\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(15,123,255,.15),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(16\,185\,129\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(16,185,129,.12),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(6\,182\,212\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(6,182,212,.12),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px 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-\[inset_0_1px_2px_rgba\(255\,255\,255\,0\.05\)\]{--tw-shadow: inset 0 1px 2px rgba(255,255,255,.05);--tw-shadow-colored: inset 0 1px 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)}.shadow-\[inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: inset 0 1px 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)}.shadow-\[inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]{--tw-shadow: inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: inset 0 2px 8px 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-glow{--tw-shadow: 0 0 25px rgba(33, 240, 255, .35);--tw-shadow-colored: 0 0 25px 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-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-neon{--tw-shadow: 0 0 15px rgba(15, 123, 255, .45);--tw-shadow-colored: 0 0 15px 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-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-1{--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(1px + 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)}.ring-inset{--tw-ring-inset: inset}.ring-white\/20{--tw-ring-color: rgb(255 255 255 / .2)}.ring-white\/5{--tw-ring-color: rgb(255 255 255 / .05)}.blur{--tw-blur: blur(8px);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)}.blur-xl{--tw-blur: blur(24px);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)}.drop-shadow{--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / .1)) drop-shadow(0 1px 1px rgb(0 0 0 / .06));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)}.drop-shadow-\[0_0_20px_rgba\(6\,182\,212\,0\.3\)\]{--tw-drop-shadow: drop-shadow(0 0 20px rgba(6,182,212,.3));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)}.drop-shadow-\[0_0_8px_rgba\(147\,51\,234\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(147,51,234,.5));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)}.drop-shadow-\[0_0_8px_rgba\(16\,185\,129\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(16,185,129,.5));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)}.drop-shadow-\[0_0_8px_rgba\(255\,255\,255\,0\.3\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(255,255,255,.3));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)}.drop-shadow-\[0_0_8px_rgba\(6\,182\,212\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(6,182,212,.5));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)}.drop-shadow-\[0_1px_4px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 1px 4px rgba(0,0,0,.5));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)}.drop-shadow-\[0_2px_12px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 2px 12px rgba(0,0,0,.5));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)}.drop-shadow-\[0_2px_4px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 2px 4px rgba(0,0,0,.5));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)}.drop-shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.6\)\]{--tw-drop-shadow: drop-shadow(0 2px 8px rgba(0,0,0,.6));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)}.grayscale{--tw-grayscale: grayscale(100%);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)}.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)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-md{--tw-backdrop-blur: blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-xl{--tw-backdrop-blur: blur(24px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.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-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}:root{font-family:Inter,system-ui,IBM Plex Sans,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:dark;color:#f7faffeb;background-color:#050816;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;min-width:320px;min-height:100vh;background:radial-gradient(circle at 15% 20%,rgba(100,150,255,.15),transparent 35%),radial-gradient(circle at 85% 15%,rgba(150,100,255,.12),transparent 40%),radial-gradient(circle at 50% 90%,rgba(33,240,255,.08),transparent 45%),radial-gradient(ellipse at 70% 60%,rgba(80,120,200,.06),transparent 50%),linear-gradient(to bottom,#000814,#001a33,#000a1a);overflow:hidden}#root{width:100vw;height:100vh}.high-contrast body,body.high-contrast{background:#000;color:#fff}.high-contrast *,body.high-contrast *{outline-offset:2px}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:linear-gradient(to right,#0000004d,#0a142340);border-radius:6px;box-shadow:inset 0 0 6px #0006}::-webkit-scrollbar-thumb{background:linear-gradient(135deg,#06b6d440,#0f7bff38,#9333ea33);border-radius:6px;border:1px solid rgba(6,182,212,.15);box-shadow:0 0 4px #06b6d426,inset 0 1px 1px #ffffff14,inset 0 -1px 1px #0000004d}::-webkit-scrollbar-thumb:hover{background:linear-gradient(135deg,#06b6d466,#0f7bff59,#9333ea4d);box-shadow:0 0 8px #06b6d440,0 0 12px #0f7bff26,inset 0 1px 1px #ffffff1f,inset 0 -1px 1px #0000004d}::-webkit-scrollbar-thumb:active{background:linear-gradient(135deg,#06b6d480,#0f7bff73,#9333ea66);box-shadow:0 0 10px #06b6d44d,0 0 16px #0f7bff33,inset 0 1px 2px #0006}.galaxy-bg{position:relative;background:radial-gradient(ellipse at 30% 20%,rgba(70,120,200,.08),transparent 60%),radial-gradient(ellipse at 80% 70%,rgba(120,80,200,.06),transparent 55%),linear-gradient(135deg,#0a1628,#0f2847 45%,#152e52)}.galaxy-bg:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:radial-gradient(circle at 25% 35%,rgba(100,150,255,.03),transparent 45%),radial-gradient(circle at 75% 65%,rgba(200,100,255,.02),transparent 40%);pointer-events:none}.glow-text{text-shadow:0 0 10px rgba(0,212,255,.5),0 0 20px rgba(123,44,191,.3)}.glow-border{border:1px solid rgba(0,212,255,.3);box-shadow:0 0 10px #00d4ff33,inset 0 0 10px #00d4ff1a}.frosted-panel{background:linear-gradient(140deg,#0b182cd9,#121220e6);border:1px solid rgba(33,240,255,.08);box-shadow:0 12px 30px #03070f73,inset 0 0 0 1px #93c5fd05}.glass-card{background:linear-gradient(165deg,#08192db8,#0a0d1ec7);border:1px solid rgba(15,123,255,.12);box-shadow:0 10px 35px #020a1899}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.animate-fade-in{animation:fadeIn .5s ease-out}@keyframes slideInLeft{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}.animate-slide-in-left{animation:slideInLeft .3s ease-out}.animate-slide-in-right{animation:slideInRight .3s ease-out}.star-static{position:absolute;border-radius:50%;will-change:transform;transform:translateZ(0)}.star-static[data-color=white]{background:radial-gradient(circle,rgba(240,245,255,1) 0%,rgba(200,220,255,.9) 20%,rgba(180,200,240,.4) 50%,transparent 100%);box-shadow:0 0 2px #f0f5ff,0 0 4px #c8dcffcc,0 0 8px #b4c8f080,0 0 12px #a0b4dc40}.star-static[data-color=blue]{background:radial-gradient(circle,rgba(220,235,255,1) 0%,rgba(180,210,255,.85) 20%,rgba(140,180,255,.4) 50%,transparent 100%);box-shadow:0 0 2px #dcebff,0 0 5px #b4d2ffb3,0 0 10px #8cb4ff66,0 0 15px #6496ff33}.star-static[data-color=yellow]{background:radial-gradient(circle,rgba(255,250,230,1) 0%,rgba(255,240,200,.9) 20%,rgba(255,220,150,.4) 50%,transparent 100%);box-shadow:0 0 2px #fffae6,0 0 4px #fff0c8cc,0 0 8px #ffdc9680,0 0 12px #ffc86440}.star-static[data-color=orange]{background:radial-gradient(circle,rgba(255,220,180,1) 0%,rgba(255,200,140,.9) 20%,rgba(255,180,100,.4) 50%,transparent 100%);box-shadow:0 0 2px #ffdcb4,0 0 4px #ffc88cbf,0 0 8px #ffb46473,0 0 12px #ffa05038}.star-static[data-color=red]{background:radial-gradient(circle,rgba(255,200,180,1) 0%,rgba(255,160,140,.9) 20%,rgba(255,120,100,.4) 50%,transparent 100%);box-shadow:0 0 2px #ffc8b4,0 0 4px #ffa08cb3,0 0 8px #ff786466,0 0 12px #ff503c33}.shooting-star-static{position:absolute;height:1.5px;background:linear-gradient(90deg,#dcebfff2,#c8dcffcc 25%,#b4c8ff66 60%,#c8dcff00);transform:rotate(15deg);box-shadow:0 0 8px #c8dcff99,0 0 4px #dcebff66}.noise-overlay:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.08'/%3E%3C/svg%3E");pointer-events:none;mix-blend-mode:screen}.compose-area-shadow{box-shadow:0 -12px 30px #050c1999}.react-flow__edge-path{stroke-linecap:round;stroke-linejoin:round;transition:stroke-width .3s ease,filter .3s ease}.react-flow__edge.animated .react-flow__edge-path{stroke-dasharray:8 4;animation:edgeFlow 1.5s linear infinite,edgePulse 2s ease-in-out infinite}@keyframes edgeFlow{0%{stroke-dashoffset:12}to{stroke-dashoffset:0}}@keyframes edgePulse{0%,to{opacity:.7}50%{opacity:1}}.react-flow__arrowhead polyline{stroke-linejoin:round;stroke-linecap:round}.react-flow__edge.selected .react-flow__edge-path{stroke-width:3px!important;filter:brightness(1.3) drop-shadow(0 0 6px currentColor)!important}.placeholder\:text-slate-500::-moz-placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.placeholder\:text-slate-500::placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.focus-within\:border-white\/15:focus-within{border-color:#ffffff26}.focus-within\:shadow-\[0_0_8px_rgba\(15\,123\,255\,0\.08\)\,inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]:focus-within{--tw-shadow: 0 0 8px rgba(15,123,255,.08),inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus-within\:shadow-\[0_0_8px_rgba\(16\,185\,129\,0\.08\)\,inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]:focus-within{--tw-shadow: 0 0 8px rgba(16,185,129,.08),inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:translate-y-\[-2px\]:hover{--tw-translate-y: -2px;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))}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;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))}.hover\:border-\[rgba\(10\,186\,181\,0\.6\)\]:hover{border-color:#0abab599}.hover\:border-amber-400\/40:hover{border-color:#fbbf2466}.hover\:border-emerald-400\/40:hover{border-color:#34d39966}.hover\:border-emerald-400\/60:hover{border-color:#34d39999}.hover\:border-purple-400\/40:hover{border-color:#c084fc66}.hover\:border-rose-800\/50:hover{border-color:#9f123980}.hover\:border-white\/20:hover{border-color:#fff3}.hover\:border-white\/25:hover{border-color:#ffffff40}.hover\:border-white\/30:hover{border-color:#ffffff4d}.hover\:border-white\/35:hover{border-color:#ffffff59}.hover\:bg-black\/40:hover{background-color:#0006}.hover\:bg-cyan-500\/10:hover{background-color:#06b6d41a}.hover\:bg-cyan-500\/40:hover{background-color:#06b6d466}.hover\:bg-emerald-500\/30:hover{background-color:#10b9814d}.hover\:bg-slate-800:hover{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.hover\:bg-white\/10:hover{background-color:#ffffff1a}.hover\:bg-white\/5:hover{background-color:#ffffff0d}.hover\:from-\[rgba\(10\,186\,181\,0\.25\)\]:hover{--tw-gradient-from: rgba(10,186,181,.25) var(--tw-gradient-from-position);--tw-gradient-to: rgba(10, 186, 181, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-\[rgba\(100\,25\,35\,0\.85\)\]:hover{--tw-gradient-from: rgba(100,25,35,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(100, 25, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-\[rgba\(28\,45\,65\,0\.85\)\]:hover{--tw-gradient-from: rgba(28,45,65,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(28, 45, 65, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-\[rgba\(6\,182\,212\,0\.95\)\]:hover{--tw-gradient-from: rgba(6,182,212,.95) var(--tw-gradient-from-position);--tw-gradient-to: rgba(6, 182, 212, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-cyan-500\/30:hover{--tw-gradient-from: rgb(6 182 212 / .3) var(--tw-gradient-from-position);--tw-gradient-to: rgb(6 182 212 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-emerald-500\/25:hover{--tw-gradient-from: rgb(16 185 129 / .25) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:via-\[rgba\(120\,30\,40\,0\.80\)\]:hover{--tw-gradient-to: rgba(120, 30, 40, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(120,30,40,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.hover\:via-\[rgba\(147\,51\,234\,0\.90\)\]:hover{--tw-gradient-to: rgba(147, 51, 234, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(147,51,234,.9) var(--tw-gradient-via-position), var(--tw-gradient-to)}.hover\:via-\[rgba\(23\,38\,56\,0\.8\)\]:hover{--tw-gradient-to: rgba(23, 38, 56, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(23,38,56,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.hover\:to-\[rgba\(100\,25\,35\,0\.85\)\]:hover{--tw-gradient-to: rgba(100,25,35,.85) var(--tw-gradient-to-position)}.hover\:to-\[rgba\(18\,30\,48\,0\.85\)\]:hover{--tw-gradient-to: rgba(18,30,48,.85) var(--tw-gradient-to-position)}.hover\:to-\[rgba\(236\,72\,153\,0\.95\)\]:hover{--tw-gradient-to: rgba(236,72,153,.95) var(--tw-gradient-to-position)}.hover\:to-\[rgba\(6\,182\,212\,0\.25\)\]:hover{--tw-gradient-to: rgba(6,182,212,.25) var(--tw-gradient-to-position)}.hover\:to-blue-600\/25:hover{--tw-gradient-to: rgb(37 99 235 / .25) var(--tw-gradient-to-position)}.hover\:to-cyan-500\/25:hover{--tw-gradient-to: rgb(6 182 212 / .25) var(--tw-gradient-to-position)}.hover\:text-cyan-300:hover{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity, 1))}.hover\:text-rose-400:hover{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.hover\:text-slate-200:hover{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:shadow-\[0_0_10px_rgba\(15\,123\,255\,0\.15\)\]:hover{--tw-shadow: 0 0 10px rgba(15,123,255,.15);--tw-shadow-colored: 0 0 10px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_0_20px_rgba\(6\,182\,212\,0\.3\)\]:hover{--tw-shadow: 0 0 20px rgba(6,182,212,.3);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(15\,123\,255\,0\.15\)\]:hover{--tw-shadow: 0 4px 12px rgba(0,0,0,.25),0 0 15px rgba(15,123,255,.15);--tw-shadow-colored: 0 4px 12px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(16\,185\,129\,0\.2\)\]:hover{--tw-shadow: 0 4px 12px rgba(0,0,0,.25),0 0 15px rgba(16,185,129,.2);--tw-shadow-colored: 0 4px 12px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(245\,158\,11\,0\.2\)\]:hover{--tw-shadow: 0 4px 12px rgba(0,0,0,.25),0 0 15px rgba(245,158,11,.2);--tw-shadow-colored: 0 4px 12px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.3\)\,0_0_25px_rgba\(10\,186\,181\,0\.3\)\]:hover{--tw-shadow: 0 8px 24px rgba(0,0,0,.3),0 0 25px rgba(10,186,181,.3);--tw-shadow-colored: 0 8px 24px var(--tw-shadow-color), 0 0 25px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.3\)\,0_0_25px_rgba\(16\,185\,129\,0\.3\)\]:hover{--tw-shadow: 0 8px 24px rgba(0,0,0,.3),0 0 25px rgba(16,185,129,.3);--tw-shadow-colored: 0 8px 24px var(--tw-shadow-color), 0 0 25px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.35\)\,0_0_20px_rgba\(15\,123\,255\,0\.2\)\,0_0_30px_rgba\(6\,182\,212\,0\.15\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.15\)\,inset_0_0_30px_rgba\(15\,123\,255\,0\.06\)\]:hover{--tw-shadow: 0 8px 24px rgba(0,0,0,.35),0 0 20px rgba(15,123,255,.2),0 0 30px rgba(6,182,212,.15),inset 0 1px 2px rgba(255,255,255,.15),inset 0 0 30px rgba(15,123,255,.06);--tw-shadow-colored: 0 8px 24px var(--tw-shadow-color), 0 0 20px var(--tw-shadow-color), 0 0 30px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color), inset 0 0 30px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:border-cyan-400:focus{--tw-border-opacity: 1;border-color:rgb(34 211 238 / var(--tw-border-opacity, 1))}.focus\:border-rose-400:focus{--tw-border-opacity: 1;border-color:rgb(251 113 133 / var(--tw-border-opacity, 1))}.focus\:border-white\/15:focus{border-color:#ffffff26}.focus\:shadow-\[0_0_8px_rgba\(15\,123\,255\,0\.08\)\,inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]:focus{--tw-shadow: 0 0 8px rgba(15,123,255,.08),inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1: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(1px + 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-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-cyan-400\/30:focus{--tw-ring-color: rgb(34 211 238 / .3)}.focus\:ring-rose-400\/30:focus{--tw-ring-color: rgb(251 113 133 / .3)}.focus\:ring-white\/10:focus{--tw-ring-color: rgb(255 255 255 / .1)}.active\:scale-95:active{--tw-scale-x: .95;--tw-scale-y: .95;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))}.active\:shadow-\[0_0_15px_rgba\(6\,182\,212\,0\.4\)\,0_2px_8px_rgba\(0\,0\,0\,0\.4\)\]:active{--tw-shadow: 0 0 15px rgba(6,182,212,.4),0 2px 8px rgba(0,0,0,.4);--tw-shadow-colored: 0 0 15px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:scale-110{--tw-scale-x: 1.1;--tw-scale-y: 1.1;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))}.group:hover .group-hover\:text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:drop-shadow-\[0_0_6px_rgba\(6\,182\,212\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 6px rgba(6,182,212,.5));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)}@media (min-width: 640px){.sm\:block{display:block}.sm\:h-16{height:4rem}.sm\:h-2\.5{height:.625rem}.sm\:w-16{width:4rem}.sm\:w-2\.5{width:.625rem}.sm\:w-\[74\%\]{width:74%}.sm\:w-\[calc\(74\%-3rem\)\]{width:calc(74% - 3rem)}.sm\:gap-4{gap:1rem}.sm\:px-5{padding-left:1.25rem;padding-right:1.25rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-\[11px\]{font-size:11px}.sm\:text-base{font-size:1rem;line-height:1.5rem}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}.sm\:text-xs{font-size:.75rem;line-height:1rem}}@media (min-width: 768px){.md\:inline{display:inline}}@media (min-width: 1024px){.lg\:ml-3{margin-left:.75rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:h-20{height:5rem}.lg\:w-20{width:5rem}.lg\:w-\[520px\]{width:520px}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:text-3xl{font-size:1.875rem;line-height:2.25rem}.lg\:text-lg{font-size:1.125rem;line-height:1.75rem}.lg\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media (min-width: 1280px){.xl\:flex{display:flex}.xl\:w-72{width:18rem}.xl\:w-\[560px\]{width:560px}}@media (min-width: 1536px){.\32xl\:w-80{width:20rem}.\32xl\:w-\[640px\]{width:640px}} diff --git a/galaxy/webui/frontend/dist/assets/index-CtX83iUy.js b/galaxy/webui/frontend/dist/assets/index-CtX83iUy.js new file mode 100644 index 000000000..2e5a469bf --- /dev/null +++ b/galaxy/webui/frontend/dist/assets/index-CtX83iUy.js @@ -0,0 +1,344 @@ +var X2=Object.defineProperty;var Q2=(e,t,n)=>t in e?X2(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var Cn=(e,t,n)=>Q2(e,typeof t!="symbol"?t+"":t,n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();var Ja=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Ul(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Xy={exports:{}},Wl={},Qy={exports:{}},ce={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Lo=Symbol.for("react.element"),Z2=Symbol.for("react.portal"),J2=Symbol.for("react.fragment"),eS=Symbol.for("react.strict_mode"),tS=Symbol.for("react.profiler"),nS=Symbol.for("react.provider"),rS=Symbol.for("react.context"),iS=Symbol.for("react.forward_ref"),sS=Symbol.for("react.suspense"),oS=Symbol.for("react.memo"),aS=Symbol.for("react.lazy"),zp=Symbol.iterator;function lS(e){return e===null||typeof e!="object"?null:(e=zp&&e[zp]||e["@@iterator"],typeof e=="function"?e:null)}var Zy={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Jy=Object.assign,ex={};function us(e,t,n){this.props=e,this.context=t,this.refs=ex,this.updater=n||Zy}us.prototype.isReactComponent={};us.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};us.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function tx(){}tx.prototype=us.prototype;function Md(e,t,n){this.props=e,this.context=t,this.refs=ex,this.updater=n||Zy}var Dd=Md.prototype=new tx;Dd.constructor=Md;Jy(Dd,us.prototype);Dd.isPureReactComponent=!0;var Fp=Array.isArray,nx=Object.prototype.hasOwnProperty,Id={current:null},rx={key:!0,ref:!0,__self:!0,__source:!0};function ix(e,t,n){var r,i={},s=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(s=""+t.key),t)nx.call(t,r)&&!rx.hasOwnProperty(r)&&(i[r]=t[r]);var a=arguments.length-2;if(a===1)i.children=n;else if(1>>1,H=z[F];if(0>>1;Fi(X,b))Gi(ne,X)?(z[F]=ne,z[G]=b,F=G):(z[F]=X,z[q]=b,F=q);else if(Gi(ne,b))z[F]=ne,z[G]=b,F=G;else break e}}return j}function i(z,j){var b=z.sortIndex-j.sortIndex;return b!==0?b:z.id-j.id}if(typeof performance=="object"&&typeof performance.now=="function"){var s=performance;e.unstable_now=function(){return s.now()}}else{var o=Date,a=o.now();e.unstable_now=function(){return o.now()-a}}var l=[],u=[],c=1,f=null,d=3,h=!1,g=!1,y=!1,w=typeof setTimeout=="function"?setTimeout:null,m=typeof clearTimeout=="function"?clearTimeout:null,x=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function v(z){for(var j=n(u);j!==null;){if(j.callback===null)r(u);else if(j.startTime<=z)r(u),j.sortIndex=j.expirationTime,t(l,j);else break;j=n(u)}}function k(z){if(y=!1,v(z),!g)if(n(l)!==null)g=!0,I(N);else{var j=n(u);j!==null&&V(k,j.startTime-z)}}function N(z,j){g=!1,y&&(y=!1,m(P),P=-1),h=!0;var b=d;try{for(v(j),f=n(l);f!==null&&(!(f.expirationTime>j)||z&&!L());){var F=f.callback;if(typeof F=="function"){f.callback=null,d=f.priorityLevel;var H=F(f.expirationTime<=j);j=e.unstable_now(),typeof H=="function"?f.callback=H:f===n(l)&&r(l),v(j)}else r(l);f=n(l)}if(f!==null)var E=!0;else{var q=n(u);q!==null&&V(k,q.startTime-j),E=!1}return E}finally{f=null,d=b,h=!1}}var S=!1,A=null,P=-1,D=5,C=-1;function L(){return!(e.unstable_now()-Cz||125F?(z.sortIndex=b,t(u,z),n(l)===null&&z===n(u)&&(y?(m(P),P=-1):y=!0,V(k,b-F))):(z.sortIndex=H,t(l,z),g||h||(g=!0,I(N))),z},e.unstable_shouldYield=L,e.unstable_wrapCallback=function(z){var j=d;return function(){var b=d;d=j;try{return z.apply(this,arguments)}finally{d=b}}}})(ux);lx.exports=ux;var vS=lx.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var wS=T,Rt=vS;function U(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Kc=Object.prototype.hasOwnProperty,kS=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Vp={},$p={};function bS(e){return Kc.call($p,e)?!0:Kc.call(Vp,e)?!1:kS.test(e)?$p[e]=!0:(Vp[e]=!0,!1)}function SS(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function _S(e,t,n,r){if(t===null||typeof t>"u"||SS(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function mt(e,t,n,r,i,s,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=i,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=s,this.removeEmptyString=o}var Je={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Je[e]=new mt(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Je[t]=new mt(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Je[e]=new mt(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Je[e]=new mt(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Je[e]=new mt(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Je[e]=new mt(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Je[e]=new mt(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Je[e]=new mt(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Je[e]=new mt(e,5,!1,e.toLowerCase(),null,!1,!1)});var Rd=/[\-:]([a-z])/g;function zd(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Rd,zd);Je[t]=new mt(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Rd,zd);Je[t]=new mt(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Rd,zd);Je[t]=new mt(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Je[e]=new mt(e,1,!1,e.toLowerCase(),null,!1,!1)});Je.xlinkHref=new mt("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Je[e]=new mt(e,1,!1,e.toLowerCase(),null,!0,!0)});function Fd(e,t,n,r){var i=Je.hasOwnProperty(t)?Je[t]:null;(i!==null?i.type!==0:r||!(2a||i[o]!==s[a]){var l=` +`+i[o].replace(" at new "," at ");return e.displayName&&l.includes("")&&(l=l.replace("",e.displayName)),l}while(1<=o&&0<=a);break}}}finally{Lu=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Ms(e):""}function CS(e){switch(e.tag){case 5:return Ms(e.type);case 16:return Ms("Lazy");case 13:return Ms("Suspense");case 19:return Ms("SuspenseList");case 0:case 2:case 15:return e=Ru(e.type,!1),e;case 11:return e=Ru(e.type.render,!1),e;case 1:return e=Ru(e.type,!0),e;default:return""}}function Zc(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case xi:return"Fragment";case yi:return"Portal";case Gc:return"Profiler";case Od:return"StrictMode";case Xc:return"Suspense";case Qc:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case dx:return(e.displayName||"Context")+".Consumer";case fx:return(e._context.displayName||"Context")+".Provider";case Vd:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case $d:return t=e.displayName||null,t!==null?t:Zc(e.type)||"Memo";case Zn:t=e._payload,e=e._init;try{return Zc(e(t))}catch{}}return null}function ES(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Zc(t);case 8:return t===Od?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function vr(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function px(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function NS(e){var t=px(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var i=n.get,s=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return i.call(this)},set:function(o){r=""+o,s.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function ea(e){e._valueTracker||(e._valueTracker=NS(e))}function mx(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=px(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function el(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Jc(e,t){var n=t.checked;return Me({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Hp(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=vr(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function gx(e,t){t=t.checked,t!=null&&Fd(e,"checked",t,!1)}function ef(e,t){gx(e,t);var n=vr(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?tf(e,t.type,n):t.hasOwnProperty("defaultValue")&&tf(e,t.type,vr(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Up(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function tf(e,t,n){(t!=="number"||el(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Ds=Array.isArray;function Ii(e,t,n,r){if(e=e.options,t){t={};for(var i=0;i"+t.valueOf().toString()+"",t=ta.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function so(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Vs={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},TS=["Webkit","ms","Moz","O"];Object.keys(Vs).forEach(function(e){TS.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Vs[t]=Vs[e]})});function wx(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Vs.hasOwnProperty(e)&&Vs[e]?(""+t).trim():t+"px"}function kx(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,i=wx(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,i):e[n]=i}}var AS=Me({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function sf(e,t){if(t){if(AS[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(U(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(U(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(U(61))}if(t.style!=null&&typeof t.style!="object")throw Error(U(62))}}function of(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var af=null;function Bd(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var lf=null,Li=null,Ri=null;function qp(e){if(e=Fo(e)){if(typeof lf!="function")throw Error(U(280));var t=e.stateNode;t&&(t=Xl(t),lf(e.stateNode,e.type,t))}}function bx(e){Li?Ri?Ri.push(e):Ri=[e]:Li=e}function Sx(){if(Li){var e=Li,t=Ri;if(Ri=Li=null,qp(e),t)for(e=0;e>>=0,e===0?32:31-(VS(e)/$S|0)|0}var na=64,ra=4194304;function Is(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function il(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,i=e.suspendedLanes,s=e.pingedLanes,o=n&268435455;if(o!==0){var a=o&~i;a!==0?r=Is(a):(s&=o,s!==0&&(r=Is(s)))}else o=n&~i,o!==0?r=Is(o):s!==0&&(r=Is(s));if(r===0)return 0;if(t!==0&&t!==r&&!(t&i)&&(i=r&-r,s=t&-t,i>=s||i===16&&(s&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Ro(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-on(t),e[t]=n}function WS(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Bs),nm=" ",rm=!1;function Hx(e,t){switch(e){case"keyup":return v_.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ux(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var vi=!1;function k_(e,t){switch(e){case"compositionend":return Ux(t);case"keypress":return t.which!==32?null:(rm=!0,nm);case"textInput":return e=t.data,e===nm&&rm?null:e;default:return null}}function b_(e,t){if(vi)return e==="compositionend"||!Xd&&Hx(e,t)?(e=$x(),Ra=qd=or=null,vi=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=am(n)}}function Kx(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Kx(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Gx(){for(var e=window,t=el();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=el(e.document)}return t}function Qd(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function j_(e){var t=Gx(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Kx(n.ownerDocument.documentElement,n)){if(r!==null&&Qd(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var i=n.textContent.length,s=Math.min(r.start,i);r=r.end===void 0?s:Math.min(r.end,i),!e.extend&&s>r&&(i=r,r=s,s=i),i=lm(n,s);var o=lm(n,r);i&&o&&(e.rangeCount!==1||e.anchorNode!==i.node||e.anchorOffset!==i.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(i.node,i.offset),e.removeAllRanges(),s>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,wi=null,pf=null,Us=null,mf=!1;function um(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;mf||wi==null||wi!==el(r)||(r=wi,"selectionStart"in r&&Qd(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Us&&fo(Us,r)||(Us=r,r=al(pf,"onSelect"),0Si||(e.current=kf[Si],kf[Si]=null,Si--)}function ke(e,t){Si++,kf[Si]=e.current,e.current=t}var wr={},ot=_r(wr),bt=_r(!1),Qr=wr;function Ki(e,t){var n=e.type.contextTypes;if(!n)return wr;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var i={},s;for(s in n)i[s]=t[s];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=i),i}function St(e){return e=e.childContextTypes,e!=null}function ul(){_e(bt),_e(ot)}function gm(e,t,n){if(ot.current!==wr)throw Error(U(168));ke(ot,t),ke(bt,n)}function i1(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var i in r)if(!(i in t))throw Error(U(108,ES(e)||"Unknown",i));return Me({},n,r)}function cl(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||wr,Qr=ot.current,ke(ot,e),ke(bt,bt.current),!0}function ym(e,t,n){var r=e.stateNode;if(!r)throw Error(U(169));n?(e=i1(e,t,Qr),r.__reactInternalMemoizedMergedChildContext=e,_e(bt),_e(ot),ke(ot,e)):_e(bt),ke(bt,n)}var Tn=null,Ql=!1,Xu=!1;function s1(e){Tn===null?Tn=[e]:Tn.push(e)}function H_(e){Ql=!0,s1(e)}function Cr(){if(!Xu&&Tn!==null){Xu=!0;var e=0,t=ge;try{var n=Tn;for(ge=1;e>=o,i-=o,An=1<<32-on(t)+i|n<P?(D=A,A=null):D=A.sibling;var C=d(m,A,v[P],k);if(C===null){A===null&&(A=D);break}e&&A&&C.alternate===null&&t(m,A),x=s(C,x,P),S===null?N=C:S.sibling=C,S=C,A=D}if(P===v.length)return n(m,A),Ne&&Dr(m,P),N;if(A===null){for(;PP?(D=A,A=null):D=A.sibling;var L=d(m,A,C.value,k);if(L===null){A===null&&(A=D);break}e&&A&&L.alternate===null&&t(m,A),x=s(L,x,P),S===null?N=L:S.sibling=L,S=L,A=D}if(C.done)return n(m,A),Ne&&Dr(m,P),N;if(A===null){for(;!C.done;P++,C=v.next())C=f(m,C.value,k),C!==null&&(x=s(C,x,P),S===null?N=C:S.sibling=C,S=C);return Ne&&Dr(m,P),N}for(A=r(m,A);!C.done;P++,C=v.next())C=h(A,m,P,C.value,k),C!==null&&(e&&C.alternate!==null&&A.delete(C.key===null?P:C.key),x=s(C,x,P),S===null?N=C:S.sibling=C,S=C);return e&&A.forEach(function(M){return t(m,M)}),Ne&&Dr(m,P),N}function w(m,x,v,k){if(typeof v=="object"&&v!==null&&v.type===xi&&v.key===null&&(v=v.props.children),typeof v=="object"&&v!==null){switch(v.$$typeof){case Jo:e:{for(var N=v.key,S=x;S!==null;){if(S.key===N){if(N=v.type,N===xi){if(S.tag===7){n(m,S.sibling),x=i(S,v.props.children),x.return=m,m=x;break e}}else if(S.elementType===N||typeof N=="object"&&N!==null&&N.$$typeof===Zn&&wm(N)===S.type){n(m,S.sibling),x=i(S,v.props),x.ref=ws(m,S,v),x.return=m,m=x;break e}n(m,S);break}else t(m,S);S=S.sibling}v.type===xi?(x=qr(v.props.children,m.mode,k,v.key),x.return=m,m=x):(k=Ua(v.type,v.key,v.props,null,m.mode,k),k.ref=ws(m,x,v),k.return=m,m=k)}return o(m);case yi:e:{for(S=v.key;x!==null;){if(x.key===S)if(x.tag===4&&x.stateNode.containerInfo===v.containerInfo&&x.stateNode.implementation===v.implementation){n(m,x.sibling),x=i(x,v.children||[]),x.return=m,m=x;break e}else{n(m,x);break}else t(m,x);x=x.sibling}x=ic(v,m.mode,k),x.return=m,m=x}return o(m);case Zn:return S=v._init,w(m,x,S(v._payload),k)}if(Ds(v))return g(m,x,v,k);if(ms(v))return y(m,x,v,k);ca(m,v)}return typeof v=="string"&&v!==""||typeof v=="number"?(v=""+v,x!==null&&x.tag===6?(n(m,x.sibling),x=i(x,v),x.return=m,m=x):(n(m,x),x=rc(v,m.mode,k),x.return=m,m=x),o(m)):n(m,x)}return w}var Xi=u1(!0),c1=u1(!1),hl=_r(null),pl=null,Ei=null,th=null;function nh(){th=Ei=pl=null}function rh(e){var t=hl.current;_e(hl),e._currentValue=t}function _f(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Fi(e,t){pl=e,th=Ei=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(wt=!0),e.firstContext=null)}function Gt(e){var t=e._currentValue;if(th!==e)if(e={context:e,memoizedValue:t,next:null},Ei===null){if(pl===null)throw Error(U(308));Ei=e,pl.dependencies={lanes:0,firstContext:e}}else Ei=Ei.next=e;return t}var $r=null;function ih(e){$r===null?$r=[e]:$r.push(e)}function f1(e,t,n,r){var i=t.interleaved;return i===null?(n.next=n,ih(t)):(n.next=i.next,i.next=n),t.interleaved=n,Fn(e,r)}function Fn(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Jn=!1;function sh(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function d1(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Dn(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function dr(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,pe&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,Fn(e,n)}return i=r.interleaved,i===null?(t.next=t,ih(r)):(t.next=i.next,i.next=t),r.interleaved=t,Fn(e,n)}function Fa(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ud(e,n)}}function km(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,s=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};s===null?i=s=o:s=s.next=o,n=n.next}while(n!==null);s===null?i=s=t:s=s.next=t}else i=s=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:s,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ml(e,t,n,r){var i=e.updateQueue;Jn=!1;var s=i.firstBaseUpdate,o=i.lastBaseUpdate,a=i.shared.pending;if(a!==null){i.shared.pending=null;var l=a,u=l.next;l.next=null,o===null?s=u:o.next=u,o=l;var c=e.alternate;c!==null&&(c=c.updateQueue,a=c.lastBaseUpdate,a!==o&&(a===null?c.firstBaseUpdate=u:a.next=u,c.lastBaseUpdate=l))}if(s!==null){var f=i.baseState;o=0,c=u=l=null,a=s;do{var d=a.lane,h=a.eventTime;if((r&d)===d){c!==null&&(c=c.next={eventTime:h,lane:0,tag:a.tag,payload:a.payload,callback:a.callback,next:null});e:{var g=e,y=a;switch(d=t,h=n,y.tag){case 1:if(g=y.payload,typeof g=="function"){f=g.call(h,f,d);break e}f=g;break e;case 3:g.flags=g.flags&-65537|128;case 0:if(g=y.payload,d=typeof g=="function"?g.call(h,f,d):g,d==null)break e;f=Me({},f,d);break e;case 2:Jn=!0}}a.callback!==null&&a.lane!==0&&(e.flags|=64,d=i.effects,d===null?i.effects=[a]:d.push(a))}else h={eventTime:h,lane:d,tag:a.tag,payload:a.payload,callback:a.callback,next:null},c===null?(u=c=h,l=f):c=c.next=h,o|=d;if(a=a.next,a===null){if(a=i.shared.pending,a===null)break;d=a,a=d.next,d.next=null,i.lastBaseUpdate=d,i.shared.pending=null}}while(!0);if(c===null&&(l=f),i.baseState=l,i.firstBaseUpdate=u,i.lastBaseUpdate=c,t=i.shared.interleaved,t!==null){i=t;do o|=i.lane,i=i.next;while(i!==t)}else s===null&&(i.shared.lanes=0);ei|=o,e.lanes=o,e.memoizedState=f}}function bm(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Zu.transition;Zu.transition={};try{e(!1),t()}finally{ge=n,Zu.transition=r}}function A1(){return Xt().memoizedState}function q_(e,t,n){var r=pr(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},P1(e))j1(t,n);else if(n=f1(e,t,n,r),n!==null){var i=ht();an(n,e,r,i),M1(n,t,r)}}function K_(e,t,n){var r=pr(e),i={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(P1(e))j1(t,i);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var o=t.lastRenderedState,a=s(o,n);if(i.hasEagerState=!0,i.eagerState=a,cn(a,o)){var l=t.interleaved;l===null?(i.next=i,ih(t)):(i.next=l.next,l.next=i),t.interleaved=i;return}}catch{}finally{}n=f1(e,t,i,r),n!==null&&(i=ht(),an(n,e,r,i),M1(n,t,r))}}function P1(e){var t=e.alternate;return e===je||t!==null&&t===je}function j1(e,t){Ws=yl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function M1(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ud(e,n)}}var xl={readContext:Gt,useCallback:tt,useContext:tt,useEffect:tt,useImperativeHandle:tt,useInsertionEffect:tt,useLayoutEffect:tt,useMemo:tt,useReducer:tt,useRef:tt,useState:tt,useDebugValue:tt,useDeferredValue:tt,useTransition:tt,useMutableSource:tt,useSyncExternalStore:tt,useId:tt,unstable_isNewReconciler:!1},G_={readContext:Gt,useCallback:function(e,t){return mn().memoizedState=[e,t===void 0?null:t],e},useContext:Gt,useEffect:_m,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Va(4194308,4,_1.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Va(4194308,4,e,t)},useInsertionEffect:function(e,t){return Va(4,2,e,t)},useMemo:function(e,t){var n=mn();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=mn();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=q_.bind(null,je,e),[r.memoizedState,e]},useRef:function(e){var t=mn();return e={current:e},t.memoizedState=e},useState:Sm,useDebugValue:hh,useDeferredValue:function(e){return mn().memoizedState=e},useTransition:function(){var e=Sm(!1),t=e[0];return e=Y_.bind(null,e[1]),mn().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=je,i=mn();if(Ne){if(n===void 0)throw Error(U(407));n=n()}else{if(n=t(),qe===null)throw Error(U(349));Jr&30||g1(r,t,n)}i.memoizedState=n;var s={value:n,getSnapshot:t};return i.queue=s,_m(x1.bind(null,r,s,e),[e]),r.flags|=2048,wo(9,y1.bind(null,r,s,n,t),void 0,null),n},useId:function(){var e=mn(),t=qe.identifierPrefix;if(Ne){var n=Pn,r=An;n=(r&~(1<<32-on(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=xo++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[xn]=t,e[mo]=r,B1(e,t,!1,!1),t.stateNode=e;e:{switch(o=of(n,r),n){case"dialog":Se("cancel",e),Se("close",e),i=r;break;case"iframe":case"object":case"embed":Se("load",e),i=r;break;case"video":case"audio":for(i=0;iJi&&(t.flags|=128,r=!0,ks(s,!1),t.lanes=4194304)}else{if(!r)if(e=gl(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),ks(s,!0),s.tail===null&&s.tailMode==="hidden"&&!o.alternate&&!Ne)return nt(t),null}else 2*ze()-s.renderingStartTime>Ji&&n!==1073741824&&(t.flags|=128,r=!0,ks(s,!1),t.lanes=4194304);s.isBackwards?(o.sibling=t.child,t.child=o):(n=s.last,n!==null?n.sibling=o:t.child=o,s.last=o)}return s.tail!==null?(t=s.tail,s.rendering=t,s.tail=t.sibling,s.renderingStartTime=ze(),t.sibling=null,n=Ae.current,ke(Ae,r?n&1|2:n&1),t):(nt(t),null);case 22:case 23:return vh(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Mt&1073741824&&(nt(t),t.subtreeFlags&6&&(t.flags|=8192)):nt(t),null;case 24:return null;case 25:return null}throw Error(U(156,t.tag))}function rC(e,t){switch(Jd(t),t.tag){case 1:return St(t.type)&&ul(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Qi(),_e(bt),_e(ot),lh(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return ah(t),null;case 13:if(_e(Ae),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(U(340));Gi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return _e(Ae),null;case 4:return Qi(),null;case 10:return rh(t.type._context),null;case 22:case 23:return vh(),null;case 24:return null;default:return null}}var da=!1,it=!1,iC=typeof WeakSet=="function"?WeakSet:Set,K=null;function Ni(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Le(e,t,r)}else n.current=null}function Df(e,t,n){try{n()}catch(r){Le(e,t,r)}}var Lm=!1;function sC(e,t){if(gf=sl,e=Gx(),Qd(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,s=r.focusNode;r=r.focusOffset;try{n.nodeType,s.nodeType}catch{n=null;break e}var o=0,a=-1,l=-1,u=0,c=0,f=e,d=null;t:for(;;){for(var h;f!==n||i!==0&&f.nodeType!==3||(a=o+i),f!==s||r!==0&&f.nodeType!==3||(l=o+r),f.nodeType===3&&(o+=f.nodeValue.length),(h=f.firstChild)!==null;)d=f,f=h;for(;;){if(f===e)break t;if(d===n&&++u===i&&(a=o),d===s&&++c===r&&(l=o),(h=f.nextSibling)!==null)break;f=d,d=f.parentNode}f=h}n=a===-1||l===-1?null:{start:a,end:l}}else n=null}n=n||{start:0,end:0}}else n=null;for(yf={focusedElem:e,selectionRange:n},sl=!1,K=t;K!==null;)if(t=K,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,K=e;else for(;K!==null;){t=K;try{var g=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(g!==null){var y=g.memoizedProps,w=g.memoizedState,m=t.stateNode,x=m.getSnapshotBeforeUpdate(t.elementType===t.type?y:Jt(t.type,y),w);m.__reactInternalSnapshotBeforeUpdate=x}break;case 3:var v=t.stateNode.containerInfo;v.nodeType===1?v.textContent="":v.nodeType===9&&v.documentElement&&v.removeChild(v.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(U(163))}}catch(k){Le(t,t.return,k)}if(e=t.sibling,e!==null){e.return=t.return,K=e;break}K=t.return}return g=Lm,Lm=!1,g}function Ys(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var i=r=r.next;do{if((i.tag&e)===e){var s=i.destroy;i.destroy=void 0,s!==void 0&&Df(t,n,s)}i=i.next}while(i!==r)}}function eu(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function If(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function W1(e){var t=e.alternate;t!==null&&(e.alternate=null,W1(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[xn],delete t[mo],delete t[wf],delete t[$_],delete t[B_])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Y1(e){return e.tag===5||e.tag===3||e.tag===4}function Rm(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Y1(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Lf(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ll));else if(r!==4&&(e=e.child,e!==null))for(Lf(e,t,n),e=e.sibling;e!==null;)Lf(e,t,n),e=e.sibling}function Rf(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Rf(e,t,n),e=e.sibling;e!==null;)Rf(e,t,n),e=e.sibling}var Qe=null,en=!1;function Kn(e,t,n){for(n=n.child;n!==null;)q1(e,t,n),n=n.sibling}function q1(e,t,n){if(vn&&typeof vn.onCommitFiberUnmount=="function")try{vn.onCommitFiberUnmount(Yl,n)}catch{}switch(n.tag){case 5:it||Ni(n,t);case 6:var r=Qe,i=en;Qe=null,Kn(e,t,n),Qe=r,en=i,Qe!==null&&(en?(e=Qe,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Qe.removeChild(n.stateNode));break;case 18:Qe!==null&&(en?(e=Qe,n=n.stateNode,e.nodeType===8?Gu(e.parentNode,n):e.nodeType===1&&Gu(e,n),uo(e)):Gu(Qe,n.stateNode));break;case 4:r=Qe,i=en,Qe=n.stateNode.containerInfo,en=!0,Kn(e,t,n),Qe=r,en=i;break;case 0:case 11:case 14:case 15:if(!it&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){i=r=r.next;do{var s=i,o=s.destroy;s=s.tag,o!==void 0&&(s&2||s&4)&&Df(n,t,o),i=i.next}while(i!==r)}Kn(e,t,n);break;case 1:if(!it&&(Ni(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(a){Le(n,t,a)}Kn(e,t,n);break;case 21:Kn(e,t,n);break;case 22:n.mode&1?(it=(r=it)||n.memoizedState!==null,Kn(e,t,n),it=r):Kn(e,t,n);break;default:Kn(e,t,n)}}function zm(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new iC),t.forEach(function(r){var i=pC.bind(null,e,r);n.has(r)||(n.add(r),r.then(i,i))})}}function Zt(e,t){var n=t.deletions;if(n!==null)for(var r=0;ri&&(i=o),r&=~s}if(r=i,r=ze()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*aC(r/1960))-r,10e?16:e,ar===null)var r=!1;else{if(e=ar,ar=null,kl=0,pe&6)throw Error(U(331));var i=pe;for(pe|=4,K=e.current;K!==null;){var s=K,o=s.child;if(K.flags&16){var a=s.deletions;if(a!==null){for(var l=0;lze()-yh?Yr(e,0):gh|=n),_t(e,t)}function tv(e,t){t===0&&(e.mode&1?(t=ra,ra<<=1,!(ra&130023424)&&(ra=4194304)):t=1);var n=ht();e=Fn(e,t),e!==null&&(Ro(e,t,n),_t(e,n))}function hC(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),tv(e,n)}function pC(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(U(314))}r!==null&&r.delete(t),tv(e,n)}var nv;nv=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||bt.current)wt=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return wt=!1,tC(e,t,n);wt=!!(e.flags&131072)}else wt=!1,Ne&&t.flags&1048576&&o1(t,dl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;$a(e,t),e=t.pendingProps;var i=Ki(t,ot.current);Fi(t,n),i=ch(null,t,r,e,i,n);var s=fh();return t.flags|=1,typeof i=="object"&&i!==null&&typeof i.render=="function"&&i.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,St(r)?(s=!0,cl(t)):s=!1,t.memoizedState=i.state!==null&&i.state!==void 0?i.state:null,sh(t),i.updater=Jl,t.stateNode=i,i._reactInternals=t,Ef(t,r,e,n),t=Af(null,t,r,!0,s,n)):(t.tag=0,Ne&&s&&Zd(t),ft(null,t,i,n),t=t.child),t;case 16:r=t.elementType;e:{switch($a(e,t),e=t.pendingProps,i=r._init,r=i(r._payload),t.type=r,i=t.tag=gC(r),e=Jt(r,e),i){case 0:t=Tf(null,t,r,e,n);break e;case 1:t=Mm(null,t,r,e,n);break e;case 11:t=Pm(null,t,r,e,n);break e;case 14:t=jm(null,t,r,Jt(r.type,e),n);break e}throw Error(U(306,r,""))}return t;case 0:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Jt(r,i),Tf(e,t,r,i,n);case 1:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Jt(r,i),Mm(e,t,r,i,n);case 3:e:{if(O1(t),e===null)throw Error(U(387));r=t.pendingProps,s=t.memoizedState,i=s.element,d1(e,t),ml(t,r,null,n);var o=t.memoizedState;if(r=o.element,s.isDehydrated)if(s={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){i=Zi(Error(U(423)),t),t=Dm(e,t,r,n,i);break e}else if(r!==i){i=Zi(Error(U(424)),t),t=Dm(e,t,r,n,i);break e}else for(Dt=fr(t.stateNode.containerInfo.firstChild),It=t,Ne=!0,tn=null,n=c1(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Gi(),r===i){t=On(e,t,n);break e}ft(e,t,r,n)}t=t.child}return t;case 5:return h1(t),e===null&&Sf(t),r=t.type,i=t.pendingProps,s=e!==null?e.memoizedProps:null,o=i.children,xf(r,i)?o=null:s!==null&&xf(r,s)&&(t.flags|=32),F1(e,t),ft(e,t,o,n),t.child;case 6:return e===null&&Sf(t),null;case 13:return V1(e,t,n);case 4:return oh(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Xi(t,null,r,n):ft(e,t,r,n),t.child;case 11:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Jt(r,i),Pm(e,t,r,i,n);case 7:return ft(e,t,t.pendingProps,n),t.child;case 8:return ft(e,t,t.pendingProps.children,n),t.child;case 12:return ft(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,i=t.pendingProps,s=t.memoizedProps,o=i.value,ke(hl,r._currentValue),r._currentValue=o,s!==null)if(cn(s.value,o)){if(s.children===i.children&&!bt.current){t=On(e,t,n);break e}}else for(s=t.child,s!==null&&(s.return=t);s!==null;){var a=s.dependencies;if(a!==null){o=s.child;for(var l=a.firstContext;l!==null;){if(l.context===r){if(s.tag===1){l=Dn(-1,n&-n),l.tag=2;var u=s.updateQueue;if(u!==null){u=u.shared;var c=u.pending;c===null?l.next=l:(l.next=c.next,c.next=l),u.pending=l}}s.lanes|=n,l=s.alternate,l!==null&&(l.lanes|=n),_f(s.return,n,t),a.lanes|=n;break}l=l.next}}else if(s.tag===10)o=s.type===t.type?null:s.child;else if(s.tag===18){if(o=s.return,o===null)throw Error(U(341));o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),_f(o,n,t),o=s.sibling}else o=s.child;if(o!==null)o.return=s;else for(o=s;o!==null;){if(o===t){o=null;break}if(s=o.sibling,s!==null){s.return=o.return,o=s;break}o=o.return}s=o}ft(e,t,i.children,n),t=t.child}return t;case 9:return i=t.type,r=t.pendingProps.children,Fi(t,n),i=Gt(i),r=r(i),t.flags|=1,ft(e,t,r,n),t.child;case 14:return r=t.type,i=Jt(r,t.pendingProps),i=Jt(r.type,i),jm(e,t,r,i,n);case 15:return R1(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,i=t.pendingProps,i=t.elementType===r?i:Jt(r,i),$a(e,t),t.tag=1,St(r)?(e=!0,cl(t)):e=!1,Fi(t,n),D1(t,r,i),Ef(t,r,i,n),Af(null,t,r,!0,e,n);case 19:return $1(e,t,n);case 22:return z1(e,t,n)}throw Error(U(156,t.tag))};function rv(e,t){return Px(e,t)}function mC(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Yt(e,t,n,r){return new mC(e,t,n,r)}function kh(e){return e=e.prototype,!(!e||!e.isReactComponent)}function gC(e){if(typeof e=="function")return kh(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Vd)return 11;if(e===$d)return 14}return 2}function mr(e,t){var n=e.alternate;return n===null?(n=Yt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Ua(e,t,n,r,i,s){var o=2;if(r=e,typeof e=="function")kh(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case xi:return qr(n.children,i,s,t);case Od:o=8,i|=8;break;case Gc:return e=Yt(12,n,t,i|2),e.elementType=Gc,e.lanes=s,e;case Xc:return e=Yt(13,n,t,i),e.elementType=Xc,e.lanes=s,e;case Qc:return e=Yt(19,n,t,i),e.elementType=Qc,e.lanes=s,e;case hx:return nu(n,i,s,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case fx:o=10;break e;case dx:o=9;break e;case Vd:o=11;break e;case $d:o=14;break e;case Zn:o=16,r=null;break e}throw Error(U(130,e==null?e:typeof e,""))}return t=Yt(o,n,t,i),t.elementType=e,t.type=r,t.lanes=s,t}function qr(e,t,n,r){return e=Yt(7,e,r,t),e.lanes=n,e}function nu(e,t,n,r){return e=Yt(22,e,r,t),e.elementType=hx,e.lanes=n,e.stateNode={isHidden:!1},e}function rc(e,t,n){return e=Yt(6,e,null,t),e.lanes=n,e}function ic(e,t,n){return t=Yt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function yC(e,t,n,r,i){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Fu(0),this.expirationTimes=Fu(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Fu(0),this.identifierPrefix=r,this.onRecoverableError=i,this.mutableSourceEagerHydrationData=null}function bh(e,t,n,r,i,s,o,a,l){return e=new yC(e,t,n,a,l),t===1?(t=1,s===!0&&(t|=8)):t=0,s=Yt(3,null,null,t),e.current=s,s.stateNode=e,s.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},sh(s),e}function xC(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(av)}catch(e){console.error(e)}}av(),ax.exports=Ot;var SC=ax.exports,Wm=SC;qc.createRoot=Wm.createRoot,qc.hydrateRoot=Wm.hydrateRoot;function Oe(e,t){if(Object.is(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(const[r,i]of e)if(!Object.is(i,t.get(r)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(const r of e)if(!t.has(r))return!1;return!0}const n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(const r of n)if(!Object.prototype.hasOwnProperty.call(t,r)||!Object.is(e[r],t[r]))return!1;return!0}/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */var _C={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const CC=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().trim(),re=(e,t)=>{const n=T.forwardRef(({color:r="currentColor",size:i=24,strokeWidth:s=2,absoluteStrokeWidth:o,className:a="",children:l,...u},c)=>T.createElement("svg",{ref:c,..._C,width:i,height:i,stroke:r,strokeWidth:o?Number(s)*24/Number(i):s,className:["lucide",`lucide-${CC(e)}`,a].join(" "),...u},[...t.map(([f,d])=>T.createElement(f,d)),...Array.isArray(l)?l:[l]]));return n.displayName=`${e}`,n};/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ym=re("AlertCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const EC=re("AlertTriangle",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z",key:"c3ski4"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const lv=re("ArrowLeft",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const NC=re("BarChart3",[["path",{d:"M3 3v18h18",key:"1s2lah"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const TC=re("Bot",[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const AC=re("Brain",[["path",{d:"M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z",key:"1mhkh5"}],["path",{d:"M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z",key:"1d6s00"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Kr=re("CheckCircle2",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const PC=re("CheckCircle",[["path",{d:"M22 11.08V12a10 10 0 1 1-5.93-9.14",key:"g774vq"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const $f=re("ChevronDown",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const qm=re("ChevronUp",[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Gs=re("CircleDashed",[["path",{d:"M10.1 2.18a9.93 9.93 0 0 1 3.8 0",key:"1qdqn0"}],["path",{d:"M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7",key:"1bq7p6"}],["path",{d:"M21.82 10.1a9.93 9.93 0 0 1 0 3.8",key:"1rlaqf"}],["path",{d:"M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69",key:"1xk03u"}],["path",{d:"M13.9 21.82a9.94 9.94 0 0 1-3.8 0",key:"l7re25"}],["path",{d:"M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7",key:"1v18p6"}],["path",{d:"M2.18 13.9a9.93 9.93 0 0 1 0-3.8",key:"xdo6bj"}],["path",{d:"M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69",key:"1jjmaz"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const au=re("Clock",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["polyline",{points:"12 6 12 12 16 14",key:"68esgv"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const jC=re("Command",[["path",{d:"M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3",key:"11bfej"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Km=re("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const MC=re("Cpu",[["rect",{x:"4",y:"4",width:"16",height:"16",rx:"2",key:"1vbyd7"}],["rect",{x:"9",y:"9",width:"6",height:"6",key:"o3kz5p"}],["path",{d:"M15 2v2",key:"13l42r"}],["path",{d:"M15 20v2",key:"15mkzm"}],["path",{d:"M2 15h2",key:"1gxd5l"}],["path",{d:"M2 9h2",key:"1bbxkp"}],["path",{d:"M20 15h2",key:"19e6y8"}],["path",{d:"M20 9h2",key:"19tzq7"}],["path",{d:"M9 2v2",key:"165o2o"}],["path",{d:"M9 20v2",key:"i2bqo8"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const DC=re("Filter",[["polygon",{points:"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3",key:"1yg77f"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const IC=re("GitBranch",[["line",{x1:"6",x2:"6",y1:"3",y2:"15",key:"17qcm7"}],["circle",{cx:"18",cy:"6",r:"3",key:"1h7g24"}],["circle",{cx:"6",cy:"18",r:"3",key:"fqmcym"}],["path",{d:"M18 9a9 9 0 0 1-9 9",key:"n2h4wq"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const LC=re("Info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const RC=re("LayoutDashboard",[["rect",{width:"7",height:"9",x:"3",y:"3",rx:"1",key:"10lvy0"}],["rect",{width:"7",height:"5",x:"14",y:"3",rx:"1",key:"16une8"}],["rect",{width:"7",height:"9",x:"14",y:"12",rx:"1",key:"1hutg5"}],["rect",{width:"7",height:"5",x:"3",y:"16",rx:"1",key:"ldoo1y"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const zC=re("ListTree",[["path",{d:"M21 12h-8",key:"1bmf0i"}],["path",{d:"M21 6H8",key:"1pqkrb"}],["path",{d:"M21 18h-8",key:"1tm79t"}],["path",{d:"M3 6v4c0 1.1.9 2 2 2h3",key:"1ywdgy"}],["path",{d:"M3 10v6c0 1.1.9 2 2 2h3",key:"2wc746"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Vo=re("Loader2",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const sc=re("Loader",[["line",{x1:"12",x2:"12",y1:"2",y2:"6",key:"gza1u7"}],["line",{x1:"12",x2:"12",y1:"18",y2:"22",key:"1qhbu9"}],["line",{x1:"4.93",x2:"7.76",y1:"4.93",y2:"7.76",key:"xae44r"}],["line",{x1:"16.24",x2:"19.07",y1:"16.24",y2:"19.07",key:"bxnmvf"}],["line",{x1:"2",x2:"6",y1:"12",y2:"12",key:"89khin"}],["line",{x1:"18",x2:"22",y1:"12",y2:"12",key:"pb8tfm"}],["line",{x1:"4.93",x2:"7.76",y1:"19.07",y2:"16.24",key:"1uxjnu"}],["line",{x1:"16.24",x2:"19.07",y1:"7.76",y2:"4.93",key:"6duxfx"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const FC=re("Network",[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const OC=re("PanelLeft",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M9 3v18",key:"fh3hqa"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const VC=re("Play",[["polygon",{points:"5 3 19 12 5 21 5 3",key:"191637"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Bf=re("Plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const uv=re("RefreshCcw",[["path",{d:"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"14sxne"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16",key:"1hlbsb"}],["path",{d:"M16 16h5v5",key:"ccwih5"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const cv=re("Rocket",[["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",key:"m3kijz"}],["path",{d:"m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z",key:"1fmvmk"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0",key:"1f8sc4"}],["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const fv=re("Search",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["path",{d:"m21 21-4.3-4.3",key:"1qie3q"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const $C=re("SendHorizontal",[["path",{d:"m3 3 3 9-3 9 19-9Z",key:"1aobqy"}],["path",{d:"M6 12h16",key:"s4cdu5"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const BC=re("ShieldAlert",[["path",{d:"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10",key:"1irkt0"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const HC=re("SkipForward",[["polygon",{points:"5 4 15 12 5 20 5 4",key:"16p6eg"}],["line",{x1:"19",x2:"19",y1:"5",y2:"19",key:"futhcm"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Hf=re("Sparkles",[["path",{d:"m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z",key:"17u4zn"}],["path",{d:"M5 3v4",key:"bklmnn"}],["path",{d:"M19 17v4",key:"iiml17"}],["path",{d:"M3 5h4",key:"nem4j1"}],["path",{d:"M17 19h4",key:"lbex7p"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const UC=re("Star",[["polygon",{points:"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2",key:"8f66p6"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const WC=re("StopCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["rect",{width:"6",height:"6",x:"9",y:"9",key:"1wrtvo"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const YC=re("Timer",[["line",{x1:"10",x2:"14",y1:"2",y2:"2",key:"14vaq8"}],["line",{x1:"12",x2:"15",y1:"14",y2:"11",key:"17fdiu"}],["circle",{cx:"12",cy:"14",r:"8",key:"1e1u0o"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const qC=re("TrendingUp",[["polyline",{points:"22 7 13.5 15.5 8.5 10.5 2 17",key:"126l90"}],["polyline",{points:"16 7 22 7 22 13",key:"kwv8wd"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const KC=re("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const GC=re("Wand2",[["path",{d:"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z",key:"1bcowg"}],["path",{d:"m14 7 3 3",key:"1r5n42"}],["path",{d:"M5 6v4",key:"ilb8ba"}],["path",{d:"M19 14v4",key:"blhpug"}],["path",{d:"M10 2v2",key:"7u0qdc"}],["path",{d:"M7 8H3",key:"zfb6yr"}],["path",{d:"M21 16h-4",key:"1cnmox"}],["path",{d:"M11 3H9",key:"1obp7u"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const XC=re("WifiOff",[["line",{x1:"2",x2:"22",y1:"2",y2:"22",key:"a6p6uj"}],["path",{d:"M8.5 16.5a5 5 0 0 1 7 0",key:"sej527"}],["path",{d:"M2 8.82a15 15 0 0 1 4.17-2.65",key:"11utq1"}],["path",{d:"M10.66 5c4.01-.36 8.14.9 11.34 3.76",key:"hxefdu"}],["path",{d:"M16.85 11.25a10 10 0 0 1 2.22 1.68",key:"q734kn"}],["path",{d:"M5 13a10 10 0 0 1 5.24-2.76",key:"piq4yl"}],["line",{x1:"12",x2:"12.01",y1:"20",y2:"20",key:"of4bc4"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const $o=re("XCircle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Vi=re("X",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]);/** + * @license lucide-react v0.303.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const QC=re("Zap",[["polygon",{points:"13 2 3 14 12 14 11 22 21 10 12 10 13 2",key:"45s27k"}]]);function dv(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var i=e.length;for(t=0;t{let t;const n=new Set,r=(c,f)=>{const d=typeof c=="function"?c(t):c;if(!Object.is(d,t)){const h=t;t=f??(typeof d!="object"||d===null)?d:Object.assign({},t,d),n.forEach(g=>g(t,h))}},i=()=>t,l={setState:r,getState:i,getInitialState:()=>u,subscribe:c=>(n.add(c),()=>n.delete(c)),destroy:()=>{(ZC?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),n.clear()}},u=t=e(r,i,l);return l},hv=e=>e?Gm(e):Gm;var pv={exports:{}},mv={},gv={exports:{}},yv={};/** + * @license React + * use-sync-external-store-shim.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var es=T;function JC(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var e5=typeof Object.is=="function"?Object.is:JC,t5=es.useState,n5=es.useEffect,r5=es.useLayoutEffect,i5=es.useDebugValue;function s5(e,t){var n=t(),r=t5({inst:{value:n,getSnapshot:t}}),i=r[0].inst,s=r[1];return r5(function(){i.value=n,i.getSnapshot=t,oc(i)&&s({inst:i})},[e,n,t]),n5(function(){return oc(i)&&s({inst:i}),e(function(){oc(i)&&s({inst:i})})},[e]),i5(n),n}function oc(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!e5(e,n)}catch{return!0}}function o5(e,t){return t()}var a5=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?o5:s5;yv.useSyncExternalStore=es.useSyncExternalStore!==void 0?es.useSyncExternalStore:a5;gv.exports=yv;var l5=gv.exports;/** + * @license React + * use-sync-external-store-shim/with-selector.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var lu=T,u5=l5;function c5(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var f5=typeof Object.is=="function"?Object.is:c5,d5=u5.useSyncExternalStore,h5=lu.useRef,p5=lu.useEffect,m5=lu.useMemo,g5=lu.useDebugValue;mv.useSyncExternalStoreWithSelector=function(e,t,n,r,i){var s=h5(null);if(s.current===null){var o={hasValue:!1,value:null};s.current=o}else o=s.current;s=m5(function(){function l(h){if(!u){if(u=!0,c=h,h=r(h),i!==void 0&&o.hasValue){var g=o.value;if(i(g,h))return f=g}return f=h}if(g=f,f5(c,h))return g;var y=r(h);return i!==void 0&&i(g,y)?(c=h,g):(c=h,f=y)}var u=!1,c,f,d=n===void 0?null:n;return[function(){return l(t())},d===null?void 0:function(){return l(d())}]},[t,n,r,i]);var a=d5(e,s[0],s[1]);return p5(function(){o.hasValue=!0,o.value=a},[a]),g5(a),a};pv.exports=mv;var y5=pv.exports;const xv=Ul(y5),vv={},{useDebugValue:x5}=B,{useSyncExternalStoreWithSelector:v5}=xv;let Xm=!1;const w5=e=>e;function k5(e,t=w5,n){(vv?"production":void 0)!=="production"&&n&&!Xm&&(console.warn("[DEPRECATED] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`. They can be imported from 'zustand/traditional'. https://github.com/pmndrs/zustand/discussions/1937"),Xm=!0);const r=v5(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return x5(r),r}const b5=e=>{(vv?"production":void 0)!=="production"&&typeof e!="function"&&console.warn("[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`.");const t=typeof e=="function"?hv(e):e,n=(r,i)=>k5(t,r,i);return Object.assign(n,t),n},S5=e=>b5;class _5{constructor(t){Cn(this,"ws",null);Cn(this,"url");Cn(this,"reconnectAttempts",0);Cn(this,"maxReconnectAttempts",5);Cn(this,"reconnectDelay",1e3);Cn(this,"eventCallbacks",new Set);Cn(this,"isIntentionalClose",!1);Cn(this,"statusCallbacks",new Set);if(t)this.url=t;else{const n=window.location.protocol==="https:"?"wss:":"ws:",r=window.location.host;this.url=`${n}//${r}/ws`}}connect(){return new Promise((t,n)=>{try{this.notifyStatus("connecting"),this.ws=new WebSocket(this.url),this.isIntentionalClose=!1,this.ws.onopen=()=>{console.log("🌌 Connected to Galaxy WebSocket"),this.reconnectAttempts=0,this.notifyStatus("connected"),t()},this.ws.onmessage=r=>{try{console.log("📨 Raw WebSocket message received:",r.data);const i=JSON.parse(r.data);console.log("📦 Parsed event data:",i),console.log("🔔 Notifying",this.eventCallbacks.size,"callbacks"),this.notifyCallbacks(i)}catch(i){console.error("Failed to parse WebSocket message:",i)}},this.ws.onerror=r=>{console.error("WebSocket error:",r),this.notifyStatus("disconnected"),n(r)},this.ws.onclose=()=>{console.log("WebSocket connection closed"),this.notifyStatus("disconnected"),this.isIntentionalClose||this.attemptReconnect()}}catch(r){n(r)}})}attemptReconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts){console.error("Max reconnection attempts reached");return}this.reconnectAttempts++;const t=this.reconnectDelay*Math.pow(2,this.reconnectAttempts-1);console.log(`Attempting to reconnect in ${t}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`),this.notifyStatus("reconnecting"),setTimeout(()=>{this.connect().catch(()=>{})},t)}disconnect(){this.isIntentionalClose=!0,this.ws&&(this.ws.close(),this.ws=null,this.notifyStatus("disconnected"))}send(t){this.ws&&this.ws.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify(t)):console.error("WebSocket is not connected")}sendRequest(t){this.send({type:"request",text:t,timestamp:Date.now()})}sendReset(){this.send({type:"reset",timestamp:Date.now()})}sendPing(){this.send({type:"ping",timestamp:Date.now()})}onEvent(t){return this.eventCallbacks.add(t),()=>{this.eventCallbacks.delete(t)}}onStatusChange(t){return this.statusCallbacks.add(t),()=>{this.statusCallbacks.delete(t)}}notifyCallbacks(t){console.log("🎯 notifyCallbacks called with event:",t.event_type),console.log("📋 Number of registered callbacks:",this.eventCallbacks.size);let n=0;this.eventCallbacks.forEach(r=>{n++;try{console.log(`🔄 Executing callback ${n}/${this.eventCallbacks.size}`),r(t),console.log(`✅ Callback ${n} executed successfully`)}catch(i){console.error("Error in event callback:",i)}})}notifyStatus(t){this.statusCallbacks.forEach(n=>{try{n(t)}catch(r){console.error("Error in status callback:",r)}})}get isConnected(){return this.ws!==null&&this.ws.readyState===WebSocket.OPEN}}let ac=null;function sn(){return ac||(ac=new _5),ac}new Date(Date.now()-5e3).toISOString(),Date.now()-5e3,new Date(Date.now()-3e3).toISOString(),Date.now()-3e3,new Date(Date.now()-1e4).toISOString(),Date.now()-1e4,new Date(Date.now()-8e3).toISOString(),Date.now()-8e3;const C5=30,pn=()=>Date.now(),Qm=e=>{switch((e||"pending").toString().toLowerCase()){case"completed":case"complete":case"success":return"completed";case"running":case"in_progress":case"active":return"running";case"failed":case"error":return"failed";case"skipped":return"skipped";default:return"pending"}},Zm=e=>{switch((e||"unknown").toString().toLowerCase()){case"idle":return"idle";case"busy":case"running":return"busy";case"connected":case"online":return"connected";case"connecting":return"connecting";case"disconnected":return"disconnected";case"failed":return"failed";case"offline":return"offline";default:return"unknown"}},lc=(e,t,n)=>{const r={total:0,pending:0,running:0,completed:0,failed:0};return t.forEach(i=>{const s=n[i];if(!(!s||s.constellationId!==e))switch(r.total+=1,s.status){case"pending":r.pending+=1;break;case"running":r.running+=1;break;case"completed":r.completed+=1;break;case"failed":r.failed+=1;break}}),r},E5=()=>({id:null,displayName:"Galaxy Session",welcomeText:"Launch a request to orchestrate a new TaskConstellation.",startedAt:null,debugMode:!1,highContrast:!1}),Jm=()=>({searchQuery:"",messageKindFilter:"all",rightPanelTab:"constellation",activeConstellationId:null,activeTaskId:null,activeDeviceId:null,showDeviceDrawer:!1,showComposerShortcuts:!0,isTaskRunning:!1,isTaskStopped:!1,showLeftDrawer:!1,showRightDrawer:!1}),ts=()=>typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`id_${Math.random().toString(36).slice(2,10)}_${Date.now()}`,Ce=S5()((e,t)=>({connected:!1,connectionStatus:"idle",setConnected:n=>e({connected:n,connectionStatus:n?"connected":"disconnected"}),setConnectionStatus:n=>e({connectionStatus:n,connected:n==="connected"}),session:E5(),setSessionInfo:n=>e(r=>({session:{...r.session,...n}})),ensureSession:(n,r)=>{const i=t().session;if(i.id&&!n)return i.id;const s=n||`session-${ts()}`;return e(o=>({session:{...o.session,id:s,displayName:r||o.session.displayName,startedAt:o.session.startedAt||pn()}})),s},endSession:()=>e(n=>({session:{...n.session,id:null,startedAt:null}})),messages:[],addMessage:n=>e(r=>({messages:[...r.messages,n].slice(-500)})),updateMessage:(n,r)=>e(i=>({messages:i.messages.map(s=>s.id===n?{...s,...r}:s)})),clearMessages:()=>e({messages:[]}),eventLog:[],addEventToLog:n=>e(r=>({eventLog:[...r.eventLog,n].slice(-200)})),clearEventLog:()=>e({eventLog:[]}),constellations:{},upsertConstellation:n=>{e(r=>{var l,u;const i=r.constellations[n.id],s=n.taskIds||(i==null?void 0:i.taskIds)||[],o=lc(n.id,s,r.tasks),a={id:n.id,name:n.name||(i==null?void 0:i.name)||n.id,status:n.status||(i==null?void 0:i.status)||"pending",description:n.description??(i==null?void 0:i.description),metadata:n.metadata??(i==null?void 0:i.metadata),createdAt:n.createdAt??(i==null?void 0:i.createdAt)??pn(),updatedAt:pn(),taskIds:s,dag:{nodes:((l=n.dag)==null?void 0:l.nodes)??(i==null?void 0:i.dag.nodes)??[],edges:((u=n.dag)==null?void 0:u.edges)??(i==null?void 0:i.dag.edges)??[]},statistics:o};return{constellations:{...r.constellations,[n.id]:a},ui:{...r.ui,activeConstellationId:r.ui.activeConstellationId||n.id}}})},removeConstellation:n=>e(r=>{const{[n]:i,...s}=r.constellations;return{constellations:s,ui:{...r.ui,activeConstellationId:r.ui.activeConstellationId===n?null:r.ui.activeConstellationId}}}),setActiveConstellation:n=>e(r=>({ui:{...r.ui,activeConstellationId:n,activeTaskId:n?r.ui.activeTaskId:null}})),tasks:{},bulkUpsertTasks:(n,r,i={})=>{e(s=>{var d;const o={...s.tasks},a={};Object.entries(i).forEach(([h,g])=>{a[h]=Array.isArray(g)?g:[]});const l=new Set(((d=s.constellations[n])==null?void 0:d.taskIds)??[]);r.forEach(h=>{const g=Qm(h.status),y=a[h.id]||h.dependencies||[],w=s.tasks[h.id],m=new Set((w==null?void 0:w.dependents)??[]);Object.entries(a).forEach(([x,v])=>{v!=null&&v.includes(h.id)&&m.add(x)}),o[h.id]={id:h.id,constellationId:n,name:h.name||(w==null?void 0:w.name)||h.id,description:h.description??(w==null?void 0:w.description),status:g,deviceId:h.deviceId??h.device??(w==null?void 0:w.deviceId),input:h.input??(w==null?void 0:w.input),output:h.output??(w==null?void 0:w.output),result:h.result??(w==null?void 0:w.result),error:h.error??(w==null?void 0:w.error)??null,startedAt:h.startedAt??(w==null?void 0:w.startedAt),completedAt:h.completedAt??(w==null?void 0:w.completedAt),retries:h.retries??(w==null?void 0:w.retries),dependencies:y,dependents:Array.from(m),logs:h.logs??(w==null?void 0:w.logs)??[]},l.add(h.id)});const u=Array.from(l),c=lc(n,u,o),f=s.constellations[n];return{tasks:o,constellations:{...s.constellations,[n]:f?{...f,taskIds:u,statistics:c,updatedAt:pn()}:{id:n,name:n,status:"pending",taskIds:u,dag:{nodes:[],edges:[]},statistics:c,createdAt:pn(),updatedAt:pn()}}}})},updateTask:(n,r)=>{e(i=>{const s=i.tasks[n];if(!s)return i;const o={...s,...r,status:r.status?Qm(r.status):s.status},a=i.constellations[s.constellationId],l={tasks:{...i.tasks,[n]:o}};return a&&(l.constellations={...i.constellations,[a.id]:{...a,statistics:lc(a.id,a.taskIds,{...i.tasks,[n]:o}),updatedAt:pn()}}),l})},appendTaskLog:(n,r)=>e(i=>{const s=i.tasks[n];if(!s)return i;const o=[...s.logs,r];return{tasks:{...i.tasks,[n]:{...s,logs:o}}}}),devices:{},setDevicesFromSnapshot:n=>{e(r=>{const i={...r.devices};return Object.entries(n||{}).forEach(([s,o])=>{var l;const a=Zm(o==null?void 0:o.status);i[s]={id:s,name:(o==null?void 0:o.device_id)||s,status:a,os:o==null?void 0:o.os,serverUrl:o==null?void 0:o.server_url,capabilities:(o==null?void 0:o.capabilities)||[],metadata:(o==null?void 0:o.metadata)||{},lastHeartbeat:(o==null?void 0:o.last_heartbeat)||null,connectionAttempts:o==null?void 0:o.connection_attempts,maxRetries:o==null?void 0:o.max_retries,currentTaskId:o==null?void 0:o.current_task_id,tags:((l=o==null?void 0:o.metadata)==null?void 0:l.tags)||[],metrics:(o==null?void 0:o.metrics)||{},updatedAt:pn()}}),{devices:i}})},upsertDevice:n=>{const r=t().devices[n.id],i=Zm(n.status||(r==null?void 0:r.status));return e(s=>({devices:{...s.devices,[n.id]:{id:n.id,name:n.name||(r==null?void 0:r.name)||n.id,status:i,os:n.os??(r==null?void 0:r.os),serverUrl:n.serverUrl??(r==null?void 0:r.serverUrl),capabilities:n.capabilities??(r==null?void 0:r.capabilities)??[],metadata:n.metadata??(r==null?void 0:r.metadata)??{},lastHeartbeat:n.lastHeartbeat??(r==null?void 0:r.lastHeartbeat)??null,connectionAttempts:n.connectionAttempts??(r==null?void 0:r.connectionAttempts),maxRetries:n.maxRetries??(r==null?void 0:r.maxRetries),currentTaskId:n.currentTaskId??(r==null?void 0:r.currentTaskId)??null,tags:n.tags??(r==null?void 0:r.tags)??[],metrics:n.metrics??(r==null?void 0:r.metrics)??{},updatedAt:pn(),highlightUntil:pn()+4e3}}})),{statusChanged:(r==null?void 0:r.status)!==i,previousStatus:r==null?void 0:r.status}},clearDeviceHighlight:n=>e(r=>{const i=r.devices[n];return i?{devices:{...r.devices,[n]:{...i,highlightUntil:0}}}:r}),notifications:[],pushNotification:n=>e(r=>({notifications:[n,...r.notifications].slice(0,C5)})),dismissNotification:n=>e(r=>({notifications:r.notifications.filter(i=>i.id!==n)})),markNotificationRead:n=>e(r=>({notifications:r.notifications.map(i=>i.id===n?{...i,read:!0}:i)})),markAllNotificationsRead:()=>e(n=>({notifications:n.notifications.map(r=>({...r,read:!0}))})),ui:{...Jm(),activeConstellationId:null},setSearchQuery:n=>e(r=>({ui:{...r.ui,searchQuery:n}})),setMessageKindFilter:n=>e(r=>({ui:{...r.ui,messageKindFilter:n}})),setRightPanelTab:n=>e(r=>({ui:{...r.ui,rightPanelTab:n}})),setActiveTask:n=>e(r=>({ui:{...r.ui,activeTaskId:n,rightPanelTab:n?"details":r.ui.rightPanelTab}})),setActiveDevice:n=>e(r=>({ui:{...r.ui,activeDeviceId:n}})),toggleDeviceDrawer:n=>e(r=>({ui:{...r.ui,showDeviceDrawer:typeof n=="boolean"?n:!r.ui.showDeviceDrawer}})),toggleComposerShortcuts:()=>e(n=>({ui:{...n.ui,showComposerShortcuts:!n.ui.showComposerShortcuts}})),setTaskRunning:n=>e(r=>({ui:{...r.ui,isTaskRunning:n,isTaskStopped:n?!1:r.ui.isTaskStopped}})),stopCurrentTask:()=>{sn().send({type:"stop_task",timestamp:Date.now()}),e(r=>({ui:{...r.ui,isTaskRunning:!1,isTaskStopped:!0}}))},toggleLeftDrawer:n=>e(r=>({ui:{...r.ui,showLeftDrawer:typeof n=="boolean"?n:!r.ui.showLeftDrawer}})),toggleRightDrawer:n=>e(r=>({ui:{...r.ui,showRightDrawer:typeof n=="boolean"?n:!r.ui.showRightDrawer}})),toggleDebugMode:()=>e(n=>({session:{...n.session,debugMode:!n.session.debugMode}})),toggleHighContrast:()=>e(n=>({session:{...n.session,highContrast:!n.session.highContrast}})),resetSessionState:()=>e(n=>({messages:[],eventLog:[],constellations:{},tasks:{},notifications:[],ui:{...Jm(),showComposerShortcuts:n.ui.showComposerShortcuts},session:{...n.session,id:null,startedAt:null}}))})),N5=[{label:"All",value:"all"},{label:"Responses",value:"response"},{label:"User",value:"user"}],T5=()=>{const{searchQuery:e,messageKindFilter:t,setSearchQuery:n,setMessageKindFilter:r}=Ce(s=>({searchQuery:s.ui.searchQuery,messageKindFilter:s.ui.messageKindFilter,setSearchQuery:s.setSearchQuery,setMessageKindFilter:s.setMessageKindFilter}),Oe),i=s=>{n(s.target.value)};return p.jsxs("div",{className:"flex flex-col gap-3 rounded-[24px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.85)] via-[rgba(8,20,35,0.82)] to-[rgba(6,15,28,0.85)] p-4 shadow-[0_8px_32px_rgba(0,0,0,0.35),0_2px_8px_rgba(15,123,255,0.1),inset_0_1px_1px_rgba(255,255,255,0.06)] ring-1 ring-inset ring-white/5",children:[p.jsxs("div",{className:"flex items-center gap-3 rounded-xl border border-white/5 bg-gradient-to-r from-black/30 to-black/20 px-3 py-2.5 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus-within:border-white/15 focus-within:shadow-[0_0_8px_rgba(15,123,255,0.08),inset_0_2px_8px_rgba(0,0,0,0.3)]",children:[p.jsx(fv,{className:"h-4 w-4 text-slate-400","aria-hidden":!0}),p.jsx("input",{type:"search",value:e,onChange:i,placeholder:"Search messages, tasks, or devices",className:"w-full bg-transparent text-sm text-slate-100 placeholder:text-slate-500 focus:outline-none"})]}),p.jsxs("div",{className:"flex flex-wrap items-center gap-2 text-xs",children:[p.jsxs("span",{className:"flex items-center gap-1 rounded-full border border-white/10 bg-white/10 px-2.5 py-1 text-[11px] uppercase tracking-[0.2em] text-slate-300 shadow-[inset_0_1px_2px_rgba(255,255,255,0.1)]",children:[p.jsx(DC,{className:"h-3 w-3","aria-hidden":!0}),"Filter"]}),N5.map(({label:s,value:o})=>p.jsx("button",{type:"button",className:me("rounded-full px-3 py-1.5 transition-all duration-200",t===o?"bg-gradient-to-r from-galaxy-blue to-galaxy-purple text-white shadow-[0_0_20px_rgba(15,123,255,0.4),0_2px_8px_rgba(123,44,191,0.3)] ring-1 ring-white/20":"border border-white/10 bg-white/5 text-slate-300 shadow-[inset_0_1px_2px_rgba(255,255,255,0.05)] hover:border-white/20 hover:bg-white/10 hover:text-white hover:shadow-[0_0_10px_rgba(15,123,255,0.15)]"),onClick:()=>r(o),children:s},o))]})]})};function A5(e,t){const n={};return(e[e.length-1]===""?[...e,""]:e).join((n.padRight?" ":"")+","+(n.padLeft===!1?"":" ")).trim()}const P5=/^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,j5=/^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,M5={};function eg(e,t){return(M5.jsx?j5:P5).test(e)}const D5=/[ \t\n\f\r]/g;function I5(e){return typeof e=="object"?e.type==="text"?tg(e.value):!1:tg(e)}function tg(e){return e.replace(D5,"")===""}class Bo{constructor(t,n,r){this.normal=n,this.property=t,r&&(this.space=r)}}Bo.prototype.normal={};Bo.prototype.property={};Bo.prototype.space=void 0;function wv(e,t){const n={},r={};for(const i of e)Object.assign(n,i.property),Object.assign(r,i.normal);return new Bo(n,r,t)}function Uf(e){return e.toLowerCase()}class Et{constructor(t,n){this.attribute=n,this.property=t}}Et.prototype.attribute="";Et.prototype.booleanish=!1;Et.prototype.boolean=!1;Et.prototype.commaOrSpaceSeparated=!1;Et.prototype.commaSeparated=!1;Et.prototype.defined=!1;Et.prototype.mustUseProperty=!1;Et.prototype.number=!1;Et.prototype.overloadedBoolean=!1;Et.prototype.property="";Et.prototype.spaceSeparated=!1;Et.prototype.space=void 0;let L5=0;const se=ai(),$e=ai(),Wf=ai(),W=ai(),we=ai(),$i=ai(),jt=ai();function ai(){return 2**++L5}const Yf=Object.freeze(Object.defineProperty({__proto__:null,boolean:se,booleanish:$e,commaOrSpaceSeparated:jt,commaSeparated:$i,number:W,overloadedBoolean:Wf,spaceSeparated:we},Symbol.toStringTag,{value:"Module"})),uc=Object.keys(Yf);class Eh extends Et{constructor(t,n,r,i){let s=-1;if(super(t,n),ng(this,"space",i),typeof r=="number")for(;++s4&&n.slice(0,4)==="data"&&V5.test(t)){if(t.charAt(4)==="-"){const s=t.slice(5).replace(rg,H5);r="data"+s.charAt(0).toUpperCase()+s.slice(1)}else{const s=t.slice(4);if(!rg.test(s)){let o=s.replace(O5,B5);o.charAt(0)!=="-"&&(o="-"+o),t="data"+o}}i=Eh}return new i(r,t)}function B5(e){return"-"+e.toLowerCase()}function H5(e){return e.charAt(1).toUpperCase()}const U5=wv([kv,R5,_v,Cv,Ev],"html"),Nh=wv([kv,z5,_v,Cv,Ev],"svg");function W5(e){return e.join(" ").trim()}var Th={},ig=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,Y5=/\n/g,q5=/^\s*/,K5=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,G5=/^:\s*/,X5=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,Q5=/^[;\s]*/,Z5=/^\s+|\s+$/g,J5=` +`,sg="/",og="*",Or="",eE="comment",tE="declaration";function nE(e,t){if(typeof e!="string")throw new TypeError("First argument must be a string");if(!e)return[];t=t||{};var n=1,r=1;function i(g){var y=g.match(Y5);y&&(n+=y.length);var w=g.lastIndexOf(J5);r=~w?g.length-w:r+g.length}function s(){var g={line:n,column:r};return function(y){return y.position=new o(g),u(),y}}function o(g){this.start=g,this.end={line:n,column:r},this.source=t.source}o.prototype.content=e;function a(g){var y=new Error(t.source+":"+n+":"+r+": "+g);if(y.reason=g,y.filename=t.source,y.line=n,y.column=r,y.source=e,!t.silent)throw y}function l(g){var y=g.exec(e);if(y){var w=y[0];return i(w),e=e.slice(w.length),y}}function u(){l(q5)}function c(g){var y;for(g=g||[];y=f();)y!==!1&&g.push(y);return g}function f(){var g=s();if(!(sg!=e.charAt(0)||og!=e.charAt(1))){for(var y=2;Or!=e.charAt(y)&&(og!=e.charAt(y)||sg!=e.charAt(y+1));)++y;if(y+=2,Or===e.charAt(y-1))return a("End of comment missing");var w=e.slice(2,y-2);return r+=2,i(w),e=e.slice(y),r+=2,g({type:eE,comment:w})}}function d(){var g=s(),y=l(K5);if(y){if(f(),!l(G5))return a("property missing ':'");var w=l(X5),m=g({type:tE,property:ag(y[0].replace(ig,Or)),value:w?ag(w[0].replace(ig,Or)):Or});return l(Q5),m}}function h(){var g=[];c(g);for(var y;y=d();)y!==!1&&(g.push(y),c(g));return g}return u(),h()}function ag(e){return e?e.replace(Z5,Or):Or}var rE=nE,iE=Ja&&Ja.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(Th,"__esModule",{value:!0});Th.default=oE;const sE=iE(rE);function oE(e,t){let n=null;if(!e||typeof e!="string")return n;const r=(0,sE.default)(e),i=typeof t=="function";return r.forEach(s=>{if(s.type!=="declaration")return;const{property:o,value:a}=s;i?t(o,a,s):a&&(n=n||{},n[o]=a)}),n}var uu={};Object.defineProperty(uu,"__esModule",{value:!0});uu.camelCase=void 0;var aE=/^--[a-zA-Z0-9_-]+$/,lE=/-([a-z])/g,uE=/^[^-]+$/,cE=/^-(webkit|moz|ms|o|khtml)-/,fE=/^-(ms)-/,dE=function(e){return!e||uE.test(e)||aE.test(e)},hE=function(e,t){return t.toUpperCase()},lg=function(e,t){return"".concat(t,"-")},pE=function(e,t){return t===void 0&&(t={}),dE(e)?e:(e=e.toLowerCase(),t.reactCompat?e=e.replace(fE,lg):e=e.replace(cE,lg),e.replace(lE,hE))};uu.camelCase=pE;var mE=Ja&&Ja.__importDefault||function(e){return e&&e.__esModule?e:{default:e}},gE=mE(Th),yE=uu;function qf(e,t){var n={};return!e||typeof e!="string"||(0,gE.default)(e,function(r,i){r&&i&&(n[(0,yE.camelCase)(r,t)]=i)}),n}qf.default=qf;var xE=qf;const vE=Ul(xE),Nv=Tv("end"),Ah=Tv("start");function Tv(e){return t;function t(n){const r=n&&n.position&&n.position[e]||{};if(typeof r.line=="number"&&r.line>0&&typeof r.column=="number"&&r.column>0)return{line:r.line,column:r.column,offset:typeof r.offset=="number"&&r.offset>-1?r.offset:void 0}}}function wE(e){const t=Ah(e),n=Nv(e);if(t&&n)return{start:t,end:n}}function Xs(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?ug(e.position):"start"in e||"end"in e?ug(e):"line"in e||"column"in e?Kf(e):""}function Kf(e){return cg(e&&e.line)+":"+cg(e&&e.column)}function ug(e){return Kf(e&&e.start)+"-"+Kf(e&&e.end)}function cg(e){return e&&typeof e=="number"?e:1}class at extends Error{constructor(t,n,r){super(),typeof n=="string"&&(r=n,n=void 0);let i="",s={},o=!1;if(n&&("line"in n&&"column"in n?s={place:n}:"start"in n&&"end"in n?s={place:n}:"type"in n?s={ancestors:[n],place:n.position}:s={...n}),typeof t=="string"?i=t:!s.cause&&t&&(o=!0,i=t.message,s.cause=t),!s.ruleId&&!s.source&&typeof r=="string"){const l=r.indexOf(":");l===-1?s.ruleId=r:(s.source=r.slice(0,l),s.ruleId=r.slice(l+1))}if(!s.place&&s.ancestors&&s.ancestors){const l=s.ancestors[s.ancestors.length-1];l&&(s.place=l.position)}const a=s.place&&"start"in s.place?s.place.start:s.place;this.ancestors=s.ancestors||void 0,this.cause=s.cause||void 0,this.column=a?a.column:void 0,this.fatal=void 0,this.file="",this.message=i,this.line=a?a.line:void 0,this.name=Xs(s.place)||"1:1",this.place=s.place||void 0,this.reason=this.message,this.ruleId=s.ruleId||void 0,this.source=s.source||void 0,this.stack=o&&s.cause&&typeof s.cause.stack=="string"?s.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}at.prototype.file="";at.prototype.name="";at.prototype.reason="";at.prototype.message="";at.prototype.stack="";at.prototype.column=void 0;at.prototype.line=void 0;at.prototype.ancestors=void 0;at.prototype.cause=void 0;at.prototype.fatal=void 0;at.prototype.place=void 0;at.prototype.ruleId=void 0;at.prototype.source=void 0;const Ph={}.hasOwnProperty,kE=new Map,bE=/[A-Z]/g,SE=new Set(["table","tbody","thead","tfoot","tr"]),_E=new Set(["td","th"]),Av="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function CE(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const n=t.filePath||void 0;let r;if(t.development){if(typeof t.jsxDEV!="function")throw new TypeError("Expected `jsxDEV` in options when `development: true`");r=DE(n,t.jsxDEV)}else{if(typeof t.jsx!="function")throw new TypeError("Expected `jsx` in production options");if(typeof t.jsxs!="function")throw new TypeError("Expected `jsxs` in production options");r=ME(n,t.jsx,t.jsxs)}const i={Fragment:t.Fragment,ancestors:[],components:t.components||{},create:r,elementAttributeNameCase:t.elementAttributeNameCase||"react",evaluater:t.createEvaluater?t.createEvaluater():void 0,filePath:n,ignoreInvalidStyle:t.ignoreInvalidStyle||!1,passKeys:t.passKeys!==!1,passNode:t.passNode||!1,schema:t.space==="svg"?Nh:U5,stylePropertyNameCase:t.stylePropertyNameCase||"dom",tableCellAlignToStyle:t.tableCellAlignToStyle!==!1},s=Pv(i,e,void 0);return s&&typeof s!="string"?s:i.create(e,i.Fragment,{children:s||void 0},void 0)}function Pv(e,t,n){if(t.type==="element")return EE(e,t,n);if(t.type==="mdxFlowExpression"||t.type==="mdxTextExpression")return NE(e,t);if(t.type==="mdxJsxFlowElement"||t.type==="mdxJsxTextElement")return AE(e,t,n);if(t.type==="mdxjsEsm")return TE(e,t);if(t.type==="root")return PE(e,t,n);if(t.type==="text")return jE(e,t)}function EE(e,t,n){const r=e.schema;let i=r;t.tagName.toLowerCase()==="svg"&&r.space==="html"&&(i=Nh,e.schema=i),e.ancestors.push(t);const s=Mv(e,t.tagName,!1),o=IE(e,t);let a=Mh(e,t);return SE.has(t.tagName)&&(a=a.filter(function(l){return typeof l=="string"?!I5(l):!0})),jv(e,o,s,t),jh(o,a),e.ancestors.pop(),e.schema=r,e.create(t,s,o,n)}function NE(e,t){if(t.data&&t.data.estree&&e.evaluater){const r=t.data.estree.body[0];return r.type,e.evaluater.evaluateExpression(r.expression)}bo(e,t.position)}function TE(e,t){if(t.data&&t.data.estree&&e.evaluater)return e.evaluater.evaluateProgram(t.data.estree);bo(e,t.position)}function AE(e,t,n){const r=e.schema;let i=r;t.name==="svg"&&r.space==="html"&&(i=Nh,e.schema=i),e.ancestors.push(t);const s=t.name===null?e.Fragment:Mv(e,t.name,!0),o=LE(e,t),a=Mh(e,t);return jv(e,o,s,t),jh(o,a),e.ancestors.pop(),e.schema=r,e.create(t,s,o,n)}function PE(e,t,n){const r={};return jh(r,Mh(e,t)),e.create(t,e.Fragment,r,n)}function jE(e,t){return t.value}function jv(e,t,n,r){typeof n!="string"&&n!==e.Fragment&&e.passNode&&(t.node=r)}function jh(e,t){if(t.length>0){const n=t.length>1?t:t[0];n&&(e.children=n)}}function ME(e,t,n){return r;function r(i,s,o,a){const u=Array.isArray(o.children)?n:t;return a?u(s,o,a):u(s,o)}}function DE(e,t){return n;function n(r,i,s,o){const a=Array.isArray(s.children),l=Ah(r);return t(i,s,o,a,{columnNumber:l?l.column-1:void 0,fileName:e,lineNumber:l?l.line:void 0},void 0)}}function IE(e,t){const n={};let r,i;for(i in t.properties)if(i!=="children"&&Ph.call(t.properties,i)){const s=RE(e,i,t.properties[i]);if(s){const[o,a]=s;e.tableCellAlignToStyle&&o==="align"&&typeof a=="string"&&_E.has(t.tagName)?r=a:n[o]=a}}if(r){const s=n.style||(n.style={});s[e.stylePropertyNameCase==="css"?"text-align":"textAlign"]=r}return n}function LE(e,t){const n={};for(const r of t.attributes)if(r.type==="mdxJsxExpressionAttribute")if(r.data&&r.data.estree&&e.evaluater){const s=r.data.estree.body[0];s.type;const o=s.expression;o.type;const a=o.properties[0];a.type,Object.assign(n,e.evaluater.evaluateExpression(a.argument))}else bo(e,t.position);else{const i=r.name;let s;if(r.value&&typeof r.value=="object")if(r.value.data&&r.value.data.estree&&e.evaluater){const a=r.value.data.estree.body[0];a.type,s=e.evaluater.evaluateExpression(a.expression)}else bo(e,t.position);else s=r.value===null?!0:r.value;n[i]=s}return n}function Mh(e,t){const n=[];let r=-1;const i=e.passKeys?new Map:kE;for(;++ri?0:i+t:t=t>i?i:t,n=n>0?n:0,r.length<1e4)o=Array.from(r),o.unshift(t,n),e.splice(...o);else for(n&&e.splice(t,n);s0?(Lt(e,e.length,0,t),e):t}const hg={}.hasOwnProperty;function Iv(e){const t={};let n=-1;for(;++n13&&n<32||n>126&&n<160||n>55295&&n<57344||n>64975&&n<65008||(n&65535)===65535||(n&65535)===65534||n>1114111?"�":String.fromCodePoint(n)}function ln(e){return e.replace(/[\t\n\r ]+/g," ").replace(/^ | $/g,"").toLowerCase().toUpperCase()}const dt=Er(/[A-Za-z]/),st=Er(/[\dA-Za-z]/),WE=Er(/[#-'*+\--9=?A-Z^-~]/);function _l(e){return e!==null&&(e<32||e===127)}const Gf=Er(/\d/),YE=Er(/[\dA-Fa-f]/),qE=Er(/[!-/:-@[-`{-~]/);function J(e){return e!==null&&e<-2}function ve(e){return e!==null&&(e<0||e===32)}function ue(e){return e===-2||e===-1||e===32}const cu=Er(new RegExp("\\p{P}|\\p{S}","u")),ni=Er(/\s/);function Er(e){return t;function t(n){return n!==null&&n>-1&&e.test(String.fromCharCode(n))}}function hs(e){const t=[];let n=-1,r=0,i=0;for(;++n55295&&s<57344){const a=e.charCodeAt(n+1);s<56320&&a>56319&&a<57344?(o=String.fromCharCode(s,a),i=1):o="�"}else o=String.fromCharCode(s);o&&(t.push(e.slice(r,n),encodeURIComponent(o)),r=n+i+1,o=""),i&&(n+=i,i=0)}return t.join("")+e.slice(r)}function fe(e,t,n,r){const i=r?r-1:Number.POSITIVE_INFINITY;let s=0;return o;function o(l){return ue(l)?(e.enter(n),a(l)):t(l)}function a(l){return ue(l)&&s++o))return;const A=t.events.length;let P=A,D,C;for(;P--;)if(t.events[P][0]==="exit"&&t.events[P][1].type==="chunkFlow"){if(D){C=t.events[P][1].end;break}D=!0}for(m(r),S=A;Sv;){const N=n[k];t.containerState=N[1],N[0].exit.call(t,e)}n.length=v}function x(){i.write([null]),s=void 0,i=void 0,t.containerState._closeFlow=void 0}}function ZE(e,t,n){return fe(e,e.attempt(this.parser.constructs.document,t,n),"linePrefix",this.parser.constructs.disable.null.includes("codeIndented")?void 0:4)}function ns(e){if(e===null||ve(e)||ni(e))return 1;if(cu(e))return 2}function fu(e,t,n){const r=[];let i=-1;for(;++i1&&e[n][1].end.offset-e[n][1].start.offset>1?2:1;const f={...e[r][1].end},d={...e[n][1].start};mg(f,-l),mg(d,l),o={type:l>1?"strongSequence":"emphasisSequence",start:f,end:{...e[r][1].end}},a={type:l>1?"strongSequence":"emphasisSequence",start:{...e[n][1].start},end:d},s={type:l>1?"strongText":"emphasisText",start:{...e[r][1].end},end:{...e[n][1].start}},i={type:l>1?"strong":"emphasis",start:{...o.start},end:{...a.end}},e[r][1].end={...o.start},e[n][1].start={...a.end},u=[],e[r][1].end.offset-e[r][1].start.offset&&(u=Wt(u,[["enter",e[r][1],t],["exit",e[r][1],t]])),u=Wt(u,[["enter",i,t],["enter",o,t],["exit",o,t],["enter",s,t]]),u=Wt(u,fu(t.parser.constructs.insideSpan.null,e.slice(r+1,n),t)),u=Wt(u,[["exit",s,t],["enter",a,t],["exit",a,t],["exit",i,t]]),e[n][1].end.offset-e[n][1].start.offset?(c=2,u=Wt(u,[["enter",e[n][1],t],["exit",e[n][1],t]])):c=0,Lt(e,r-1,n-r+3,u),n=r+u.length-c-2;break}}for(n=-1;++n0&&ue(S)?fe(e,x,"linePrefix",s+1)(S):x(S)}function x(S){return S===null||J(S)?e.check(gg,y,k)(S):(e.enter("codeFlowValue"),v(S))}function v(S){return S===null||J(S)?(e.exit("codeFlowValue"),x(S)):(e.consume(S),v)}function k(S){return e.exit("codeFenced"),t(S)}function N(S,A,P){let D=0;return C;function C(R){return S.enter("lineEnding"),S.consume(R),S.exit("lineEnding"),L}function L(R){return S.enter("codeFencedFence"),ue(R)?fe(S,M,"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(R):M(R)}function M(R){return R===a?(S.enter("codeFencedFenceSequence"),O(R)):P(R)}function O(R){return R===a?(D++,S.consume(R),O):D>=o?(S.exit("codeFencedFenceSequence"),ue(R)?fe(S,_,"whitespace")(R):_(R)):P(R)}function _(R){return R===null||J(R)?(S.exit("codeFencedFence"),A(R)):P(R)}}}function c3(e,t,n){const r=this;return i;function i(o){return o===null?n(o):(e.enter("lineEnding"),e.consume(o),e.exit("lineEnding"),s)}function s(o){return r.parser.lazy[r.now().line]?n(o):t(o)}}const fc={name:"codeIndented",tokenize:d3},f3={partial:!0,tokenize:h3};function d3(e,t,n){const r=this;return i;function i(u){return e.enter("codeIndented"),fe(e,s,"linePrefix",5)(u)}function s(u){const c=r.events[r.events.length-1];return c&&c[1].type==="linePrefix"&&c[2].sliceSerialize(c[1],!0).length>=4?o(u):n(u)}function o(u){return u===null?l(u):J(u)?e.attempt(f3,o,l)(u):(e.enter("codeFlowValue"),a(u))}function a(u){return u===null||J(u)?(e.exit("codeFlowValue"),o(u)):(e.consume(u),a)}function l(u){return e.exit("codeIndented"),t(u)}}function h3(e,t,n){const r=this;return i;function i(o){return r.parser.lazy[r.now().line]?n(o):J(o)?(e.enter("lineEnding"),e.consume(o),e.exit("lineEnding"),i):fe(e,s,"linePrefix",5)(o)}function s(o){const a=r.events[r.events.length-1];return a&&a[1].type==="linePrefix"&&a[2].sliceSerialize(a[1],!0).length>=4?t(o):J(o)?i(o):n(o)}}const p3={name:"codeText",previous:g3,resolve:m3,tokenize:y3};function m3(e){let t=e.length-4,n=3,r,i;if((e[n][1].type==="lineEnding"||e[n][1].type==="space")&&(e[t][1].type==="lineEnding"||e[t][1].type==="space")){for(r=n;++r=this.left.length+this.right.length)throw new RangeError("Cannot access index `"+t+"` in a splice buffer of size `"+(this.left.length+this.right.length)+"`");return tthis.left.length?this.right.slice(this.right.length-r+this.left.length,this.right.length-t+this.left.length).reverse():this.left.slice(t).concat(this.right.slice(this.right.length-r+this.left.length).reverse())}splice(t,n,r){const i=n||0;this.setCursor(Math.trunc(t));const s=this.right.splice(this.right.length-i,Number.POSITIVE_INFINITY);return r&&Ss(this.left,r),s.reverse()}pop(){return this.setCursor(Number.POSITIVE_INFINITY),this.left.pop()}push(t){this.setCursor(Number.POSITIVE_INFINITY),this.left.push(t)}pushMany(t){this.setCursor(Number.POSITIVE_INFINITY),Ss(this.left,t)}unshift(t){this.setCursor(0),this.right.push(t)}unshiftMany(t){this.setCursor(0),Ss(this.right,t.reverse())}setCursor(t){if(!(t===this.left.length||t>this.left.length&&this.right.length===0||t<0&&this.left.length===0))if(t=4?t(o):e.interrupt(r.parser.constructs.flow,n,t)(o)}}function Vv(e,t,n,r,i,s,o,a,l){const u=l||Number.POSITIVE_INFINITY;let c=0;return f;function f(m){return m===60?(e.enter(r),e.enter(i),e.enter(s),e.consume(m),e.exit(s),d):m===null||m===32||m===41||_l(m)?n(m):(e.enter(r),e.enter(o),e.enter(a),e.enter("chunkString",{contentType:"string"}),y(m))}function d(m){return m===62?(e.enter(s),e.consume(m),e.exit(s),e.exit(i),e.exit(r),t):(e.enter(a),e.enter("chunkString",{contentType:"string"}),h(m))}function h(m){return m===62?(e.exit("chunkString"),e.exit(a),d(m)):m===null||m===60||J(m)?n(m):(e.consume(m),m===92?g:h)}function g(m){return m===60||m===62||m===92?(e.consume(m),h):h(m)}function y(m){return!c&&(m===null||m===41||ve(m))?(e.exit("chunkString"),e.exit(a),e.exit(o),e.exit(r),t(m)):c999||h===null||h===91||h===93&&!l||h===94&&!a&&"_hiddenFootnoteSupport"in o.parser.constructs?n(h):h===93?(e.exit(s),e.enter(i),e.consume(h),e.exit(i),e.exit(r),t):J(h)?(e.enter("lineEnding"),e.consume(h),e.exit("lineEnding"),c):(e.enter("chunkString",{contentType:"string"}),f(h))}function f(h){return h===null||h===91||h===93||J(h)||a++>999?(e.exit("chunkString"),c(h)):(e.consume(h),l||(l=!ue(h)),h===92?d:f)}function d(h){return h===91||h===92||h===93?(e.consume(h),a++,f):f(h)}}function Bv(e,t,n,r,i,s){let o;return a;function a(d){return d===34||d===39||d===40?(e.enter(r),e.enter(i),e.consume(d),e.exit(i),o=d===40?41:d,l):n(d)}function l(d){return d===o?(e.enter(i),e.consume(d),e.exit(i),e.exit(r),t):(e.enter(s),u(d))}function u(d){return d===o?(e.exit(s),l(o)):d===null?n(d):J(d)?(e.enter("lineEnding"),e.consume(d),e.exit("lineEnding"),fe(e,u,"linePrefix")):(e.enter("chunkString",{contentType:"string"}),c(d))}function c(d){return d===o||d===null||J(d)?(e.exit("chunkString"),u(d)):(e.consume(d),d===92?f:c)}function f(d){return d===o||d===92?(e.consume(d),c):c(d)}}function Qs(e,t){let n;return r;function r(i){return J(i)?(e.enter("lineEnding"),e.consume(i),e.exit("lineEnding"),n=!0,r):ue(i)?fe(e,r,n?"linePrefix":"lineSuffix")(i):t(i)}}const C3={name:"definition",tokenize:N3},E3={partial:!0,tokenize:T3};function N3(e,t,n){const r=this;let i;return s;function s(h){return e.enter("definition"),o(h)}function o(h){return $v.call(r,e,a,n,"definitionLabel","definitionLabelMarker","definitionLabelString")(h)}function a(h){return i=ln(r.sliceSerialize(r.events[r.events.length-1][1]).slice(1,-1)),h===58?(e.enter("definitionMarker"),e.consume(h),e.exit("definitionMarker"),l):n(h)}function l(h){return ve(h)?Qs(e,u)(h):u(h)}function u(h){return Vv(e,c,n,"definitionDestination","definitionDestinationLiteral","definitionDestinationLiteralMarker","definitionDestinationRaw","definitionDestinationString")(h)}function c(h){return e.attempt(E3,f,f)(h)}function f(h){return ue(h)?fe(e,d,"whitespace")(h):d(h)}function d(h){return h===null||J(h)?(e.exit("definition"),r.parser.defined.push(i),t(h)):n(h)}}function T3(e,t,n){return r;function r(a){return ve(a)?Qs(e,i)(a):n(a)}function i(a){return Bv(e,s,n,"definitionTitle","definitionTitleMarker","definitionTitleString")(a)}function s(a){return ue(a)?fe(e,o,"whitespace")(a):o(a)}function o(a){return a===null||J(a)?t(a):n(a)}}const A3={name:"hardBreakEscape",tokenize:P3};function P3(e,t,n){return r;function r(s){return e.enter("hardBreakEscape"),e.consume(s),i}function i(s){return J(s)?(e.exit("hardBreakEscape"),t(s)):n(s)}}const j3={name:"headingAtx",resolve:M3,tokenize:D3};function M3(e,t){let n=e.length-2,r=3,i,s;return e[r][1].type==="whitespace"&&(r+=2),n-2>r&&e[n][1].type==="whitespace"&&(n-=2),e[n][1].type==="atxHeadingSequence"&&(r===n-1||n-4>r&&e[n-2][1].type==="whitespace")&&(n-=r+1===n?2:4),n>r&&(i={type:"atxHeadingText",start:e[r][1].start,end:e[n][1].end},s={type:"chunkText",start:e[r][1].start,end:e[n][1].end,contentType:"text"},Lt(e,r,n-r+1,[["enter",i,t],["enter",s,t],["exit",s,t],["exit",i,t]])),e}function D3(e,t,n){let r=0;return i;function i(c){return e.enter("atxHeading"),s(c)}function s(c){return e.enter("atxHeadingSequence"),o(c)}function o(c){return c===35&&r++<6?(e.consume(c),o):c===null||ve(c)?(e.exit("atxHeadingSequence"),a(c)):n(c)}function a(c){return c===35?(e.enter("atxHeadingSequence"),l(c)):c===null||J(c)?(e.exit("atxHeading"),t(c)):ue(c)?fe(e,a,"whitespace")(c):(e.enter("atxHeadingText"),u(c))}function l(c){return c===35?(e.consume(c),l):(e.exit("atxHeadingSequence"),a(c))}function u(c){return c===null||c===35||ve(c)?(e.exit("atxHeadingText"),a(c)):(e.consume(c),u)}}const I3=["address","article","aside","base","basefont","blockquote","body","caption","center","col","colgroup","dd","details","dialog","dir","div","dl","dt","fieldset","figcaption","figure","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hr","html","iframe","legend","li","link","main","menu","menuitem","nav","noframes","ol","optgroup","option","p","param","search","section","summary","table","tbody","td","tfoot","th","thead","title","tr","track","ul"],xg=["pre","script","style","textarea"],L3={concrete:!0,name:"htmlFlow",resolveTo:F3,tokenize:O3},R3={partial:!0,tokenize:$3},z3={partial:!0,tokenize:V3};function F3(e){let t=e.length;for(;t--&&!(e[t][0]==="enter"&&e[t][1].type==="htmlFlow"););return t>1&&e[t-2][1].type==="linePrefix"&&(e[t][1].start=e[t-2][1].start,e[t+1][1].start=e[t-2][1].start,e.splice(t-2,2)),e}function O3(e,t,n){const r=this;let i,s,o,a,l;return u;function u(E){return c(E)}function c(E){return e.enter("htmlFlow"),e.enter("htmlFlowData"),e.consume(E),f}function f(E){return E===33?(e.consume(E),d):E===47?(e.consume(E),s=!0,y):E===63?(e.consume(E),i=3,r.interrupt?t:b):dt(E)?(e.consume(E),o=String.fromCharCode(E),w):n(E)}function d(E){return E===45?(e.consume(E),i=2,h):E===91?(e.consume(E),i=5,a=0,g):dt(E)?(e.consume(E),i=4,r.interrupt?t:b):n(E)}function h(E){return E===45?(e.consume(E),r.interrupt?t:b):n(E)}function g(E){const q="CDATA[";return E===q.charCodeAt(a++)?(e.consume(E),a===q.length?r.interrupt?t:M:g):n(E)}function y(E){return dt(E)?(e.consume(E),o=String.fromCharCode(E),w):n(E)}function w(E){if(E===null||E===47||E===62||ve(E)){const q=E===47,X=o.toLowerCase();return!q&&!s&&xg.includes(X)?(i=1,r.interrupt?t(E):M(E)):I3.includes(o.toLowerCase())?(i=6,q?(e.consume(E),m):r.interrupt?t(E):M(E)):(i=7,r.interrupt&&!r.parser.lazy[r.now().line]?n(E):s?x(E):v(E))}return E===45||st(E)?(e.consume(E),o+=String.fromCharCode(E),w):n(E)}function m(E){return E===62?(e.consume(E),r.interrupt?t:M):n(E)}function x(E){return ue(E)?(e.consume(E),x):C(E)}function v(E){return E===47?(e.consume(E),C):E===58||E===95||dt(E)?(e.consume(E),k):ue(E)?(e.consume(E),v):C(E)}function k(E){return E===45||E===46||E===58||E===95||st(E)?(e.consume(E),k):N(E)}function N(E){return E===61?(e.consume(E),S):ue(E)?(e.consume(E),N):v(E)}function S(E){return E===null||E===60||E===61||E===62||E===96?n(E):E===34||E===39?(e.consume(E),l=E,A):ue(E)?(e.consume(E),S):P(E)}function A(E){return E===l?(e.consume(E),l=null,D):E===null||J(E)?n(E):(e.consume(E),A)}function P(E){return E===null||E===34||E===39||E===47||E===60||E===61||E===62||E===96||ve(E)?N(E):(e.consume(E),P)}function D(E){return E===47||E===62||ue(E)?v(E):n(E)}function C(E){return E===62?(e.consume(E),L):n(E)}function L(E){return E===null||J(E)?M(E):ue(E)?(e.consume(E),L):n(E)}function M(E){return E===45&&i===2?(e.consume(E),I):E===60&&i===1?(e.consume(E),V):E===62&&i===4?(e.consume(E),F):E===63&&i===3?(e.consume(E),b):E===93&&i===5?(e.consume(E),j):J(E)&&(i===6||i===7)?(e.exit("htmlFlowData"),e.check(R3,H,O)(E)):E===null||J(E)?(e.exit("htmlFlowData"),O(E)):(e.consume(E),M)}function O(E){return e.check(z3,_,H)(E)}function _(E){return e.enter("lineEnding"),e.consume(E),e.exit("lineEnding"),R}function R(E){return E===null||J(E)?O(E):(e.enter("htmlFlowData"),M(E))}function I(E){return E===45?(e.consume(E),b):M(E)}function V(E){return E===47?(e.consume(E),o="",z):M(E)}function z(E){if(E===62){const q=o.toLowerCase();return xg.includes(q)?(e.consume(E),F):M(E)}return dt(E)&&o.length<8?(e.consume(E),o+=String.fromCharCode(E),z):M(E)}function j(E){return E===93?(e.consume(E),b):M(E)}function b(E){return E===62?(e.consume(E),F):E===45&&i===2?(e.consume(E),b):M(E)}function F(E){return E===null||J(E)?(e.exit("htmlFlowData"),H(E)):(e.consume(E),F)}function H(E){return e.exit("htmlFlow"),t(E)}}function V3(e,t,n){const r=this;return i;function i(o){return J(o)?(e.enter("lineEnding"),e.consume(o),e.exit("lineEnding"),s):n(o)}function s(o){return r.parser.lazy[r.now().line]?n(o):t(o)}}function $3(e,t,n){return r;function r(i){return e.enter("lineEnding"),e.consume(i),e.exit("lineEnding"),e.attempt(Ho,t,n)}}const B3={name:"htmlText",tokenize:H3};function H3(e,t,n){const r=this;let i,s,o;return a;function a(b){return e.enter("htmlText"),e.enter("htmlTextData"),e.consume(b),l}function l(b){return b===33?(e.consume(b),u):b===47?(e.consume(b),N):b===63?(e.consume(b),v):dt(b)?(e.consume(b),P):n(b)}function u(b){return b===45?(e.consume(b),c):b===91?(e.consume(b),s=0,g):dt(b)?(e.consume(b),x):n(b)}function c(b){return b===45?(e.consume(b),h):n(b)}function f(b){return b===null?n(b):b===45?(e.consume(b),d):J(b)?(o=f,V(b)):(e.consume(b),f)}function d(b){return b===45?(e.consume(b),h):f(b)}function h(b){return b===62?I(b):b===45?d(b):f(b)}function g(b){const F="CDATA[";return b===F.charCodeAt(s++)?(e.consume(b),s===F.length?y:g):n(b)}function y(b){return b===null?n(b):b===93?(e.consume(b),w):J(b)?(o=y,V(b)):(e.consume(b),y)}function w(b){return b===93?(e.consume(b),m):y(b)}function m(b){return b===62?I(b):b===93?(e.consume(b),m):y(b)}function x(b){return b===null||b===62?I(b):J(b)?(o=x,V(b)):(e.consume(b),x)}function v(b){return b===null?n(b):b===63?(e.consume(b),k):J(b)?(o=v,V(b)):(e.consume(b),v)}function k(b){return b===62?I(b):v(b)}function N(b){return dt(b)?(e.consume(b),S):n(b)}function S(b){return b===45||st(b)?(e.consume(b),S):A(b)}function A(b){return J(b)?(o=A,V(b)):ue(b)?(e.consume(b),A):I(b)}function P(b){return b===45||st(b)?(e.consume(b),P):b===47||b===62||ve(b)?D(b):n(b)}function D(b){return b===47?(e.consume(b),I):b===58||b===95||dt(b)?(e.consume(b),C):J(b)?(o=D,V(b)):ue(b)?(e.consume(b),D):I(b)}function C(b){return b===45||b===46||b===58||b===95||st(b)?(e.consume(b),C):L(b)}function L(b){return b===61?(e.consume(b),M):J(b)?(o=L,V(b)):ue(b)?(e.consume(b),L):D(b)}function M(b){return b===null||b===60||b===61||b===62||b===96?n(b):b===34||b===39?(e.consume(b),i=b,O):J(b)?(o=M,V(b)):ue(b)?(e.consume(b),M):(e.consume(b),_)}function O(b){return b===i?(e.consume(b),i=void 0,R):b===null?n(b):J(b)?(o=O,V(b)):(e.consume(b),O)}function _(b){return b===null||b===34||b===39||b===60||b===61||b===96?n(b):b===47||b===62||ve(b)?D(b):(e.consume(b),_)}function R(b){return b===47||b===62||ve(b)?D(b):n(b)}function I(b){return b===62?(e.consume(b),e.exit("htmlTextData"),e.exit("htmlText"),t):n(b)}function V(b){return e.exit("htmlTextData"),e.enter("lineEnding"),e.consume(b),e.exit("lineEnding"),z}function z(b){return ue(b)?fe(e,j,"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(b):j(b)}function j(b){return e.enter("htmlTextData"),o(b)}}const Lh={name:"labelEnd",resolveAll:q3,resolveTo:K3,tokenize:G3},U3={tokenize:X3},W3={tokenize:Q3},Y3={tokenize:Z3};function q3(e){let t=-1;const n=[];for(;++t=3&&(u===null||J(u))?(e.exit("thematicBreak"),t(u)):n(u)}function l(u){return u===i?(e.consume(u),r++,l):(e.exit("thematicBreakSequence"),ue(u)?fe(e,a,"whitespace")(u):a(u))}}const xt={continuation:{tokenize:lN},exit:cN,name:"list",tokenize:aN},sN={partial:!0,tokenize:fN},oN={partial:!0,tokenize:uN};function aN(e,t,n){const r=this,i=r.events[r.events.length-1];let s=i&&i[1].type==="linePrefix"?i[2].sliceSerialize(i[1],!0).length:0,o=0;return a;function a(h){const g=r.containerState.type||(h===42||h===43||h===45?"listUnordered":"listOrdered");if(g==="listUnordered"?!r.containerState.marker||h===r.containerState.marker:Gf(h)){if(r.containerState.type||(r.containerState.type=g,e.enter(g,{_container:!0})),g==="listUnordered")return e.enter("listItemPrefix"),h===42||h===45?e.check(Wa,n,u)(h):u(h);if(!r.interrupt||h===49)return e.enter("listItemPrefix"),e.enter("listItemValue"),l(h)}return n(h)}function l(h){return Gf(h)&&++o<10?(e.consume(h),l):(!r.interrupt||o<2)&&(r.containerState.marker?h===r.containerState.marker:h===41||h===46)?(e.exit("listItemValue"),u(h)):n(h)}function u(h){return e.enter("listItemMarker"),e.consume(h),e.exit("listItemMarker"),r.containerState.marker=r.containerState.marker||h,e.check(Ho,r.interrupt?n:c,e.attempt(sN,d,f))}function c(h){return r.containerState.initialBlankLine=!0,s++,d(h)}function f(h){return ue(h)?(e.enter("listItemPrefixWhitespace"),e.consume(h),e.exit("listItemPrefixWhitespace"),d):n(h)}function d(h){return r.containerState.size=s+r.sliceSerialize(e.exit("listItemPrefix"),!0).length,t(h)}}function lN(e,t,n){const r=this;return r.containerState._closeFlow=void 0,e.check(Ho,i,s);function i(a){return r.containerState.furtherBlankLines=r.containerState.furtherBlankLines||r.containerState.initialBlankLine,fe(e,t,"listItemIndent",r.containerState.size+1)(a)}function s(a){return r.containerState.furtherBlankLines||!ue(a)?(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,o(a)):(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,e.attempt(oN,t,o)(a))}function o(a){return r.containerState._closeFlow=!0,r.interrupt=void 0,fe(e,e.attempt(xt,t,n),"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(a)}}function uN(e,t,n){const r=this;return fe(e,i,"listItemIndent",r.containerState.size+1);function i(s){const o=r.events[r.events.length-1];return o&&o[1].type==="listItemIndent"&&o[2].sliceSerialize(o[1],!0).length===r.containerState.size?t(s):n(s)}}function cN(e){e.exit(this.containerState.type)}function fN(e,t,n){const r=this;return fe(e,i,"listItemPrefixWhitespace",r.parser.constructs.disable.null.includes("codeIndented")?void 0:5);function i(s){const o=r.events[r.events.length-1];return!ue(s)&&o&&o[1].type==="listItemPrefixWhitespace"?t(s):n(s)}}const vg={name:"setextUnderline",resolveTo:dN,tokenize:hN};function dN(e,t){let n=e.length,r,i,s;for(;n--;)if(e[n][0]==="enter"){if(e[n][1].type==="content"){r=n;break}e[n][1].type==="paragraph"&&(i=n)}else e[n][1].type==="content"&&e.splice(n,1),!s&&e[n][1].type==="definition"&&(s=n);const o={type:"setextHeading",start:{...e[r][1].start},end:{...e[e.length-1][1].end}};return e[i][1].type="setextHeadingText",s?(e.splice(i,0,["enter",o,t]),e.splice(s+1,0,["exit",e[r][1],t]),e[r][1].end={...e[s][1].end}):e[r][1]=o,e.push(["exit",o,t]),e}function hN(e,t,n){const r=this;let i;return s;function s(u){let c=r.events.length,f;for(;c--;)if(r.events[c][1].type!=="lineEnding"&&r.events[c][1].type!=="linePrefix"&&r.events[c][1].type!=="content"){f=r.events[c][1].type==="paragraph";break}return!r.parser.lazy[r.now().line]&&(r.interrupt||f)?(e.enter("setextHeadingLine"),i=u,o(u)):n(u)}function o(u){return e.enter("setextHeadingLineSequence"),a(u)}function a(u){return u===i?(e.consume(u),a):(e.exit("setextHeadingLineSequence"),ue(u)?fe(e,l,"lineSuffix")(u):l(u))}function l(u){return u===null||J(u)?(e.exit("setextHeadingLine"),t(u)):n(u)}}const pN={tokenize:mN};function mN(e){const t=this,n=e.attempt(Ho,r,e.attempt(this.parser.constructs.flowInitial,i,fe(e,e.attempt(this.parser.constructs.flow,i,e.attempt(w3,i)),"linePrefix")));return n;function r(s){if(s===null){e.consume(s);return}return e.enter("lineEndingBlank"),e.consume(s),e.exit("lineEndingBlank"),t.currentConstruct=void 0,n}function i(s){if(s===null){e.consume(s);return}return e.enter("lineEnding"),e.consume(s),e.exit("lineEnding"),t.currentConstruct=void 0,n}}const gN={resolveAll:Uv()},yN=Hv("string"),xN=Hv("text");function Hv(e){return{resolveAll:Uv(e==="text"?vN:void 0),tokenize:t};function t(n){const r=this,i=this.parser.constructs[e],s=n.attempt(i,o,a);return o;function o(c){return u(c)?s(c):a(c)}function a(c){if(c===null){n.consume(c);return}return n.enter("data"),n.consume(c),l}function l(c){return u(c)?(n.exit("data"),s(c)):(n.consume(c),l)}function u(c){if(c===null)return!0;const f=i[c];let d=-1;if(f)for(;++d-1){const a=o[0];typeof a=="string"?o[0]=a.slice(r):o.shift()}s>0&&o.push(e[i].slice(0,s))}return o}function MN(e,t){let n=-1;const r=[];let i;for(;++n0){const xe=ee.tokenStack[ee.tokenStack.length-1];(xe[1]||kg).call(ee,void 0,xe[0])}for(Y.position={start:Gn($.length>0?$[0][1].start:{line:1,column:1,offset:0}),end:Gn($.length>0?$[$.length-2][1].end:{line:1,column:1,offset:0})},he=-1;++he1?"-"+a:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(o)}]};e.patch(t,l);const u={type:"element",tagName:"sup",properties:{},children:[l]};return e.patch(t,u),e.applyData(t,u)}function GN(e,t){const n={type:"element",tagName:"h"+t.depth,properties:{},children:e.all(t)};return e.patch(t,n),e.applyData(t,n)}function XN(e,t){if(e.options.allowDangerousHtml){const n={type:"raw",value:t.value};return e.patch(t,n),e.applyData(t,n)}}function qv(e,t){const n=t.referenceType;let r="]";if(n==="collapsed"?r+="[]":n==="full"&&(r+="["+(t.label||t.identifier)+"]"),t.type==="imageReference")return[{type:"text",value:"!["+t.alt+r}];const i=e.all(t),s=i[0];s&&s.type==="text"?s.value="["+s.value:i.unshift({type:"text",value:"["});const o=i[i.length-1];return o&&o.type==="text"?o.value+=r:i.push({type:"text",value:r}),i}function QN(e,t){const n=String(t.identifier).toUpperCase(),r=e.definitionById.get(n);if(!r)return qv(e,t);const i={src:hs(r.url||""),alt:t.alt};r.title!==null&&r.title!==void 0&&(i.title=r.title);const s={type:"element",tagName:"img",properties:i,children:[]};return e.patch(t,s),e.applyData(t,s)}function ZN(e,t){const n={src:hs(t.url)};t.alt!==null&&t.alt!==void 0&&(n.alt=t.alt),t.title!==null&&t.title!==void 0&&(n.title=t.title);const r={type:"element",tagName:"img",properties:n,children:[]};return e.patch(t,r),e.applyData(t,r)}function JN(e,t){const n={type:"text",value:t.value.replace(/\r?\n|\r/g," ")};e.patch(t,n);const r={type:"element",tagName:"code",properties:{},children:[n]};return e.patch(t,r),e.applyData(t,r)}function eT(e,t){const n=String(t.identifier).toUpperCase(),r=e.definitionById.get(n);if(!r)return qv(e,t);const i={href:hs(r.url||"")};r.title!==null&&r.title!==void 0&&(i.title=r.title);const s={type:"element",tagName:"a",properties:i,children:e.all(t)};return e.patch(t,s),e.applyData(t,s)}function tT(e,t){const n={href:hs(t.url)};t.title!==null&&t.title!==void 0&&(n.title=t.title);const r={type:"element",tagName:"a",properties:n,children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function nT(e,t,n){const r=e.all(t),i=n?rT(n):Kv(t),s={},o=[];if(typeof t.checked=="boolean"){const c=r[0];let f;c&&c.type==="element"&&c.tagName==="p"?f=c:(f={type:"element",tagName:"p",properties:{},children:[]},r.unshift(f)),f.children.length>0&&f.children.unshift({type:"text",value:" "}),f.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),s.className=["task-list-item"]}let a=-1;for(;++a1}function iT(e,t){const n={},r=e.all(t);let i=-1;for(typeof t.start=="number"&&t.start!==1&&(n.start=t.start);++i0){const o={type:"element",tagName:"tbody",properties:{},children:e.wrap(n,!0)},a=Ah(t.children[1]),l=Nv(t.children[t.children.length-1]);a&&l&&(o.position={start:a,end:l}),i.push(o)}const s={type:"element",tagName:"table",properties:{},children:e.wrap(i,!0)};return e.patch(t,s),e.applyData(t,s)}function uT(e,t,n){const r=n?n.children:void 0,s=(r?r.indexOf(t):1)===0?"th":"td",o=n&&n.type==="table"?n.align:void 0,a=o?o.length:t.children.length;let l=-1;const u=[];for(;++l0,!0),r[0]),i=r.index+r[0].length,r=n.exec(t);return s.push(_g(t.slice(i),i>0,!1)),s.join("")}function _g(e,t,n){let r=0,i=e.length;if(t){let s=e.codePointAt(r);for(;s===bg||s===Sg;)r++,s=e.codePointAt(r)}if(n){let s=e.codePointAt(i-1);for(;s===bg||s===Sg;)i--,s=e.codePointAt(i-1)}return i>r?e.slice(r,i):""}function dT(e,t){const n={type:"text",value:fT(String(t.value))};return e.patch(t,n),e.applyData(t,n)}function hT(e,t){const n={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,n),e.applyData(t,n)}const pT={blockquote:HN,break:UN,code:WN,delete:YN,emphasis:qN,footnoteReference:KN,heading:GN,html:XN,imageReference:QN,image:ZN,inlineCode:JN,linkReference:eT,link:tT,listItem:nT,list:iT,paragraph:sT,root:oT,strong:aT,table:lT,tableCell:cT,tableRow:uT,text:dT,thematicBreak:hT,toml:ma,yaml:ma,definition:ma,footnoteDefinition:ma};function ma(){}const Gv=-1,du=0,Zs=1,Cl=2,Rh=3,zh=4,Fh=5,Oh=6,Xv=7,Qv=8,Cg=typeof self=="object"?self:globalThis,mT=(e,t)=>{const n=(i,s)=>(e.set(s,i),i),r=i=>{if(e.has(i))return e.get(i);const[s,o]=t[i];switch(s){case du:case Gv:return n(o,i);case Zs:{const a=n([],i);for(const l of o)a.push(r(l));return a}case Cl:{const a=n({},i);for(const[l,u]of o)a[r(l)]=r(u);return a}case Rh:return n(new Date(o),i);case zh:{const{source:a,flags:l}=o;return n(new RegExp(a,l),i)}case Fh:{const a=n(new Map,i);for(const[l,u]of o)a.set(r(l),r(u));return a}case Oh:{const a=n(new Set,i);for(const l of o)a.add(r(l));return a}case Xv:{const{name:a,message:l}=o;return n(new Cg[a](l),i)}case Qv:return n(BigInt(o),i);case"BigInt":return n(Object(BigInt(o)),i);case"ArrayBuffer":return n(new Uint8Array(o).buffer,o);case"DataView":{const{buffer:a}=new Uint8Array(o);return n(new DataView(a),o)}}return n(new Cg[s](o),i)};return r},Eg=e=>mT(new Map,e)(0),fi="",{toString:gT}={},{keys:yT}=Object,_s=e=>{const t=typeof e;if(t!=="object"||!e)return[du,t];const n=gT.call(e).slice(8,-1);switch(n){case"Array":return[Zs,fi];case"Object":return[Cl,fi];case"Date":return[Rh,fi];case"RegExp":return[zh,fi];case"Map":return[Fh,fi];case"Set":return[Oh,fi];case"DataView":return[Zs,n]}return n.includes("Array")?[Zs,n]:n.includes("Error")?[Xv,n]:[Cl,n]},ga=([e,t])=>e===du&&(t==="function"||t==="symbol"),xT=(e,t,n,r)=>{const i=(o,a)=>{const l=r.push(o)-1;return n.set(a,l),l},s=o=>{if(n.has(o))return n.get(o);let[a,l]=_s(o);switch(a){case du:{let c=o;switch(l){case"bigint":a=Qv,c=o.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+l);c=null;break;case"undefined":return i([Gv],o)}return i([a,c],o)}case Zs:{if(l){let d=o;return l==="DataView"?d=new Uint8Array(o.buffer):l==="ArrayBuffer"&&(d=new Uint8Array(o)),i([l,[...d]],o)}const c=[],f=i([a,c],o);for(const d of o)c.push(s(d));return f}case Cl:{if(l)switch(l){case"BigInt":return i([l,o.toString()],o);case"Boolean":case"Number":case"String":return i([l,o.valueOf()],o)}if(t&&"toJSON"in o)return s(o.toJSON());const c=[],f=i([a,c],o);for(const d of yT(o))(e||!ga(_s(o[d])))&&c.push([s(d),s(o[d])]);return f}case Rh:return i([a,o.toISOString()],o);case zh:{const{source:c,flags:f}=o;return i([a,{source:c,flags:f}],o)}case Fh:{const c=[],f=i([a,c],o);for(const[d,h]of o)(e||!(ga(_s(d))||ga(_s(h))))&&c.push([s(d),s(h)]);return f}case Oh:{const c=[],f=i([a,c],o);for(const d of o)(e||!ga(_s(d)))&&c.push(s(d));return f}}const{message:u}=o;return i([a,{name:l,message:u}],o)};return s},Ng=(e,{json:t,lossy:n}={})=>{const r=[];return xT(!(t||n),!!t,new Map,r)(e),r},El=typeof structuredClone=="function"?(e,t)=>t&&("json"in t||"lossy"in t)?Eg(Ng(e,t)):structuredClone(e):(e,t)=>Eg(Ng(e,t));function vT(e,t){const n=[{type:"text",value:"↩"}];return t>1&&n.push({type:"element",tagName:"sup",properties:{},children:[{type:"text",value:String(t)}]}),n}function wT(e,t){return"Back to reference "+(e+1)+(t>1?"-"+t:"")}function kT(e){const t=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",n=e.options.footnoteBackContent||vT,r=e.options.footnoteBackLabel||wT,i=e.options.footnoteLabel||"Footnotes",s=e.options.footnoteLabelTagName||"h2",o=e.options.footnoteLabelProperties||{className:["sr-only"]},a=[];let l=-1;for(;++l0&&g.push({type:"text",value:" "});let x=typeof n=="string"?n:n(l,h);typeof x=="string"&&(x={type:"text",value:x}),g.push({type:"element",tagName:"a",properties:{href:"#"+t+"fnref-"+d+(h>1?"-"+h:""),dataFootnoteBackref:"",ariaLabel:typeof r=="string"?r:r(l,h),className:["data-footnote-backref"]},children:Array.isArray(x)?x:[x]})}const w=c[c.length-1];if(w&&w.type==="element"&&w.tagName==="p"){const x=w.children[w.children.length-1];x&&x.type==="text"?x.value+=" ":w.children.push({type:"text",value:" "}),w.children.push(...g)}else c.push(...g);const m={type:"element",tagName:"li",properties:{id:t+"fn-"+d},children:e.wrap(c,!0)};e.patch(u,m),a.push(m)}if(a.length!==0)return{type:"element",tagName:"section",properties:{dataFootnotes:!0,className:["footnotes"]},children:[{type:"element",tagName:s,properties:{...El(o),id:"footnote-label"},children:[{type:"text",value:i}]},{type:"text",value:` +`},{type:"element",tagName:"ol",properties:{},children:e.wrap(a,!0)},{type:"text",value:` +`}]}}const hu=function(e){if(e==null)return CT;if(typeof e=="function")return pu(e);if(typeof e=="object")return Array.isArray(e)?bT(e):ST(e);if(typeof e=="string")return _T(e);throw new Error("Expected function, string, or object as test")};function bT(e){const t=[];let n=-1;for(;++n":""))+")"})}return d;function d(){let h=Zv,g,y,w;if((!t||s(l,u,c[c.length-1]||void 0))&&(h=AT(n(l,c)),h[0]===Qf))return h;if("children"in l&&l.children){const m=l;if(m.children&&h[0]!==TT)for(y=(r?m.children.length:-1)+o,w=c.concat(m);y>-1&&y0&&n.push({type:"text",value:` +`}),n}function Tg(e){let t=0,n=e.charCodeAt(t);for(;n===9||n===32;)t++,n=e.charCodeAt(t);return e.slice(t)}function Ag(e,t){const n=jT(e,t),r=n.one(e,void 0),i=kT(n),s=Array.isArray(r)?{type:"root",children:r}:r||{type:"root",children:[]};return i&&s.children.push({type:"text",value:` +`},i),s}function RT(e,t){return e&&"run"in e?async function(n,r){const i=Ag(n,{file:r,...t});await e.run(i,r)}:function(n,r){return Ag(n,{file:r,...e||t})}}function Pg(e){if(e)throw e}var Ya=Object.prototype.hasOwnProperty,ew=Object.prototype.toString,jg=Object.defineProperty,Mg=Object.getOwnPropertyDescriptor,Dg=function(t){return typeof Array.isArray=="function"?Array.isArray(t):ew.call(t)==="[object Array]"},Ig=function(t){if(!t||ew.call(t)!=="[object Object]")return!1;var n=Ya.call(t,"constructor"),r=t.constructor&&t.constructor.prototype&&Ya.call(t.constructor.prototype,"isPrototypeOf");if(t.constructor&&!n&&!r)return!1;var i;for(i in t);return typeof i>"u"||Ya.call(t,i)},Lg=function(t,n){jg&&n.name==="__proto__"?jg(t,n.name,{enumerable:!0,configurable:!0,value:n.newValue,writable:!0}):t[n.name]=n.newValue},Rg=function(t,n){if(n==="__proto__")if(Ya.call(t,n)){if(Mg)return Mg(t,n).value}else return;return t[n]},zT=function e(){var t,n,r,i,s,o,a=arguments[0],l=1,u=arguments.length,c=!1;for(typeof a=="boolean"&&(c=a,a=arguments[1]||{},l=2),(a==null||typeof a!="object"&&typeof a!="function")&&(a={});lo.length;let l;a&&o.push(i);try{l=e.apply(this,o)}catch(u){const c=u;if(a&&n)throw c;return i(c)}a||(l&&l.then&&typeof l.then=="function"?l.then(s,i):l instanceof Error?i(l):s(l))}function i(o,...a){n||(n=!0,t(o,...a))}function s(o){i(null,o)}}const gn={basename:VT,dirname:$T,extname:BT,join:HT,sep:"/"};function VT(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Uo(e);let n=0,r=-1,i=e.length,s;if(t===void 0||t.length===0||t.length>e.length){for(;i--;)if(e.codePointAt(i)===47){if(s){n=i+1;break}}else r<0&&(s=!0,r=i+1);return r<0?"":e.slice(n,r)}if(t===e)return"";let o=-1,a=t.length-1;for(;i--;)if(e.codePointAt(i)===47){if(s){n=i+1;break}}else o<0&&(s=!0,o=i+1),a>-1&&(e.codePointAt(i)===t.codePointAt(a--)?a<0&&(r=i):(a=-1,r=o));return n===r?r=o:r<0&&(r=e.length),e.slice(n,r)}function $T(e){if(Uo(e),e.length===0)return".";let t=-1,n=e.length,r;for(;--n;)if(e.codePointAt(n)===47){if(r){t=n;break}}else r||(r=!0);return t<0?e.codePointAt(0)===47?"/":".":t===1&&e.codePointAt(0)===47?"//":e.slice(0,t)}function BT(e){Uo(e);let t=e.length,n=-1,r=0,i=-1,s=0,o;for(;t--;){const a=e.codePointAt(t);if(a===47){if(o){r=t+1;break}continue}n<0&&(o=!0,n=t+1),a===46?i<0?i=t:s!==1&&(s=1):i>-1&&(s=-1)}return i<0||n<0||s===0||s===1&&i===n-1&&i===r+1?"":e.slice(i,n)}function HT(...e){let t=-1,n;for(;++t0&&e.codePointAt(e.length-1)===47&&(n+="/"),t?"/"+n:n}function WT(e,t){let n="",r=0,i=-1,s=0,o=-1,a,l;for(;++o<=e.length;){if(o2){if(l=n.lastIndexOf("/"),l!==n.length-1){l<0?(n="",r=0):(n=n.slice(0,l),r=n.length-1-n.lastIndexOf("/")),i=o,s=0;continue}}else if(n.length>0){n="",r=0,i=o,s=0;continue}}t&&(n=n.length>0?n+"/..":"..",r=2)}else n.length>0?n+="/"+e.slice(i+1,o):n=e.slice(i+1,o),r=o-i-1;i=o,s=0}else a===46&&s>-1?s++:s=-1}return n}function Uo(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const YT={cwd:qT};function qT(){return"/"}function ed(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function KT(e){if(typeof e=="string")e=new URL(e);else if(!ed(e)){const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code="ERR_INVALID_ARG_TYPE",t}if(e.protocol!=="file:"){const t=new TypeError("The URL must be of scheme file");throw t.code="ERR_INVALID_URL_SCHEME",t}return GT(e)}function GT(e){if(e.hostname!==""){const r=new TypeError('File URL host must be "localhost" or empty on darwin');throw r.code="ERR_INVALID_FILE_URL_HOST",r}const t=e.pathname;let n=-1;for(;++n0){let[h,...g]=c;const y=r[d][1];Jf(y)&&Jf(h)&&(h=hc(!0,y,h)),r[d]=[u,h,...g]}}}}const JT=new $h().freeze();function yc(e,t){if(typeof t!="function")throw new TypeError("Cannot `"+e+"` without `parser`")}function xc(e,t){if(typeof t!="function")throw new TypeError("Cannot `"+e+"` without `compiler`")}function vc(e,t){if(t)throw new Error("Cannot call `"+e+"` on a frozen processor.\nCreate a new processor first, by calling it: use `processor()` instead of `processor`.")}function Fg(e){if(!Jf(e)||typeof e.type!="string")throw new TypeError("Expected node, got `"+e+"`")}function Og(e,t,n){if(!n)throw new Error("`"+e+"` finished async. Use `"+t+"` instead")}function ya(e){return e4(e)?e:new tw(e)}function e4(e){return!!(e&&typeof e=="object"&&"message"in e&&"messages"in e)}function t4(e){return typeof e=="string"||n4(e)}function n4(e){return!!(e&&typeof e=="object"&&"byteLength"in e&&"byteOffset"in e)}const r4="https://github.com/remarkjs/react-markdown/blob/main/changelog.md",Vg=[],$g={allowDangerousHtml:!0},i4=/^(https?|ircs?|mailto|xmpp)$/i,s4=[{from:"astPlugins",id:"remove-buggy-html-in-markdown-parser"},{from:"allowDangerousHtml",id:"remove-buggy-html-in-markdown-parser"},{from:"allowNode",id:"replace-allownode-allowedtypes-and-disallowedtypes",to:"allowElement"},{from:"allowedTypes",id:"replace-allownode-allowedtypes-and-disallowedtypes",to:"allowedElements"},{from:"disallowedTypes",id:"replace-allownode-allowedtypes-and-disallowedtypes",to:"disallowedElements"},{from:"escapeHtml",id:"remove-buggy-html-in-markdown-parser"},{from:"includeElementIndex",id:"#remove-includeelementindex"},{from:"includeNodeIndex",id:"change-includenodeindex-to-includeelementindex"},{from:"linkTarget",id:"remove-linktarget"},{from:"plugins",id:"change-plugins-to-remarkplugins",to:"remarkPlugins"},{from:"rawSourcePos",id:"#remove-rawsourcepos"},{from:"renderers",id:"change-renderers-to-components",to:"components"},{from:"source",id:"change-source-to-children",to:"children"},{from:"sourcePos",id:"#remove-sourcepos"},{from:"transformImageUri",id:"#add-urltransform",to:"urlTransform"},{from:"transformLinkUri",id:"#add-urltransform",to:"urlTransform"}];function Bg(e){const t=o4(e),n=a4(e);return l4(t.runSync(t.parse(n),n),e)}function o4(e){const t=e.rehypePlugins||Vg,n=e.remarkPlugins||Vg,r=e.remarkRehypeOptions?{...e.remarkRehypeOptions,...$g}:$g;return JT().use(BN).use(n).use(RT,r).use(t)}function a4(e){const t=e.children||"",n=new tw;return typeof t=="string"&&(n.value=t),n}function l4(e,t){const n=t.allowedElements,r=t.allowElement,i=t.components,s=t.disallowedElements,o=t.skipHtml,a=t.unwrapDisallowed,l=t.urlTransform||u4;for(const c of s4)Object.hasOwn(t,c.from)&&(""+c.from+(c.to?"use `"+c.to+"` instead":"remove it")+r4+c.id,void 0);return t.className&&(e={type:"element",tagName:"div",properties:{className:t.className},children:e.type==="root"?e.children:[e]}),Vh(e,u),CE(e,{Fragment:p.Fragment,components:i,ignoreInvalidStyle:!0,jsx:p.jsx,jsxs:p.jsxs,passKeys:!0,passNode:!0});function u(c,f,d){if(c.type==="raw"&&d&&typeof f=="number")return o?d.children.splice(f,1):d.children[f]={type:"text",value:c.value},f;if(c.type==="element"){let h;for(h in cc)if(Object.hasOwn(cc,h)&&Object.hasOwn(c.properties,h)){const g=c.properties[h],y=cc[h];(y===null||y.includes(c.tagName))&&(c.properties[h]=l(String(g||""),h,c))}}if(c.type==="element"){let h=n?!n.includes(c.tagName):s?s.includes(c.tagName):!1;if(!h&&r&&typeof f=="number"&&(h=!r(c,f,d)),h&&d&&typeof f=="number")return a&&c.children?d.children.splice(f,1,...c.children):d.children.splice(f,1),f}}}function u4(e){const t=e.indexOf(":"),n=e.indexOf("?"),r=e.indexOf("#"),i=e.indexOf("/");return t===-1||i!==-1&&t>i||n!==-1&&t>n||r!==-1&&t>r||i4.test(e.slice(0,t))?e:""}function Hg(e,t){const n=String(e);if(typeof t!="string")throw new TypeError("Expected character");let r=0,i=n.indexOf(t);for(;i!==-1;)r++,i=n.indexOf(t,i+t.length);return r}function c4(e){if(typeof e!="string")throw new TypeError("Expected a string");return e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&").replace(/-/g,"\\x2d")}function f4(e,t,n){const i=hu((n||{}).ignore||[]),s=d4(t);let o=-1;for(;++o0?{type:"text",value:S}:void 0),S===!1?d.lastIndex=k+1:(g!==k&&x.push({type:"text",value:u.value.slice(g,k)}),Array.isArray(S)?x.push(...S):S&&x.push(S),g=k+v[0].length,m=!0),!d.global)break;v=d.exec(u.value)}return m?(g?\]}]+$/.exec(e);if(!t)return[e,void 0];e=e.slice(0,t.index);let n=t[0],r=n.indexOf(")");const i=Hg(e,"(");let s=Hg(e,")");for(;r!==-1&&i>s;)e+=n.slice(0,r+1),n=n.slice(r+1),r=n.indexOf(")"),s++;return[e,n]}function nw(e,t){const n=e.input.charCodeAt(e.index-1);return(e.index===0||ni(n)||cu(n))&&(!t||n!==47)}rw.peek=L4;function N4(){this.buffer()}function T4(e){this.enter({type:"footnoteReference",identifier:"",label:""},e)}function A4(){this.buffer()}function P4(e){this.enter({type:"footnoteDefinition",identifier:"",label:"",children:[]},e)}function j4(e){const t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=ln(this.sliceSerialize(e)).toLowerCase(),n.label=t}function M4(e){this.exit(e)}function D4(e){const t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=ln(this.sliceSerialize(e)).toLowerCase(),n.label=t}function I4(e){this.exit(e)}function L4(){return"["}function rw(e,t,n,r){const i=n.createTracker(r);let s=i.move("[^");const o=n.enter("footnoteReference"),a=n.enter("reference");return s+=i.move(n.safe(n.associationId(e),{after:"]",before:s})),a(),o(),s+=i.move("]"),s}function R4(){return{enter:{gfmFootnoteCallString:N4,gfmFootnoteCall:T4,gfmFootnoteDefinitionLabelString:A4,gfmFootnoteDefinition:P4},exit:{gfmFootnoteCallString:j4,gfmFootnoteCall:M4,gfmFootnoteDefinitionLabelString:D4,gfmFootnoteDefinition:I4}}}function z4(e){let t=!1;return e&&e.firstLineBlank&&(t=!0),{handlers:{footnoteDefinition:n,footnoteReference:rw},unsafe:[{character:"[",inConstruct:["label","phrasing","reference"]}]};function n(r,i,s,o){const a=s.createTracker(o);let l=a.move("[^");const u=s.enter("footnoteDefinition"),c=s.enter("label");return l+=a.move(s.safe(s.associationId(r),{before:l,after:"]"})),c(),l+=a.move("]:"),r.children&&r.children.length>0&&(a.shift(4),l+=a.move((t?` +`:" ")+s.indentLines(s.containerFlow(r,a.current()),t?iw:F4))),u(),l}}function F4(e,t,n){return t===0?e:iw(e,t,n)}function iw(e,t,n){return(n?"":" ")+e}const O4=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];sw.peek=U4;function V4(){return{canContainEols:["delete"],enter:{strikethrough:B4},exit:{strikethrough:H4}}}function $4(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:O4}],handlers:{delete:sw}}}function B4(e){this.enter({type:"delete",children:[]},e)}function H4(e){this.exit(e)}function sw(e,t,n,r){const i=n.createTracker(r),s=n.enter("strikethrough");let o=i.move("~~");return o+=n.containerPhrasing(e,{...i.current(),before:o,after:"~"}),o+=i.move("~~"),s(),o}function U4(){return"~"}function W4(e){return e.length}function Y4(e,t){const n=t||{},r=(n.align||[]).concat(),i=n.stringLength||W4,s=[],o=[],a=[],l=[];let u=0,c=-1;for(;++cu&&(u=e[c].length);++ml[m])&&(l[m]=v)}y.push(x)}o[c]=y,a[c]=w}let f=-1;if(typeof r=="object"&&"length"in r)for(;++fl[f]&&(l[f]=x),h[f]=x),d[f]=v}o.splice(1,0,d),a.splice(1,0,h),c=-1;const g=[];for(;++c "),s.shift(2);const o=n.indentLines(n.containerFlow(e,s.current()),G4);return i(),o}function G4(e,t,n){return">"+(n?"":" ")+e}function X4(e,t){return Wg(e,t.inConstruct,!0)&&!Wg(e,t.notInConstruct,!1)}function Wg(e,t,n){if(typeof t=="string"&&(t=[t]),!t||t.length===0)return n;let r=-1;for(;++ro&&(o=s):s=1,i=r+t.length,r=n.indexOf(t,i);return o}function Z4(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function J4(e){const t=e.options.fence||"`";if(t!=="`"&&t!=="~")throw new Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function eA(e,t,n,r){const i=J4(n),s=e.value||"",o=i==="`"?"GraveAccent":"Tilde";if(Z4(e,n)){const f=n.enter("codeIndented"),d=n.indentLines(s,tA);return f(),d}const a=n.createTracker(r),l=i.repeat(Math.max(Q4(s,i)+1,3)),u=n.enter("codeFenced");let c=a.move(l);if(e.lang){const f=n.enter(`codeFencedLang${o}`);c+=a.move(n.safe(e.lang,{before:c,after:" ",encode:["`"],...a.current()})),f()}if(e.lang&&e.meta){const f=n.enter(`codeFencedMeta${o}`);c+=a.move(" "),c+=a.move(n.safe(e.meta,{before:c,after:` +`,encode:["`"],...a.current()})),f()}return c+=a.move(` +`),s&&(c+=a.move(s+` +`)),c+=a.move(l),u(),c}function tA(e,t,n){return(n?"":" ")+e}function Bh(e){const t=e.options.quote||'"';if(t!=='"'&&t!=="'")throw new Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function nA(e,t,n,r){const i=Bh(n),s=i==='"'?"Quote":"Apostrophe",o=n.enter("definition");let a=n.enter("label");const l=n.createTracker(r);let u=l.move("[");return u+=l.move(n.safe(n.associationId(e),{before:u,after:"]",...l.current()})),u+=l.move("]: "),a(),!e.url||/[\0- \u007F]/.test(e.url)?(a=n.enter("destinationLiteral"),u+=l.move("<"),u+=l.move(n.safe(e.url,{before:u,after:">",...l.current()})),u+=l.move(">")):(a=n.enter("destinationRaw"),u+=l.move(n.safe(e.url,{before:u,after:e.title?" ":` +`,...l.current()}))),a(),e.title&&(a=n.enter(`title${s}`),u+=l.move(" "+i),u+=l.move(n.safe(e.title,{before:u,after:i,...l.current()})),u+=l.move(i),a()),o(),u}function rA(e){const t=e.options.emphasis||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function So(e){return"&#x"+e.toString(16).toUpperCase()+";"}function Nl(e,t,n){const r=ns(e),i=ns(t);return r===void 0?i===void 0?n==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:i===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:r===1?i===void 0?{inside:!1,outside:!1}:i===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:i===void 0?{inside:!1,outside:!1}:i===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}ow.peek=iA;function ow(e,t,n,r){const i=rA(n),s=n.enter("emphasis"),o=n.createTracker(r),a=o.move(i);let l=o.move(n.containerPhrasing(e,{after:i,before:a,...o.current()}));const u=l.charCodeAt(0),c=Nl(r.before.charCodeAt(r.before.length-1),u,i);c.inside&&(l=So(u)+l.slice(1));const f=l.charCodeAt(l.length-1),d=Nl(r.after.charCodeAt(0),f,i);d.inside&&(l=l.slice(0,-1)+So(f));const h=o.move(i);return s(),n.attentionEncodeSurroundingInfo={after:d.outside,before:c.outside},a+l+h}function iA(e,t,n){return n.options.emphasis||"*"}function sA(e,t){let n=!1;return Vh(e,function(r){if("value"in r&&/\r?\n|\r/.test(r.value)||r.type==="break")return n=!0,Qf}),!!((!e.depth||e.depth<3)&&Dh(e)&&(t.options.setext||n))}function oA(e,t,n,r){const i=Math.max(Math.min(6,e.depth||1),1),s=n.createTracker(r);if(sA(e,n)){const c=n.enter("headingSetext"),f=n.enter("phrasing"),d=n.containerPhrasing(e,{...s.current(),before:` +`,after:` +`});return f(),c(),d+` +`+(i===1?"=":"-").repeat(d.length-(Math.max(d.lastIndexOf("\r"),d.lastIndexOf(` +`))+1))}const o="#".repeat(i),a=n.enter("headingAtx"),l=n.enter("phrasing");s.move(o+" ");let u=n.containerPhrasing(e,{before:"# ",after:` +`,...s.current()});return/^[\t ]/.test(u)&&(u=So(u.charCodeAt(0))+u.slice(1)),u=u?o+" "+u:o,n.options.closeAtx&&(u+=" "+o),l(),a(),u}aw.peek=aA;function aw(e){return e.value||""}function aA(){return"<"}lw.peek=lA;function lw(e,t,n,r){const i=Bh(n),s=i==='"'?"Quote":"Apostrophe",o=n.enter("image");let a=n.enter("label");const l=n.createTracker(r);let u=l.move("![");return u+=l.move(n.safe(e.alt,{before:u,after:"]",...l.current()})),u+=l.move("]("),a(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(a=n.enter("destinationLiteral"),u+=l.move("<"),u+=l.move(n.safe(e.url,{before:u,after:">",...l.current()})),u+=l.move(">")):(a=n.enter("destinationRaw"),u+=l.move(n.safe(e.url,{before:u,after:e.title?" ":")",...l.current()}))),a(),e.title&&(a=n.enter(`title${s}`),u+=l.move(" "+i),u+=l.move(n.safe(e.title,{before:u,after:i,...l.current()})),u+=l.move(i),a()),u+=l.move(")"),o(),u}function lA(){return"!"}uw.peek=uA;function uw(e,t,n,r){const i=e.referenceType,s=n.enter("imageReference");let o=n.enter("label");const a=n.createTracker(r);let l=a.move("![");const u=n.safe(e.alt,{before:l,after:"]",...a.current()});l+=a.move(u+"]["),o();const c=n.stack;n.stack=[],o=n.enter("reference");const f=n.safe(n.associationId(e),{before:l,after:"]",...a.current()});return o(),n.stack=c,s(),i==="full"||!u||u!==f?l+=a.move(f+"]"):i==="shortcut"?l=l.slice(0,-1):l+=a.move("]"),l}function uA(){return"!"}cw.peek=cA;function cw(e,t,n){let r=e.value||"",i="`",s=-1;for(;new RegExp("(^|[^`])"+i+"([^`]|$)").test(r);)i+="`";for(/[^ \r\n]/.test(r)&&(/^[ \r\n]/.test(r)&&/[ \r\n]$/.test(r)||/^`|`$/.test(r))&&(r=" "+r+" ");++s\u007F]/.test(e.url))}dw.peek=fA;function dw(e,t,n,r){const i=Bh(n),s=i==='"'?"Quote":"Apostrophe",o=n.createTracker(r);let a,l;if(fw(e,n)){const c=n.stack;n.stack=[],a=n.enter("autolink");let f=o.move("<");return f+=o.move(n.containerPhrasing(e,{before:f,after:">",...o.current()})),f+=o.move(">"),a(),n.stack=c,f}a=n.enter("link"),l=n.enter("label");let u=o.move("[");return u+=o.move(n.containerPhrasing(e,{before:u,after:"](",...o.current()})),u+=o.move("]("),l(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(l=n.enter("destinationLiteral"),u+=o.move("<"),u+=o.move(n.safe(e.url,{before:u,after:">",...o.current()})),u+=o.move(">")):(l=n.enter("destinationRaw"),u+=o.move(n.safe(e.url,{before:u,after:e.title?" ":")",...o.current()}))),l(),e.title&&(l=n.enter(`title${s}`),u+=o.move(" "+i),u+=o.move(n.safe(e.title,{before:u,after:i,...o.current()})),u+=o.move(i),l()),u+=o.move(")"),a(),u}function fA(e,t,n){return fw(e,n)?"<":"["}hw.peek=dA;function hw(e,t,n,r){const i=e.referenceType,s=n.enter("linkReference");let o=n.enter("label");const a=n.createTracker(r);let l=a.move("[");const u=n.containerPhrasing(e,{before:l,after:"]",...a.current()});l+=a.move(u+"]["),o();const c=n.stack;n.stack=[],o=n.enter("reference");const f=n.safe(n.associationId(e),{before:l,after:"]",...a.current()});return o(),n.stack=c,s(),i==="full"||!u||u!==f?l+=a.move(f+"]"):i==="shortcut"?l=l.slice(0,-1):l+=a.move("]"),l}function dA(){return"["}function Hh(e){const t=e.options.bullet||"*";if(t!=="*"&&t!=="+"&&t!=="-")throw new Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function hA(e){const t=Hh(e),n=e.options.bulletOther;if(!n)return t==="*"?"-":"*";if(n!=="*"&&n!=="+"&&n!=="-")throw new Error("Cannot serialize items with `"+n+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(n===t)throw new Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+n+"`) to be different");return n}function pA(e){const t=e.options.bulletOrdered||".";if(t!=="."&&t!==")")throw new Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function pw(e){const t=e.options.rule||"*";if(t!=="*"&&t!=="-"&&t!=="_")throw new Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function mA(e,t,n,r){const i=n.enter("list"),s=n.bulletCurrent;let o=e.ordered?pA(n):Hh(n);const a=e.ordered?o==="."?")":".":hA(n);let l=t&&n.bulletLastUsed?o===n.bulletLastUsed:!1;if(!e.ordered){const c=e.children?e.children[0]:void 0;if((o==="*"||o==="-")&&c&&(!c.children||!c.children[0])&&n.stack[n.stack.length-1]==="list"&&n.stack[n.stack.length-2]==="listItem"&&n.stack[n.stack.length-3]==="list"&&n.stack[n.stack.length-4]==="listItem"&&n.indexStack[n.indexStack.length-1]===0&&n.indexStack[n.indexStack.length-2]===0&&n.indexStack[n.indexStack.length-3]===0&&(l=!0),pw(n)===o&&c){let f=-1;for(;++f-1?t.start:1)+(n.options.incrementListMarker===!1?0:t.children.indexOf(e))+s);let o=s.length+1;(i==="tab"||i==="mixed"&&(t&&t.type==="list"&&t.spread||e.spread))&&(o=Math.ceil(o/4)*4);const a=n.createTracker(r);a.move(s+" ".repeat(o-s.length)),a.shift(o);const l=n.enter("listItem"),u=n.indentLines(n.containerFlow(e,a.current()),c);return l(),u;function c(f,d,h){return d?(h?"":" ".repeat(o))+f:(h?s:s+" ".repeat(o-s.length))+f}}function xA(e,t,n,r){const i=n.enter("paragraph"),s=n.enter("phrasing"),o=n.containerPhrasing(e,r);return s(),i(),o}const vA=hu(["break","delete","emphasis","footnote","footnoteReference","image","imageReference","inlineCode","inlineMath","link","linkReference","mdxJsxTextElement","mdxTextExpression","strong","text","textDirective"]);function wA(e,t,n,r){return(e.children.some(function(o){return vA(o)})?n.containerPhrasing:n.containerFlow).call(n,e,r)}function kA(e){const t=e.options.strong||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize strong with `"+t+"` for `options.strong`, expected `*`, or `_`");return t}mw.peek=bA;function mw(e,t,n,r){const i=kA(n),s=n.enter("strong"),o=n.createTracker(r),a=o.move(i+i);let l=o.move(n.containerPhrasing(e,{after:i,before:a,...o.current()}));const u=l.charCodeAt(0),c=Nl(r.before.charCodeAt(r.before.length-1),u,i);c.inside&&(l=So(u)+l.slice(1));const f=l.charCodeAt(l.length-1),d=Nl(r.after.charCodeAt(0),f,i);d.inside&&(l=l.slice(0,-1)+So(f));const h=o.move(i+i);return s(),n.attentionEncodeSurroundingInfo={after:d.outside,before:c.outside},a+l+h}function bA(e,t,n){return n.options.strong||"*"}function SA(e,t,n,r){return n.safe(e.value,r)}function _A(e){const t=e.options.ruleRepetition||3;if(t<3)throw new Error("Cannot serialize rules with repetition `"+t+"` for `options.ruleRepetition`, expected `3` or more");return t}function CA(e,t,n){const r=(pw(n)+(n.options.ruleSpaces?" ":"")).repeat(_A(n));return n.options.ruleSpaces?r.slice(0,-1):r}const gw={blockquote:K4,break:Yg,code:eA,definition:nA,emphasis:ow,hardBreak:Yg,heading:oA,html:aw,image:lw,imageReference:uw,inlineCode:cw,link:dw,linkReference:hw,list:mA,listItem:yA,paragraph:xA,root:wA,strong:mw,text:SA,thematicBreak:CA};function EA(){return{enter:{table:NA,tableData:qg,tableHeader:qg,tableRow:AA},exit:{codeText:PA,table:TA,tableData:Sc,tableHeader:Sc,tableRow:Sc}}}function NA(e){const t=e._align;this.enter({type:"table",align:t.map(function(n){return n==="none"?null:n}),children:[]},e),this.data.inTable=!0}function TA(e){this.exit(e),this.data.inTable=void 0}function AA(e){this.enter({type:"tableRow",children:[]},e)}function Sc(e){this.exit(e)}function qg(e){this.enter({type:"tableCell",children:[]},e)}function PA(e){let t=this.resume();this.data.inTable&&(t=t.replace(/\\([\\|])/g,jA));const n=this.stack[this.stack.length-1];n.type,n.value=t,this.exit(e)}function jA(e,t){return t==="|"?t:e}function MA(e){const t=e||{},n=t.tableCellPadding,r=t.tablePipeAlign,i=t.stringLength,s=n?" ":"|";return{unsafe:[{character:"\r",inConstruct:"tableCell"},{character:` +`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:d,table:o,tableCell:l,tableRow:a}};function o(h,g,y,w){return u(c(h,y,w),h.align)}function a(h,g,y,w){const m=f(h,y,w),x=u([m]);return x.slice(0,x.indexOf(` +`))}function l(h,g,y,w){const m=y.enter("tableCell"),x=y.enter("phrasing"),v=y.containerPhrasing(h,{...w,before:s,after:s});return x(),m(),v}function u(h,g){return Y4(h,{align:g,alignDelimiters:r,padding:n,stringLength:i})}function c(h,g,y){const w=h.children;let m=-1;const x=[],v=g.enter("table");for(;++m0&&!n&&(e[e.length-1][1]._gfmAutolinkLiteralWalkedInto=!0),n}const XA={tokenize:iP,partial:!0};function QA(){return{document:{91:{name:"gfmFootnoteDefinition",tokenize:tP,continuation:{tokenize:nP},exit:rP}},text:{91:{name:"gfmFootnoteCall",tokenize:eP},93:{name:"gfmPotentialFootnoteCall",add:"after",tokenize:ZA,resolveTo:JA}}}}function ZA(e,t,n){const r=this;let i=r.events.length;const s=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]);let o;for(;i--;){const l=r.events[i][1];if(l.type==="labelImage"){o=l;break}if(l.type==="gfmFootnoteCall"||l.type==="labelLink"||l.type==="label"||l.type==="image"||l.type==="link")break}return a;function a(l){if(!o||!o._balanced)return n(l);const u=ln(r.sliceSerialize({start:o.end,end:r.now()}));return u.codePointAt(0)!==94||!s.includes(u.slice(1))?n(l):(e.enter("gfmFootnoteCallLabelMarker"),e.consume(l),e.exit("gfmFootnoteCallLabelMarker"),t(l))}}function JA(e,t){let n=e.length;for(;n--;)if(e[n][1].type==="labelImage"&&e[n][0]==="enter"){e[n][1];break}e[n+1][1].type="data",e[n+3][1].type="gfmFootnoteCallLabelMarker";const r={type:"gfmFootnoteCall",start:Object.assign({},e[n+3][1].start),end:Object.assign({},e[e.length-1][1].end)},i={type:"gfmFootnoteCallMarker",start:Object.assign({},e[n+3][1].end),end:Object.assign({},e[n+3][1].end)};i.end.column++,i.end.offset++,i.end._bufferIndex++;const s={type:"gfmFootnoteCallString",start:Object.assign({},i.end),end:Object.assign({},e[e.length-1][1].start)},o={type:"chunkString",contentType:"string",start:Object.assign({},s.start),end:Object.assign({},s.end)},a=[e[n+1],e[n+2],["enter",r,t],e[n+3],e[n+4],["enter",i,t],["exit",i,t],["enter",s,t],["enter",o,t],["exit",o,t],["exit",s,t],e[e.length-2],e[e.length-1],["exit",r,t]];return e.splice(n,e.length-n+1,...a),e}function eP(e,t,n){const r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]);let s=0,o;return a;function a(f){return e.enter("gfmFootnoteCall"),e.enter("gfmFootnoteCallLabelMarker"),e.consume(f),e.exit("gfmFootnoteCallLabelMarker"),l}function l(f){return f!==94?n(f):(e.enter("gfmFootnoteCallMarker"),e.consume(f),e.exit("gfmFootnoteCallMarker"),e.enter("gfmFootnoteCallString"),e.enter("chunkString").contentType="string",u)}function u(f){if(s>999||f===93&&!o||f===null||f===91||ve(f))return n(f);if(f===93){e.exit("chunkString");const d=e.exit("gfmFootnoteCallString");return i.includes(ln(r.sliceSerialize(d)))?(e.enter("gfmFootnoteCallLabelMarker"),e.consume(f),e.exit("gfmFootnoteCallLabelMarker"),e.exit("gfmFootnoteCall"),t):n(f)}return ve(f)||(o=!0),s++,e.consume(f),f===92?c:u}function c(f){return f===91||f===92||f===93?(e.consume(f),s++,u):u(f)}}function tP(e,t,n){const r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]);let s,o=0,a;return l;function l(g){return e.enter("gfmFootnoteDefinition")._container=!0,e.enter("gfmFootnoteDefinitionLabel"),e.enter("gfmFootnoteDefinitionLabelMarker"),e.consume(g),e.exit("gfmFootnoteDefinitionLabelMarker"),u}function u(g){return g===94?(e.enter("gfmFootnoteDefinitionMarker"),e.consume(g),e.exit("gfmFootnoteDefinitionMarker"),e.enter("gfmFootnoteDefinitionLabelString"),e.enter("chunkString").contentType="string",c):n(g)}function c(g){if(o>999||g===93&&!a||g===null||g===91||ve(g))return n(g);if(g===93){e.exit("chunkString");const y=e.exit("gfmFootnoteDefinitionLabelString");return s=ln(r.sliceSerialize(y)),e.enter("gfmFootnoteDefinitionLabelMarker"),e.consume(g),e.exit("gfmFootnoteDefinitionLabelMarker"),e.exit("gfmFootnoteDefinitionLabel"),d}return ve(g)||(a=!0),o++,e.consume(g),g===92?f:c}function f(g){return g===91||g===92||g===93?(e.consume(g),o++,c):c(g)}function d(g){return g===58?(e.enter("definitionMarker"),e.consume(g),e.exit("definitionMarker"),i.includes(s)||i.push(s),fe(e,h,"gfmFootnoteDefinitionWhitespace")):n(g)}function h(g){return t(g)}}function nP(e,t,n){return e.check(Ho,t,e.attempt(XA,t,n))}function rP(e){e.exit("gfmFootnoteDefinition")}function iP(e,t,n){const r=this;return fe(e,i,"gfmFootnoteDefinitionIndent",5);function i(s){const o=r.events[r.events.length-1];return o&&o[1].type==="gfmFootnoteDefinitionIndent"&&o[2].sliceSerialize(o[1],!0).length===4?t(s):n(s)}}function sP(e){let n=(e||{}).singleTilde;const r={name:"strikethrough",tokenize:s,resolveAll:i};return n==null&&(n=!0),{text:{126:r},insideSpan:{null:[r]},attentionMarkers:{null:[126]}};function i(o,a){let l=-1;for(;++l1?l(g):(o.consume(g),f++,h);if(f<2&&!n)return l(g);const w=o.exit("strikethroughSequenceTemporary"),m=ns(g);return w._open=!m||m===2&&!!y,w._close=!y||y===2&&!!m,a(g)}}}class oP{constructor(){this.map=[]}add(t,n,r){aP(this,t,n,r)}consume(t){if(this.map.sort(function(s,o){return s[0]-o[0]}),this.map.length===0)return;let n=this.map.length;const r=[];for(;n>0;)n-=1,r.push(t.slice(this.map[n][0]+this.map[n][1]),this.map[n][2]),t.length=this.map[n][0];r.push(t.slice()),t.length=0;let i=r.pop();for(;i;){for(const s of i)t.push(s);i=r.pop()}this.map.length=0}}function aP(e,t,n,r){let i=0;if(!(n===0&&r.length===0)){for(;i-1;){const _=r.events[L][1].type;if(_==="lineEnding"||_==="linePrefix")L--;else break}const M=L>-1?r.events[L][1].type:null,O=M==="tableHead"||M==="tableRow"?S:l;return O===S&&r.parser.lazy[r.now().line]?n(C):O(C)}function l(C){return e.enter("tableHead"),e.enter("tableRow"),u(C)}function u(C){return C===124||(o=!0,s+=1),c(C)}function c(C){return C===null?n(C):J(C)?s>1?(s=0,r.interrupt=!0,e.exit("tableRow"),e.enter("lineEnding"),e.consume(C),e.exit("lineEnding"),h):n(C):ue(C)?fe(e,c,"whitespace")(C):(s+=1,o&&(o=!1,i+=1),C===124?(e.enter("tableCellDivider"),e.consume(C),e.exit("tableCellDivider"),o=!0,c):(e.enter("data"),f(C)))}function f(C){return C===null||C===124||ve(C)?(e.exit("data"),c(C)):(e.consume(C),C===92?d:f)}function d(C){return C===92||C===124?(e.consume(C),f):f(C)}function h(C){return r.interrupt=!1,r.parser.lazy[r.now().line]?n(C):(e.enter("tableDelimiterRow"),o=!1,ue(C)?fe(e,g,"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(C):g(C))}function g(C){return C===45||C===58?w(C):C===124?(o=!0,e.enter("tableCellDivider"),e.consume(C),e.exit("tableCellDivider"),y):N(C)}function y(C){return ue(C)?fe(e,w,"whitespace")(C):w(C)}function w(C){return C===58?(s+=1,o=!0,e.enter("tableDelimiterMarker"),e.consume(C),e.exit("tableDelimiterMarker"),m):C===45?(s+=1,m(C)):C===null||J(C)?k(C):N(C)}function m(C){return C===45?(e.enter("tableDelimiterFiller"),x(C)):N(C)}function x(C){return C===45?(e.consume(C),x):C===58?(o=!0,e.exit("tableDelimiterFiller"),e.enter("tableDelimiterMarker"),e.consume(C),e.exit("tableDelimiterMarker"),v):(e.exit("tableDelimiterFiller"),v(C))}function v(C){return ue(C)?fe(e,k,"whitespace")(C):k(C)}function k(C){return C===124?g(C):C===null||J(C)?!o||i!==s?N(C):(e.exit("tableDelimiterRow"),e.exit("tableHead"),t(C)):N(C)}function N(C){return n(C)}function S(C){return e.enter("tableRow"),A(C)}function A(C){return C===124?(e.enter("tableCellDivider"),e.consume(C),e.exit("tableCellDivider"),A):C===null||J(C)?(e.exit("tableRow"),t(C)):ue(C)?fe(e,A,"whitespace")(C):(e.enter("data"),P(C))}function P(C){return C===null||C===124||ve(C)?(e.exit("data"),A(C)):(e.consume(C),C===92?D:P)}function D(C){return C===92||C===124?(e.consume(C),P):P(C)}}function fP(e,t){let n=-1,r=!0,i=0,s=[0,0,0,0],o=[0,0,0,0],a=!1,l=0,u,c,f;const d=new oP;for(;++nn[2]+1){const g=n[2]+1,y=n[3]-n[2]-1;e.add(g,y,[])}}e.add(n[3]+1,0,[["exit",f,t]])}return i!==void 0&&(s.end=Object.assign({},gi(t.events,i)),e.add(i,0,[["exit",s,t]]),s=void 0),s}function Gg(e,t,n,r,i){const s=[],o=gi(t.events,n);i&&(i.end=Object.assign({},o),s.push(["exit",i,t])),r.end=Object.assign({},o),s.push(["exit",r,t]),e.add(n+1,0,s)}function gi(e,t){const n=e[t],r=n[0]==="enter"?"start":"end";return n[1][r]}const dP={name:"tasklistCheck",tokenize:pP};function hP(){return{text:{91:dP}}}function pP(e,t,n){const r=this;return i;function i(l){return r.previous!==null||!r._gfmTasklistFirstContentOfListItem?n(l):(e.enter("taskListCheck"),e.enter("taskListCheckMarker"),e.consume(l),e.exit("taskListCheckMarker"),s)}function s(l){return ve(l)?(e.enter("taskListCheckValueUnchecked"),e.consume(l),e.exit("taskListCheckValueUnchecked"),o):l===88||l===120?(e.enter("taskListCheckValueChecked"),e.consume(l),e.exit("taskListCheckValueChecked"),o):n(l)}function o(l){return l===93?(e.enter("taskListCheckMarker"),e.consume(l),e.exit("taskListCheckMarker"),e.exit("taskListCheck"),a):n(l)}function a(l){return J(l)?t(l):ue(l)?e.check({tokenize:mP},t,n)(l):n(l)}}function mP(e,t,n){return fe(e,r,"whitespace");function r(i){return i===null?n(i):t(i)}}function gP(e){return Iv([$A(),QA(),sP(e),uP(),hP()])}const yP={};function Xg(e){const t=this,n=e||yP,r=t.data(),i=r.micromarkExtensions||(r.micromarkExtensions=[]),s=r.fromMarkdownExtensions||(r.fromMarkdownExtensions=[]),o=r.toMarkdownExtensions||(r.toMarkdownExtensions=[]);i.push(gP(n)),s.push(zA()),o.push(FA(n))}const Cw=T.createContext({transformPagePoint:e=>e,isStatic:!1,reducedMotion:"never"}),mu=T.createContext({}),gu=T.createContext(null),yu=typeof document<"u",Wh=yu?T.useLayoutEffect:T.useEffect,Ew=T.createContext({strict:!1}),Yh=e=>e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),xP="framerAppearId",Nw="data-"+Yh(xP);function vP(e,t,n,r){const{visualElement:i}=T.useContext(mu),s=T.useContext(Ew),o=T.useContext(gu),a=T.useContext(Cw).reducedMotion,l=T.useRef();r=r||s.renderer,!l.current&&r&&(l.current=r(e,{visualState:t,parent:i,props:n,presenceContext:o,blockInitialAnimation:o?o.initial===!1:!1,reducedMotionConfig:a}));const u=l.current;T.useInsertionEffect(()=>{u&&u.update(n,o)});const c=T.useRef(!!(n[Nw]&&!window.HandoffComplete));return Wh(()=>{u&&(u.render(),c.current&&u.animationState&&u.animationState.animateChanges())}),T.useEffect(()=>{u&&(u.updateFeatures(),!c.current&&u.animationState&&u.animationState.animateChanges(),c.current&&(c.current=!1,window.HandoffComplete=!0))}),u}function Ai(e){return e&&typeof e=="object"&&Object.prototype.hasOwnProperty.call(e,"current")}function wP(e,t,n){return T.useCallback(r=>{r&&e.mount&&e.mount(r),t&&(r?t.mount(r):t.unmount()),n&&(typeof n=="function"?n(r):Ai(n)&&(n.current=r))},[t])}function _o(e){return typeof e=="string"||Array.isArray(e)}function xu(e){return e!==null&&typeof e=="object"&&typeof e.start=="function"}const qh=["animate","whileInView","whileFocus","whileHover","whileTap","whileDrag","exit"],Kh=["initial",...qh];function vu(e){return xu(e.animate)||Kh.some(t=>_o(e[t]))}function Tw(e){return!!(vu(e)||e.variants)}function kP(e,t){if(vu(e)){const{initial:n,animate:r}=e;return{initial:n===!1||_o(n)?n:void 0,animate:_o(r)?r:void 0}}return e.inherit!==!1?t:{}}function bP(e){const{initial:t,animate:n}=kP(e,T.useContext(mu));return T.useMemo(()=>({initial:t,animate:n}),[Qg(t),Qg(n)])}function Qg(e){return Array.isArray(e)?e.join(" "):e}const Zg={animation:["animate","variants","whileHover","whileTap","exit","whileInView","whileFocus","whileDrag"],exit:["exit"],drag:["drag","dragControls"],focus:["whileFocus"],hover:["whileHover","onHoverStart","onHoverEnd"],tap:["whileTap","onTap","onTapStart","onTapCancel"],pan:["onPan","onPanStart","onPanSessionStart","onPanEnd"],inView:["whileInView","onViewportEnter","onViewportLeave"],layout:["layout","layoutId"]},Co={};for(const e in Zg)Co[e]={isEnabled:t=>Zg[e].some(n=>!!t[n])};function SP(e){for(const t in e)Co[t]={...Co[t],...e[t]}}const Gh=T.createContext({}),Aw=T.createContext({}),_P=Symbol.for("motionComponentSymbol");function CP({preloadedFeatures:e,createVisualElement:t,useRender:n,useVisualState:r,Component:i}){e&&SP(e);function s(a,l){let u;const c={...T.useContext(Cw),...a,layoutId:EP(a)},{isStatic:f}=c,d=bP(a),h=r(a,f);if(!f&&yu){d.visualElement=vP(i,h,c,t);const g=T.useContext(Aw),y=T.useContext(Ew).strict;d.visualElement&&(u=d.visualElement.loadFeatures(c,y,e,g))}return T.createElement(mu.Provider,{value:d},u&&d.visualElement?T.createElement(u,{visualElement:d.visualElement,...c}):null,n(i,a,wP(h,d.visualElement,l),h,f,d.visualElement))}const o=T.forwardRef(s);return o[_P]=i,o}function EP({layoutId:e}){const t=T.useContext(Gh).id;return t&&e!==void 0?t+"-"+e:e}function NP(e){function t(r,i={}){return CP(e(r,i))}if(typeof Proxy>"u")return t;const n=new Map;return new Proxy(t,{get:(r,i)=>(n.has(i)||n.set(i,t(i)),n.get(i))})}const TP=["animate","circle","defs","desc","ellipse","g","image","line","filter","marker","mask","metadata","path","pattern","polygon","polyline","rect","stop","switch","symbol","svg","text","tspan","use","view"];function Xh(e){return typeof e!="string"||e.includes("-")?!1:!!(TP.indexOf(e)>-1||/[A-Z]/.test(e))}const Tl={};function AP(e){Object.assign(Tl,e)}const Wo=["transformPerspective","x","y","z","translateX","translateY","translateZ","scale","scaleX","scaleY","rotate","rotateX","rotateY","rotateZ","skew","skewX","skewY"],li=new Set(Wo);function Pw(e,{layout:t,layoutId:n}){return li.has(e)||e.startsWith("origin")||(t||n!==void 0)&&(!!Tl[e]||e==="opacity")}const Ct=e=>!!(e&&e.getVelocity),PP={x:"translateX",y:"translateY",z:"translateZ",transformPerspective:"perspective"},jP=Wo.length;function MP(e,{enableHardwareAcceleration:t=!0,allowTransformNone:n=!0},r,i){let s="";for(let o=0;ot=>typeof t=="string"&&t.startsWith(e),Mw=jw("--"),nd=jw("var(--"),DP=/var\s*\(\s*--[\w-]+(\s*,\s*(?:(?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)+)?\s*\)/g,IP=(e,t)=>t&&typeof e=="number"?t.transform(e):e,kr=(e,t,n)=>Math.min(Math.max(n,e),t),ui={test:e=>typeof e=="number",parse:parseFloat,transform:e=>e},Js={...ui,transform:e=>kr(0,1,e)},va={...ui,default:1},eo=e=>Math.round(e*1e5)/1e5,wu=/(-)?([\d]*\.?[\d])+/g,Dw=/(#[0-9a-f]{3,8}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))/gi,LP=/^(#[0-9a-f]{3,8}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))$/i;function Yo(e){return typeof e=="string"}const qo=e=>({test:t=>Yo(t)&&t.endsWith(e)&&t.split(" ").length===1,parse:parseFloat,transform:t=>`${t}${e}`}),Qn=qo("deg"),kn=qo("%"),te=qo("px"),RP=qo("vh"),zP=qo("vw"),Jg={...kn,parse:e=>kn.parse(e)/100,transform:e=>kn.transform(e*100)},e0={...ui,transform:Math.round},Iw={borderWidth:te,borderTopWidth:te,borderRightWidth:te,borderBottomWidth:te,borderLeftWidth:te,borderRadius:te,radius:te,borderTopLeftRadius:te,borderTopRightRadius:te,borderBottomRightRadius:te,borderBottomLeftRadius:te,width:te,maxWidth:te,height:te,maxHeight:te,size:te,top:te,right:te,bottom:te,left:te,padding:te,paddingTop:te,paddingRight:te,paddingBottom:te,paddingLeft:te,margin:te,marginTop:te,marginRight:te,marginBottom:te,marginLeft:te,rotate:Qn,rotateX:Qn,rotateY:Qn,rotateZ:Qn,scale:va,scaleX:va,scaleY:va,scaleZ:va,skew:Qn,skewX:Qn,skewY:Qn,distance:te,translateX:te,translateY:te,translateZ:te,x:te,y:te,z:te,perspective:te,transformPerspective:te,opacity:Js,originX:Jg,originY:Jg,originZ:te,zIndex:e0,fillOpacity:Js,strokeOpacity:Js,numOctaves:e0};function Qh(e,t,n,r){const{style:i,vars:s,transform:o,transformOrigin:a}=e;let l=!1,u=!1,c=!0;for(const f in t){const d=t[f];if(Mw(f)){s[f]=d;continue}const h=Iw[f],g=IP(d,h);if(li.has(f)){if(l=!0,o[f]=g,!c)continue;d!==(h.default||0)&&(c=!1)}else f.startsWith("origin")?(u=!0,a[f]=g):i[f]=g}if(t.transform||(l||r?i.transform=MP(e.transform,n,c,r):i.transform&&(i.transform="none")),u){const{originX:f="50%",originY:d="50%",originZ:h=0}=a;i.transformOrigin=`${f} ${d} ${h}`}}const Zh=()=>({style:{},transform:{},transformOrigin:{},vars:{}});function Lw(e,t,n){for(const r in t)!Ct(t[r])&&!Pw(r,n)&&(e[r]=t[r])}function FP({transformTemplate:e},t,n){return T.useMemo(()=>{const r=Zh();return Qh(r,t,{enableHardwareAcceleration:!n},e),Object.assign({},r.vars,r.style)},[t])}function OP(e,t,n){const r=e.style||{},i={};return Lw(i,r,e),Object.assign(i,FP(e,t,n)),e.transformValues?e.transformValues(i):i}function VP(e,t,n){const r={},i=OP(e,t,n);return e.drag&&e.dragListener!==!1&&(r.draggable=!1,i.userSelect=i.WebkitUserSelect=i.WebkitTouchCallout="none",i.touchAction=e.drag===!0?"none":`pan-${e.drag==="x"?"y":"x"}`),e.tabIndex===void 0&&(e.onTap||e.onTapStart||e.whileTap)&&(r.tabIndex=0),r.style=i,r}const $P=new Set(["animate","exit","variants","initial","style","values","variants","transition","transformTemplate","transformValues","custom","inherit","onBeforeLayoutMeasure","onAnimationStart","onAnimationComplete","onUpdate","onDragStart","onDrag","onDragEnd","onMeasureDragConstraints","onDirectionLock","onDragTransitionEnd","_dragX","_dragY","onHoverStart","onHoverEnd","onViewportEnter","onViewportLeave","globalTapTarget","ignoreStrict","viewport"]);function Al(e){return e.startsWith("while")||e.startsWith("drag")&&e!=="draggable"||e.startsWith("layout")||e.startsWith("onTap")||e.startsWith("onPan")||e.startsWith("onLayout")||$P.has(e)}let Rw=e=>!Al(e);function BP(e){e&&(Rw=t=>t.startsWith("on")?!Al(t):e(t))}try{BP(require("@emotion/is-prop-valid").default)}catch{}function HP(e,t,n){const r={};for(const i in e)i==="values"&&typeof e.values=="object"||(Rw(i)||n===!0&&Al(i)||!t&&!Al(i)||e.draggable&&i.startsWith("onDrag"))&&(r[i]=e[i]);return r}function t0(e,t,n){return typeof e=="string"?e:te.transform(t+n*e)}function UP(e,t,n){const r=t0(t,e.x,e.width),i=t0(n,e.y,e.height);return`${r} ${i}`}const WP={offset:"stroke-dashoffset",array:"stroke-dasharray"},YP={offset:"strokeDashoffset",array:"strokeDasharray"};function qP(e,t,n=1,r=0,i=!0){e.pathLength=1;const s=i?WP:YP;e[s.offset]=te.transform(-r);const o=te.transform(t),a=te.transform(n);e[s.array]=`${o} ${a}`}function Jh(e,{attrX:t,attrY:n,attrScale:r,originX:i,originY:s,pathLength:o,pathSpacing:a=1,pathOffset:l=0,...u},c,f,d){if(Qh(e,u,c,d),f){e.style.viewBox&&(e.attrs.viewBox=e.style.viewBox);return}e.attrs=e.style,e.style={};const{attrs:h,style:g,dimensions:y}=e;h.transform&&(y&&(g.transform=h.transform),delete h.transform),y&&(i!==void 0||s!==void 0||g.transform)&&(g.transformOrigin=UP(y,i!==void 0?i:.5,s!==void 0?s:.5)),t!==void 0&&(h.x=t),n!==void 0&&(h.y=n),r!==void 0&&(h.scale=r),o!==void 0&&qP(h,o,a,l,!1)}const zw=()=>({...Zh(),attrs:{}}),ep=e=>typeof e=="string"&&e.toLowerCase()==="svg";function KP(e,t,n,r){const i=T.useMemo(()=>{const s=zw();return Jh(s,t,{enableHardwareAcceleration:!1},ep(r),e.transformTemplate),{...s.attrs,style:{...s.style}}},[t]);if(e.style){const s={};Lw(s,e.style,e),i.style={...s,...i.style}}return i}function GP(e=!1){return(n,r,i,{latestValues:s},o)=>{const l=(Xh(n)?KP:VP)(r,s,o,n),c={...HP(r,typeof n=="string",e),...l,ref:i},{children:f}=r,d=T.useMemo(()=>Ct(f)?f.get():f,[f]);return T.createElement(n,{...c,children:d})}}function Fw(e,{style:t,vars:n},r,i){Object.assign(e.style,t,i&&i.getProjectionStyles(r));for(const s in n)e.style.setProperty(s,n[s])}const Ow=new Set(["baseFrequency","diffuseConstant","kernelMatrix","kernelUnitLength","keySplines","keyTimes","limitingConeAngle","markerHeight","markerWidth","numOctaves","targetX","targetY","surfaceScale","specularConstant","specularExponent","stdDeviation","tableValues","viewBox","gradientTransform","pathLength","startOffset","textLength","lengthAdjust"]);function Vw(e,t,n,r){Fw(e,t,void 0,r);for(const i in t.attrs)e.setAttribute(Ow.has(i)?i:Yh(i),t.attrs[i])}function tp(e,t){const{style:n}=e,r={};for(const i in n)(Ct(n[i])||t.style&&Ct(t.style[i])||Pw(i,e))&&(r[i]=n[i]);return r}function $w(e,t){const n=tp(e,t);for(const r in e)if(Ct(e[r])||Ct(t[r])){const i=Wo.indexOf(r)!==-1?"attr"+r.charAt(0).toUpperCase()+r.substring(1):r;n[i]=e[r]}return n}function np(e,t,n,r={},i={}){return typeof t=="function"&&(t=t(n!==void 0?n:e.custom,r,i)),typeof t=="string"&&(t=e.variants&&e.variants[t]),typeof t=="function"&&(t=t(n!==void 0?n:e.custom,r,i)),t}function Bw(e){const t=T.useRef(null);return t.current===null&&(t.current=e()),t.current}const Pl=e=>Array.isArray(e),XP=e=>!!(e&&typeof e=="object"&&e.mix&&e.toValue),QP=e=>Pl(e)?e[e.length-1]||0:e;function qa(e){const t=Ct(e)?e.get():e;return XP(t)?t.toValue():t}function ZP({scrapeMotionValuesFromProps:e,createRenderState:t,onMount:n},r,i,s){const o={latestValues:JP(r,i,s,e),renderState:t()};return n&&(o.mount=a=>n(r,a,o)),o}const Hw=e=>(t,n)=>{const r=T.useContext(mu),i=T.useContext(gu),s=()=>ZP(e,t,r,i);return n?s():Bw(s)};function JP(e,t,n,r){const i={},s=r(e,{});for(const d in s)i[d]=qa(s[d]);let{initial:o,animate:a}=e;const l=vu(e),u=Tw(e);t&&u&&!l&&e.inherit!==!1&&(o===void 0&&(o=t.initial),a===void 0&&(a=t.animate));let c=n?n.initial===!1:!1;c=c||o===!1;const f=c?a:o;return f&&typeof f!="boolean"&&!xu(f)&&(Array.isArray(f)?f:[f]).forEach(h=>{const g=np(e,h);if(!g)return;const{transitionEnd:y,transition:w,...m}=g;for(const x in m){let v=m[x];if(Array.isArray(v)){const k=c?v.length-1:0;v=v[k]}v!==null&&(i[x]=v)}for(const x in y)i[x]=y[x]}),i}const Fe=e=>e;class n0{constructor(){this.order=[],this.scheduled=new Set}add(t){if(!this.scheduled.has(t))return this.scheduled.add(t),this.order.push(t),!0}remove(t){const n=this.order.indexOf(t);n!==-1&&(this.order.splice(n,1),this.scheduled.delete(t))}clear(){this.order.length=0,this.scheduled.clear()}}function ej(e){let t=new n0,n=new n0,r=0,i=!1,s=!1;const o=new WeakSet,a={schedule:(l,u=!1,c=!1)=>{const f=c&&i,d=f?t:n;return u&&o.add(l),d.add(l)&&f&&i&&(r=t.order.length),l},cancel:l=>{n.remove(l),o.delete(l)},process:l=>{if(i){s=!0;return}if(i=!0,[t,n]=[n,t],n.clear(),r=t.order.length,r)for(let u=0;u(f[d]=ej(()=>n=!0),f),{}),o=f=>s[f].process(i),a=()=>{const f=performance.now();n=!1,i.delta=r?1e3/60:Math.max(Math.min(f-i.timestamp,tj),1),i.timestamp=f,i.isProcessing=!0,wa.forEach(o),i.isProcessing=!1,n&&t&&(r=!1,e(a))},l=()=>{n=!0,r=!0,i.isProcessing||e(a)};return{schedule:wa.reduce((f,d)=>{const h=s[d];return f[d]=(g,y=!1,w=!1)=>(n||l(),h.schedule(g,y,w)),f},{}),cancel:f=>wa.forEach(d=>s[d].cancel(f)),state:i,steps:s}}const{schedule:be,cancel:Vn,state:rt,steps:_c}=nj(typeof requestAnimationFrame<"u"?requestAnimationFrame:Fe,!0),rj={useVisualState:Hw({scrapeMotionValuesFromProps:$w,createRenderState:zw,onMount:(e,t,{renderState:n,latestValues:r})=>{be.read(()=>{try{n.dimensions=typeof t.getBBox=="function"?t.getBBox():t.getBoundingClientRect()}catch{n.dimensions={x:0,y:0,width:0,height:0}}}),be.render(()=>{Jh(n,r,{enableHardwareAcceleration:!1},ep(t.tagName),e.transformTemplate),Vw(t,n)})}})},ij={useVisualState:Hw({scrapeMotionValuesFromProps:tp,createRenderState:Zh})};function sj(e,{forwardMotionProps:t=!1},n,r){return{...Xh(e)?rj:ij,preloadedFeatures:n,useRender:GP(t),createVisualElement:r,Component:e}}function jn(e,t,n,r={passive:!0}){return e.addEventListener(t,n,r),()=>e.removeEventListener(t,n)}const Uw=e=>e.pointerType==="mouse"?typeof e.button!="number"||e.button<=0:e.isPrimary!==!1;function ku(e,t="page"){return{point:{x:e[t+"X"],y:e[t+"Y"]}}}const oj=e=>t=>Uw(t)&&e(t,ku(t));function In(e,t,n,r){return jn(e,t,oj(n),r)}const aj=(e,t)=>n=>t(e(n)),gr=(...e)=>e.reduce(aj);function Ww(e){let t=null;return()=>{const n=()=>{t=null};return t===null?(t=e,n):!1}}const r0=Ww("dragHorizontal"),i0=Ww("dragVertical");function Yw(e){let t=!1;if(e==="y")t=i0();else if(e==="x")t=r0();else{const n=r0(),r=i0();n&&r?t=()=>{n(),r()}:(n&&n(),r&&r())}return t}function qw(){const e=Yw(!0);return e?(e(),!1):!0}class Nr{constructor(t){this.isMounted=!1,this.node=t}update(){}}function s0(e,t){const n="pointer"+(t?"enter":"leave"),r="onHover"+(t?"Start":"End"),i=(s,o)=>{if(s.pointerType==="touch"||qw())return;const a=e.getProps();e.animationState&&a.whileHover&&e.animationState.setActive("whileHover",t),a[r]&&be.update(()=>a[r](s,o))};return In(e.current,n,i,{passive:!e.getProps()[r]})}class lj extends Nr{mount(){this.unmount=gr(s0(this.node,!0),s0(this.node,!1))}unmount(){}}class uj extends Nr{constructor(){super(...arguments),this.isActive=!1}onFocus(){let t=!1;try{t=this.node.current.matches(":focus-visible")}catch{t=!0}!t||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!0),this.isActive=!0)}onBlur(){!this.isActive||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!1),this.isActive=!1)}mount(){this.unmount=gr(jn(this.node.current,"focus",()=>this.onFocus()),jn(this.node.current,"blur",()=>this.onBlur()))}unmount(){}}const Kw=(e,t)=>t?e===t?!0:Kw(e,t.parentElement):!1;function Cc(e,t){if(!t)return;const n=new PointerEvent("pointer"+e);t(n,ku(n))}class cj extends Nr{constructor(){super(...arguments),this.removeStartListeners=Fe,this.removeEndListeners=Fe,this.removeAccessibleListeners=Fe,this.startPointerPress=(t,n)=>{if(this.isPressing)return;this.removeEndListeners();const r=this.node.getProps(),s=In(window,"pointerup",(a,l)=>{if(!this.checkPressEnd())return;const{onTap:u,onTapCancel:c,globalTapTarget:f}=this.node.getProps();be.update(()=>{!f&&!Kw(this.node.current,a.target)?c&&c(a,l):u&&u(a,l)})},{passive:!(r.onTap||r.onPointerUp)}),o=In(window,"pointercancel",(a,l)=>this.cancelPress(a,l),{passive:!(r.onTapCancel||r.onPointerCancel)});this.removeEndListeners=gr(s,o),this.startPress(t,n)},this.startAccessiblePress=()=>{const t=s=>{if(s.key!=="Enter"||this.isPressing)return;const o=a=>{a.key!=="Enter"||!this.checkPressEnd()||Cc("up",(l,u)=>{const{onTap:c}=this.node.getProps();c&&be.update(()=>c(l,u))})};this.removeEndListeners(),this.removeEndListeners=jn(this.node.current,"keyup",o),Cc("down",(a,l)=>{this.startPress(a,l)})},n=jn(this.node.current,"keydown",t),r=()=>{this.isPressing&&Cc("cancel",(s,o)=>this.cancelPress(s,o))},i=jn(this.node.current,"blur",r);this.removeAccessibleListeners=gr(n,i)}}startPress(t,n){this.isPressing=!0;const{onTapStart:r,whileTap:i}=this.node.getProps();i&&this.node.animationState&&this.node.animationState.setActive("whileTap",!0),r&&be.update(()=>r(t,n))}checkPressEnd(){return this.removeEndListeners(),this.isPressing=!1,this.node.getProps().whileTap&&this.node.animationState&&this.node.animationState.setActive("whileTap",!1),!qw()}cancelPress(t,n){if(!this.checkPressEnd())return;const{onTapCancel:r}=this.node.getProps();r&&be.update(()=>r(t,n))}mount(){const t=this.node.getProps(),n=In(t.globalTapTarget?window:this.node.current,"pointerdown",this.startPointerPress,{passive:!(t.onTapStart||t.onPointerStart)}),r=jn(this.node.current,"focus",this.startAccessiblePress);this.removeStartListeners=gr(n,r)}unmount(){this.removeStartListeners(),this.removeEndListeners(),this.removeAccessibleListeners()}}const rd=new WeakMap,Ec=new WeakMap,fj=e=>{const t=rd.get(e.target);t&&t(e)},dj=e=>{e.forEach(fj)};function hj({root:e,...t}){const n=e||document;Ec.has(n)||Ec.set(n,{});const r=Ec.get(n),i=JSON.stringify(t);return r[i]||(r[i]=new IntersectionObserver(dj,{root:e,...t})),r[i]}function pj(e,t,n){const r=hj(t);return rd.set(e,n),r.observe(e),()=>{rd.delete(e),r.unobserve(e)}}const mj={some:0,all:1};class gj extends Nr{constructor(){super(...arguments),this.hasEnteredView=!1,this.isInView=!1}startObserver(){this.unmount();const{viewport:t={}}=this.node.getProps(),{root:n,margin:r,amount:i="some",once:s}=t,o={root:n?n.current:void 0,rootMargin:r,threshold:typeof i=="number"?i:mj[i]},a=l=>{const{isIntersecting:u}=l;if(this.isInView===u||(this.isInView=u,s&&!u&&this.hasEnteredView))return;u&&(this.hasEnteredView=!0),this.node.animationState&&this.node.animationState.setActive("whileInView",u);const{onViewportEnter:c,onViewportLeave:f}=this.node.getProps(),d=u?c:f;d&&d(l)};return pj(this.node.current,o,a)}mount(){this.startObserver()}update(){if(typeof IntersectionObserver>"u")return;const{props:t,prevProps:n}=this.node;["amount","margin","root"].some(yj(t,n))&&this.startObserver()}unmount(){}}function yj({viewport:e={}},{viewport:t={}}={}){return n=>e[n]!==t[n]}const xj={inView:{Feature:gj},tap:{Feature:cj},focus:{Feature:uj},hover:{Feature:lj}};function Gw(e,t){if(!Array.isArray(t))return!1;const n=t.length;if(n!==e.length)return!1;for(let r=0;rt[r]=n.get()),t}function wj(e){const t={};return e.values.forEach((n,r)=>t[r]=n.getVelocity()),t}function bu(e,t,n){const r=e.getProps();return np(r,t,n!==void 0?n:r.custom,vj(e),wj(e))}let rp=Fe;const Gr=e=>e*1e3,Ln=e=>e/1e3,kj={current:!1},Xw=e=>Array.isArray(e)&&typeof e[0]=="number";function Qw(e){return!!(!e||typeof e=="string"&&Zw[e]||Xw(e)||Array.isArray(e)&&e.every(Qw))}const Rs=([e,t,n,r])=>`cubic-bezier(${e}, ${t}, ${n}, ${r})`,Zw={linear:"linear",ease:"ease",easeIn:"ease-in",easeOut:"ease-out",easeInOut:"ease-in-out",circIn:Rs([0,.65,.55,1]),circOut:Rs([.55,0,1,.45]),backIn:Rs([.31,.01,.66,-.59]),backOut:Rs([.33,1.53,.69,.99])};function Jw(e){if(e)return Xw(e)?Rs(e):Array.isArray(e)?e.map(Jw):Zw[e]}function bj(e,t,n,{delay:r=0,duration:i,repeat:s=0,repeatType:o="loop",ease:a,times:l}={}){const u={[t]:n};l&&(u.offset=l);const c=Jw(a);return Array.isArray(c)&&(u.easing=c),e.animate(u,{delay:r,duration:i,easing:Array.isArray(c)?"linear":c,fill:"both",iterations:s+1,direction:o==="reverse"?"alternate":"normal"})}function Sj(e,{repeat:t,repeatType:n="loop"}){const r=t&&n!=="loop"&&t%2===1?0:e.length-1;return e[r]}const ek=(e,t,n)=>(((1-3*n+3*t)*e+(3*n-6*t))*e+3*t)*e,_j=1e-7,Cj=12;function Ej(e,t,n,r,i){let s,o,a=0;do o=t+(n-t)/2,s=ek(o,r,i)-e,s>0?n=o:t=o;while(Math.abs(s)>_j&&++aEj(s,0,1,e,n);return s=>s===0||s===1?s:ek(i(s),t,r)}const Nj=Ko(.42,0,1,1),Tj=Ko(0,0,.58,1),tk=Ko(.42,0,.58,1),Aj=e=>Array.isArray(e)&&typeof e[0]!="number",nk=e=>t=>t<=.5?e(2*t)/2:(2-e(2*(1-t)))/2,rk=e=>t=>1-e(1-t),ip=e=>1-Math.sin(Math.acos(e)),ik=rk(ip),Pj=nk(ip),sk=Ko(.33,1.53,.69,.99),sp=rk(sk),jj=nk(sp),Mj=e=>(e*=2)<1?.5*sp(e):.5*(2-Math.pow(2,-10*(e-1))),Dj={linear:Fe,easeIn:Nj,easeInOut:tk,easeOut:Tj,circIn:ip,circInOut:Pj,circOut:ik,backIn:sp,backInOut:jj,backOut:sk,anticipate:Mj},o0=e=>{if(Array.isArray(e)){rp(e.length===4);const[t,n,r,i]=e;return Ko(t,n,r,i)}else if(typeof e=="string")return Dj[e];return e},op=(e,t)=>n=>!!(Yo(n)&&LP.test(n)&&n.startsWith(e)||t&&Object.prototype.hasOwnProperty.call(n,t)),ok=(e,t,n)=>r=>{if(!Yo(r))return r;const[i,s,o,a]=r.match(wu);return{[e]:parseFloat(i),[t]:parseFloat(s),[n]:parseFloat(o),alpha:a!==void 0?parseFloat(a):1}},Ij=e=>kr(0,255,e),Nc={...ui,transform:e=>Math.round(Ij(e))},Hr={test:op("rgb","red"),parse:ok("red","green","blue"),transform:({red:e,green:t,blue:n,alpha:r=1})=>"rgba("+Nc.transform(e)+", "+Nc.transform(t)+", "+Nc.transform(n)+", "+eo(Js.transform(r))+")"};function Lj(e){let t="",n="",r="",i="";return e.length>5?(t=e.substring(1,3),n=e.substring(3,5),r=e.substring(5,7),i=e.substring(7,9)):(t=e.substring(1,2),n=e.substring(2,3),r=e.substring(3,4),i=e.substring(4,5),t+=t,n+=n,r+=r,i+=i),{red:parseInt(t,16),green:parseInt(n,16),blue:parseInt(r,16),alpha:i?parseInt(i,16)/255:1}}const id={test:op("#"),parse:Lj,transform:Hr.transform},Pi={test:op("hsl","hue"),parse:ok("hue","saturation","lightness"),transform:({hue:e,saturation:t,lightness:n,alpha:r=1})=>"hsla("+Math.round(e)+", "+kn.transform(eo(t))+", "+kn.transform(eo(n))+", "+eo(Js.transform(r))+")"},ct={test:e=>Hr.test(e)||id.test(e)||Pi.test(e),parse:e=>Hr.test(e)?Hr.parse(e):Pi.test(e)?Pi.parse(e):id.parse(e),transform:e=>Yo(e)?e:e.hasOwnProperty("red")?Hr.transform(e):Pi.transform(e)},Pe=(e,t,n)=>-n*e+n*t+e;function Tc(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*6*n:n<1/2?t:n<2/3?e+(t-e)*(2/3-n)*6:e}function Rj({hue:e,saturation:t,lightness:n,alpha:r}){e/=360,t/=100,n/=100;let i=0,s=0,o=0;if(!t)i=s=o=n;else{const a=n<.5?n*(1+t):n+t-n*t,l=2*n-a;i=Tc(l,a,e+1/3),s=Tc(l,a,e),o=Tc(l,a,e-1/3)}return{red:Math.round(i*255),green:Math.round(s*255),blue:Math.round(o*255),alpha:r}}const Ac=(e,t,n)=>{const r=e*e;return Math.sqrt(Math.max(0,n*(t*t-r)+r))},zj=[id,Hr,Pi],Fj=e=>zj.find(t=>t.test(e));function a0(e){const t=Fj(e);let n=t.parse(e);return t===Pi&&(n=Rj(n)),n}const ak=(e,t)=>{const n=a0(e),r=a0(t),i={...n};return s=>(i.red=Ac(n.red,r.red,s),i.green=Ac(n.green,r.green,s),i.blue=Ac(n.blue,r.blue,s),i.alpha=Pe(n.alpha,r.alpha,s),Hr.transform(i))};function Oj(e){var t,n;return isNaN(e)&&Yo(e)&&(((t=e.match(wu))===null||t===void 0?void 0:t.length)||0)+(((n=e.match(Dw))===null||n===void 0?void 0:n.length)||0)>0}const lk={regex:DP,countKey:"Vars",token:"${v}",parse:Fe},uk={regex:Dw,countKey:"Colors",token:"${c}",parse:ct.parse},ck={regex:wu,countKey:"Numbers",token:"${n}",parse:ui.parse};function Pc(e,{regex:t,countKey:n,token:r,parse:i}){const s=e.tokenised.match(t);s&&(e["num"+n]=s.length,e.tokenised=e.tokenised.replace(t,r),e.values.push(...s.map(i)))}function jl(e){const t=e.toString(),n={value:t,tokenised:t,values:[],numVars:0,numColors:0,numNumbers:0};return n.value.includes("var(--")&&Pc(n,lk),Pc(n,uk),Pc(n,ck),n}function fk(e){return jl(e).values}function dk(e){const{values:t,numColors:n,numVars:r,tokenised:i}=jl(e),s=t.length;return o=>{let a=i;for(let l=0;ltypeof e=="number"?0:e;function $j(e){const t=fk(e);return dk(e)(t.map(Vj))}const br={test:Oj,parse:fk,createTransformer:dk,getAnimatableNone:$j},hk=(e,t)=>n=>`${n>0?t:e}`;function pk(e,t){return typeof e=="number"?n=>Pe(e,t,n):ct.test(e)?ak(e,t):e.startsWith("var(")?hk(e,t):gk(e,t)}const mk=(e,t)=>{const n=[...e],r=n.length,i=e.map((s,o)=>pk(s,t[o]));return s=>{for(let o=0;o{const n={...e,...t},r={};for(const i in n)e[i]!==void 0&&t[i]!==void 0&&(r[i]=pk(e[i],t[i]));return i=>{for(const s in r)n[s]=r[s](i);return n}},gk=(e,t)=>{const n=br.createTransformer(t),r=jl(e),i=jl(t);return r.numVars===i.numVars&&r.numColors===i.numColors&&r.numNumbers>=i.numNumbers?gr(mk(r.values,i.values),n):hk(e,t)},Eo=(e,t,n)=>{const r=t-e;return r===0?1:(n-e)/r},l0=(e,t)=>n=>Pe(e,t,n);function Hj(e){return typeof e=="number"?l0:typeof e=="string"?ct.test(e)?ak:gk:Array.isArray(e)?mk:typeof e=="object"?Bj:l0}function Uj(e,t,n){const r=[],i=n||Hj(e[0]),s=e.length-1;for(let o=0;ot[0];e[0]>e[s-1]&&(e=[...e].reverse(),t=[...t].reverse());const o=Uj(t,r,i),a=o.length,l=u=>{let c=0;if(a>1)for(;cl(kr(e[0],e[s-1],u)):l}function Wj(e,t){const n=e[e.length-1];for(let r=1;r<=t;r++){const i=Eo(0,t,r);e.push(Pe(n,1,i))}}function Yj(e){const t=[0];return Wj(t,e.length-1),t}function qj(e,t){return e.map(n=>n*t)}function Kj(e,t){return e.map(()=>t||tk).splice(0,e.length-1)}function Ml({duration:e=300,keyframes:t,times:n,ease:r="easeInOut"}){const i=Aj(r)?r.map(o0):o0(r),s={done:!1,value:t[0]},o=qj(n&&n.length===t.length?n:Yj(t),e),a=yk(o,t,{ease:Array.isArray(i)?i:Kj(t,i)});return{calculatedDuration:e,next:l=>(s.value=a(l),s.done=l>=e,s)}}function xk(e,t){return t?e*(1e3/t):0}const Gj=5;function vk(e,t,n){const r=Math.max(t-Gj,0);return xk(n-e(r),t-r)}const jc=.001,Xj=.01,Qj=10,Zj=.05,Jj=1;function eM({duration:e=800,bounce:t=.25,velocity:n=0,mass:r=1}){let i,s,o=1-t;o=kr(Zj,Jj,o),e=kr(Xj,Qj,Ln(e)),o<1?(i=u=>{const c=u*o,f=c*e,d=c-n,h=sd(u,o),g=Math.exp(-f);return jc-d/h*g},s=u=>{const f=u*o*e,d=f*n+n,h=Math.pow(o,2)*Math.pow(u,2)*e,g=Math.exp(-f),y=sd(Math.pow(u,2),o);return(-i(u)+jc>0?-1:1)*((d-h)*g)/y}):(i=u=>{const c=Math.exp(-u*e),f=(u-n)*e+1;return-jc+c*f},s=u=>{const c=Math.exp(-u*e),f=(n-u)*(e*e);return c*f});const a=5/e,l=nM(i,s,a);if(e=Gr(e),isNaN(l))return{stiffness:100,damping:10,duration:e};{const u=Math.pow(l,2)*r;return{stiffness:u,damping:o*2*Math.sqrt(r*u),duration:e}}}const tM=12;function nM(e,t,n){let r=n;for(let i=1;ie[n]!==void 0)}function sM(e){let t={velocity:0,stiffness:100,damping:10,mass:1,isResolvedFromDuration:!1,...e};if(!u0(e,iM)&&u0(e,rM)){const n=eM(e);t={...t,...n,mass:1},t.isResolvedFromDuration=!0}return t}function wk({keyframes:e,restDelta:t,restSpeed:n,...r}){const i=e[0],s=e[e.length-1],o={done:!1,value:i},{stiffness:a,damping:l,mass:u,duration:c,velocity:f,isResolvedFromDuration:d}=sM({...r,velocity:-Ln(r.velocity||0)}),h=f||0,g=l/(2*Math.sqrt(a*u)),y=s-i,w=Ln(Math.sqrt(a/u)),m=Math.abs(y)<5;n||(n=m?.01:2),t||(t=m?.005:.5);let x;if(g<1){const v=sd(w,g);x=k=>{const N=Math.exp(-g*w*k);return s-N*((h+g*w*y)/v*Math.sin(v*k)+y*Math.cos(v*k))}}else if(g===1)x=v=>s-Math.exp(-w*v)*(y+(h+w*y)*v);else{const v=w*Math.sqrt(g*g-1);x=k=>{const N=Math.exp(-g*w*k),S=Math.min(v*k,300);return s-N*((h+g*w*y)*Math.sinh(S)+v*y*Math.cosh(S))/v}}return{calculatedDuration:d&&c||null,next:v=>{const k=x(v);if(d)o.done=v>=c;else{let N=h;v!==0&&(g<1?N=vk(x,v,k):N=0);const S=Math.abs(N)<=n,A=Math.abs(s-k)<=t;o.done=S&&A}return o.value=o.done?s:k,o}}}function c0({keyframes:e,velocity:t=0,power:n=.8,timeConstant:r=325,bounceDamping:i=10,bounceStiffness:s=500,modifyTarget:o,min:a,max:l,restDelta:u=.5,restSpeed:c}){const f=e[0],d={done:!1,value:f},h=P=>a!==void 0&&Pl,g=P=>a===void 0?l:l===void 0||Math.abs(a-P)-y*Math.exp(-P/r),v=P=>m+x(P),k=P=>{const D=x(P),C=v(P);d.done=Math.abs(D)<=u,d.value=d.done?m:C};let N,S;const A=P=>{h(d.value)&&(N=P,S=wk({keyframes:[d.value,g(d.value)],velocity:vk(v,P,d.value),damping:i,stiffness:s,restDelta:u,restSpeed:c}))};return A(0),{calculatedDuration:null,next:P=>{let D=!1;return!S&&N===void 0&&(D=!0,k(P),A(P)),N!==void 0&&P>N?S.next(P-N):(!D&&k(P),d)}}}const oM=e=>{const t=({timestamp:n})=>e(n);return{start:()=>be.update(t,!0),stop:()=>Vn(t),now:()=>rt.isProcessing?rt.timestamp:performance.now()}},f0=2e4;function d0(e){let t=0;const n=50;let r=e.next(t);for(;!r.done&&t=f0?1/0:t}const aM={decay:c0,inertia:c0,tween:Ml,keyframes:Ml,spring:wk};function Dl({autoplay:e=!0,delay:t=0,driver:n=oM,keyframes:r,type:i="keyframes",repeat:s=0,repeatDelay:o=0,repeatType:a="loop",onPlay:l,onStop:u,onComplete:c,onUpdate:f,...d}){let h=1,g=!1,y,w;const m=()=>{w=new Promise(F=>{y=F})};m();let x;const v=aM[i]||Ml;let k;v!==Ml&&typeof r[0]!="number"&&(k=yk([0,100],r,{clamp:!1}),r=[0,100]);const N=v({...d,keyframes:r});let S;a==="mirror"&&(S=v({...d,keyframes:[...r].reverse(),velocity:-(d.velocity||0)}));let A="idle",P=null,D=null,C=null;N.calculatedDuration===null&&s&&(N.calculatedDuration=d0(N));const{calculatedDuration:L}=N;let M=1/0,O=1/0;L!==null&&(M=L+o,O=M*(s+1)-o);let _=0;const R=F=>{if(D===null)return;h>0&&(D=Math.min(D,F)),h<0&&(D=Math.min(F-O/h,D)),P!==null?_=P:_=Math.round(F-D)*h;const H=_-t*(h>=0?1:-1),E=h>=0?H<0:H>O;_=Math.max(H,0),A==="finished"&&P===null&&(_=O);let q=_,X=N;if(s){const de=Math.min(_,O)/M;let le=Math.floor(de),Ee=de%1;!Ee&&de>=1&&(Ee=1),Ee===1&&le--,le=Math.min(le,s+1),!!(le%2)&&(a==="reverse"?(Ee=1-Ee,o&&(Ee-=o/M)):a==="mirror"&&(X=S)),q=kr(0,1,Ee)*M}const G=E?{done:!1,value:r[0]}:X.next(q);k&&(G.value=k(G.value));let{done:ne}=G;!E&&L!==null&&(ne=h>=0?_>=O:_<=0);const oe=P===null&&(A==="finished"||A==="running"&&ne);return f&&f(G.value),oe&&z(),G},I=()=>{x&&x.stop(),x=void 0},V=()=>{A="idle",I(),y(),m(),D=C=null},z=()=>{A="finished",c&&c(),I(),y()},j=()=>{if(g)return;x||(x=n(R));const F=x.now();l&&l(),P!==null?D=F-P:(!D||A==="finished")&&(D=F),A==="finished"&&m(),C=D,P=null,A="running",x.start()};e&&j();const b={then(F,H){return w.then(F,H)},get time(){return Ln(_)},set time(F){F=Gr(F),_=F,P!==null||!x||h===0?P=F:D=x.now()-F/h},get duration(){const F=N.calculatedDuration===null?d0(N):N.calculatedDuration;return Ln(F)},get speed(){return h},set speed(F){F===h||!x||(h=F,b.time=Ln(_))},get state(){return A},play:j,pause:()=>{A="paused",P=_},stop:()=>{g=!0,A!=="idle"&&(A="idle",u&&u(),V())},cancel:()=>{C!==null&&R(C),V()},complete:()=>{A="finished"},sample:F=>(D=0,R(F))};return b}function lM(e){let t;return()=>(t===void 0&&(t=e()),t)}const uM=lM(()=>Object.hasOwnProperty.call(Element.prototype,"animate")),cM=new Set(["opacity","clipPath","filter","transform","backgroundColor"]),ka=10,fM=2e4,dM=(e,t)=>t.type==="spring"||e==="backgroundColor"||!Qw(t.ease);function hM(e,t,{onUpdate:n,onComplete:r,...i}){if(!(uM()&&cM.has(t)&&!i.repeatDelay&&i.repeatType!=="mirror"&&i.damping!==0&&i.type!=="inertia"))return!1;let o=!1,a,l,u=!1;const c=()=>{l=new Promise(v=>{a=v})};c();let{keyframes:f,duration:d=300,ease:h,times:g}=i;if(dM(t,i)){const v=Dl({...i,repeat:0,delay:0});let k={done:!1,value:f[0]};const N=[];let S=0;for(;!k.done&&S{u=!1,y.cancel()},m=()=>{u=!0,be.update(w),a(),c()};return y.onfinish=()=>{u||(e.set(Sj(f,i)),r&&r(),m())},{then(v,k){return l.then(v,k)},attachTimeline(v){return y.timeline=v,y.onfinish=null,Fe},get time(){return Ln(y.currentTime||0)},set time(v){y.currentTime=Gr(v)},get speed(){return y.playbackRate},set speed(v){y.playbackRate=v},get duration(){return Ln(d)},play:()=>{o||(y.play(),Vn(w))},pause:()=>y.pause(),stop:()=>{if(o=!0,y.playState==="idle")return;const{currentTime:v}=y;if(v){const k=Dl({...i,autoplay:!1});e.setWithVelocity(k.sample(v-ka).value,k.sample(v).value,ka)}m()},complete:()=>{u||y.finish()},cancel:m}}function pM({keyframes:e,delay:t,onUpdate:n,onComplete:r}){const i=()=>(n&&n(e[e.length-1]),r&&r(),{time:0,speed:1,duration:0,play:Fe,pause:Fe,stop:Fe,then:s=>(s(),Promise.resolve()),cancel:Fe,complete:Fe});return t?Dl({keyframes:[0,1],duration:0,delay:t,onComplete:i}):i()}const mM={type:"spring",stiffness:500,damping:25,restSpeed:10},gM=e=>({type:"spring",stiffness:550,damping:e===0?2*Math.sqrt(550):30,restSpeed:10}),yM={type:"keyframes",duration:.8},xM={type:"keyframes",ease:[.25,.1,.35,1],duration:.3},vM=(e,{keyframes:t})=>t.length>2?yM:li.has(e)?e.startsWith("scale")?gM(t[1]):mM:xM,od=(e,t)=>e==="zIndex"?!1:!!(typeof t=="number"||Array.isArray(t)||typeof t=="string"&&(br.test(t)||t==="0")&&!t.startsWith("url(")),wM=new Set(["brightness","contrast","saturate","opacity"]);function kM(e){const[t,n]=e.slice(0,-1).split("(");if(t==="drop-shadow")return e;const[r]=n.match(wu)||[];if(!r)return e;const i=n.replace(r,"");let s=wM.has(t)?1:0;return r!==n&&(s*=100),t+"("+s+i+")"}const bM=/([a-z-]*)\(.*?\)/g,ad={...br,getAnimatableNone:e=>{const t=e.match(bM);return t?t.map(kM).join(" "):e}},SM={...Iw,color:ct,backgroundColor:ct,outlineColor:ct,fill:ct,stroke:ct,borderColor:ct,borderTopColor:ct,borderRightColor:ct,borderBottomColor:ct,borderLeftColor:ct,filter:ad,WebkitFilter:ad},ap=e=>SM[e];function kk(e,t){let n=ap(e);return n!==ad&&(n=br),n.getAnimatableNone?n.getAnimatableNone(t):void 0}const bk=e=>/^0[^.\s]+$/.test(e);function _M(e){if(typeof e=="number")return e===0;if(e!==null)return e==="none"||e==="0"||bk(e)}function CM(e,t,n,r){const i=od(t,n);let s;Array.isArray(n)?s=[...n]:s=[null,n];const o=r.from!==void 0?r.from:e.get();let a;const l=[];for(let u=0;ui=>{const s=lp(r,e)||{},o=s.delay||r.delay||0;let{elapsed:a=0}=r;a=a-Gr(o);const l=CM(t,e,n,s),u=l[0],c=l[l.length-1],f=od(e,u),d=od(e,c);let h={keyframes:l,velocity:t.getVelocity(),ease:"easeOut",...s,delay:-a,onUpdate:g=>{t.set(g),s.onUpdate&&s.onUpdate(g)},onComplete:()=>{i(),s.onComplete&&s.onComplete()}};if(EM(s)||(h={...h,...vM(e,h)}),h.duration&&(h.duration=Gr(h.duration)),h.repeatDelay&&(h.repeatDelay=Gr(h.repeatDelay)),!f||!d||kj.current||s.type===!1||NM.skipAnimations)return pM(h);if(!r.isHandoff&&t.owner&&t.owner.current instanceof HTMLElement&&!t.owner.getProps().onUpdate){const g=hM(t,e,h);if(g)return g}return Dl(h)};function Il(e){return!!(Ct(e)&&e.add)}const Sk=e=>/^\-?\d*\.?\d+$/.test(e);function cp(e,t){e.indexOf(t)===-1&&e.push(t)}function fp(e,t){const n=e.indexOf(t);n>-1&&e.splice(n,1)}class dp{constructor(){this.subscriptions=[]}add(t){return cp(this.subscriptions,t),()=>fp(this.subscriptions,t)}notify(t,n,r){const i=this.subscriptions.length;if(i)if(i===1)this.subscriptions[0](t,n,r);else for(let s=0;s!isNaN(parseFloat(e));class AM{constructor(t,n={}){this.version="10.18.0",this.timeDelta=0,this.lastUpdated=0,this.canTrackVelocity=!1,this.events={},this.updateAndNotify=(r,i=!0)=>{this.prev=this.current,this.current=r;const{delta:s,timestamp:o}=rt;this.lastUpdated!==o&&(this.timeDelta=s,this.lastUpdated=o,be.postRender(this.scheduleVelocityCheck)),this.prev!==this.current&&this.events.change&&this.events.change.notify(this.current),this.events.velocityChange&&this.events.velocityChange.notify(this.getVelocity()),i&&this.events.renderRequest&&this.events.renderRequest.notify(this.current)},this.scheduleVelocityCheck=()=>be.postRender(this.velocityCheck),this.velocityCheck=({timestamp:r})=>{r!==this.lastUpdated&&(this.prev=this.current,this.events.velocityChange&&this.events.velocityChange.notify(this.getVelocity()))},this.hasAnimated=!1,this.prev=this.current=t,this.canTrackVelocity=TM(this.current),this.owner=n.owner}onChange(t){return this.on("change",t)}on(t,n){this.events[t]||(this.events[t]=new dp);const r=this.events[t].add(n);return t==="change"?()=>{r(),be.read(()=>{this.events.change.getSize()||this.stop()})}:r}clearListeners(){for(const t in this.events)this.events[t].clear()}attach(t,n){this.passiveEffect=t,this.stopPassiveEffect=n}set(t,n=!0){!n||!this.passiveEffect?this.updateAndNotify(t,n):this.passiveEffect(t,this.updateAndNotify)}setWithVelocity(t,n,r){this.set(n),this.prev=t,this.timeDelta=r}jump(t){this.updateAndNotify(t),this.prev=t,this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}get(){return this.current}getPrevious(){return this.prev}getVelocity(){return this.canTrackVelocity?xk(parseFloat(this.current)-parseFloat(this.prev),this.timeDelta):0}start(t){return this.stop(),new Promise(n=>{this.hasAnimated=!0,this.animation=t(n),this.events.animationStart&&this.events.animationStart.notify()}).then(()=>{this.events.animationComplete&&this.events.animationComplete.notify(),this.clearAnimation()})}stop(){this.animation&&(this.animation.stop(),this.events.animationCancel&&this.events.animationCancel.notify()),this.clearAnimation()}isAnimating(){return!!this.animation}clearAnimation(){delete this.animation}destroy(){this.clearListeners(),this.stop(),this.stopPassiveEffect&&this.stopPassiveEffect()}}function rs(e,t){return new AM(e,t)}const _k=e=>t=>t.test(e),PM={test:e=>e==="auto",parse:e=>e},Ck=[ui,te,kn,Qn,zP,RP,PM],Cs=e=>Ck.find(_k(e)),jM=[...Ck,ct,br],MM=e=>jM.find(_k(e));function DM(e,t,n){e.hasValue(t)?e.getValue(t).set(n):e.addValue(t,rs(n))}function IM(e,t){const n=bu(e,t);let{transitionEnd:r={},transition:i={},...s}=n?e.makeTargetAnimatable(n,!1):{};s={...s,...r};for(const o in s){const a=QP(s[o]);DM(e,o,a)}}function LM(e,t,n){var r,i;const s=Object.keys(t).filter(a=>!e.hasValue(a)),o=s.length;if(o)for(let a=0;al.remove(f))),u.push(w)}return o&&Promise.all(u).then(()=>{o&&IM(e,o)}),u}function ld(e,t,n={}){const r=bu(e,t,n.custom);let{transition:i=e.getDefaultTransition()||{}}=r||{};n.transitionOverride&&(i=n.transitionOverride);const s=r?()=>Promise.all(Ek(e,r,n)):()=>Promise.resolve(),o=e.variantChildren&&e.variantChildren.size?(l=0)=>{const{delayChildren:u=0,staggerChildren:c,staggerDirection:f}=i;return VM(e,t,u+l,c,f,n)}:()=>Promise.resolve(),{when:a}=i;if(a){const[l,u]=a==="beforeChildren"?[s,o]:[o,s];return l().then(()=>u())}else return Promise.all([s(),o(n.delay)])}function VM(e,t,n=0,r=0,i=1,s){const o=[],a=(e.variantChildren.size-1)*r,l=i===1?(u=0)=>u*r:(u=0)=>a-u*r;return Array.from(e.variantChildren).sort($M).forEach((u,c)=>{u.notify("AnimationStart",t),o.push(ld(u,t,{...s,delay:n+l(c)}).then(()=>u.notify("AnimationComplete",t)))}),Promise.all(o)}function $M(e,t){return e.sortNodePosition(t)}function BM(e,t,n={}){e.notify("AnimationStart",t);let r;if(Array.isArray(t)){const i=t.map(s=>ld(e,s,n));r=Promise.all(i)}else if(typeof t=="string")r=ld(e,t,n);else{const i=typeof t=="function"?bu(e,t,n.custom):t;r=Promise.all(Ek(e,i,n))}return r.then(()=>e.notify("AnimationComplete",t))}const HM=[...qh].reverse(),UM=qh.length;function WM(e){return t=>Promise.all(t.map(({animation:n,options:r})=>BM(e,n,r)))}function YM(e){let t=WM(e);const n=KM();let r=!0;const i=(l,u)=>{const c=bu(e,u);if(c){const{transition:f,transitionEnd:d,...h}=c;l={...l,...h,...d}}return l};function s(l){t=l(e)}function o(l,u){const c=e.getProps(),f=e.getVariantContext(!0)||{},d=[],h=new Set;let g={},y=1/0;for(let m=0;my&&N,C=!1;const L=Array.isArray(k)?k:[k];let M=L.reduce(i,{});S===!1&&(M={});const{prevResolvedValues:O={}}=v,_={...O,...M},R=I=>{D=!0,h.has(I)&&(C=!0,h.delete(I)),v.needsAnimating[I]=!0};for(const I in _){const V=M[I],z=O[I];if(g.hasOwnProperty(I))continue;let j=!1;Pl(V)&&Pl(z)?j=!Gw(V,z):j=V!==z,j?V!==void 0?R(I):h.add(I):V!==void 0&&h.has(I)?R(I):v.protectedKeys[I]=!0}v.prevProp=k,v.prevResolvedValues=M,v.isActive&&(g={...g,...M}),r&&e.blockInitialAnimation&&(D=!1),D&&(!A||C)&&d.push(...L.map(I=>({animation:I,options:{type:x,...l}})))}if(h.size){const m={};h.forEach(x=>{const v=e.getBaseTarget(x);v!==void 0&&(m[x]=v)}),d.push({animation:m})}let w=!!d.length;return r&&(c.initial===!1||c.initial===c.animate)&&!e.manuallyAnimateOnMount&&(w=!1),r=!1,w?t(d):Promise.resolve()}function a(l,u,c){var f;if(n[l].isActive===u)return Promise.resolve();(f=e.variantChildren)===null||f===void 0||f.forEach(h=>{var g;return(g=h.animationState)===null||g===void 0?void 0:g.setActive(l,u)}),n[l].isActive=u;const d=o(c,l);for(const h in n)n[h].protectedKeys={};return d}return{animateChanges:o,setActive:a,setAnimateFunction:s,getState:()=>n}}function qM(e,t){return typeof t=="string"?t!==e:Array.isArray(t)?!Gw(t,e):!1}function jr(e=!1){return{isActive:e,protectedKeys:{},needsAnimating:{},prevResolvedValues:{}}}function KM(){return{animate:jr(!0),whileInView:jr(),whileHover:jr(),whileTap:jr(),whileDrag:jr(),whileFocus:jr(),exit:jr()}}class GM extends Nr{constructor(t){super(t),t.animationState||(t.animationState=YM(t))}updateAnimationControlsSubscription(){const{animate:t}=this.node.getProps();this.unmount(),xu(t)&&(this.unmount=t.subscribe(this.node))}mount(){this.updateAnimationControlsSubscription()}update(){const{animate:t}=this.node.getProps(),{animate:n}=this.node.prevProps||{};t!==n&&this.updateAnimationControlsSubscription()}unmount(){}}let XM=0;class QM extends Nr{constructor(){super(...arguments),this.id=XM++}update(){if(!this.node.presenceContext)return;const{isPresent:t,onExitComplete:n,custom:r}=this.node.presenceContext,{isPresent:i}=this.node.prevPresenceContext||{};if(!this.node.animationState||t===i)return;const s=this.node.animationState.setActive("exit",!t,{custom:r??this.node.getProps().custom});n&&!t&&s.then(()=>n(this.id))}mount(){const{register:t}=this.node.presenceContext||{};t&&(this.unmount=t(this.id))}unmount(){}}const ZM={animation:{Feature:GM},exit:{Feature:QM}},h0=(e,t)=>Math.abs(e-t);function JM(e,t){const n=h0(e.x,t.x),r=h0(e.y,t.y);return Math.sqrt(n**2+r**2)}class Nk{constructor(t,n,{transformPagePoint:r,contextWindow:i,dragSnapToOrigin:s=!1}={}){if(this.startEvent=null,this.lastMoveEvent=null,this.lastMoveEventInfo=null,this.handlers={},this.contextWindow=window,this.updatePoint=()=>{if(!(this.lastMoveEvent&&this.lastMoveEventInfo))return;const f=Dc(this.lastMoveEventInfo,this.history),d=this.startEvent!==null,h=JM(f.offset,{x:0,y:0})>=3;if(!d&&!h)return;const{point:g}=f,{timestamp:y}=rt;this.history.push({...g,timestamp:y});const{onStart:w,onMove:m}=this.handlers;d||(w&&w(this.lastMoveEvent,f),this.startEvent=this.lastMoveEvent),m&&m(this.lastMoveEvent,f)},this.handlePointerMove=(f,d)=>{this.lastMoveEvent=f,this.lastMoveEventInfo=Mc(d,this.transformPagePoint),be.update(this.updatePoint,!0)},this.handlePointerUp=(f,d)=>{this.end();const{onEnd:h,onSessionEnd:g,resumeAnimation:y}=this.handlers;if(this.dragSnapToOrigin&&y&&y(),!(this.lastMoveEvent&&this.lastMoveEventInfo))return;const w=Dc(f.type==="pointercancel"?this.lastMoveEventInfo:Mc(d,this.transformPagePoint),this.history);this.startEvent&&h&&h(f,w),g&&g(f,w)},!Uw(t))return;this.dragSnapToOrigin=s,this.handlers=n,this.transformPagePoint=r,this.contextWindow=i||window;const o=ku(t),a=Mc(o,this.transformPagePoint),{point:l}=a,{timestamp:u}=rt;this.history=[{...l,timestamp:u}];const{onSessionStart:c}=n;c&&c(t,Dc(a,this.history)),this.removeListeners=gr(In(this.contextWindow,"pointermove",this.handlePointerMove),In(this.contextWindow,"pointerup",this.handlePointerUp),In(this.contextWindow,"pointercancel",this.handlePointerUp))}updateHandlers(t){this.handlers=t}end(){this.removeListeners&&this.removeListeners(),Vn(this.updatePoint)}}function Mc(e,t){return t?{point:t(e.point)}:e}function p0(e,t){return{x:e.x-t.x,y:e.y-t.y}}function Dc({point:e},t){return{point:e,delta:p0(e,Tk(t)),offset:p0(e,eD(t)),velocity:tD(t,.1)}}function eD(e){return e[0]}function Tk(e){return e[e.length-1]}function tD(e,t){if(e.length<2)return{x:0,y:0};let n=e.length-1,r=null;const i=Tk(e);for(;n>=0&&(r=e[n],!(i.timestamp-r.timestamp>Gr(t)));)n--;if(!r)return{x:0,y:0};const s=Ln(i.timestamp-r.timestamp);if(s===0)return{x:0,y:0};const o={x:(i.x-r.x)/s,y:(i.y-r.y)/s};return o.x===1/0&&(o.x=0),o.y===1/0&&(o.y=0),o}function zt(e){return e.max-e.min}function ud(e,t=0,n=.01){return Math.abs(e-t)<=n}function m0(e,t,n,r=.5){e.origin=r,e.originPoint=Pe(t.min,t.max,e.origin),e.scale=zt(n)/zt(t),(ud(e.scale,1,1e-4)||isNaN(e.scale))&&(e.scale=1),e.translate=Pe(n.min,n.max,e.origin)-e.originPoint,(ud(e.translate)||isNaN(e.translate))&&(e.translate=0)}function to(e,t,n,r){m0(e.x,t.x,n.x,r?r.originX:void 0),m0(e.y,t.y,n.y,r?r.originY:void 0)}function g0(e,t,n){e.min=n.min+t.min,e.max=e.min+zt(t)}function nD(e,t,n){g0(e.x,t.x,n.x),g0(e.y,t.y,n.y)}function y0(e,t,n){e.min=t.min-n.min,e.max=e.min+zt(t)}function no(e,t,n){y0(e.x,t.x,n.x),y0(e.y,t.y,n.y)}function rD(e,{min:t,max:n},r){return t!==void 0&&en&&(e=r?Pe(n,e,r.max):Math.min(e,n)),e}function x0(e,t,n){return{min:t!==void 0?e.min+t:void 0,max:n!==void 0?e.max+n-(e.max-e.min):void 0}}function iD(e,{top:t,left:n,bottom:r,right:i}){return{x:x0(e.x,n,i),y:x0(e.y,t,r)}}function v0(e,t){let n=t.min-e.min,r=t.max-e.max;return t.max-t.minr?n=Eo(t.min,t.max-r,e.min):r>i&&(n=Eo(e.min,e.max-i,t.min)),kr(0,1,n)}function aD(e,t){const n={};return t.min!==void 0&&(n.min=t.min-e.min),t.max!==void 0&&(n.max=t.max-e.min),n}const cd=.35;function lD(e=cd){return e===!1?e=0:e===!0&&(e=cd),{x:w0(e,"left","right"),y:w0(e,"top","bottom")}}function w0(e,t,n){return{min:k0(e,t),max:k0(e,n)}}function k0(e,t){return typeof e=="number"?e:e[t]||0}const b0=()=>({translate:0,scale:1,origin:0,originPoint:0}),ji=()=>({x:b0(),y:b0()}),S0=()=>({min:0,max:0}),Ve=()=>({x:S0(),y:S0()});function Bt(e){return[e("x"),e("y")]}function Ak({top:e,left:t,right:n,bottom:r}){return{x:{min:t,max:n},y:{min:e,max:r}}}function uD({x:e,y:t}){return{top:t.min,right:e.max,bottom:t.max,left:e.min}}function cD(e,t){if(!t)return e;const n=t({x:e.left,y:e.top}),r=t({x:e.right,y:e.bottom});return{top:n.y,left:n.x,bottom:r.y,right:r.x}}function Ic(e){return e===void 0||e===1}function fd({scale:e,scaleX:t,scaleY:n}){return!Ic(e)||!Ic(t)||!Ic(n)}function Lr(e){return fd(e)||Pk(e)||e.z||e.rotate||e.rotateX||e.rotateY}function Pk(e){return _0(e.x)||_0(e.y)}function _0(e){return e&&e!=="0%"}function Ll(e,t,n){const r=e-n,i=t*r;return n+i}function C0(e,t,n,r,i){return i!==void 0&&(e=Ll(e,i,r)),Ll(e,n,r)+t}function dd(e,t=0,n=1,r,i){e.min=C0(e.min,t,n,r,i),e.max=C0(e.max,t,n,r,i)}function jk(e,{x:t,y:n}){dd(e.x,t.translate,t.scale,t.originPoint),dd(e.y,n.translate,n.scale,n.originPoint)}function fD(e,t,n,r=!1){const i=n.length;if(!i)return;t.x=t.y=1;let s,o;for(let a=0;a1.0000000000001||e<.999999999999?e:1}function er(e,t){e.min=e.min+t,e.max=e.max+t}function N0(e,t,[n,r,i]){const s=t[i]!==void 0?t[i]:.5,o=Pe(e.min,e.max,s);dd(e,t[n],t[r],o,t.scale)}const dD=["x","scaleX","originX"],hD=["y","scaleY","originY"];function Mi(e,t){N0(e.x,t,dD),N0(e.y,t,hD)}function Mk(e,t){return Ak(cD(e.getBoundingClientRect(),t))}function pD(e,t,n){const r=Mk(e,n),{scroll:i}=t;return i&&(er(r.x,i.offset.x),er(r.y,i.offset.y)),r}const Dk=({current:e})=>e?e.ownerDocument.defaultView:null,mD=new WeakMap;class gD{constructor(t){this.openGlobalLock=null,this.isDragging=!1,this.currentDirection=null,this.originPoint={x:0,y:0},this.constraints=!1,this.hasMutatedConstraints=!1,this.elastic=Ve(),this.visualElement=t}start(t,{snapToCursor:n=!1}={}){const{presenceContext:r}=this.visualElement;if(r&&r.isPresent===!1)return;const i=c=>{const{dragSnapToOrigin:f}=this.getProps();f?this.pauseAnimation():this.stopAnimation(),n&&this.snapToCursor(ku(c,"page").point)},s=(c,f)=>{const{drag:d,dragPropagation:h,onDragStart:g}=this.getProps();if(d&&!h&&(this.openGlobalLock&&this.openGlobalLock(),this.openGlobalLock=Yw(d),!this.openGlobalLock))return;this.isDragging=!0,this.currentDirection=null,this.resolveConstraints(),this.visualElement.projection&&(this.visualElement.projection.isAnimationBlocked=!0,this.visualElement.projection.target=void 0),Bt(w=>{let m=this.getAxisMotionValue(w).get()||0;if(kn.test(m)){const{projection:x}=this.visualElement;if(x&&x.layout){const v=x.layout.layoutBox[w];v&&(m=zt(v)*(parseFloat(m)/100))}}this.originPoint[w]=m}),g&&be.update(()=>g(c,f),!1,!0);const{animationState:y}=this.visualElement;y&&y.setActive("whileDrag",!0)},o=(c,f)=>{const{dragPropagation:d,dragDirectionLock:h,onDirectionLock:g,onDrag:y}=this.getProps();if(!d&&!this.openGlobalLock)return;const{offset:w}=f;if(h&&this.currentDirection===null){this.currentDirection=yD(w),this.currentDirection!==null&&g&&g(this.currentDirection);return}this.updateAxis("x",f.point,w),this.updateAxis("y",f.point,w),this.visualElement.render(),y&&y(c,f)},a=(c,f)=>this.stop(c,f),l=()=>Bt(c=>{var f;return this.getAnimationState(c)==="paused"&&((f=this.getAxisMotionValue(c).animation)===null||f===void 0?void 0:f.play())}),{dragSnapToOrigin:u}=this.getProps();this.panSession=new Nk(t,{onSessionStart:i,onStart:s,onMove:o,onSessionEnd:a,resumeAnimation:l},{transformPagePoint:this.visualElement.getTransformPagePoint(),dragSnapToOrigin:u,contextWindow:Dk(this.visualElement)})}stop(t,n){const r=this.isDragging;if(this.cancel(),!r)return;const{velocity:i}=n;this.startAnimation(i);const{onDragEnd:s}=this.getProps();s&&be.update(()=>s(t,n))}cancel(){this.isDragging=!1;const{projection:t,animationState:n}=this.visualElement;t&&(t.isAnimationBlocked=!1),this.panSession&&this.panSession.end(),this.panSession=void 0;const{dragPropagation:r}=this.getProps();!r&&this.openGlobalLock&&(this.openGlobalLock(),this.openGlobalLock=null),n&&n.setActive("whileDrag",!1)}updateAxis(t,n,r){const{drag:i}=this.getProps();if(!r||!ba(t,i,this.currentDirection))return;const s=this.getAxisMotionValue(t);let o=this.originPoint[t]+r[t];this.constraints&&this.constraints[t]&&(o=rD(o,this.constraints[t],this.elastic[t])),s.set(o)}resolveConstraints(){var t;const{dragConstraints:n,dragElastic:r}=this.getProps(),i=this.visualElement.projection&&!this.visualElement.projection.layout?this.visualElement.projection.measure(!1):(t=this.visualElement.projection)===null||t===void 0?void 0:t.layout,s=this.constraints;n&&Ai(n)?this.constraints||(this.constraints=this.resolveRefConstraints()):n&&i?this.constraints=iD(i.layoutBox,n):this.constraints=!1,this.elastic=lD(r),s!==this.constraints&&i&&this.constraints&&!this.hasMutatedConstraints&&Bt(o=>{this.getAxisMotionValue(o)&&(this.constraints[o]=aD(i.layoutBox[o],this.constraints[o]))})}resolveRefConstraints(){const{dragConstraints:t,onMeasureDragConstraints:n}=this.getProps();if(!t||!Ai(t))return!1;const r=t.current,{projection:i}=this.visualElement;if(!i||!i.layout)return!1;const s=pD(r,i.root,this.visualElement.getTransformPagePoint());let o=sD(i.layout.layoutBox,s);if(n){const a=n(uD(o));this.hasMutatedConstraints=!!a,a&&(o=Ak(a))}return o}startAnimation(t){const{drag:n,dragMomentum:r,dragElastic:i,dragTransition:s,dragSnapToOrigin:o,onDragTransitionEnd:a}=this.getProps(),l=this.constraints||{},u=Bt(c=>{if(!ba(c,n,this.currentDirection))return;let f=l&&l[c]||{};o&&(f={min:0,max:0});const d=i?200:1e6,h=i?40:1e7,g={type:"inertia",velocity:r?t[c]:0,bounceStiffness:d,bounceDamping:h,timeConstant:750,restDelta:1,restSpeed:10,...s,...f};return this.startAxisValueAnimation(c,g)});return Promise.all(u).then(a)}startAxisValueAnimation(t,n){const r=this.getAxisMotionValue(t);return r.start(up(t,r,0,n))}stopAnimation(){Bt(t=>this.getAxisMotionValue(t).stop())}pauseAnimation(){Bt(t=>{var n;return(n=this.getAxisMotionValue(t).animation)===null||n===void 0?void 0:n.pause()})}getAnimationState(t){var n;return(n=this.getAxisMotionValue(t).animation)===null||n===void 0?void 0:n.state}getAxisMotionValue(t){const n="_drag"+t.toUpperCase(),r=this.visualElement.getProps(),i=r[n];return i||this.visualElement.getValue(t,(r.initial?r.initial[t]:void 0)||0)}snapToCursor(t){Bt(n=>{const{drag:r}=this.getProps();if(!ba(n,r,this.currentDirection))return;const{projection:i}=this.visualElement,s=this.getAxisMotionValue(n);if(i&&i.layout){const{min:o,max:a}=i.layout.layoutBox[n];s.set(t[n]-Pe(o,a,.5))}})}scalePositionWithinConstraints(){if(!this.visualElement.current)return;const{drag:t,dragConstraints:n}=this.getProps(),{projection:r}=this.visualElement;if(!Ai(n)||!r||!this.constraints)return;this.stopAnimation();const i={x:0,y:0};Bt(o=>{const a=this.getAxisMotionValue(o);if(a){const l=a.get();i[o]=oD({min:l,max:l},this.constraints[o])}});const{transformTemplate:s}=this.visualElement.getProps();this.visualElement.current.style.transform=s?s({},""):"none",r.root&&r.root.updateScroll(),r.updateLayout(),this.resolveConstraints(),Bt(o=>{if(!ba(o,t,null))return;const a=this.getAxisMotionValue(o),{min:l,max:u}=this.constraints[o];a.set(Pe(l,u,i[o]))})}addListeners(){if(!this.visualElement.current)return;mD.set(this.visualElement,this);const t=this.visualElement.current,n=In(t,"pointerdown",l=>{const{drag:u,dragListener:c=!0}=this.getProps();u&&c&&this.start(l)}),r=()=>{const{dragConstraints:l}=this.getProps();Ai(l)&&(this.constraints=this.resolveRefConstraints())},{projection:i}=this.visualElement,s=i.addEventListener("measure",r);i&&!i.layout&&(i.root&&i.root.updateScroll(),i.updateLayout()),r();const o=jn(window,"resize",()=>this.scalePositionWithinConstraints()),a=i.addEventListener("didUpdate",({delta:l,hasLayoutChanged:u})=>{this.isDragging&&u&&(Bt(c=>{const f=this.getAxisMotionValue(c);f&&(this.originPoint[c]+=l[c].translate,f.set(f.get()+l[c].translate))}),this.visualElement.render())});return()=>{o(),n(),s(),a&&a()}}getProps(){const t=this.visualElement.getProps(),{drag:n=!1,dragDirectionLock:r=!1,dragPropagation:i=!1,dragConstraints:s=!1,dragElastic:o=cd,dragMomentum:a=!0}=t;return{...t,drag:n,dragDirectionLock:r,dragPropagation:i,dragConstraints:s,dragElastic:o,dragMomentum:a}}}function ba(e,t,n){return(t===!0||t===e)&&(n===null||n===e)}function yD(e,t=10){let n=null;return Math.abs(e.y)>t?n="y":Math.abs(e.x)>t&&(n="x"),n}class xD extends Nr{constructor(t){super(t),this.removeGroupControls=Fe,this.removeListeners=Fe,this.controls=new gD(t)}mount(){const{dragControls:t}=this.node.getProps();t&&(this.removeGroupControls=t.subscribe(this.controls)),this.removeListeners=this.controls.addListeners()||Fe}unmount(){this.removeGroupControls(),this.removeListeners()}}const T0=e=>(t,n)=>{e&&be.update(()=>e(t,n))};class vD extends Nr{constructor(){super(...arguments),this.removePointerDownListener=Fe}onPointerDown(t){this.session=new Nk(t,this.createPanHandlers(),{transformPagePoint:this.node.getTransformPagePoint(),contextWindow:Dk(this.node)})}createPanHandlers(){const{onPanSessionStart:t,onPanStart:n,onPan:r,onPanEnd:i}=this.node.getProps();return{onSessionStart:T0(t),onStart:T0(n),onMove:r,onEnd:(s,o)=>{delete this.session,i&&be.update(()=>i(s,o))}}}mount(){this.removePointerDownListener=In(this.node.current,"pointerdown",t=>this.onPointerDown(t))}update(){this.session&&this.session.updateHandlers(this.createPanHandlers())}unmount(){this.removePointerDownListener(),this.session&&this.session.end()}}function wD(){const e=T.useContext(gu);if(e===null)return[!0,null];const{isPresent:t,onExitComplete:n,register:r}=e,i=T.useId();return T.useEffect(()=>r(i),[]),!t&&n?[!1,()=>n&&n(i)]:[!0]}const Ka={hasAnimatedSinceResize:!0,hasEverUpdated:!1};function A0(e,t){return t.max===t.min?0:e/(t.max-t.min)*100}const Es={correct:(e,t)=>{if(!t.target)return e;if(typeof e=="string")if(te.test(e))e=parseFloat(e);else return e;const n=A0(e,t.target.x),r=A0(e,t.target.y);return`${n}% ${r}%`}},kD={correct:(e,{treeScale:t,projectionDelta:n})=>{const r=e,i=br.parse(e);if(i.length>5)return r;const s=br.createTransformer(e),o=typeof i[0]!="number"?1:0,a=n.x.scale*t.x,l=n.y.scale*t.y;i[0+o]/=a,i[1+o]/=l;const u=Pe(a,l,.5);return typeof i[2+o]=="number"&&(i[2+o]/=u),typeof i[3+o]=="number"&&(i[3+o]/=u),s(i)}};class bD extends B.Component{componentDidMount(){const{visualElement:t,layoutGroup:n,switchLayoutGroup:r,layoutId:i}=this.props,{projection:s}=t;AP(SD),s&&(n.group&&n.group.add(s),r&&r.register&&i&&r.register(s),s.root.didUpdate(),s.addEventListener("animationComplete",()=>{this.safeToRemove()}),s.setOptions({...s.options,onExitComplete:()=>this.safeToRemove()})),Ka.hasEverUpdated=!0}getSnapshotBeforeUpdate(t){const{layoutDependency:n,visualElement:r,drag:i,isPresent:s}=this.props,o=r.projection;return o&&(o.isPresent=s,i||t.layoutDependency!==n||n===void 0?o.willUpdate():this.safeToRemove(),t.isPresent!==s&&(s?o.promote():o.relegate()||be.postRender(()=>{const a=o.getStack();(!a||!a.members.length)&&this.safeToRemove()}))),null}componentDidUpdate(){const{projection:t}=this.props.visualElement;t&&(t.root.didUpdate(),queueMicrotask(()=>{!t.currentAnimation&&t.isLead()&&this.safeToRemove()}))}componentWillUnmount(){const{visualElement:t,layoutGroup:n,switchLayoutGroup:r}=this.props,{projection:i}=t;i&&(i.scheduleCheckAfterUnmount(),n&&n.group&&n.group.remove(i),r&&r.deregister&&r.deregister(i))}safeToRemove(){const{safeToRemove:t}=this.props;t&&t()}render(){return null}}function Ik(e){const[t,n]=wD(),r=T.useContext(Gh);return B.createElement(bD,{...e,layoutGroup:r,switchLayoutGroup:T.useContext(Aw),isPresent:t,safeToRemove:n})}const SD={borderRadius:{...Es,applyTo:["borderTopLeftRadius","borderTopRightRadius","borderBottomLeftRadius","borderBottomRightRadius"]},borderTopLeftRadius:Es,borderTopRightRadius:Es,borderBottomLeftRadius:Es,borderBottomRightRadius:Es,boxShadow:kD},Lk=["TopLeft","TopRight","BottomLeft","BottomRight"],_D=Lk.length,P0=e=>typeof e=="string"?parseFloat(e):e,j0=e=>typeof e=="number"||te.test(e);function CD(e,t,n,r,i,s){i?(e.opacity=Pe(0,n.opacity!==void 0?n.opacity:1,ED(r)),e.opacityExit=Pe(t.opacity!==void 0?t.opacity:1,0,ND(r))):s&&(e.opacity=Pe(t.opacity!==void 0?t.opacity:1,n.opacity!==void 0?n.opacity:1,r));for(let o=0;o<_D;o++){const a=`border${Lk[o]}Radius`;let l=M0(t,a),u=M0(n,a);if(l===void 0&&u===void 0)continue;l||(l=0),u||(u=0),l===0||u===0||j0(l)===j0(u)?(e[a]=Math.max(Pe(P0(l),P0(u),r),0),(kn.test(u)||kn.test(l))&&(e[a]+="%")):e[a]=u}(t.rotate||n.rotate)&&(e.rotate=Pe(t.rotate||0,n.rotate||0,r))}function M0(e,t){return e[t]!==void 0?e[t]:e.borderRadius}const ED=Rk(0,.5,ik),ND=Rk(.5,.95,Fe);function Rk(e,t,n){return r=>rt?1:n(Eo(e,t,r))}function D0(e,t){e.min=t.min,e.max=t.max}function $t(e,t){D0(e.x,t.x),D0(e.y,t.y)}function I0(e,t,n,r,i){return e-=t,e=Ll(e,1/n,r),i!==void 0&&(e=Ll(e,1/i,r)),e}function TD(e,t=0,n=1,r=.5,i,s=e,o=e){if(kn.test(t)&&(t=parseFloat(t),t=Pe(o.min,o.max,t/100)-o.min),typeof t!="number")return;let a=Pe(s.min,s.max,r);e===s&&(a-=t),e.min=I0(e.min,t,n,a,i),e.max=I0(e.max,t,n,a,i)}function L0(e,t,[n,r,i],s,o){TD(e,t[n],t[r],t[i],t.scale,s,o)}const AD=["x","scaleX","originX"],PD=["y","scaleY","originY"];function R0(e,t,n,r){L0(e.x,t,AD,n?n.x:void 0,r?r.x:void 0),L0(e.y,t,PD,n?n.y:void 0,r?r.y:void 0)}function z0(e){return e.translate===0&&e.scale===1}function zk(e){return z0(e.x)&&z0(e.y)}function jD(e,t){return e.x.min===t.x.min&&e.x.max===t.x.max&&e.y.min===t.y.min&&e.y.max===t.y.max}function Fk(e,t){return Math.round(e.x.min)===Math.round(t.x.min)&&Math.round(e.x.max)===Math.round(t.x.max)&&Math.round(e.y.min)===Math.round(t.y.min)&&Math.round(e.y.max)===Math.round(t.y.max)}function F0(e){return zt(e.x)/zt(e.y)}class MD{constructor(){this.members=[]}add(t){cp(this.members,t),t.scheduleRender()}remove(t){if(fp(this.members,t),t===this.prevLead&&(this.prevLead=void 0),t===this.lead){const n=this.members[this.members.length-1];n&&this.promote(n)}}relegate(t){const n=this.members.findIndex(i=>t===i);if(n===0)return!1;let r;for(let i=n;i>=0;i--){const s=this.members[i];if(s.isPresent!==!1){r=s;break}}return r?(this.promote(r),!0):!1}promote(t,n){const r=this.lead;if(t!==r&&(this.prevLead=r,this.lead=t,t.show(),r)){r.instance&&r.scheduleRender(),t.scheduleRender(),t.resumeFrom=r,n&&(t.resumeFrom.preserveOpacity=!0),r.snapshot&&(t.snapshot=r.snapshot,t.snapshot.latestValues=r.animationValues||r.latestValues),t.root&&t.root.isUpdating&&(t.isLayoutDirty=!0);const{crossfade:i}=t.options;i===!1&&r.hide()}}exitAnimationComplete(){this.members.forEach(t=>{const{options:n,resumingFrom:r}=t;n.onExitComplete&&n.onExitComplete(),r&&r.options.onExitComplete&&r.options.onExitComplete()})}scheduleRender(){this.members.forEach(t=>{t.instance&&t.scheduleRender(!1)})}removeLeadSnapshot(){this.lead&&this.lead.snapshot&&(this.lead.snapshot=void 0)}}function O0(e,t,n){let r="";const i=e.x.translate/t.x,s=e.y.translate/t.y;if((i||s)&&(r=`translate3d(${i}px, ${s}px, 0) `),(t.x!==1||t.y!==1)&&(r+=`scale(${1/t.x}, ${1/t.y}) `),n){const{rotate:l,rotateX:u,rotateY:c}=n;l&&(r+=`rotate(${l}deg) `),u&&(r+=`rotateX(${u}deg) `),c&&(r+=`rotateY(${c}deg) `)}const o=e.x.scale*t.x,a=e.y.scale*t.y;return(o!==1||a!==1)&&(r+=`scale(${o}, ${a})`),r||"none"}const DD=(e,t)=>e.depth-t.depth;class ID{constructor(){this.children=[],this.isDirty=!1}add(t){cp(this.children,t),this.isDirty=!0}remove(t){fp(this.children,t),this.isDirty=!0}forEach(t){this.isDirty&&this.children.sort(DD),this.isDirty=!1,this.children.forEach(t)}}function LD(e,t){const n=performance.now(),r=({timestamp:i})=>{const s=i-n;s>=t&&(Vn(r),e(s-t))};return be.read(r,!0),()=>Vn(r)}function RD(e){window.MotionDebug&&window.MotionDebug.record(e)}function zD(e){return e instanceof SVGElement&&e.tagName!=="svg"}function FD(e,t,n){const r=Ct(e)?e:rs(e);return r.start(up("",r,t,n)),r.animation}const V0=["","X","Y","Z"],OD={visibility:"hidden"},$0=1e3;let VD=0;const Rr={type:"projectionFrame",totalNodes:0,resolvedTargetDeltas:0,recalculatedProjection:0};function Ok({attachResizeListener:e,defaultParent:t,measureScroll:n,checkIsScrollRoot:r,resetTransform:i}){return class{constructor(o={},a=t==null?void 0:t()){this.id=VD++,this.animationId=0,this.children=new Set,this.options={},this.isTreeAnimating=!1,this.isAnimationBlocked=!1,this.isLayoutDirty=!1,this.isProjectionDirty=!1,this.isSharedProjectionDirty=!1,this.isTransformDirty=!1,this.updateManuallyBlocked=!1,this.updateBlockedByResize=!1,this.isUpdating=!1,this.isSVG=!1,this.needsReset=!1,this.shouldResetTransform=!1,this.treeScale={x:1,y:1},this.eventHandlers=new Map,this.hasTreeAnimated=!1,this.updateScheduled=!1,this.projectionUpdateScheduled=!1,this.checkUpdateFailed=()=>{this.isUpdating&&(this.isUpdating=!1,this.clearAllSnapshots())},this.updateProjection=()=>{this.projectionUpdateScheduled=!1,Rr.totalNodes=Rr.resolvedTargetDeltas=Rr.recalculatedProjection=0,this.nodes.forEach(HD),this.nodes.forEach(KD),this.nodes.forEach(GD),this.nodes.forEach(UD),RD(Rr)},this.hasProjected=!1,this.isVisible=!0,this.animationProgress=0,this.sharedNodes=new Map,this.latestValues=o,this.root=a?a.root||a:this,this.path=a?[...a.path,a]:[],this.parent=a,this.depth=a?a.depth+1:0;for(let l=0;lthis.root.updateBlockedByResize=!1;e(o,()=>{this.root.updateBlockedByResize=!0,f&&f(),f=LD(d,250),Ka.hasAnimatedSinceResize&&(Ka.hasAnimatedSinceResize=!1,this.nodes.forEach(H0))})}l&&this.root.registerSharedNode(l,this),this.options.animate!==!1&&c&&(l||u)&&this.addEventListener("didUpdate",({delta:f,hasLayoutChanged:d,hasRelativeTargetChanged:h,layout:g})=>{if(this.isTreeAnimationBlocked()){this.target=void 0,this.relativeTarget=void 0;return}const y=this.options.transition||c.getDefaultTransition()||eI,{onLayoutAnimationStart:w,onLayoutAnimationComplete:m}=c.getProps(),x=!this.targetLayout||!Fk(this.targetLayout,g)||h,v=!d&&h;if(this.options.layoutRoot||this.resumeFrom&&this.resumeFrom.instance||v||d&&(x||!this.currentAnimation)){this.resumeFrom&&(this.resumingFrom=this.resumeFrom,this.resumingFrom.resumingFrom=void 0),this.setAnimationOrigin(f,v);const k={...lp(y,"layout"),onPlay:w,onComplete:m};(c.shouldReduceMotion||this.options.layoutRoot)&&(k.delay=0,k.type=!1),this.startAnimation(k)}else d||H0(this),this.isLead()&&this.options.onExitComplete&&this.options.onExitComplete();this.targetLayout=g})}unmount(){this.options.layoutId&&this.willUpdate(),this.root.nodes.remove(this);const o=this.getStack();o&&o.remove(this),this.parent&&this.parent.children.delete(this),this.instance=void 0,Vn(this.updateProjection)}blockUpdate(){this.updateManuallyBlocked=!0}unblockUpdate(){this.updateManuallyBlocked=!1}isUpdateBlocked(){return this.updateManuallyBlocked||this.updateBlockedByResize}isTreeAnimationBlocked(){return this.isAnimationBlocked||this.parent&&this.parent.isTreeAnimationBlocked()||!1}startUpdate(){this.isUpdateBlocked()||(this.isUpdating=!0,this.nodes&&this.nodes.forEach(XD),this.animationId++)}getTransformTemplate(){const{visualElement:o}=this.options;return o&&o.getProps().transformTemplate}willUpdate(o=!0){if(this.root.hasTreeAnimated=!0,this.root.isUpdateBlocked()){this.options.onExitComplete&&this.options.onExitComplete();return}if(!this.root.isUpdating&&this.root.startUpdate(),this.isLayoutDirty)return;this.isLayoutDirty=!0;for(let c=0;cthis.update()))}clearAllSnapshots(){this.nodes.forEach(WD),this.sharedNodes.forEach(QD)}scheduleUpdateProjection(){this.projectionUpdateScheduled||(this.projectionUpdateScheduled=!0,be.preRender(this.updateProjection,!1,!0))}scheduleCheckAfterUnmount(){be.postRender(()=>{this.isLayoutDirty?this.root.didUpdate():this.root.checkUpdateFailed()})}updateSnapshot(){this.snapshot||!this.instance||(this.snapshot=this.measure())}updateLayout(){if(!this.instance||(this.updateScroll(),!(this.options.alwaysMeasureLayout&&this.isLead())&&!this.isLayoutDirty))return;if(this.resumeFrom&&!this.resumeFrom.instance)for(let l=0;l{const N=k/1e3;U0(f.x,o.x,N),U0(f.y,o.y,N),this.setTargetDelta(f),this.relativeTarget&&this.relativeTargetOrigin&&this.layout&&this.relativeParent&&this.relativeParent.layout&&(no(d,this.layout.layoutBox,this.relativeParent.layout.layoutBox),ZD(this.relativeTarget,this.relativeTargetOrigin,d,N),v&&jD(this.relativeTarget,v)&&(this.isProjectionDirty=!1),v||(v=Ve()),$t(v,this.relativeTarget)),y&&(this.animationValues=c,CD(c,u,this.latestValues,N,x,m)),this.root.scheduleUpdateProjection(),this.scheduleRender(),this.animationProgress=N},this.mixTargetDelta(this.options.layoutRoot?1e3:0)}startAnimation(o){this.notifyListeners("animationStart"),this.currentAnimation&&this.currentAnimation.stop(),this.resumingFrom&&this.resumingFrom.currentAnimation&&this.resumingFrom.currentAnimation.stop(),this.pendingAnimation&&(Vn(this.pendingAnimation),this.pendingAnimation=void 0),this.pendingAnimation=be.update(()=>{Ka.hasAnimatedSinceResize=!0,this.currentAnimation=FD(0,$0,{...o,onUpdate:a=>{this.mixTargetDelta(a),o.onUpdate&&o.onUpdate(a)},onComplete:()=>{o.onComplete&&o.onComplete(),this.completeAnimation()}}),this.resumingFrom&&(this.resumingFrom.currentAnimation=this.currentAnimation),this.pendingAnimation=void 0})}completeAnimation(){this.resumingFrom&&(this.resumingFrom.currentAnimation=void 0,this.resumingFrom.preserveOpacity=void 0);const o=this.getStack();o&&o.exitAnimationComplete(),this.resumingFrom=this.currentAnimation=this.animationValues=void 0,this.notifyListeners("animationComplete")}finishAnimation(){this.currentAnimation&&(this.mixTargetDelta&&this.mixTargetDelta($0),this.currentAnimation.stop()),this.completeAnimation()}applyTransformsToTarget(){const o=this.getLead();let{targetWithTransforms:a,target:l,layout:u,latestValues:c}=o;if(!(!a||!l||!u)){if(this!==o&&this.layout&&u&&Vk(this.options.animationType,this.layout.layoutBox,u.layoutBox)){l=this.target||Ve();const f=zt(this.layout.layoutBox.x);l.x.min=o.target.x.min,l.x.max=l.x.min+f;const d=zt(this.layout.layoutBox.y);l.y.min=o.target.y.min,l.y.max=l.y.min+d}$t(a,l),Mi(a,c),to(this.projectionDeltaWithTransform,this.layoutCorrected,a,c)}}registerSharedNode(o,a){this.sharedNodes.has(o)||this.sharedNodes.set(o,new MD),this.sharedNodes.get(o).add(a);const u=a.options.initialPromotionConfig;a.promote({transition:u?u.transition:void 0,preserveFollowOpacity:u&&u.shouldPreserveFollowOpacity?u.shouldPreserveFollowOpacity(a):void 0})}isLead(){const o=this.getStack();return o?o.lead===this:!0}getLead(){var o;const{layoutId:a}=this.options;return a?((o=this.getStack())===null||o===void 0?void 0:o.lead)||this:this}getPrevLead(){var o;const{layoutId:a}=this.options;return a?(o=this.getStack())===null||o===void 0?void 0:o.prevLead:void 0}getStack(){const{layoutId:o}=this.options;if(o)return this.root.sharedNodes.get(o)}promote({needsReset:o,transition:a,preserveFollowOpacity:l}={}){const u=this.getStack();u&&u.promote(this,l),o&&(this.projectionDelta=void 0,this.needsReset=!0),a&&this.setOptions({transition:a})}relegate(){const o=this.getStack();return o?o.relegate(this):!1}resetRotation(){const{visualElement:o}=this.options;if(!o)return;let a=!1;const{latestValues:l}=o;if((l.rotate||l.rotateX||l.rotateY||l.rotateZ)&&(a=!0),!a)return;const u={};for(let c=0;c{var a;return(a=o.currentAnimation)===null||a===void 0?void 0:a.stop()}),this.root.nodes.forEach(B0),this.root.sharedNodes.clear()}}}function $D(e){e.updateLayout()}function BD(e){var t;const n=((t=e.resumeFrom)===null||t===void 0?void 0:t.snapshot)||e.snapshot;if(e.isLead()&&e.layout&&n&&e.hasListeners("didUpdate")){const{layoutBox:r,measuredBox:i}=e.layout,{animationType:s}=e.options,o=n.source!==e.layout.source;s==="size"?Bt(f=>{const d=o?n.measuredBox[f]:n.layoutBox[f],h=zt(d);d.min=r[f].min,d.max=d.min+h}):Vk(s,n.layoutBox,r)&&Bt(f=>{const d=o?n.measuredBox[f]:n.layoutBox[f],h=zt(r[f]);d.max=d.min+h,e.relativeTarget&&!e.currentAnimation&&(e.isProjectionDirty=!0,e.relativeTarget[f].max=e.relativeTarget[f].min+h)});const a=ji();to(a,r,n.layoutBox);const l=ji();o?to(l,e.applyTransform(i,!0),n.measuredBox):to(l,r,n.layoutBox);const u=!zk(a);let c=!1;if(!e.resumeFrom){const f=e.getClosestProjectingParent();if(f&&!f.resumeFrom){const{snapshot:d,layout:h}=f;if(d&&h){const g=Ve();no(g,n.layoutBox,d.layoutBox);const y=Ve();no(y,r,h.layoutBox),Fk(g,y)||(c=!0),f.options.layoutRoot&&(e.relativeTarget=y,e.relativeTargetOrigin=g,e.relativeParent=f)}}}e.notifyListeners("didUpdate",{layout:r,snapshot:n,delta:l,layoutDelta:a,hasLayoutChanged:u,hasRelativeTargetChanged:c})}else if(e.isLead()){const{onExitComplete:r}=e.options;r&&r()}e.options.transition=void 0}function HD(e){Rr.totalNodes++,e.parent&&(e.isProjecting()||(e.isProjectionDirty=e.parent.isProjectionDirty),e.isSharedProjectionDirty||(e.isSharedProjectionDirty=!!(e.isProjectionDirty||e.parent.isProjectionDirty||e.parent.isSharedProjectionDirty)),e.isTransformDirty||(e.isTransformDirty=e.parent.isTransformDirty))}function UD(e){e.isProjectionDirty=e.isSharedProjectionDirty=e.isTransformDirty=!1}function WD(e){e.clearSnapshot()}function B0(e){e.clearMeasurements()}function YD(e){e.isLayoutDirty=!1}function qD(e){const{visualElement:t}=e.options;t&&t.getProps().onBeforeLayoutMeasure&&t.notify("BeforeLayoutMeasure"),e.resetTransform()}function H0(e){e.finishAnimation(),e.targetDelta=e.relativeTarget=e.target=void 0,e.isProjectionDirty=!0}function KD(e){e.resolveTargetDelta()}function GD(e){e.calcProjection()}function XD(e){e.resetRotation()}function QD(e){e.removeLeadSnapshot()}function U0(e,t,n){e.translate=Pe(t.translate,0,n),e.scale=Pe(t.scale,1,n),e.origin=t.origin,e.originPoint=t.originPoint}function W0(e,t,n,r){e.min=Pe(t.min,n.min,r),e.max=Pe(t.max,n.max,r)}function ZD(e,t,n,r){W0(e.x,t.x,n.x,r),W0(e.y,t.y,n.y,r)}function JD(e){return e.animationValues&&e.animationValues.opacityExit!==void 0}const eI={duration:.45,ease:[.4,0,.1,1]},Y0=e=>typeof navigator<"u"&&navigator.userAgent.toLowerCase().includes(e),q0=Y0("applewebkit/")&&!Y0("chrome/")?Math.round:Fe;function K0(e){e.min=q0(e.min),e.max=q0(e.max)}function tI(e){K0(e.x),K0(e.y)}function Vk(e,t,n){return e==="position"||e==="preserve-aspect"&&!ud(F0(t),F0(n),.2)}const nI=Ok({attachResizeListener:(e,t)=>jn(e,"resize",t),measureScroll:()=>({x:document.documentElement.scrollLeft||document.body.scrollLeft,y:document.documentElement.scrollTop||document.body.scrollTop}),checkIsScrollRoot:()=>!0}),Lc={current:void 0},$k=Ok({measureScroll:e=>({x:e.scrollLeft,y:e.scrollTop}),defaultParent:()=>{if(!Lc.current){const e=new nI({});e.mount(window),e.setOptions({layoutScroll:!0}),Lc.current=e}return Lc.current},resetTransform:(e,t)=>{e.style.transform=t!==void 0?t:"none"},checkIsScrollRoot:e=>window.getComputedStyle(e).position==="fixed"}),rI={pan:{Feature:vD},drag:{Feature:xD,ProjectionNode:$k,MeasureLayout:Ik}},iI=/var\((--[a-zA-Z0-9-_]+),? ?([a-zA-Z0-9 ()%#.,-]+)?\)/;function sI(e){const t=iI.exec(e);if(!t)return[,];const[,n,r]=t;return[n,r]}function hd(e,t,n=1){const[r,i]=sI(e);if(!r)return;const s=window.getComputedStyle(t).getPropertyValue(r);if(s){const o=s.trim();return Sk(o)?parseFloat(o):o}else return nd(i)?hd(i,t,n+1):i}function oI(e,{...t},n){const r=e.current;if(!(r instanceof Element))return{target:t,transitionEnd:n};n&&(n={...n}),e.values.forEach(i=>{const s=i.get();if(!nd(s))return;const o=hd(s,r);o&&i.set(o)});for(const i in t){const s=t[i];if(!nd(s))continue;const o=hd(s,r);o&&(t[i]=o,n||(n={}),n[i]===void 0&&(n[i]=s))}return{target:t,transitionEnd:n}}const aI=new Set(["width","height","top","left","right","bottom","x","y","translateX","translateY"]),Bk=e=>aI.has(e),lI=e=>Object.keys(e).some(Bk),G0=e=>e===ui||e===te,X0=(e,t)=>parseFloat(e.split(", ")[t]),Q0=(e,t)=>(n,{transform:r})=>{if(r==="none"||!r)return 0;const i=r.match(/^matrix3d\((.+)\)$/);if(i)return X0(i[1],t);{const s=r.match(/^matrix\((.+)\)$/);return s?X0(s[1],e):0}},uI=new Set(["x","y","z"]),cI=Wo.filter(e=>!uI.has(e));function fI(e){const t=[];return cI.forEach(n=>{const r=e.getValue(n);r!==void 0&&(t.push([n,r.get()]),r.set(n.startsWith("scale")?1:0))}),t.length&&e.render(),t}const is={width:({x:e},{paddingLeft:t="0",paddingRight:n="0"})=>e.max-e.min-parseFloat(t)-parseFloat(n),height:({y:e},{paddingTop:t="0",paddingBottom:n="0"})=>e.max-e.min-parseFloat(t)-parseFloat(n),top:(e,{top:t})=>parseFloat(t),left:(e,{left:t})=>parseFloat(t),bottom:({y:e},{top:t})=>parseFloat(t)+(e.max-e.min),right:({x:e},{left:t})=>parseFloat(t)+(e.max-e.min),x:Q0(4,13),y:Q0(5,14)};is.translateX=is.x;is.translateY=is.y;const dI=(e,t,n)=>{const r=t.measureViewportBox(),i=t.current,s=getComputedStyle(i),{display:o}=s,a={};o==="none"&&t.setStaticValue("display",e.display||"block"),n.forEach(u=>{a[u]=is[u](r,s)}),t.render();const l=t.measureViewportBox();return n.forEach(u=>{const c=t.getValue(u);c&&c.jump(a[u]),e[u]=is[u](l,s)}),e},hI=(e,t,n={},r={})=>{t={...t},r={...r};const i=Object.keys(t).filter(Bk);let s=[],o=!1;const a=[];if(i.forEach(l=>{const u=e.getValue(l);if(!e.hasValue(l))return;let c=n[l],f=Cs(c);const d=t[l];let h;if(Pl(d)){const g=d.length,y=d[0]===null?1:0;c=d[y],f=Cs(c);for(let w=y;w=0?window.pageYOffset:null,u=dI(t,e,a);return s.length&&s.forEach(([c,f])=>{e.getValue(c).set(f)}),e.render(),yu&&l!==null&&window.scrollTo({top:l}),{target:u,transitionEnd:r}}else return{target:t,transitionEnd:r}};function pI(e,t,n,r){return lI(t)?hI(e,t,n,r):{target:t,transitionEnd:r}}const mI=(e,t,n,r)=>{const i=oI(e,t,r);return t=i.target,r=i.transitionEnd,pI(e,t,n,r)},pd={current:null},Hk={current:!1};function gI(){if(Hk.current=!0,!!yu)if(window.matchMedia){const e=window.matchMedia("(prefers-reduced-motion)"),t=()=>pd.current=e.matches;e.addListener(t),t()}else pd.current=!1}function yI(e,t,n){const{willChange:r}=t;for(const i in t){const s=t[i],o=n[i];if(Ct(s))e.addValue(i,s),Il(r)&&r.add(i);else if(Ct(o))e.addValue(i,rs(s,{owner:e})),Il(r)&&r.remove(i);else if(o!==s)if(e.hasValue(i)){const a=e.getValue(i);!a.hasAnimated&&a.set(s)}else{const a=e.getStaticValue(i);e.addValue(i,rs(a!==void 0?a:s,{owner:e}))}}for(const i in n)t[i]===void 0&&e.removeValue(i);return t}const Z0=new WeakMap,Uk=Object.keys(Co),xI=Uk.length,J0=["AnimationStart","AnimationComplete","Update","BeforeLayoutMeasure","LayoutMeasure","LayoutAnimationStart","LayoutAnimationComplete"],vI=Kh.length;class wI{constructor({parent:t,props:n,presenceContext:r,reducedMotionConfig:i,visualState:s},o={}){this.current=null,this.children=new Set,this.isVariantNode=!1,this.isControllingVariants=!1,this.shouldReduceMotion=null,this.values=new Map,this.features={},this.valueSubscriptions=new Map,this.prevMotionValues={},this.events={},this.propEventSubscriptions={},this.notifyUpdate=()=>this.notify("Update",this.latestValues),this.render=()=>{this.current&&(this.triggerBuild(),this.renderInstance(this.current,this.renderState,this.props.style,this.projection))},this.scheduleRender=()=>be.render(this.render,!1,!0);const{latestValues:a,renderState:l}=s;this.latestValues=a,this.baseTarget={...a},this.initialValues=n.initial?{...a}:{},this.renderState=l,this.parent=t,this.props=n,this.presenceContext=r,this.depth=t?t.depth+1:0,this.reducedMotionConfig=i,this.options=o,this.isControllingVariants=vu(n),this.isVariantNode=Tw(n),this.isVariantNode&&(this.variantChildren=new Set),this.manuallyAnimateOnMount=!!(t&&t.current);const{willChange:u,...c}=this.scrapeMotionValuesFromProps(n,{});for(const f in c){const d=c[f];a[f]!==void 0&&Ct(d)&&(d.set(a[f],!1),Il(u)&&u.add(f))}}scrapeMotionValuesFromProps(t,n){return{}}mount(t){this.current=t,Z0.set(t,this),this.projection&&!this.projection.instance&&this.projection.mount(t),this.parent&&this.isVariantNode&&!this.isControllingVariants&&(this.removeFromVariantTree=this.parent.addVariantChild(this)),this.values.forEach((n,r)=>this.bindToMotionValue(r,n)),Hk.current||gI(),this.shouldReduceMotion=this.reducedMotionConfig==="never"?!1:this.reducedMotionConfig==="always"?!0:pd.current,this.parent&&this.parent.children.add(this),this.update(this.props,this.presenceContext)}unmount(){Z0.delete(this.current),this.projection&&this.projection.unmount(),Vn(this.notifyUpdate),Vn(this.render),this.valueSubscriptions.forEach(t=>t()),this.removeFromVariantTree&&this.removeFromVariantTree(),this.parent&&this.parent.children.delete(this);for(const t in this.events)this.events[t].clear();for(const t in this.features)this.features[t].unmount();this.current=null}bindToMotionValue(t,n){const r=li.has(t),i=n.on("change",o=>{this.latestValues[t]=o,this.props.onUpdate&&be.update(this.notifyUpdate,!1,!0),r&&this.projection&&(this.projection.isTransformDirty=!0)}),s=n.on("renderRequest",this.scheduleRender);this.valueSubscriptions.set(t,()=>{i(),s()})}sortNodePosition(t){return!this.current||!this.sortInstanceNodePosition||this.type!==t.type?0:this.sortInstanceNodePosition(this.current,t.current)}loadFeatures({children:t,...n},r,i,s){let o,a;for(let l=0;lthis.scheduleRender(),animationType:typeof u=="string"?u:"both",initialPromotionConfig:s,layoutScroll:d,layoutRoot:h})}return a}updateFeatures(){for(const t in this.features){const n=this.features[t];n.isMounted?n.update():(n.mount(),n.isMounted=!0)}}triggerBuild(){this.build(this.renderState,this.latestValues,this.options,this.props)}measureViewportBox(){return this.current?this.measureInstanceViewportBox(this.current,this.props):Ve()}getStaticValue(t){return this.latestValues[t]}setStaticValue(t,n){this.latestValues[t]=n}makeTargetAnimatable(t,n=!0){return this.makeTargetAnimatableFromInstance(t,this.props,n)}update(t,n){(t.transformTemplate||this.props.transformTemplate)&&this.scheduleRender(),this.prevProps=this.props,this.props=t,this.prevPresenceContext=this.presenceContext,this.presenceContext=n;for(let r=0;rn.variantChildren.delete(t)}addValue(t,n){n!==this.values.get(t)&&(this.removeValue(t),this.bindToMotionValue(t,n)),this.values.set(t,n),this.latestValues[t]=n.get()}removeValue(t){this.values.delete(t);const n=this.valueSubscriptions.get(t);n&&(n(),this.valueSubscriptions.delete(t)),delete this.latestValues[t],this.removeValueFromRenderState(t,this.renderState)}hasValue(t){return this.values.has(t)}getValue(t,n){if(this.props.values&&this.props.values[t])return this.props.values[t];let r=this.values.get(t);return r===void 0&&n!==void 0&&(r=rs(n,{owner:this}),this.addValue(t,r)),r}readValue(t){var n;return this.latestValues[t]!==void 0||!this.current?this.latestValues[t]:(n=this.getBaseTargetFromProps(this.props,t))!==null&&n!==void 0?n:this.readValueFromInstance(this.current,t,this.options)}setBaseTarget(t,n){this.baseTarget[t]=n}getBaseTarget(t){var n;const{initial:r}=this.props,i=typeof r=="string"||typeof r=="object"?(n=np(this.props,r))===null||n===void 0?void 0:n[t]:void 0;if(r&&i!==void 0)return i;const s=this.getBaseTargetFromProps(this.props,t);return s!==void 0&&!Ct(s)?s:this.initialValues[t]!==void 0&&i===void 0?void 0:this.baseTarget[t]}on(t,n){return this.events[t]||(this.events[t]=new dp),this.events[t].add(n)}notify(t,...n){this.events[t]&&this.events[t].notify(...n)}}class Wk extends wI{sortInstanceNodePosition(t,n){return t.compareDocumentPosition(n)&2?1:-1}getBaseTargetFromProps(t,n){return t.style?t.style[n]:void 0}removeValueFromRenderState(t,{vars:n,style:r}){delete n[t],delete r[t]}makeTargetAnimatableFromInstance({transition:t,transitionEnd:n,...r},{transformValues:i},s){let o=zM(r,t||{},this);if(i&&(n&&(n=i(n)),r&&(r=i(r)),o&&(o=i(o))),s){LM(this,r,o);const a=mI(this,r,o,n);n=a.transitionEnd,r=a.target}return{transition:t,transitionEnd:n,...r}}}function kI(e){return window.getComputedStyle(e)}class bI extends Wk{constructor(){super(...arguments),this.type="html"}readValueFromInstance(t,n){if(li.has(n)){const r=ap(n);return r&&r.default||0}else{const r=kI(t),i=(Mw(n)?r.getPropertyValue(n):r[n])||0;return typeof i=="string"?i.trim():i}}measureInstanceViewportBox(t,{transformPagePoint:n}){return Mk(t,n)}build(t,n,r,i){Qh(t,n,r,i.transformTemplate)}scrapeMotionValuesFromProps(t,n){return tp(t,n)}handleChildMotionValue(){this.childSubscription&&(this.childSubscription(),delete this.childSubscription);const{children:t}=this.props;Ct(t)&&(this.childSubscription=t.on("change",n=>{this.current&&(this.current.textContent=`${n}`)}))}renderInstance(t,n,r,i){Fw(t,n,r,i)}}class SI extends Wk{constructor(){super(...arguments),this.type="svg",this.isSVGTag=!1}getBaseTargetFromProps(t,n){return t[n]}readValueFromInstance(t,n){if(li.has(n)){const r=ap(n);return r&&r.default||0}return n=Ow.has(n)?n:Yh(n),t.getAttribute(n)}measureInstanceViewportBox(){return Ve()}scrapeMotionValuesFromProps(t,n){return $w(t,n)}build(t,n,r,i){Jh(t,n,r,this.isSVGTag,i.transformTemplate)}renderInstance(t,n,r,i){Vw(t,n,r,i)}mount(t){this.isSVGTag=ep(t.tagName),super.mount(t)}}const _I=(e,t)=>Xh(e)?new SI(t,{enableHardwareAcceleration:!1}):new bI(t,{enableHardwareAcceleration:!0}),CI={layout:{ProjectionNode:$k,MeasureLayout:Ik}},EI={...ZM,...xj,...rI,...CI},Yk=NP((e,t)=>sj(e,t,EI,_I));function qk(){const e=T.useRef(!1);return Wh(()=>(e.current=!0,()=>{e.current=!1}),[]),e}function NI(){const e=qk(),[t,n]=T.useState(0),r=T.useCallback(()=>{e.current&&n(t+1)},[t]);return[T.useCallback(()=>be.postRender(r),[r]),t]}class TI extends T.Component{getSnapshotBeforeUpdate(t){const n=this.props.childRef.current;if(n&&t.isPresent&&!this.props.isPresent){const r=this.props.sizeRef.current;r.height=n.offsetHeight||0,r.width=n.offsetWidth||0,r.top=n.offsetTop,r.left=n.offsetLeft}return null}componentDidUpdate(){}render(){return this.props.children}}function AI({children:e,isPresent:t}){const n=T.useId(),r=T.useRef(null),i=T.useRef({width:0,height:0,top:0,left:0});return T.useInsertionEffect(()=>{const{width:s,height:o,top:a,left:l}=i.current;if(t||!r.current||!s||!o)return;r.current.dataset.motionPopId=n;const u=document.createElement("style");return document.head.appendChild(u),u.sheet&&u.sheet.insertRule(` + [data-motion-pop-id="${n}"] { + position: absolute !important; + width: ${s}px !important; + height: ${o}px !important; + top: ${a}px !important; + left: ${l}px !important; + } + `),()=>{document.head.removeChild(u)}},[t]),T.createElement(TI,{isPresent:t,childRef:r,sizeRef:i},T.cloneElement(e,{ref:r}))}const Rc=({children:e,initial:t,isPresent:n,onExitComplete:r,custom:i,presenceAffectsLayout:s,mode:o})=>{const a=Bw(PI),l=T.useId(),u=T.useMemo(()=>({id:l,initial:t,isPresent:n,custom:i,onExitComplete:c=>{a.set(c,!0);for(const f of a.values())if(!f)return;r&&r()},register:c=>(a.set(c,!1),()=>a.delete(c))}),s?void 0:[n]);return T.useMemo(()=>{a.forEach((c,f)=>a.set(f,!1))},[n]),T.useEffect(()=>{!n&&!a.size&&r&&r()},[n]),o==="popLayout"&&(e=T.createElement(AI,{isPresent:n},e)),T.createElement(gu.Provider,{value:u},e)};function PI(){return new Map}function jI(e){return T.useEffect(()=>()=>e(),[])}const zr=e=>e.key||"";function MI(e,t){e.forEach(n=>{const r=zr(n);t.set(r,n)})}function DI(e){const t=[];return T.Children.forEach(e,n=>{T.isValidElement(n)&&t.push(n)}),t}const Kk=({children:e,custom:t,initial:n=!0,onExitComplete:r,exitBeforeEnter:i,presenceAffectsLayout:s=!0,mode:o="sync"})=>{const a=T.useContext(Gh).forceRender||NI()[0],l=qk(),u=DI(e);let c=u;const f=T.useRef(new Map).current,d=T.useRef(c),h=T.useRef(new Map).current,g=T.useRef(!0);if(Wh(()=>{g.current=!1,MI(u,h),d.current=c}),jI(()=>{g.current=!0,h.clear(),f.clear()}),g.current)return T.createElement(T.Fragment,null,c.map(x=>T.createElement(Rc,{key:zr(x),isPresent:!0,initial:n?void 0:!1,presenceAffectsLayout:s,mode:o},x)));c=[...c];const y=d.current.map(zr),w=u.map(zr),m=y.length;for(let x=0;x{if(w.indexOf(v)!==-1)return;const k=h.get(v);if(!k)return;const N=y.indexOf(v);let S=x;if(!S){const A=()=>{f.delete(v);const P=Array.from(h.keys()).filter(D=>!w.includes(D));if(P.forEach(D=>h.delete(D)),d.current=u.filter(D=>{const C=zr(D);return C===v||P.includes(C)}),!f.size){if(l.current===!1)return;a(),r&&r()}};S=T.createElement(Rc,{key:zr(k),isPresent:!1,onExitComplete:A,custom:t,presenceAffectsLayout:s,mode:o},k),f.set(v,S)}c.splice(N,0,S)}),c=c.map(x=>{const v=x.key;return f.has(v)?x:T.createElement(Rc,{key:zr(x),isPresent:!0,presenceAffectsLayout:s,mode:o},x)}),T.createElement(T.Fragment,null,f.size?c:c.map(x=>T.cloneElement(x)))},II=e=>{try{return new Intl.DateTimeFormat("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit"}).format(e)}catch{return""}},LI=e=>{if(!e)return"bg-slate-500/30 text-slate-200 border border-white/10";const t=e.toLowerCase();return["finish","completed","success","ready"].some(n=>t.includes(n))?"bg-emerald-500/20 text-emerald-200 border border-emerald-400/30":["fail","error"].some(n=>t.includes(n))?"bg-rose-500/20 text-rose-200 border border-rose-400/30":["continue","running","in_progress"].some(n=>t.includes(n))?"bg-amber-500/20 text-amber-100 border border-amber-400/30":"bg-slate-500/30 text-slate-200 border border-white/10"},Mr=({title:e,icon:t,children:n})=>p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4 text-sm text-slate-200",children:[p.jsxs("div",{className:"mb-2 flex items-center gap-2 text-[12px] uppercase tracking-[0.18em] text-slate-400",children:[p.jsx("span",{className:"inline-flex h-7 w-7 items-center justify-center rounded-full bg-white/10 text-slate-200 shadow-[0_0_12px_rgba(33,240,255,0.25)]",children:t}),e]}),p.jsx("div",{className:"space-y-2 whitespace-pre-wrap text-sm leading-relaxed text-slate-200",children:n})]}),RI=e=>{const t=e==null?void 0:e.function,n=(e==null?void 0:e.arguments)||{};if(!t)return(e==null?void 0:e.action)||(e==null?void 0:e.command)||"Unknown Action";switch(t){case"add_task":{const r=n.task_id||"?",i=n.name||"";return i?`Add Task: '${r}' (${i})`:`Add Task: '${r}'`}case"remove_task":return`Remove Task: '${n.task_id||"?"}'`;case"update_task":{const r=n.task_id||"?",i=Object.keys(n).filter(o=>o!=="task_id"&&n[o]!==null&&n[o]!==void 0),s=i.length>0?i.join(", "):"fields";return`Update Task: '${r}' (${s})`}case"add_dependency":{const r=n.dependency_id||"?",i=n.from_task_id||"?",s=n.to_task_id||"?";return`Add Dependency (ID ${r}): ${i} → ${s}`}case"remove_dependency":return`Remove Dependency: '${n.dependency_id||"?"}'`;case"update_dependency":return`Update Dependency: '${n.dependency_id||"?"}'`;case"build_constellation":{const r=n.config||{};if(n.task_count!==void 0||n.dependency_count!==void 0){const i=n.task_count||0,s=n.dependency_count||0;return`Build Constellation (${i} tasks, ${s} dependencies)`}if(typeof r=="object"&&r!==null){const i=Array.isArray(r.tasks)?r.tasks.length:0,s=Array.isArray(r.dependencies)?r.dependencies.length:0;return`Build Constellation (${i} tasks, ${s} dependencies)`}return"Build Constellation"}case"clear_constellation":return"Clear Constellation (remove all tasks)";case"load_constellation":{const r=n.file_path||"?";return`Load Constellation from '${r.split(/[/\\]/).pop()||r}'`}case"save_constellation":{const r=n.file_path||"?";return`Save Constellation to '${r.split(/[/\\]/).pop()||r}'`}default:{const r=Object.entries(n).slice(0,2);if(r.length>0){const i=r.map(([s,o])=>`${s}=${o}`).join(", ");return`${t}(${i})`}return t}}},zI=e=>{if(!e)return p.jsx(sc,{className:"h-3.5 w-3.5"});const t=e.toLowerCase();return["finish","completed","success","ready"].some(n=>t.includes(n))?p.jsx(Kr,{className:"h-3.5 w-3.5"}):["fail","error"].some(n=>t.includes(n))?p.jsx($o,{className:"h-3.5 w-3.5"}):["continue","running","in_progress"].some(n=>t.includes(n))?p.jsx(sc,{className:"h-3.5 w-3.5"}):p.jsx(sc,{className:"h-3.5 w-3.5"})},FI=({action:e,isLast:t,isExpanded:n,onToggle:r})=>{var u,c,f,d;const i=((u=e==null?void 0:e.result)==null?void 0:u.status)||(e==null?void 0:e.status)||((c=e==null?void 0:e.arguments)==null?void 0:c.status),s=((f=e==null?void 0:e.result)==null?void 0:f.error)||((d=e==null?void 0:e.result)==null?void 0:d.message),o=i&&String(i).toLowerCase()==="continue",a=RI(e),l=()=>{if(!i)return"text-slate-400";const h=i.toLowerCase();return["finish","completed","success","ready"].some(g=>h.includes(g))?"text-emerald-400":["fail","error"].some(g=>h.includes(g))?"text-rose-400":["continue","running","in_progress"].some(g=>h.includes(g))?"text-amber-400":"text-slate-400"};return p.jsxs("div",{className:"relative",children:[p.jsxs("div",{className:"absolute left-0 top-0 flex h-full w-6",children:[p.jsx("div",{className:"w-px bg-white/10"}),!t&&p.jsx("div",{className:"absolute left-0 top-7 h-[calc(100%-1.75rem)] w-px bg-white/10"})]}),p.jsx("div",{className:"ml-6 pb-3",children:p.jsxs("div",{className:"flex items-start gap-2",children:[p.jsx("div",{className:"mt-3 h-px w-3 flex-shrink-0 bg-white/10"}),p.jsxs("div",{className:"flex-1 min-w-0",children:[p.jsxs("button",{onClick:r,className:"group flex w-full items-center gap-2 rounded-lg border border-white/5 bg-white/5 px-3 py-2 text-left text-sm transition hover:border-white/20 hover:bg-white/10",children:[p.jsx("span",{className:me("flex-shrink-0",l()),children:zI(i)}),p.jsx("span",{className:"flex-1 truncate font-medium text-slate-200",children:a}),!o&&(e.arguments||s)&&p.jsx($f,{className:me("h-3.5 w-3.5 flex-shrink-0 text-slate-400 transition-transform",n&&"rotate-180")})]}),n&&!o&&p.jsxs("div",{className:"mt-2 space-y-2 rounded-lg border border-white/5 bg-black/20 p-3",children:[i&&p.jsxs("div",{children:[p.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-slate-400",children:"Status"}),p.jsx("div",{className:me("text-sm font-medium",l()),children:String(i).toUpperCase()})]}),e.arguments&&p.jsxs("div",{children:[p.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-slate-400",children:"Arguments"}),p.jsx("pre",{className:"whitespace-pre-wrap rounded-lg border border-white/5 bg-black/30 p-2 text-xs text-slate-300",children:JSON.stringify(e.arguments,null,2)})]}),p.jsxs("div",{children:[p.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-slate-400",children:"Full Action Object (Debug)"}),p.jsx("pre",{className:"whitespace-pre-wrap rounded-lg border border-white/5 bg-black/30 p-2 text-xs text-slate-300",children:JSON.stringify(e,null,2)})]}),s&&p.jsxs("div",{className:"rounded-lg border border-rose-400/30 bg-rose-500/10 p-2",children:[p.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-rose-300",children:"Error"}),p.jsx("div",{className:"text-xs text-rose-100",children:String(s)})]})]})]})]})})]})},OI=({message:e,nextMessage:t,stepNumber:n})=>{const[r,i]=T.useState(!1),[s,o]=T.useState(!1),[a,l]=T.useState(new Set),u=e.role==="user",c=e.kind==="action",f=e.kind==="response"?e.payload:void 0,d=!!e.payload&&(c||e.kind==="system"),h=T.useMemo(()=>II(e.timestamp),[e.timestamp]),g=T.useMemo(()=>u?"You":e.agentName?e.agentName.toLowerCase().includes("constellation")?"UFO":e.agentName:"UFO",[u,e.agentName]),y=f==null?void 0:f.status,w=e.kind==="response"&&(t==null?void 0:t.kind)==="action",m=w?t==null?void 0:t.payload:void 0;if(c)return null;const x=()=>{e.payload&&sn().send({type:"replay_action",timestamp:Date.now(),payload:e.payload})};return p.jsxs("div",{className:me("flex w-full flex-col gap-2 transition-all",{"items-end":u,"items-start":!u}),children:[p.jsxs("div",{className:me("w-[88%] rounded-3xl border px-6 py-5 shadow-xl sm:w-[74%]",u?"rounded-br-xl border-galaxy-blue/50 bg-gradient-to-br from-galaxy-blue/25 via-galaxy-purple/25 to-galaxy-blue/15 text-slate-50 shadow-[0_0_30px_rgba(15,123,255,0.2),inset_0_1px_0_rgba(147,197,253,0.15)]":"rounded-bl-xl border-[rgba(10,186,181,0.35)] bg-gradient-to-br from-[rgba(10,186,181,0.12)] via-[rgba(12,50,65,0.8)] to-[rgba(11,30,45,0.85)] text-slate-100 shadow-[0_0_25px_rgba(10,186,181,0.18),inset_0_1px_0_rgba(10,186,181,0.12)]"),children:[!u&&p.jsxs("div",{className:"mb-4 flex items-center justify-between gap-3",children:[p.jsxs("div",{className:"flex items-center gap-3",children:[p.jsx("div",{className:"flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-cyan-500/20 to-blue-500/20 border border-cyan-400/30 shadow-lg",children:p.jsx(QC,{className:"h-5 w-5 text-cyan-300","aria-hidden":!0})}),p.jsxs("div",{className:"flex flex-col gap-0.5",children:[p.jsxs("div",{className:"flex items-center gap-2",children:[p.jsx("span",{className:"font-bold text-base text-slate-100",children:g}),n!==void 0&&p.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-gradient-to-r from-cyan-500/20 to-blue-500/20 border border-cyan-400/30 px-2 py-0.5 text-[10px] font-semibold text-cyan-300",children:[p.jsx("span",{className:"opacity-70",children:"STEP"}),p.jsx("span",{children:n})]})]}),p.jsx("span",{className:"text-[10px] text-slate-400",children:h})]})]}),p.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[p.jsxs("span",{className:"inline-flex items-center gap-1.5 rounded-lg border border-white/10 bg-white/5 px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider text-slate-300",children:[p.jsx(Hf,{className:"h-3 w-3","aria-hidden":!0}),e.kind]}),y&&p.jsxs("span",{className:me("inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider",LI(y)),children:[p.jsx(Kr,{className:"h-3 w-3","aria-hidden":!0}),String(y).toUpperCase()]})]})]}),u&&p.jsx("div",{className:"mb-4 flex items-center justify-between gap-3",children:p.jsxs("div",{className:"flex items-center gap-3",children:[p.jsx("div",{className:"flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-purple-500/20 to-pink-500/20 border border-purple-400/30 shadow-lg",children:p.jsx(KC,{className:"h-5 w-5 text-purple-300","aria-hidden":!0})}),p.jsxs("div",{className:"flex flex-col gap-0.5",children:[p.jsx("span",{className:"font-bold text-base text-slate-100",children:g}),p.jsx("span",{className:"text-[10px] text-slate-400",children:h})]})]})}),e.kind==="response"&&f?p.jsxs("div",{className:"space-y-4",children:[f.thought&&p.jsx(Mr,{title:"Thought",icon:p.jsx(AC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:(()=>{const v=String(f.thought),k=100;if(!(v.length>k))return p.jsx("p",{children:v});let S=k;const A=[". ",`. +`,"! ",`! +`,"? ",`? +`];for(const P of A){const D=v.lastIndexOf(P,k);if(D>k*.7){S=D+P.length;break}}return p.jsxs("div",{children:[p.jsx("p",{children:s?v:v.substring(0,S).trim()+"..."}),p.jsx("button",{onClick:()=>o(!s),className:"mt-2 inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs text-slate-300 transition hover:border-white/30 hover:bg-white/10",children:s?p.jsxs(p.Fragment,{children:[p.jsx(qm,{className:"h-3 w-3","aria-hidden":!0}),"Show less"]}):p.jsxs(p.Fragment,{children:[p.jsx($f,{className:"h-3 w-3","aria-hidden":!0}),"Show more (",v.length," chars)"]})})]})})()}),f.plan&&p.jsx(Mr,{title:"Plan",icon:p.jsx(zC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:Array.isArray(f.plan)?p.jsx("ul",{className:"space-y-1 text-sm",children:f.plan.map((v,k)=>p.jsxs("li",{className:"flex items-start gap-2 text-slate-200",children:[p.jsx("span",{className:"mt-[2px] h-2 w-2 rounded-full bg-galaxy-blue","aria-hidden":!0}),p.jsx("span",{children:v})]},k))}):p.jsx("p",{children:f.plan})}),f.decomposition_strategy&&p.jsx(Mr,{title:"Decomposition",icon:p.jsx(jC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:p.jsx("p",{children:f.decomposition_strategy})}),f.ask_details&&p.jsx(Mr,{title:"Ask Details",icon:p.jsx(cv,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:p.jsx("pre",{className:"whitespace-pre-wrap text-xs text-slate-200/90",children:JSON.stringify(f.ask_details,null,2)})}),f.actions_summary&&p.jsx(Mr,{title:"Action Summary",icon:p.jsx(Hf,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:p.jsx("p",{children:f.actions_summary})}),(f.response||f.final_response)&&p.jsx(Mr,{title:"Response",icon:p.jsx(Kr,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:p.jsx("p",{children:f.final_response||f.response})}),f.validation&&p.jsx(Mr,{title:"Validation",icon:p.jsx(EC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:p.jsx("pre",{className:"whitespace-pre-wrap text-xs text-slate-200/90",children:JSON.stringify(f.validation,null,2)})}),!(f.thought||f.plan||f.actions_summary||f.response||f.final_response)&&p.jsx("div",{className:"prose prose-invert max-w-none text-sm leading-relaxed prose-headings:text-slate-100 prose-p:mb-3 prose-p:text-slate-200 prose-pre:bg-slate-900/80 prose-strong:text-slate-100",children:p.jsx(Bg,{remarkPlugins:[Xg],children:e.content})}),f.results&&y&&String(y).toLowerCase()!=="continue"&&p.jsxs("div",{className:me("mt-6 rounded-2xl border-2 p-6 shadow-xl",String(y).toLowerCase().includes("fail")||String(y).toLowerCase().includes("error")?"border-rose-500/50 bg-gradient-to-br from-rose-500/15 to-rose-600/8":"border-emerald-500/50 bg-gradient-to-br from-emerald-500/15 to-emerald-600/8"),children:[p.jsxs("div",{className:"mb-4 flex items-center gap-3",children:[p.jsx("div",{className:me("flex h-10 w-10 items-center justify-center rounded-xl shadow-lg",String(y).toLowerCase().includes("fail")||String(y).toLowerCase().includes("error")?"bg-gradient-to-br from-rose-500/35 to-rose-600/25 border border-rose-400/40":"bg-gradient-to-br from-emerald-500/35 to-emerald-600/25 border border-emerald-400/40"),children:String(y).toLowerCase().includes("fail")||String(y).toLowerCase().includes("error")?p.jsx($o,{className:"h-5 w-5 text-rose-300","aria-hidden":!0}):p.jsx(Kr,{className:"h-5 w-5 text-emerald-300","aria-hidden":!0})}),p.jsxs("div",{children:[p.jsx("h3",{className:me("text-base font-bold uppercase tracking-wider",String(y).toLowerCase().includes("fail")||String(y).toLowerCase().includes("error")?"text-rose-200":"text-emerald-200"),children:"Final Results"}),p.jsxs("p",{className:"text-xs text-slate-400 mt-0.5",children:["Status: ",String(y).toUpperCase()]})]})]}),p.jsx("div",{className:me("rounded-xl border p-4",String(y).toLowerCase().includes("fail")||String(y).toLowerCase().includes("error")?"border-rose-400/20 bg-rose-950/30":"border-emerald-400/20 bg-emerald-950/30"),children:typeof f.results=="string"?p.jsx("div",{className:me("whitespace-pre-wrap text-sm leading-relaxed",String(y).toLowerCase().includes("fail")||String(y).toLowerCase().includes("error")?"text-rose-100/90":"text-emerald-100/90"),children:f.results}):p.jsx("pre",{className:me("whitespace-pre-wrap text-sm leading-relaxed",String(y).toLowerCase().includes("fail")||String(y).toLowerCase().includes("error")?"text-rose-100/90":"text-emerald-100/90"),children:JSON.stringify(f.results,null,2)})})]})]}):p.jsx("div",{className:"prose prose-invert max-w-none text-sm leading-relaxed prose-headings:text-slate-100 prose-p:mb-3 prose-p:text-slate-200 prose-pre:bg-slate-900/80 prose-strong:text-slate-100",children:p.jsx(Bg,{remarkPlugins:[Xg],children:e.content})}),(d||c)&&p.jsxs("div",{className:"mt-5 flex items-center gap-3 text-xs text-slate-300",children:[c&&p.jsxs("button",{type:"button",onClick:x,className:"inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1 transition hover:border-white/30 hover:bg-white/10",children:[p.jsx(uv,{className:"h-3 w-3","aria-hidden":!0}),"Replay"]}),d&&p.jsxs("button",{type:"button",onClick:()=>i(v=>!v),className:"inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-3 py-1 transition hover:border-white/30 hover:bg-white/10",children:[r?"Hide JSON":"View JSON",r?p.jsx(qm,{className:"h-3 w-3","aria-hidden":!0}):p.jsx($f,{className:"h-3 w-3","aria-hidden":!0})]})]}),p.jsx(Kk,{initial:!1,children:d&&r&&p.jsx(Yk.pre,{initial:{height:0,opacity:0},animate:{height:"auto",opacity:1},exit:{height:0,opacity:0},transition:{duration:.2},className:"mt-3 max-h-80 overflow-auto rounded-xl border border-white/10 bg-black/40 p-4 text-xs text-cyan-100",children:JSON.stringify(e.payload,null,2)})})]}),w&&m&&Array.isArray(m.actions)&&m.actions.length>0&&p.jsx("div",{className:"ml-12 w-[calc(88%-3rem)] sm:w-[calc(74%-3rem)]",children:m.actions.map((v,k)=>p.jsx(FI,{action:v,index:k,isLast:k===m.actions.length-1,isExpanded:a.has(k),onToggle:()=>{const N=new Set(a);N.has(k)?N.delete(k):N.add(k),l(N)}},k))})]})},VI=[{label:"/reset",description:"Reset the current session state."},{label:"/replay",description:"Start next session and replay last request."}],$I=()=>{const[e,t]=T.useState(""),[n,r]=T.useState(!1),{connected:i,session:s,ui:o,toggleComposerShortcuts:a,resetSessionState:l,messages:u,setTaskRunning:c,stopCurrentTask:f}=Ce(y=>({connected:y.connected,session:y.session,ui:y.ui,toggleComposerShortcuts:y.toggleComposerShortcuts,resetSessionState:y.resetSessionState,messages:y.messages,setTaskRunning:y.setTaskRunning,stopCurrentTask:y.stopCurrentTask})),d=T.useCallback(y=>{switch(y){case"/reset":return sn().sendReset(),l(),!0;case"/replay":{const w=[...u].reverse().find(m=>m.role==="user");return w?(sn().send({type:"next_session",timestamp:Date.now()}),l(),setTimeout(()=>{sn().sendRequest(w.content);const m=Ce.getState(),x=m.ensureSession(s.id,s.displayName),v=ts();m.addMessage({id:v,sessionId:x,role:"user",kind:"user",author:"You",content:w.content,timestamp:Date.now(),status:"sent"})},500),!0):(console.warn("No previous user message to replay"),!0)}default:return!1}},[l,u,s.id,s.displayName]),h=T.useCallback(async()=>{const y=e.trim();if(!y||!i)return;if(y.startsWith("/")&&d(y.toLowerCase())){t("");return}const w=Ce.getState(),m=w.ensureSession(s.id,s.displayName),x=ts();if(w.addMessage({id:x,sessionId:m,role:"user",kind:"user",author:"You",content:y,timestamp:Date.now(),status:"sent"}),Object.keys(w.constellations).length>0){const k=`temp-${Date.now()}`;w.upsertConstellation({id:k,name:"Loading...",status:"pending",description:"Waiting for constellation to be created...",taskIds:[],dag:{nodes:[],edges:[]},statistics:{total:0,pending:0,running:0,completed:0,failed:0},createdAt:Date.now()}),w.setActiveConstellation(k),console.log("📊 Created temporary constellation for new request")}r(!0),c(!0);try{sn().sendRequest(y)}catch(k){console.error("Failed to send request",k),w.updateMessage(x,{status:"error"}),c(!1)}finally{t(""),r(!1)}},[i,e,d,s.displayName,s.id,c]),g=y=>{if(o.isTaskRunning){y.key==="Enter"&&y.preventDefault();return}y.key==="Enter"&&!y.shiftKey&&(y.preventDefault(),h())};return p.jsx("div",{className:"relative rounded-[30px] border border-white/10 bg-gradient-to-br from-[rgba(11,24,44,0.82)] to-[rgba(8,15,28,0.75)] p-4 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(15,123,255,0.12),inset_0_1px_1px_rgba(255,255,255,0.06)] ring-1 ring-inset ring-white/5",children:p.jsxs("div",{className:"relative",children:[p.jsx("textarea",{value:e,onChange:y=>t(y.target.value),onKeyDown:g,placeholder:i?"Ask Galaxy to orchestrate a new mission…":"Waiting for connection…",rows:3,className:"w-full resize-none rounded-3xl border border-white/5 bg-black/40 px-5 py-4 text-sm text-slate-100 placeholder:text-slate-500 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus:border-white/15 focus:outline-none focus:ring-1 focus:ring-white/10 focus:shadow-[0_0_8px_rgba(15,123,255,0.08),inset_0_2px_8px_rgba(0,0,0,0.3)]",disabled:!i||n||o.isTaskRunning}),p.jsxs("div",{className:"mt-3 flex items-center justify-between gap-2 text-xs text-slate-400",children:[p.jsxs("div",{className:"flex items-center gap-2",children:[p.jsxs("button",{type:"button",onClick:()=>a(),className:"inline-flex items-center gap-2 rounded-full border border-white/10 px-3 py-1 hover:border-white/30",children:[p.jsx(GC,{className:"h-3 w-3","aria-hidden":!0}),"Shortcuts"]}),o.showComposerShortcuts&&p.jsx(p.Fragment,{children:VI.map(y=>p.jsx("button",{type:"button",onClick:()=>{t(y.label),a()},title:y.description,className:"rounded-full border border-white/10 bg-black/30 px-3 py-1 text-xs font-medium text-slate-200 transition hover:border-white/30 hover:bg-black/40",children:y.label},y.label))})]}),p.jsx("button",{type:"button",onClick:o.isTaskRunning?f:h,disabled:!i||!o.isTaskRunning&&e.trim().length===0||n,className:me("inline-flex items-center gap-2 rounded-full px-4 py-2 text-sm font-semibold text-white transition-all duration-300",o.isTaskRunning?"bg-gradient-to-br from-[rgba(80,20,30,0.75)] via-[rgba(100,25,35,0.70)] to-[rgba(80,20,30,0.75)] hover:from-[rgba(100,25,35,0.85)] hover:via-[rgba(120,30,40,0.80)] hover:to-[rgba(100,25,35,0.85)] border border-rose-900/40 hover:border-rose-800/50 shadow-[0_0_16px_rgba(139,0,0,0.25),0_4px_12px_rgba(0,0,0,0.4),inset_0_1px_1px_rgba(255,255,255,0.08)]":"bg-gradient-to-br from-[rgba(6,182,212,0.85)] via-[rgba(147,51,234,0.80)] to-[rgba(236,72,153,0.85)] hover:from-[rgba(6,182,212,0.95)] hover:via-[rgba(147,51,234,0.90)] hover:to-[rgba(236,72,153,0.95)] border border-cyan-400/30 hover:border-purple-400/40 shadow-[0_0_20px_rgba(6,182,212,0.3),0_0_30px_rgba(147,51,234,0.2),0_4px_16px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(255,255,255,0.15),inset_0_-1px_2px_rgba(0,0,0,0.2)] active:scale-95 active:shadow-[0_0_15px_rgba(6,182,212,0.4),0_2px_8px_rgba(0,0,0,0.4)]",(!i||!o.isTaskRunning&&e.trim().length===0||n)&&"opacity-50 grayscale"),children:n?p.jsxs(p.Fragment,{children:[p.jsx(Vo,{className:"h-4 w-4 animate-spin","aria-hidden":!0}),"Sending"]}):o.isTaskRunning?p.jsxs(p.Fragment,{children:[p.jsx(WC,{className:"h-4 w-4","aria-hidden":!0}),"Stop"]}):p.jsxs(p.Fragment,{children:[p.jsx($C,{className:"h-4 w-4","aria-hidden":!0}),"Launch"]})})]})]})})},BI=(e,t,n)=>{const r=t.toLowerCase().trim();return e.filter(i=>n==="all"||i.kind===n?r?[i.content,i.agentName,i.role].filter(Boolean).map(a=>String(a).toLowerCase()).join(" ").includes(r):!0:!1)},HI=()=>{const{messages:e,searchQuery:t,messageKind:n,isTaskStopped:r}=Ce(l=>({messages:l.messages,searchQuery:l.ui.searchQuery,messageKind:l.ui.messageKindFilter,isTaskStopped:l.ui.isTaskStopped}),Oe),i=T.useRef(null),s=T.useMemo(()=>BI(e,t,n),[e,n,t]),o=T.useMemo(()=>{const l=new Map;let u=0;return s.forEach(c=>{c.role==="user"?u=0:c.kind!=="action"&&(u++,l.set(c.id,u))}),l},[s]),a=T.useMemo(()=>{var u,c,f;if(e.length===0)return!1;const l=e[e.length-1];if(l.role==="user"||l.role==="assistant"&&l.kind==="action")return!0;if(l.role==="assistant"&&l.kind==="response"){const d=String(((u=l.payload)==null?void 0:u.status)||((f=(c=l.payload)==null?void 0:c.result)==null?void 0:f.status)||"").toLowerCase();if(d==="continue"||d==="running"||d==="pending"||d==="")return!0}return!1},[e]);return T.useEffect(()=>{i.current&&i.current.scrollTo({top:i.current.scrollHeight,behavior:"smooth"})},[s.length]),p.jsxs("div",{className:"flex h-full min-h-0 flex-col gap-4",children:[p.jsx(T5,{}),p.jsx("div",{ref:i,className:"flex-1 overflow-y-auto rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-6 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(15,123,255,0.15),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:p.jsx("div",{className:"flex flex-col gap-5",children:s.length===0?p.jsxs("div",{className:"flex h-full flex-col items-center justify-center gap-3 text-center text-slate-400",children:[p.jsx("span",{className:"text-3xl",children:"✨"}),p.jsx("p",{className:"max-w-sm text-sm",children:"Ready to launch. Describe a mission for the Galaxy Agent, or use quick commands below to explore diagnostics."})]}):p.jsxs(p.Fragment,{children:[s.map((l,u)=>p.jsx(OI,{message:l,nextMessage:s[u+1],stepNumber:o.get(l.id)},l.id)),a&&!r&&p.jsxs("div",{className:"ml-14 flex items-center gap-2 rounded-xl border border-cyan-500/30 bg-gradient-to-r from-cyan-950/30 to-blue-950/20 px-4 py-2.5 shadow-[0_0_20px_rgba(6,182,212,0.15)]",children:[p.jsx(Vo,{className:"h-3.5 w-3.5 animate-spin text-cyan-400"}),p.jsx("span",{className:"text-xs font-medium text-cyan-300/90",children:"UFO is thinking..."})]}),r&&p.jsxs("div",{className:"ml-14 flex items-center gap-2 rounded-xl border border-purple-400/20 bg-gradient-to-r from-purple-950/20 to-indigo-950/15 px-4 py-2.5 shadow-[0_0_16px_rgba(147,51,234,0.08)]",children:[p.jsx("div",{className:"h-2 w-2 rounded-full bg-purple-300/80 animate-pulse"}),p.jsx("span",{className:"text-xs font-medium text-purple-200/80",children:"Task stopped by user. Ready for new mission."})]})]})})}),p.jsx($I,{})]})},UI=()=>{const{session:e,resetSessionState:t}=Ce(i=>({session:i.session,resetSessionState:i.resetSessionState})),n=()=>{sn().sendReset(),t()},r=()=>{sn().send({type:"next_session",timestamp:Date.now()}),t()};return p.jsxs("div",{className:"flex flex-col gap-4 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-5 text-sm text-slate-100 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(6,182,212,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:[p.jsx("div",{className:"flex items-start justify-start",children:p.jsxs("div",{className:"flex items-center gap-2",children:[p.jsx(Hf,{className:"h-5 w-5 text-cyan-400 drop-shadow-[0_0_8px_rgba(6,182,212,0.5)]","aria-hidden":!0}),p.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:e.displayName})]})}),p.jsxs("div",{className:"grid grid-cols-1 gap-3",children:[p.jsxs("button",{type:"button",onClick:n,className:"flex items-center gap-3 rounded-2xl border border-[rgba(10,186,181,0.4)] bg-gradient-to-r from-[rgba(10,186,181,0.15)] to-[rgba(6,182,212,0.15)] px-4 py-3 shadow-[0_4px_16px_rgba(0,0,0,0.25),0_0_15px_rgba(10,186,181,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)] transition-all duration-200 hover:border-[rgba(10,186,181,0.6)] hover:from-[rgba(10,186,181,0.25)] hover:to-[rgba(6,182,212,0.25)] hover:shadow-[0_8px_24px_rgba(0,0,0,0.3),0_0_25px_rgba(10,186,181,0.3)]",children:[p.jsx(uv,{className:"h-4 w-4 text-[rgb(10,186,181)]","aria-hidden":!0}),p.jsxs("div",{className:"text-left",children:[p.jsx("div",{className:"text-sm font-medium text-white",children:"Reset Session"}),p.jsx("div",{className:"text-xs text-slate-400",children:"Clear chat, tasks, and devices"})]})]}),p.jsxs("button",{type:"button",onClick:r,className:"flex items-center gap-3 rounded-2xl border border-emerald-400/40 bg-gradient-to-r from-emerald-500/15 to-cyan-500/15 px-4 py-3 shadow-[0_4px_16px_rgba(0,0,0,0.25),0_0_15px_rgba(16,185,129,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)] transition-all duration-200 hover:border-emerald-400/60 hover:from-emerald-500/25 hover:to-cyan-500/25 hover:shadow-[0_8px_24px_rgba(0,0,0,0.3),0_0_25px_rgba(16,185,129,0.3)]",children:[p.jsx(cv,{className:"h-4 w-4 text-emerald-300","aria-hidden":!0}),p.jsxs("div",{className:"text-left",children:[p.jsx("div",{className:"text-sm font-medium text-white",children:"Next Session"}),p.jsx("div",{className:"text-xs text-slate-400",children:"Launch with a fresh constellation"})]})]})]})]})},WI=({isOpen:e,onClose:t,onSubmit:n,existingDeviceIds:r})=>{const[i,s]=T.useState({device_id:"",server_url:"",os:"",capabilities:[],metadata:{},auto_connect:!0,max_retries:5}),[o,a]=T.useState(""),[l,u]=T.useState(""),[c,f]=T.useState(""),[d,h]=T.useState({}),[g,y]=T.useState(!1),[w,m]=T.useState(!1),[x,v]=T.useState(""),k=()=>{const M={};return i.device_id.trim()?r.includes(i.device_id.trim())&&(M.device_id="Device ID already exists"):M.device_id="Device ID is required",i.server_url.trim()?i.server_url.match(/^wss?:\/\/.+/)||(M.server_url="Invalid WebSocket URL (must start with ws:// or wss://)"):M.server_url="Server URL is required",i.os.trim()||(M.os="OS is required"),i.capabilities.length===0&&(M.capabilities="At least one capability is required"),h(M),Object.keys(M).length===0},N=async M=>{if(M.preventDefault(),!!k()){y(!0);try{await n(i),S()}catch(O){h({submit:O instanceof Error?O.message:"Failed to add device"})}finally{y(!1)}}},S=T.useCallback(()=>{s({device_id:"",server_url:"",os:"",capabilities:[],metadata:{},auto_connect:!0,max_retries:5}),a(""),u(""),f(""),h({}),y(!1),m(!1),v(""),t()},[t]),A=T.useCallback(()=>{o.trim()&&!i.capabilities.includes(o.trim())&&(s(M=>({...M,capabilities:[...M.capabilities,o.trim()]})),a(""),h(M=>({...M,capabilities:""})))},[o,i.capabilities]),P=T.useCallback(M=>{s(O=>({...O,capabilities:O.capabilities.filter(_=>_!==M)}))},[]),D=T.useCallback(()=>{l.trim()&&c.trim()&&(s(M=>({...M,metadata:{...M.metadata,[l.trim()]:c.trim()}})),u(""),f(""))},[l,c]),C=T.useCallback(M=>{s(O=>{const _={...O.metadata};return delete _[M],{...O,metadata:_}})},[]),L=T.useMemo(()=>Object.entries(i.metadata||{}),[i.metadata]);return e?p.jsxs("div",{className:"fixed inset-0 z-50 flex items-center justify-center p-4",children:[p.jsx("div",{className:"absolute inset-0 bg-slate-950/95",onClick:S,"aria-hidden":!0}),p.jsxs("div",{className:"relative z-10 w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-2xl border border-cyan-500/30 bg-slate-900/95 p-8 shadow-2xl",children:[p.jsxs("div",{className:"mb-6 flex items-center justify-between",children:[p.jsx("h2",{className:"text-2xl font-bold text-cyan-400",children:"Add New Device"}),p.jsx("button",{onClick:S,className:"rounded-lg p-2 text-slate-400 transition-colors hover:bg-cyan-500/10 hover:text-cyan-300","aria-label":"Close",children:p.jsx(Vi,{className:"h-5 w-5"})})]}),p.jsxs("form",{onSubmit:N,className:"space-y-5",children:[p.jsxs("div",{children:[p.jsxs("label",{className:"mb-2 block text-sm font-medium text-cyan-300",children:["Device ID ",p.jsx("span",{className:"text-rose-400",children:"*"})]}),p.jsx("input",{type:"text",value:i.device_id,onChange:M=>s({...i,device_id:M.target.value}),placeholder:"e.g., windows_agent_01",className:me("w-full rounded-lg border bg-slate-800/80 px-4 py-3 text-sm text-white placeholder-slate-500 transition-colors focus:outline-none",d.device_id?"border-rose-500/50 focus:border-rose-400 focus:ring-2 focus:ring-rose-400/30":"border-slate-700 focus:border-cyan-400 focus:ring-2 focus:ring-cyan-400/30")}),d.device_id&&p.jsx("p",{className:"mt-1.5 text-xs text-rose-400",children:d.device_id})]}),p.jsxs("div",{children:[p.jsxs("label",{className:"mb-2 block text-sm font-medium text-cyan-300",children:["Server URL ",p.jsx("span",{className:"text-rose-400",children:"*"})]}),p.jsx("input",{type:"text",value:i.server_url,onChange:M=>s({...i,server_url:M.target.value}),placeholder:"ws://localhost:5001/ws",className:me("w-full rounded-lg border bg-slate-800/80 px-4 py-3 text-sm text-white placeholder-slate-500 transition-colors focus:outline-none",d.server_url?"border-rose-500/50 focus:border-rose-400 focus:ring-2 focus:ring-rose-400/30":"border-slate-700 focus:border-cyan-400 focus:ring-2 focus:ring-cyan-400/30")}),d.server_url&&p.jsx("p",{className:"mt-1.5 text-xs text-rose-400",children:d.server_url})]}),p.jsxs("div",{children:[p.jsxs("label",{className:"mb-2 block text-sm font-medium text-cyan-300",children:["Operating System ",p.jsx("span",{className:"text-rose-400",children:"*"})]}),p.jsxs("select",{value:w?"custom":i.os,onChange:M=>{const O=M.target.value;O==="custom"?(m(!0),s({...i,os:x})):(m(!1),v(""),s({...i,os:O}))},className:me("w-full rounded-lg border bg-slate-800/80 px-4 py-3 text-sm text-white transition-colors focus:outline-none",d.os?"border-rose-500/50 focus:border-rose-400 focus:ring-2 focus:ring-rose-400/30":"border-slate-700 focus:border-cyan-400 focus:ring-2 focus:ring-cyan-400/30"),children:[p.jsx("option",{value:"",disabled:!0,className:"bg-slate-900",children:"Select OS"}),p.jsx("option",{value:"windows",className:"bg-slate-900",children:"Windows"}),p.jsx("option",{value:"linux",className:"bg-slate-900",children:"Linux"}),p.jsx("option",{value:"macos",className:"bg-slate-900",children:"macOS"}),p.jsx("option",{value:"custom",className:"bg-slate-900",children:"Custom / Other..."})]}),w&&p.jsx("input",{type:"text",value:x,onChange:M=>{v(M.target.value),s({...i,os:M.target.value})},placeholder:"Enter custom OS name",className:"mt-2 w-full rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-3 text-sm text-white placeholder-slate-500 transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30",autoFocus:!0}),d.os&&p.jsx("p",{className:"mt-1.5 text-xs text-rose-400",children:d.os})]}),p.jsxs("div",{children:[p.jsxs("label",{className:"mb-2 block text-sm font-medium text-cyan-300",children:["Capabilities ",p.jsx("span",{className:"text-rose-400",children:"*"})]}),p.jsxs("div",{className:"flex gap-2",children:[p.jsx("input",{type:"text",value:o,onChange:M=>a(M.target.value),onKeyDown:M=>{M.key==="Enter"&&(M.preventDefault(),A())},placeholder:"e.g., web_browsing",className:"flex-1 rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-3 text-sm text-white placeholder-slate-500 transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30"}),p.jsx("button",{type:"button",onClick:A,className:"rounded-lg border border-emerald-400/40 bg-emerald-500/20 px-4 py-3 transition-colors hover:bg-emerald-500/30",children:p.jsx(Bf,{className:"h-4 w-4 text-emerald-300"})})]}),d.capabilities&&p.jsx("p",{className:"mt-1 text-xs text-rose-400",children:d.capabilities}),i.capabilities.length>0&&p.jsx("div",{className:"mt-2 flex flex-wrap gap-2",children:i.capabilities.map(M=>p.jsxs("span",{className:"inline-flex items-center gap-1.5 rounded-lg border border-indigo-400/40 bg-indigo-500/20 px-3 py-1.5 text-xs font-medium text-indigo-300",children:[M,p.jsx("button",{type:"button",onClick:()=>P(M),className:"text-indigo-300 hover:text-rose-400",children:p.jsx(Vi,{className:"h-3 w-3"})})]},M))})]}),p.jsxs("div",{children:[p.jsxs("label",{className:"mb-1.5 block text-sm font-medium text-slate-300",children:["Metadata ",p.jsx("span",{className:"text-xs text-slate-500",children:"(Optional)"})]}),p.jsxs("div",{className:"flex gap-2",children:[p.jsx("input",{type:"text",value:l,onChange:M=>u(M.target.value),placeholder:"Key",className:"flex-1 rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-2.5 text-sm text-white placeholder-slate-500 transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30"}),p.jsx("input",{type:"text",value:c,onChange:M=>f(M.target.value),onKeyDown:M=>{M.key==="Enter"&&(M.preventDefault(),D())},placeholder:"Value",className:"flex-1 rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-2.5 text-sm text-white placeholder-slate-500 transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30"}),p.jsx("button",{type:"button",onClick:D,className:"rounded-lg border border-emerald-400/40 bg-emerald-500/20 px-4 py-2.5 text-sm font-medium text-emerald-300 transition-colors hover:bg-emerald-500/30",children:p.jsx(Bf,{className:"h-4 w-4"})})]}),L.length>0&&p.jsx("div",{className:"mt-2 space-y-1.5",children:L.map(([M,O])=>p.jsxs("div",{className:"flex items-center justify-between rounded-lg border border-slate-700 bg-slate-800/60 px-3 py-2 text-xs",children:[p.jsxs("span",{className:"text-slate-300",children:[p.jsxs("span",{className:"font-medium text-cyan-400",children:[M,":"]})," ",String(O)]}),p.jsx("button",{type:"button",onClick:()=>C(M),className:"text-slate-400 hover:text-rose-400",children:p.jsx(Vi,{className:"h-3 w-3"})})]},M))})]}),p.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[p.jsxs("div",{children:[p.jsx("label",{className:"mb-1.5 block text-sm font-medium text-slate-300",children:"Auto Connect"}),p.jsxs("label",{className:"flex cursor-pointer items-center gap-2",children:[p.jsx("input",{type:"checkbox",checked:i.auto_connect,onChange:M=>s({...i,auto_connect:M.target.checked}),className:"h-4 w-4 cursor-pointer rounded border-slate-700 bg-slate-800 text-cyan-500 focus:ring-2 focus:ring-cyan-400/30"}),p.jsx("span",{className:"text-xs text-slate-400",children:"Connect on startup"})]})]}),p.jsxs("div",{children:[p.jsx("label",{className:"mb-1.5 block text-sm font-medium text-slate-300",children:"Max Retries"}),p.jsx("input",{type:"number",min:"1",max:"20",value:i.max_retries,onChange:M=>s({...i,max_retries:parseInt(M.target.value)||5}),className:"w-full rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-2.5 text-sm text-white transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30"})]})]}),d.submit&&p.jsx("div",{className:"rounded-lg border border-rose-500/40 bg-rose-500/10 px-4 py-3 text-sm text-rose-300",children:d.submit}),p.jsxs("div",{className:"flex gap-3 pt-2",children:[p.jsx("button",{type:"button",onClick:S,disabled:g,className:"flex-1 rounded-lg border border-slate-700 bg-slate-800/60 px-4 py-3 text-sm font-medium text-slate-300 transition-colors hover:bg-slate-800 disabled:opacity-50",children:"Cancel"}),p.jsx("button",{type:"submit",disabled:g,className:"flex-1 rounded-lg border border-cyan-400/40 bg-cyan-500/30 px-4 py-3 text-sm font-semibold text-white transition-colors hover:bg-cyan-500/40 disabled:opacity-50",children:g?p.jsxs("span",{className:"flex items-center justify-center gap-2",children:[p.jsx(Vo,{className:"h-4 w-4 animate-spin"}),"Adding..."]}):"Add Device"})]})]})]})]}):null};function YI(){return""}const qI=YI();function KI(e){const t=e.startsWith("/")?e.slice(1):e;return`${qI}/${t}`}const ey={connected:{label:"Connected",dot:"bg-emerald-400",text:"text-emerald-300"},idle:{label:"Idle",dot:"bg-cyan-400",text:"text-cyan-200"},busy:{label:"Busy",dot:"bg-amber-400",text:"text-amber-200"},connecting:{label:"Connecting",dot:"bg-blue-400",text:"text-blue-200"},failed:{label:"Failed",dot:"bg-rose-500",text:"text-rose-200"},disconnected:{label:"Disconnected",dot:"bg-slate-500",text:"text-slate-300"},offline:{label:"Offline",dot:"bg-slate-600",text:"text-slate-400"},unknown:{label:"Unknown",dot:"bg-slate-600",text:"text-slate-400"}},GI=e=>{if(!e)return"No heartbeat yet";const t=Date.parse(e);if(Number.isNaN(t))return e;const n=Date.now()-t;if(n<6e4)return"Just now";const r=Math.round(n/6e4);return r<60?`${r} min ago`:`${Math.round(r/60)} hr ago`},XI=({device:e})=>{const t=ey[e.status]||ey.unknown,n=e.highlightUntil&&e.highlightUntil>Date.now();return p.jsxs("div",{className:me("group rounded-2xl border bg-gradient-to-br p-4 text-xs transition-all duration-300","border-white/20 from-[rgba(25,40,60,0.75)] via-[rgba(20,35,52,0.7)] to-[rgba(15,28,45,0.75)]","shadow-[0_4px_16px_rgba(0,0,0,0.3),0_0_8px_rgba(15,123,255,0.1),inset_0_1px_2px_rgba(255,255,255,0.1),inset_0_0_20px_rgba(15,123,255,0.03)]","hover:border-white/35 hover:from-[rgba(28,45,65,0.85)] hover:via-[rgba(23,38,56,0.8)] hover:to-[rgba(18,30,48,0.85)]","hover:shadow-[0_8px_24px_rgba(0,0,0,0.35),0_0_20px_rgba(15,123,255,0.2),0_0_30px_rgba(6,182,212,0.15),inset_0_1px_2px_rgba(255,255,255,0.15),inset_0_0_30px_rgba(15,123,255,0.06)]","hover:translate-y-[-2px]",n&&"border-cyan-400/50 from-[rgba(6,182,212,0.2)] via-[rgba(15,123,255,0.15)] to-[rgba(15,28,45,0.8)] shadow-[0_0_30px_rgba(6,182,212,0.4),0_0_40px_rgba(6,182,212,0.25),0_4px_16px_rgba(0,0,0,0.3),inset_0_0_30px_rgba(6,182,212,0.1)]"),children:[p.jsxs("div",{className:"flex items-start justify-between gap-3",children:[p.jsxs("div",{children:[p.jsx("div",{className:"font-mono text-sm text-white drop-shadow-[0_1px_4px_rgba(0,0,0,0.5)]",children:e.name}),p.jsxs("div",{className:"mt-1 flex items-center gap-2",children:[p.jsx("span",{className:me("h-2 w-2 rounded-full shadow-[0_0_6px_currentColor]",t.dot),"aria-hidden":!0}),p.jsx("span",{className:me("text-[11px] uppercase tracking-[0.2em]",t.text),children:t.label}),e.os&&p.jsxs(p.Fragment,{children:[p.jsx("span",{className:"text-slate-600",children:"|"}),p.jsx("span",{className:"rounded-full border border-indigo-400/30 bg-indigo-500/20 px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.15em] text-indigo-300 shadow-[0_0_8px_rgba(99,102,241,0.2),inset_0_1px_1px_rgba(255,255,255,0.1)]",children:e.os})]})]})]}),p.jsx(MC,{className:"h-4 w-4 text-slate-400 transition-all group-hover:text-cyan-400 group-hover:drop-shadow-[0_0_6px_rgba(6,182,212,0.5)]","aria-hidden":!0})]}),p.jsxs("div",{className:"mt-3 grid gap-2 text-[11px] text-slate-300",children:[e.capabilities&&e.capabilities.length>0&&p.jsxs("div",{children:["Capabilities: ",e.capabilities.join(", ")]}),p.jsxs("div",{className:"flex items-center gap-2 text-slate-400",children:[p.jsx(au,{className:"h-3 w-3","aria-hidden":!0}),GI(e.lastHeartbeat)]}),e.metadata&&e.metadata.region&&p.jsxs("div",{children:["Region: ",e.metadata.region]})]})]})},QI=()=>{const{devices:e}=Ce(c=>({devices:c.devices}),Oe),[t,n]=T.useState(""),[r,i]=T.useState(!1),s=T.useMemo(()=>{const c=Object.values(e);if(!t)return c;const f=t.toLowerCase();return c.filter(d=>{var h;return[d.name,d.id,d.os,(h=d.metadata)==null?void 0:h.region].filter(Boolean).map(g=>String(g).toLowerCase()).some(g=>g.includes(f))})},[e,t]),o=s.length,a=s.filter(c=>c.status==="connected"||c.status==="idle"||c.status==="busy").length,l=async c=>{try{const f=await fetch(KI("api/devices"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)});if(!f.ok){const d=await f.json();throw new Error(d.message||"Failed to add device")}}catch(f){throw f}},u=Object.keys(e);return p.jsxs("div",{className:"flex h-full flex-col gap-4 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-5 text-sm text-slate-100 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(16,185,129,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:[p.jsxs("div",{className:"flex items-center justify-between",children:[p.jsxs("div",{className:"flex items-center gap-3",children:[p.jsx(TC,{className:"h-5 w-5 text-emerald-400 drop-shadow-[0_0_8px_rgba(16,185,129,0.5)]","aria-hidden":!0}),p.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:"Device Agent"}),p.jsxs("div",{className:"mt-0.5 rounded-lg border border-emerald-400/40 bg-gradient-to-r from-emerald-500/15 to-emerald-600/10 px-2.5 py-1 text-xs font-medium text-emerald-200 shadow-[0_0_15px_rgba(16,185,129,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)]",children:[a,"/",o," online"]})]}),p.jsx("button",{onClick:()=>i(!0),className:"group rounded-lg border border-cyan-400/30 bg-gradient-to-r from-cyan-500/20 to-blue-600/15 p-2 shadow-[0_0_15px_rgba(6,182,212,0.2)] transition-all hover:from-cyan-500/30 hover:to-blue-600/25 hover:shadow-[0_0_20px_rgba(6,182,212,0.3)]","aria-label":"Add device",title:"Add new device",children:p.jsx(Bf,{className:"h-4 w-4 text-cyan-300 transition-transform group-hover:scale-110"})})]}),p.jsxs("div",{className:"flex items-center gap-2 rounded-xl border border-white/5 bg-gradient-to-r from-black/30 to-black/20 px-3 py-2.5 text-xs text-slate-300 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus-within:border-white/15 focus-within:shadow-[0_0_8px_rgba(16,185,129,0.08),inset_0_2px_8px_rgba(0,0,0,0.3)]",children:[p.jsx(fv,{className:"h-3.5 w-3.5","aria-hidden":!0}),p.jsx("input",{type:"search",value:t,onChange:c=>n(c.target.value),placeholder:"Filter by id, region, or OS",className:"w-full bg-transparent focus:outline-none"})]}),p.jsx("div",{className:"flex-1 space-y-3 overflow-y-auto",children:s.length===0?p.jsxs("div",{className:"flex flex-col items-center gap-2 rounded-2xl border border-dashed border-white/10 bg-white/5 p-6 text-center text-xs text-slate-400",children:[p.jsx(XC,{className:"h-5 w-5","aria-hidden":!0}),"No devices reported yet."]}):s.map(c=>p.jsx(XI,{device:c},c.id))}),p.jsx(WI,{isOpen:r,onClose:()=>i(!1),onSubmit:l,existingDeviceIds:u})]})},ty=()=>p.jsxs("div",{className:"flex h-full w-full flex-col gap-4 overflow-hidden",children:[p.jsx(UI,{}),p.jsx("div",{className:"flex-1 overflow-y-auto space-y-4 pr-1",children:p.jsx(QI,{})})]}),ZI={info:{icon:p.jsx(LC,{className:"h-4 w-4","aria-hidden":!0}),className:"border-cyan-400/40 bg-cyan-500/20 text-cyan-100"},success:{icon:p.jsx(Kr,{className:"h-4 w-4","aria-hidden":!0}),className:"border-emerald-400/40 bg-emerald-500/20 text-emerald-100"},warning:{icon:p.jsx(Ym,{className:"h-4 w-4","aria-hidden":!0}),className:"border-amber-400/40 bg-amber-500/20 text-amber-100"},error:{icon:p.jsx(Ym,{className:"h-4 w-4","aria-hidden":!0}),className:"border-rose-400/40 bg-rose-500/20 text-rose-100"}},JI=5e3,eL=()=>{const{notifications:e,dismissNotification:t,markNotificationRead:n}=Ce(r=>({notifications:r.notifications,dismissNotification:r.dismissNotification,markNotificationRead:r.markNotificationRead}));return T.useEffect(()=>{const r=[];return e.forEach(i=>{const s=setTimeout(()=>{t(i.id)},JI);r.push(s)}),()=>{r.forEach(i=>clearTimeout(i))}},[e,t]),p.jsx("div",{className:"pointer-events-none fixed bottom-6 left-6 z-50 flex w-80 flex-col gap-3",children:p.jsx(Kk,{children:e.map(r=>{const i=ZI[r.severity];return p.jsxs(Yk.div,{initial:{y:20,opacity:0},animate:{y:0,opacity:1},exit:{y:10,opacity:0},transition:{duration:.2},className:me("pointer-events-auto relative rounded-2xl border px-4 py-3 shadow-lg",i.className),onMouseEnter:()=>n(r.id),children:[p.jsx("button",{type:"button",className:"absolute right-2 top-2 rounded-full border border-white/20 p-1 text-slate-200 transition hover:bg-white/10",onClick:()=>t(r.id),children:p.jsx(Vi,{className:"h-3 w-3","aria-hidden":!0})}),p.jsxs("div",{className:"flex items-start gap-3 pr-6",children:[p.jsx("div",{className:"mt-1 flex-shrink-0",children:i.icon}),p.jsxs("div",{className:"flex-1 min-w-0 text-xs",children:[p.jsx("div",{className:"font-semibold text-white break-words",children:r.title}),r.description&&p.jsx("div",{className:"mt-1 text-[11px] text-slate-200/80 break-words",children:r.description}),p.jsxs("div",{className:"mt-2 flex items-center justify-between text-[10px] uppercase tracking-[0.18em] text-slate-300/70",children:[p.jsx("span",{className:"truncate",children:r.source||"system"}),p.jsx("span",{className:"flex-shrink-0 ml-2",children:new Date(r.timestamp).toLocaleTimeString()})]})]})]})]},r.id)})})})};function gt(e){if(typeof e=="string"||typeof e=="number")return""+e;let t="";if(Array.isArray(e))for(let n=0,r;ne;function Gk(e,t=rL,n){const r=nL(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return tL(r),r}const ny=(e,t)=>{const n=hv(e),r=(i,s=t)=>Gk(n,i,s);return Object.assign(r,n),r},iL=(e,t)=>e?ny(e,t):ny;var sL={value:()=>{}};function Su(){for(var e=0,t=arguments.length,n={},r;e=0&&(r=n.slice(i+1),n=n.slice(0,i)),n&&!t.hasOwnProperty(n))throw new Error("unknown type: "+n);return{type:n,name:r}})}Ga.prototype=Su.prototype={constructor:Ga,on:function(e,t){var n=this._,r=oL(e+"",n),i,s=-1,o=r.length;if(arguments.length<2){for(;++s0)for(var n=new Array(i),r=0,i,s;r=0&&(t=e.slice(0,n))!=="xmlns"&&(e=e.slice(n+1)),iy.hasOwnProperty(t)?{space:iy[t],local:e}:e}function lL(e){return function(){var t=this.ownerDocument,n=this.namespaceURI;return n===md&&t.documentElement.namespaceURI===md?t.createElement(e):t.createElementNS(n,e)}}function uL(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}function Xk(e){var t=_u(e);return(t.local?uL:lL)(t)}function cL(){}function hp(e){return e==null?cL:function(){return this.querySelector(e)}}function fL(e){typeof e!="function"&&(e=hp(e));for(var t=this._groups,n=t.length,r=new Array(n),i=0;i=v&&(v=x+1);!(N=w[v])&&++v=0;)(o=r[i])&&(s&&o.compareDocumentPosition(s)^4&&s.parentNode.insertBefore(o,s),s=o);return this}function RL(e){e||(e=zL);function t(f,d){return f&&d?e(f.__data__,d.__data__):!f-!d}for(var n=this._groups,r=n.length,i=new Array(r),s=0;st?1:e>=t?0:NaN}function FL(){var e=arguments[0];return arguments[0]=this,e.apply(null,arguments),this}function OL(){return Array.from(this)}function VL(){for(var e=this._groups,t=0,n=e.length;t1?this.each((t==null?QL:typeof t=="function"?JL:ZL)(e,t,n??"")):ss(this.node(),e)}function ss(e,t){return e.style.getPropertyValue(t)||tb(e).getComputedStyle(e,null).getPropertyValue(t)}function tR(e){return function(){delete this[e]}}function nR(e,t){return function(){this[e]=t}}function rR(e,t){return function(){var n=t.apply(this,arguments);n==null?delete this[e]:this[e]=n}}function iR(e,t){return arguments.length>1?this.each((t==null?tR:typeof t=="function"?rR:nR)(e,t)):this.node()[e]}function nb(e){return e.trim().split(/^|\s+/)}function pp(e){return e.classList||new rb(e)}function rb(e){this._node=e,this._names=nb(e.getAttribute("class")||"")}rb.prototype={add:function(e){var t=this._names.indexOf(e);t<0&&(this._names.push(e),this._node.setAttribute("class",this._names.join(" ")))},remove:function(e){var t=this._names.indexOf(e);t>=0&&(this._names.splice(t,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(e){return this._names.indexOf(e)>=0}};function ib(e,t){for(var n=pp(e),r=-1,i=t.length;++r=0&&(n=t.slice(r+1),t=t.slice(0,r)),{type:t,name:n}})}function MR(e){return function(){var t=this.__on;if(t){for(var n=0,r=-1,i=t.length,s;n()=>e;function gd(e,{sourceEvent:t,subject:n,target:r,identifier:i,active:s,x:o,y:a,dx:l,dy:u,dispatch:c}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},subject:{value:n,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:s,enumerable:!0,configurable:!0},x:{value:o,enumerable:!0,configurable:!0},y:{value:a,enumerable:!0,configurable:!0},dx:{value:l,enumerable:!0,configurable:!0},dy:{value:u,enumerable:!0,configurable:!0},_:{value:c}})}gd.prototype.on=function(){var e=this._.on.apply(this._,arguments);return e===this._?this:e};function BR(e){return!e.ctrlKey&&!e.button}function HR(){return this.parentNode}function UR(e,t){return t??{x:e.x,y:e.y}}function WR(){return navigator.maxTouchPoints||"ontouchstart"in this}function YR(){var e=BR,t=HR,n=UR,r=WR,i={},s=Su("start","drag","end"),o=0,a,l,u,c,f=0;function d(k){k.on("mousedown.drag",h).filter(r).on("touchstart.drag",w).on("touchmove.drag",m,$R).on("touchend.drag touchcancel.drag",x).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function h(k,N){if(!(c||!e.call(this,k,N))){var S=v(this,t.call(this,k,N),k,N,"mouse");S&&(nn(k.view).on("mousemove.drag",g,No).on("mouseup.drag",y,No),lb(k.view),zc(k),u=!1,a=k.clientX,l=k.clientY,S("start",k))}}function g(k){if(Bi(k),!u){var N=k.clientX-a,S=k.clientY-l;u=N*N+S*S>f}i.mouse("drag",k)}function y(k){nn(k.view).on("mousemove.drag mouseup.drag",null),ub(k.view,u),Bi(k),i.mouse("end",k)}function w(k,N){if(e.call(this,k,N)){var S=k.changedTouches,A=t.call(this,k,N),P=S.length,D,C;for(D=0;D>8&15|t>>4&240,t>>4&15|t&240,(t&15)<<4|t&15,1):n===8?_a(t>>24&255,t>>16&255,t>>8&255,(t&255)/255):n===4?_a(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|t&240,((t&15)<<4|t&15)/255):null):(t=KR.exec(e))?new kt(t[1],t[2],t[3],1):(t=GR.exec(e))?new kt(t[1]*255/100,t[2]*255/100,t[3]*255/100,1):(t=XR.exec(e))?_a(t[1],t[2],t[3],t[4]):(t=QR.exec(e))?_a(t[1]*255/100,t[2]*255/100,t[3]*255/100,t[4]):(t=ZR.exec(e))?fy(t[1],t[2]/100,t[3]/100,1):(t=JR.exec(e))?fy(t[1],t[2]/100,t[3]/100,t[4]):sy.hasOwnProperty(e)?ly(sy[e]):e==="transparent"?new kt(NaN,NaN,NaN,0):null}function ly(e){return new kt(e>>16&255,e>>8&255,e&255,1)}function _a(e,t,n,r){return r<=0&&(e=t=n=NaN),new kt(e,t,n,r)}function n8(e){return e instanceof Xo||(e=Po(e)),e?(e=e.rgb(),new kt(e.r,e.g,e.b,e.opacity)):new kt}function yd(e,t,n,r){return arguments.length===1?n8(e):new kt(e,t,n,r??1)}function kt(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}mp(kt,yd,cb(Xo,{brighter(e){return e=e==null?zl:Math.pow(zl,e),new kt(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?To:Math.pow(To,e),new kt(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new kt(Xr(this.r),Xr(this.g),Xr(this.b),Fl(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:uy,formatHex:uy,formatHex8:r8,formatRgb:cy,toString:cy}));function uy(){return`#${Ur(this.r)}${Ur(this.g)}${Ur(this.b)}`}function r8(){return`#${Ur(this.r)}${Ur(this.g)}${Ur(this.b)}${Ur((isNaN(this.opacity)?1:this.opacity)*255)}`}function cy(){const e=Fl(this.opacity);return`${e===1?"rgb(":"rgba("}${Xr(this.r)}, ${Xr(this.g)}, ${Xr(this.b)}${e===1?")":`, ${e})`}`}function Fl(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function Xr(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function Ur(e){return e=Xr(e),(e<16?"0":"")+e.toString(16)}function fy(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new rn(e,t,n,r)}function fb(e){if(e instanceof rn)return new rn(e.h,e.s,e.l,e.opacity);if(e instanceof Xo||(e=Po(e)),!e)return new rn;if(e instanceof rn)return e;e=e.rgb();var t=e.r/255,n=e.g/255,r=e.b/255,i=Math.min(t,n,r),s=Math.max(t,n,r),o=NaN,a=s-i,l=(s+i)/2;return a?(t===s?o=(n-r)/a+(n0&&l<1?0:o,new rn(o,a,l,e.opacity)}function i8(e,t,n,r){return arguments.length===1?fb(e):new rn(e,t,n,r??1)}function rn(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}mp(rn,i8,cb(Xo,{brighter(e){return e=e==null?zl:Math.pow(zl,e),new rn(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=e==null?To:Math.pow(To,e),new rn(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,t=isNaN(e)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*t,i=2*n-r;return new kt(Fc(e>=240?e-240:e+120,i,r),Fc(e,i,r),Fc(e<120?e+240:e-120,i,r),this.opacity)},clamp(){return new rn(dy(this.h),Ca(this.s),Ca(this.l),Fl(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const e=Fl(this.opacity);return`${e===1?"hsl(":"hsla("}${dy(this.h)}, ${Ca(this.s)*100}%, ${Ca(this.l)*100}%${e===1?")":`, ${e})`}`}}));function dy(e){return e=(e||0)%360,e<0?e+360:e}function Ca(e){return Math.max(0,Math.min(1,e||0))}function Fc(e,t,n){return(e<60?t+(n-t)*e/60:e<180?n:e<240?t+(n-t)*(240-e)/60:t)*255}const db=e=>()=>e;function s8(e,t){return function(n){return e+n*t}}function o8(e,t,n){return e=Math.pow(e,n),t=Math.pow(t,n)-e,n=1/n,function(r){return Math.pow(e+r*t,n)}}function a8(e){return(e=+e)==1?hb:function(t,n){return n-t?o8(t,n,e):db(isNaN(t)?n:t)}}function hb(e,t){var n=t-e;return n?s8(e,n):db(isNaN(e)?t:e)}const hy=function e(t){var n=a8(t);function r(i,s){var o=n((i=yd(i)).r,(s=yd(s)).r),a=n(i.g,s.g),l=n(i.b,s.b),u=hb(i.opacity,s.opacity);return function(c){return i.r=o(c),i.g=a(c),i.b=l(c),i.opacity=u(c),i+""}}return r.gamma=e,r}(1);function tr(e,t){return e=+e,t=+t,function(n){return e*(1-n)+t*n}}var xd=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,Oc=new RegExp(xd.source,"g");function l8(e){return function(){return e}}function u8(e){return function(t){return e(t)+""}}function c8(e,t){var n=xd.lastIndex=Oc.lastIndex=0,r,i,s,o=-1,a=[],l=[];for(e=e+"",t=t+"";(r=xd.exec(e))&&(i=Oc.exec(t));)(s=i.index)>n&&(s=t.slice(n,s),a[o]?a[o]+=s:a[++o]=s),(r=r[0])===(i=i[0])?a[o]?a[o]+=i:a[++o]=i:(a[++o]=null,l.push({i:o,x:tr(r,i)})),n=Oc.lastIndex;return n180?c+=360:c-u>180&&(u+=360),d.push({i:f.push(i(f)+"rotate(",null,r)-2,x:tr(u,c)})):c&&f.push(i(f)+"rotate("+c+r)}function a(u,c,f,d){u!==c?d.push({i:f.push(i(f)+"skewX(",null,r)-2,x:tr(u,c)}):c&&f.push(i(f)+"skewX("+c+r)}function l(u,c,f,d,h,g){if(u!==f||c!==d){var y=h.push(i(h)+"scale(",null,",",null,")");g.push({i:y-4,x:tr(u,f)},{i:y-2,x:tr(c,d)})}else(f!==1||d!==1)&&h.push(i(h)+"scale("+f+","+d+")")}return function(u,c){var f=[],d=[];return u=e(u),c=e(c),s(u.translateX,u.translateY,c.translateX,c.translateY,f,d),o(u.rotate,c.rotate,f,d),a(u.skewX,c.skewX,f,d),l(u.scaleX,u.scaleY,c.scaleX,c.scaleY,f,d),u=c=null,function(h){for(var g=-1,y=d.length,w;++g=0&&e._call.call(void 0,t),e=e._next;--os}function gy(){ri=(Vl=jo.now())+Cu,os=zs=0;try{w8()}finally{os=0,b8(),ri=0}}function k8(){var e=jo.now(),t=e-Vl;t>gb&&(Cu-=t,Vl=e)}function b8(){for(var e,t=Ol,n,r=1/0;t;)t._call?(r>t._time&&(r=t._time),e=t,t=t._next):(n=t._next,t._next=null,t=e?e._next=n:Ol=n);Fs=e,wd(r)}function wd(e){if(!os){zs&&(zs=clearTimeout(zs));var t=e-ri;t>24?(e<1/0&&(zs=setTimeout(gy,e-jo.now()-Cu)),Ns&&(Ns=clearInterval(Ns))):(Ns||(Vl=jo.now(),Ns=setInterval(k8,gb)),os=1,yb(gy))}}function yy(e,t,n){var r=new $l;return t=t==null?0:+t,r.restart(i=>{r.stop(),e(i+t)},t,n),r}var S8=Su("start","end","cancel","interrupt"),_8=[],vb=0,xy=1,kd=2,Xa=3,vy=4,bd=5,Qa=6;function Eu(e,t,n,r,i,s){var o=e.__transition;if(!o)e.__transition={};else if(n in o)return;C8(e,n,{name:t,index:r,group:i,on:S8,tween:_8,time:s.time,delay:s.delay,duration:s.duration,ease:s.ease,timer:null,state:vb})}function yp(e,t){var n=fn(e,t);if(n.state>vb)throw new Error("too late; already scheduled");return n}function _n(e,t){var n=fn(e,t);if(n.state>Xa)throw new Error("too late; already running");return n}function fn(e,t){var n=e.__transition;if(!n||!(n=n[t]))throw new Error("transition not found");return n}function C8(e,t,n){var r=e.__transition,i;r[t]=n,n.timer=xb(s,0,n.time);function s(u){n.state=xy,n.timer.restart(o,n.delay,n.time),n.delay<=u&&o(u-n.delay)}function o(u){var c,f,d,h;if(n.state!==xy)return l();for(c in r)if(h=r[c],h.name===n.name){if(h.state===Xa)return yy(o);h.state===vy?(h.state=Qa,h.timer.stop(),h.on.call("interrupt",e,e.__data__,h.index,h.group),delete r[c]):+ckd&&r.state=0&&(t=t.slice(0,n)),!t||t==="start"})}function tz(e,t,n){var r,i,s=ez(t)?yp:_n;return function(){var o=s(this,e),a=o.on;a!==r&&(i=(r=a).copy()).on(t,n),o.on=i}}function nz(e,t){var n=this._id;return arguments.length<2?fn(this.node(),n).on.on(e):this.each(tz(n,e,t))}function rz(e){return function(){var t=this.parentNode;for(var n in this.__transition)if(+n!==e)return;t&&t.removeChild(this)}}function iz(){return this.on("end.remove",rz(this._id))}function sz(e){var t=this._name,n=this._id;typeof e!="function"&&(e=hp(e));for(var r=this._groups,i=r.length,s=new Array(i),o=0;o()=>e;function Pz(e,{sourceEvent:t,target:n,transform:r,dispatch:i}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},transform:{value:r,enumerable:!0,configurable:!0},_:{value:i}})}function Mn(e,t,n){this.k=e,this.x=t,this.y=n}Mn.prototype={constructor:Mn,scale:function(e){return e===1?this:new Mn(this.k*e,this.x,this.y)},translate:function(e,t){return e===0&t===0?this:new Mn(this.k,this.x+this.k*e,this.y+this.k*t)},apply:function(e){return[e[0]*this.k+this.x,e[1]*this.k+this.y]},applyX:function(e){return e*this.k+this.x},applyY:function(e){return e*this.k+this.y},invert:function(e){return[(e[0]-this.x)/this.k,(e[1]-this.y)/this.k]},invertX:function(e){return(e-this.x)/this.k},invertY:function(e){return(e-this.y)/this.k},rescaleX:function(e){return e.copy().domain(e.range().map(this.invertX,this).map(e.invert,e))},rescaleY:function(e){return e.copy().domain(e.range().map(this.invertY,this).map(e.invert,e))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var yr=new Mn(1,0,0);Mn.prototype;function Vc(e){e.stopImmediatePropagation()}function Ts(e){e.preventDefault(),e.stopImmediatePropagation()}function jz(e){return(!e.ctrlKey||e.type==="wheel")&&!e.button}function Mz(){var e=this;return e instanceof SVGElement?(e=e.ownerSVGElement||e,e.hasAttribute("viewBox")?(e=e.viewBox.baseVal,[[e.x,e.y],[e.x+e.width,e.y+e.height]]):[[0,0],[e.width.baseVal.value,e.height.baseVal.value]]):[[0,0],[e.clientWidth,e.clientHeight]]}function wy(){return this.__zoom||yr}function Dz(e){return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*(e.ctrlKey?10:1)}function Iz(){return navigator.maxTouchPoints||"ontouchstart"in this}function Lz(e,t,n){var r=e.invertX(t[0][0])-n[0][0],i=e.invertX(t[1][0])-n[1][0],s=e.invertY(t[0][1])-n[0][1],o=e.invertY(t[1][1])-n[1][1];return e.translate(i>r?(r+i)/2:Math.min(0,r)||Math.max(0,i),o>s?(s+o)/2:Math.min(0,s)||Math.max(0,o))}function Rz(){var e=jz,t=Mz,n=Lz,r=Dz,i=Iz,s=[0,1/0],o=[[-1/0,-1/0],[1/0,1/0]],a=250,l=x8,u=Su("start","zoom","end"),c,f,d,h=500,g=150,y=0,w=10;function m(_){_.property("__zoom",wy).on("wheel.zoom",P,{passive:!1}).on("mousedown.zoom",D).on("dblclick.zoom",C).filter(i).on("touchstart.zoom",L).on("touchmove.zoom",M).on("touchend.zoom touchcancel.zoom",O).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}m.transform=function(_,R,I,V){var z=_.selection?_.selection():_;z.property("__zoom",wy),_!==z?N(_,R,I,V):z.interrupt().each(function(){S(this,arguments).event(V).start().zoom(null,typeof R=="function"?R.apply(this,arguments):R).end()})},m.scaleBy=function(_,R,I,V){m.scaleTo(_,function(){var z=this.__zoom.k,j=typeof R=="function"?R.apply(this,arguments):R;return z*j},I,V)},m.scaleTo=function(_,R,I,V){m.transform(_,function(){var z=t.apply(this,arguments),j=this.__zoom,b=I==null?k(z):typeof I=="function"?I.apply(this,arguments):I,F=j.invert(b),H=typeof R=="function"?R.apply(this,arguments):R;return n(v(x(j,H),b,F),z,o)},I,V)},m.translateBy=function(_,R,I,V){m.transform(_,function(){return n(this.__zoom.translate(typeof R=="function"?R.apply(this,arguments):R,typeof I=="function"?I.apply(this,arguments):I),t.apply(this,arguments),o)},null,V)},m.translateTo=function(_,R,I,V,z){m.transform(_,function(){var j=t.apply(this,arguments),b=this.__zoom,F=V==null?k(j):typeof V=="function"?V.apply(this,arguments):V;return n(yr.translate(F[0],F[1]).scale(b.k).translate(typeof R=="function"?-R.apply(this,arguments):-R,typeof I=="function"?-I.apply(this,arguments):-I),j,o)},V,z)};function x(_,R){return R=Math.max(s[0],Math.min(s[1],R)),R===_.k?_:new Mn(R,_.x,_.y)}function v(_,R,I){var V=R[0]-I[0]*_.k,z=R[1]-I[1]*_.k;return V===_.x&&z===_.y?_:new Mn(_.k,V,z)}function k(_){return[(+_[0][0]+ +_[1][0])/2,(+_[0][1]+ +_[1][1])/2]}function N(_,R,I,V){_.on("start.zoom",function(){S(this,arguments).event(V).start()}).on("interrupt.zoom end.zoom",function(){S(this,arguments).event(V).end()}).tween("zoom",function(){var z=this,j=arguments,b=S(z,j).event(V),F=t.apply(z,j),H=I==null?k(F):typeof I=="function"?I.apply(z,j):I,E=Math.max(F[1][0]-F[0][0],F[1][1]-F[0][1]),q=z.__zoom,X=typeof R=="function"?R.apply(z,j):R,G=l(q.invert(H).concat(E/q.k),X.invert(H).concat(E/X.k));return function(ne){if(ne===1)ne=X;else{var oe=G(ne),de=E/oe[2];ne=new Mn(de,H[0]-oe[0]*de,H[1]-oe[1]*de)}b.zoom(null,ne)}})}function S(_,R,I){return!I&&_.__zooming||new A(_,R)}function A(_,R){this.that=_,this.args=R,this.active=0,this.sourceEvent=null,this.extent=t.apply(_,R),this.taps=0}A.prototype={event:function(_){return _&&(this.sourceEvent=_),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(_,R){return this.mouse&&_!=="mouse"&&(this.mouse[1]=R.invert(this.mouse[0])),this.touch0&&_!=="touch"&&(this.touch0[1]=R.invert(this.touch0[0])),this.touch1&&_!=="touch"&&(this.touch1[1]=R.invert(this.touch1[0])),this.that.__zoom=R,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(_){var R=nn(this.that).datum();u.call(_,this.that,new Pz(_,{sourceEvent:this.sourceEvent,target:m,transform:this.that.__zoom,dispatch:u}),R)}};function P(_,...R){if(!e.apply(this,arguments))return;var I=S(this,R).event(_),V=this.__zoom,z=Math.max(s[0],Math.min(s[1],V.k*Math.pow(2,r.apply(this,arguments)))),j=yn(_);if(I.wheel)(I.mouse[0][0]!==j[0]||I.mouse[0][1]!==j[1])&&(I.mouse[1]=V.invert(I.mouse[0]=j)),clearTimeout(I.wheel);else{if(V.k===z)return;I.mouse=[j,V.invert(j)],Za(this),I.start()}Ts(_),I.wheel=setTimeout(b,g),I.zoom("mouse",n(v(x(V,z),I.mouse[0],I.mouse[1]),I.extent,o));function b(){I.wheel=null,I.end()}}function D(_,...R){if(d||!e.apply(this,arguments))return;var I=_.currentTarget,V=S(this,R,!0).event(_),z=nn(_.view).on("mousemove.zoom",H,!0).on("mouseup.zoom",E,!0),j=yn(_,I),b=_.clientX,F=_.clientY;lb(_.view),Vc(_),V.mouse=[j,this.__zoom.invert(j)],Za(this),V.start();function H(q){if(Ts(q),!V.moved){var X=q.clientX-b,G=q.clientY-F;V.moved=X*X+G*G>y}V.event(q).zoom("mouse",n(v(V.that.__zoom,V.mouse[0]=yn(q,I),V.mouse[1]),V.extent,o))}function E(q){z.on("mousemove.zoom mouseup.zoom",null),ub(q.view,V.moved),Ts(q),V.event(q).end()}}function C(_,...R){if(e.apply(this,arguments)){var I=this.__zoom,V=yn(_.changedTouches?_.changedTouches[0]:_,this),z=I.invert(V),j=I.k*(_.shiftKey?.5:2),b=n(v(x(I,j),V,z),t.apply(this,R),o);Ts(_),a>0?nn(this).transition().duration(a).call(N,b,V,_):nn(this).call(m.transform,b,V,_)}}function L(_,...R){if(e.apply(this,arguments)){var I=_.touches,V=I.length,z=S(this,R,_.changedTouches.length===V).event(_),j,b,F,H;for(Vc(_),b=0;b"[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#001",error002:()=>"It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",error003:e=>`Node type "${e}" not found. Using fallback type "default".`,error004:()=>"The React Flow parent container needs a width and a height to render the graph.",error005:()=>"Only child nodes can use a parent extent.",error006:()=>"Can't create edge. An edge needs a source and a target.",error007:e=>`The old edge with id=${e} does not exist.`,error009:e=>`Marker type "${e}" doesn't exist.`,error008:(e,t)=>`Couldn't create edge for ${e?"target":"source"} handle id: "${e?t.targetHandle:t.sourceHandle}", edge id: ${t.id}.`,error010:()=>"Handle: No node id found. Make sure to only use a Handle inside a custom Node.",error011:e=>`Edge type "${e}" not found. Using fallback type "default".`,error012:e=>`Node with id "${e}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`},Sb=Bn.error001();function De(e,t){const n=T.useContext(Nu);if(n===null)throw new Error(Sb);return Gk(n,e,t)}const Ke=()=>{const e=T.useContext(Nu);if(e===null)throw new Error(Sb);return T.useMemo(()=>({getState:e.getState,setState:e.setState,subscribe:e.subscribe,destroy:e.destroy}),[e])},Fz=e=>e.userSelectionActive?"none":"all";function _b({position:e,children:t,className:n,style:r,...i}){const s=De(Fz),o=`${e}`.split("-");return B.createElement("div",{className:gt(["react-flow__panel",n,...o]),style:{...r,pointerEvents:s},...i},t)}function Oz({proOptions:e,position:t="bottom-right"}){return e!=null&&e.hideAttribution?null:B.createElement(_b,{position:t,className:"react-flow__attribution","data-message":"Please only hide this attribution when you are subscribed to React Flow Pro: https://reactflow.dev/pro"},B.createElement("a",{href:"https://reactflow.dev",target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution"},"React Flow"))}const Vz=({x:e,y:t,label:n,labelStyle:r={},labelShowBg:i=!0,labelBgStyle:s={},labelBgPadding:o=[2,4],labelBgBorderRadius:a=2,children:l,className:u,...c})=>{const f=T.useRef(null),[d,h]=T.useState({x:0,y:0,width:0,height:0}),g=gt(["react-flow__edge-textwrapper",u]);return T.useEffect(()=>{if(f.current){const y=f.current.getBBox();h({x:y.x,y:y.y,width:y.width,height:y.height})}},[n]),typeof n>"u"||!n?null:B.createElement("g",{transform:`translate(${e-d.width/2} ${t-d.height/2})`,className:g,visibility:d.width?"visible":"hidden",...c},i&&B.createElement("rect",{width:d.width+2*o[0],x:-o[0],y:-o[1],height:d.height+2*o[1],className:"react-flow__edge-textbg",style:s,rx:a,ry:a}),B.createElement("text",{className:"react-flow__edge-text",y:d.height/2,dy:"0.3em",ref:f,style:r},n),l)};var $z=T.memo(Vz);const vp=e=>({width:e.offsetWidth,height:e.offsetHeight}),as=(e,t=0,n=1)=>Math.min(Math.max(e,t),n),wp=(e={x:0,y:0},t)=>({x:as(e.x,t[0][0],t[1][0]),y:as(e.y,t[0][1],t[1][1])}),ky=(e,t,n)=>en?-as(Math.abs(e-n),1,50)/50:0,Cb=(e,t)=>{const n=ky(e.x,35,t.width-35)*20,r=ky(e.y,35,t.height-35)*20;return[n,r]},Eb=e=>{var t;return((t=e.getRootNode)==null?void 0:t.call(e))||(window==null?void 0:window.document)},Bz=(e,t)=>({x:Math.min(e.x,t.x),y:Math.min(e.y,t.y),x2:Math.max(e.x2,t.x2),y2:Math.max(e.y2,t.y2)}),kp=({x:e,y:t,width:n,height:r})=>({x:e,y:t,x2:e+n,y2:t+r}),Hz=({x:e,y:t,x2:n,y2:r})=>({x:e,y:t,width:n-e,height:r-t}),by=e=>({...e.positionAbsolute||{x:0,y:0},width:e.width||0,height:e.height||0}),Sd=(e,t)=>{const n=Math.max(0,Math.min(e.x+e.width,t.x+t.width)-Math.max(e.x,t.x)),r=Math.max(0,Math.min(e.y+e.height,t.y+t.height)-Math.max(e.y,t.y));return Math.ceil(n*r)},Uz=e=>qt(e.width)&&qt(e.height)&&qt(e.x)&&qt(e.y),qt=e=>!isNaN(e)&&isFinite(e),Re=Symbol.for("internals"),Nb=["Enter"," ","Escape"],Wz=(e,t)=>{},Yz=e=>"nativeEvent"in e;function _d(e){var i,s;const t=Yz(e)?e.nativeEvent:e,n=((s=(i=t.composedPath)==null?void 0:i.call(t))==null?void 0:s[0])||e.target;return["INPUT","SELECT","TEXTAREA"].includes(n==null?void 0:n.nodeName)||(n==null?void 0:n.hasAttribute("contenteditable"))||!!(n!=null&&n.closest(".nokey"))}const Tb=e=>"clientX"in e,xr=(e,t)=>{var s,o;const n=Tb(e),r=n?e.clientX:(s=e.touches)==null?void 0:s[0].clientX,i=n?e.clientY:(o=e.touches)==null?void 0:o[0].clientY;return{x:r-((t==null?void 0:t.left)??0),y:i-((t==null?void 0:t.top)??0)}},Bl=()=>{var e;return typeof navigator<"u"&&((e=navigator==null?void 0:navigator.userAgent)==null?void 0:e.indexOf("Mac"))>=0},Qo=({id:e,path:t,labelX:n,labelY:r,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u,style:c,markerEnd:f,markerStart:d,interactionWidth:h=20})=>B.createElement(B.Fragment,null,B.createElement("path",{id:e,style:c,d:t,fill:"none",className:"react-flow__edge-path",markerEnd:f,markerStart:d}),h&&B.createElement("path",{d:t,fill:"none",strokeOpacity:0,strokeWidth:h,className:"react-flow__edge-interaction"}),i&&qt(n)&&qt(r)?B.createElement($z,{x:n,y:r,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u}):null);Qo.displayName="BaseEdge";function As(e,t,n){return n===void 0?n:r=>{const i=t().edges.find(s=>s.id===e);i&&n(r,{...i})}}function Ab({sourceX:e,sourceY:t,targetX:n,targetY:r}){const i=Math.abs(n-e)/2,s=n{const[w,m,x]=jb({sourceX:e,sourceY:t,sourcePosition:i,targetX:n,targetY:r,targetPosition:s});return B.createElement(Qo,{path:w,labelX:m,labelY:x,label:o,labelStyle:a,labelShowBg:l,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:f,style:d,markerEnd:h,markerStart:g,interactionWidth:y})});bp.displayName="SimpleBezierEdge";const _y={[Q.Left]:{x:-1,y:0},[Q.Right]:{x:1,y:0},[Q.Top]:{x:0,y:-1},[Q.Bottom]:{x:0,y:1}},qz=({source:e,sourcePosition:t=Q.Bottom,target:n})=>t===Q.Left||t===Q.Right?e.xMath.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2));function Kz({source:e,sourcePosition:t=Q.Bottom,target:n,targetPosition:r=Q.Top,center:i,offset:s}){const o=_y[t],a=_y[r],l={x:e.x+o.x*s,y:e.y+o.y*s},u={x:n.x+a.x*s,y:n.y+a.y*s},c=qz({source:l,sourcePosition:t,target:u}),f=c.x!==0?"x":"y",d=c[f];let h=[],g,y;const w={x:0,y:0},m={x:0,y:0},[x,v,k,N]=Ab({sourceX:e.x,sourceY:e.y,targetX:n.x,targetY:n.y});if(o[f]*a[f]===-1){g=i.x??x,y=i.y??v;const A=[{x:g,y:l.y},{x:g,y:u.y}],P=[{x:l.x,y},{x:u.x,y}];o[f]===d?h=f==="x"?A:P:h=f==="x"?P:A}else{const A=[{x:l.x,y:u.y}],P=[{x:u.x,y:l.y}];if(f==="x"?h=o.x===d?P:A:h=o.y===d?A:P,t===r){const O=Math.abs(e[f]-n[f]);if(O<=s){const _=Math.min(s-1,s-O);o[f]===d?w[f]=(l[f]>e[f]?-1:1)*_:m[f]=(u[f]>n[f]?-1:1)*_}}if(t!==r){const O=f==="x"?"y":"x",_=o[f]===a[O],R=l[O]>u[O],I=l[O]=M?(g=(D.x+C.x)/2,y=h[0].y):(g=h[0].x,y=(D.y+C.y)/2)}return[[e,{x:l.x+w.x,y:l.y+w.y},...h,{x:u.x+m.x,y:u.y+m.y},n],g,y,k,N]}function Gz(e,t,n,r){const i=Math.min(Cy(e,t)/2,Cy(t,n)/2,r),{x:s,y:o}=t;if(e.x===s&&s===n.x||e.y===o&&o===n.y)return`L${s} ${o}`;if(e.y===o){const u=e.x{let v="";return x>0&&x{const[m,x,v]=Cd({sourceX:e,sourceY:t,sourcePosition:f,targetX:n,targetY:r,targetPosition:d,borderRadius:y==null?void 0:y.borderRadius,offset:y==null?void 0:y.offset});return B.createElement(Qo,{path:m,labelX:x,labelY:v,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u,style:c,markerEnd:h,markerStart:g,interactionWidth:w})});Tu.displayName="SmoothStepEdge";const Sp=T.memo(e=>{var t;return B.createElement(Tu,{...e,pathOptions:T.useMemo(()=>{var n;return{borderRadius:0,offset:(n=e.pathOptions)==null?void 0:n.offset}},[(t=e.pathOptions)==null?void 0:t.offset])})});Sp.displayName="StepEdge";function Xz({sourceX:e,sourceY:t,targetX:n,targetY:r}){const[i,s,o,a]=Ab({sourceX:e,sourceY:t,targetX:n,targetY:r});return[`M ${e},${t}L ${n},${r}`,i,s,o,a]}const _p=T.memo(({sourceX:e,sourceY:t,targetX:n,targetY:r,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u,style:c,markerEnd:f,markerStart:d,interactionWidth:h})=>{const[g,y,w]=Xz({sourceX:e,sourceY:t,targetX:n,targetY:r});return B.createElement(Qo,{path:g,labelX:y,labelY:w,label:i,labelStyle:s,labelShowBg:o,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:u,style:c,markerEnd:f,markerStart:d,interactionWidth:h})});_p.displayName="StraightEdge";function Ta(e,t){return e>=0?.5*e:t*25*Math.sqrt(-e)}function Ey({pos:e,x1:t,y1:n,x2:r,y2:i,c:s}){switch(e){case Q.Left:return[t-Ta(t-r,s),n];case Q.Right:return[t+Ta(r-t,s),n];case Q.Top:return[t,n-Ta(n-i,s)];case Q.Bottom:return[t,n+Ta(i-n,s)]}}function Mb({sourceX:e,sourceY:t,sourcePosition:n=Q.Bottom,targetX:r,targetY:i,targetPosition:s=Q.Top,curvature:o=.25}){const[a,l]=Ey({pos:n,x1:e,y1:t,x2:r,y2:i,c:o}),[u,c]=Ey({pos:s,x1:r,y1:i,x2:e,y2:t,c:o}),[f,d,h,g]=Pb({sourceX:e,sourceY:t,targetX:r,targetY:i,sourceControlX:a,sourceControlY:l,targetControlX:u,targetControlY:c});return[`M${e},${t} C${a},${l} ${u},${c} ${r},${i}`,f,d,h,g]}const Hl=T.memo(({sourceX:e,sourceY:t,targetX:n,targetY:r,sourcePosition:i=Q.Bottom,targetPosition:s=Q.Top,label:o,labelStyle:a,labelShowBg:l,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:f,style:d,markerEnd:h,markerStart:g,pathOptions:y,interactionWidth:w})=>{const[m,x,v]=Mb({sourceX:e,sourceY:t,sourcePosition:i,targetX:n,targetY:r,targetPosition:s,curvature:y==null?void 0:y.curvature});return B.createElement(Qo,{path:m,labelX:x,labelY:v,label:o,labelStyle:a,labelShowBg:l,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:f,style:d,markerEnd:h,markerStart:g,interactionWidth:w})});Hl.displayName="BezierEdge";const Cp=T.createContext(null),Qz=Cp.Provider;Cp.Consumer;const Zz=()=>T.useContext(Cp),Jz=e=>"id"in e&&"source"in e&&"target"in e,e6=({source:e,sourceHandle:t,target:n,targetHandle:r})=>`reactflow__edge-${e}${t||""}-${n}${r||""}`,Ed=(e,t)=>typeof e>"u"?"":typeof e=="string"?e:`${t?`${t}__`:""}${Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("&")}`,t6=(e,t)=>t.some(n=>n.source===e.source&&n.target===e.target&&(n.sourceHandle===e.sourceHandle||!n.sourceHandle&&!e.sourceHandle)&&(n.targetHandle===e.targetHandle||!n.targetHandle&&!e.targetHandle)),n6=(e,t)=>{if(!e.source||!e.target)return t;let n;return Jz(e)?n={...e}:n={...e,id:e6(e)},t6(n,t)?t:t.concat(n)},Nd=({x:e,y:t},[n,r,i],s,[o,a])=>{const l={x:(e-n)/i,y:(t-r)/i};return s?{x:o*Math.round(l.x/o),y:a*Math.round(l.y/a)}:l},Db=({x:e,y:t},[n,r,i])=>({x:e*i+n,y:t*i+r}),Ui=(e,t=[0,0])=>{if(!e)return{x:0,y:0,positionAbsolute:{x:0,y:0}};const n=(e.width??0)*t[0],r=(e.height??0)*t[1],i={x:e.position.x-n,y:e.position.y-r};return{...i,positionAbsolute:e.positionAbsolute?{x:e.positionAbsolute.x-n,y:e.positionAbsolute.y-r}:i}},Ep=(e,t=[0,0])=>{if(e.length===0)return{x:0,y:0,width:0,height:0};const n=e.reduce((r,i)=>{const{x:s,y:o}=Ui(i,t).positionAbsolute;return Bz(r,kp({x:s,y:o,width:i.width||0,height:i.height||0}))},{x:1/0,y:1/0,x2:-1/0,y2:-1/0});return Hz(n)},Ib=(e,t,[n,r,i]=[0,0,1],s=!1,o=!1,a=[0,0])=>{const l={x:(t.x-n)/i,y:(t.y-r)/i,width:t.width/i,height:t.height/i},u=[];return e.forEach(c=>{const{width:f,height:d,selectable:h=!0,hidden:g=!1}=c;if(o&&!h||g)return!1;const{positionAbsolute:y}=Ui(c,a),w={x:y.x,y:y.y,width:f||0,height:d||0},m=Sd(l,w),x=typeof f>"u"||typeof d>"u"||f===null||d===null,v=s&&m>0,k=(f||0)*(d||0);(x||v||m>=k||c.dragging)&&u.push(c)}),u},Lb=(e,t)=>{const n=e.map(r=>r.id);return t.filter(r=>n.includes(r.source)||n.includes(r.target))},Rb=(e,t,n,r,i,s=.1)=>{const o=t/(e.width*(1+s)),a=n/(e.height*(1+s)),l=Math.min(o,a),u=as(l,r,i),c=e.x+e.width/2,f=e.y+e.height/2,d=t/2-c*u,h=n/2-f*u;return{x:d,y:h,zoom:u}},Fr=(e,t=0)=>e.transition().duration(t);function Ny(e,t,n,r){return(t[n]||[]).reduce((i,s)=>{var o,a;return`${e.id}-${s.id}-${n}`!==r&&i.push({id:s.id||null,type:n,nodeId:e.id,x:(((o=e.positionAbsolute)==null?void 0:o.x)??0)+s.x+s.width/2,y:(((a=e.positionAbsolute)==null?void 0:a.y)??0)+s.y+s.height/2}),i},[])}function r6(e,t,n,r,i,s){const{x:o,y:a}=xr(e),u=t.elementsFromPoint(o,a).find(g=>g.classList.contains("react-flow__handle"));if(u){const g=u.getAttribute("data-nodeid");if(g){const y=Np(void 0,u),w=u.getAttribute("data-handleid"),m=s({nodeId:g,id:w,type:y});if(m){const x=i.find(v=>v.nodeId===g&&v.type===y&&v.id===w);return{handle:{id:w,type:y,nodeId:g,x:(x==null?void 0:x.x)||n.x,y:(x==null?void 0:x.y)||n.y},validHandleResult:m}}}}let c=[],f=1/0;if(i.forEach(g=>{const y=Math.sqrt((g.x-n.x)**2+(g.y-n.y)**2);if(y<=r){const w=s(g);y<=f&&(yg.isValid),h=c.some(({handle:g})=>g.type==="target");return c.find(({handle:g,validHandleResult:y})=>h?g.type==="target":d?y.isValid:!0)||c[0]}const i6={source:null,target:null,sourceHandle:null,targetHandle:null},zb=()=>({handleDomNode:null,isValid:!1,connection:i6,endHandle:null});function Fb(e,t,n,r,i,s,o){const a=i==="target",l=o.querySelector(`.react-flow__handle[data-id="${e==null?void 0:e.nodeId}-${e==null?void 0:e.id}-${e==null?void 0:e.type}"]`),u={...zb(),handleDomNode:l};if(l){const c=Np(void 0,l),f=l.getAttribute("data-nodeid"),d=l.getAttribute("data-handleid"),h=l.classList.contains("connectable"),g=l.classList.contains("connectableend"),y={source:a?f:n,sourceHandle:a?d:r,target:a?n:f,targetHandle:a?r:d};u.connection=y,h&&g&&(t===ii.Strict?a&&c==="source"||!a&&c==="target":f!==n||d!==r)&&(u.endHandle={nodeId:f,handleId:d,type:c},u.isValid=s(y))}return u}function s6({nodes:e,nodeId:t,handleId:n,handleType:r}){return e.reduce((i,s)=>{if(s[Re]){const{handleBounds:o}=s[Re];let a=[],l=[];o&&(a=Ny(s,o,"source",`${t}-${n}-${r}`),l=Ny(s,o,"target",`${t}-${n}-${r}`)),i.push(...a,...l)}return i},[])}function Np(e,t){return e||(t!=null&&t.classList.contains("target")?"target":t!=null&&t.classList.contains("source")?"source":null)}function $c(e){e==null||e.classList.remove("valid","connecting","react-flow__handle-valid","react-flow__handle-connecting")}function o6(e,t){let n=null;return t?n="valid":e&&!t&&(n="invalid"),n}function Ob({event:e,handleId:t,nodeId:n,onConnect:r,isTarget:i,getState:s,setState:o,isValidConnection:a,edgeUpdaterType:l,onReconnectEnd:u}){const c=Eb(e.target),{connectionMode:f,domNode:d,autoPanOnConnect:h,connectionRadius:g,onConnectStart:y,panBy:w,getNodes:m,cancelConnection:x}=s();let v=0,k;const{x:N,y:S}=xr(e),A=c==null?void 0:c.elementFromPoint(N,S),P=Np(l,A),D=d==null?void 0:d.getBoundingClientRect();if(!D||!P)return;let C,L=xr(e,D),M=!1,O=null,_=!1,R=null;const I=s6({nodes:m(),nodeId:n,handleId:t,handleType:P}),V=()=>{if(!h)return;const[b,F]=Cb(L,D);w({x:b,y:F}),v=requestAnimationFrame(V)};o({connectionPosition:L,connectionStatus:null,connectionNodeId:n,connectionHandleId:t,connectionHandleType:P,connectionStartHandle:{nodeId:n,handleId:t,type:P},connectionEndHandle:null}),y==null||y(e,{nodeId:n,handleId:t,handleType:P});function z(b){const{transform:F}=s();L=xr(b,D);const{handle:H,validHandleResult:E}=r6(b,c,Nd(L,F,!1,[1,1]),g,I,q=>Fb(q,f,n,t,i?"target":"source",a,c));if(k=H,M||(V(),M=!0),R=E.handleDomNode,O=E.connection,_=E.isValid,o({connectionPosition:k&&_?Db({x:k.x,y:k.y},F):L,connectionStatus:o6(!!k,_),connectionEndHandle:E.endHandle}),!k&&!_&&!R)return $c(C);O.source!==O.target&&R&&($c(C),C=R,R.classList.add("connecting","react-flow__handle-connecting"),R.classList.toggle("valid",_),R.classList.toggle("react-flow__handle-valid",_))}function j(b){var F,H;(k||R)&&O&&_&&(r==null||r(O)),(H=(F=s()).onConnectEnd)==null||H.call(F,b),l&&(u==null||u(b)),$c(C),x(),cancelAnimationFrame(v),M=!1,_=!1,O=null,R=null,c.removeEventListener("mousemove",z),c.removeEventListener("mouseup",j),c.removeEventListener("touchmove",z),c.removeEventListener("touchend",j)}c.addEventListener("mousemove",z),c.addEventListener("mouseup",j),c.addEventListener("touchmove",z),c.addEventListener("touchend",j)}const Ty=()=>!0,a6=e=>({connectionStartHandle:e.connectionStartHandle,connectOnClick:e.connectOnClick,noPanClassName:e.noPanClassName}),l6=(e,t,n)=>r=>{const{connectionStartHandle:i,connectionEndHandle:s,connectionClickStartHandle:o}=r;return{connecting:(i==null?void 0:i.nodeId)===e&&(i==null?void 0:i.handleId)===t&&(i==null?void 0:i.type)===n||(s==null?void 0:s.nodeId)===e&&(s==null?void 0:s.handleId)===t&&(s==null?void 0:s.type)===n,clickConnecting:(o==null?void 0:o.nodeId)===e&&(o==null?void 0:o.handleId)===t&&(o==null?void 0:o.type)===n}},Vb=T.forwardRef(({type:e="source",position:t=Q.Top,isValidConnection:n,isConnectable:r=!0,isConnectableStart:i=!0,isConnectableEnd:s=!0,id:o,onConnect:a,children:l,className:u,onMouseDown:c,onTouchStart:f,...d},h)=>{var D,C;const g=o||null,y=e==="target",w=Ke(),m=Zz(),{connectOnClick:x,noPanClassName:v}=De(a6,Oe),{connecting:k,clickConnecting:N}=De(l6(m,g,e),Oe);m||(C=(D=w.getState()).onError)==null||C.call(D,"010",Bn.error010());const S=L=>{const{defaultEdgeOptions:M,onConnect:O,hasDefaultEdges:_}=w.getState(),R={...M,...L};if(_){const{edges:I,setEdges:V}=w.getState();V(n6(R,I))}O==null||O(R),a==null||a(R)},A=L=>{if(!m)return;const M=Tb(L);i&&(M&&L.button===0||!M)&&Ob({event:L,handleId:g,nodeId:m,onConnect:S,isTarget:y,getState:w.getState,setState:w.setState,isValidConnection:n||w.getState().isValidConnection||Ty}),M?c==null||c(L):f==null||f(L)},P=L=>{const{onClickConnectStart:M,onClickConnectEnd:O,connectionClickStartHandle:_,connectionMode:R,isValidConnection:I}=w.getState();if(!m||!_&&!i)return;if(!_){M==null||M(L,{nodeId:m,handleId:g,handleType:e}),w.setState({connectionClickStartHandle:{nodeId:m,type:e,handleId:g}});return}const V=Eb(L.target),z=n||I||Ty,{connection:j,isValid:b}=Fb({nodeId:m,id:g,type:e},R,_.nodeId,_.handleId||null,_.type,z,V);b&&S(j),O==null||O(L),w.setState({connectionClickStartHandle:null})};return B.createElement("div",{"data-handleid":g,"data-nodeid":m,"data-handlepos":t,"data-id":`${m}-${g}-${e}`,className:gt(["react-flow__handle",`react-flow__handle-${t}`,"nodrag",v,u,{source:!y,target:y,connectable:r,connectablestart:i,connectableend:s,connecting:N,connectionindicator:r&&(i&&!k||s&&k)}]),onMouseDown:A,onTouchStart:A,onClick:x?P:void 0,ref:h,...d},l)});Vb.displayName="Handle";var ls=T.memo(Vb);const $b=({data:e,isConnectable:t,targetPosition:n=Q.Top,sourcePosition:r=Q.Bottom})=>B.createElement(B.Fragment,null,B.createElement(ls,{type:"target",position:n,isConnectable:t}),e==null?void 0:e.label,B.createElement(ls,{type:"source",position:r,isConnectable:t}));$b.displayName="DefaultNode";var Td=T.memo($b);const Bb=({data:e,isConnectable:t,sourcePosition:n=Q.Bottom})=>B.createElement(B.Fragment,null,e==null?void 0:e.label,B.createElement(ls,{type:"source",position:n,isConnectable:t}));Bb.displayName="InputNode";var Hb=T.memo(Bb);const Ub=({data:e,isConnectable:t,targetPosition:n=Q.Top})=>B.createElement(B.Fragment,null,B.createElement(ls,{type:"target",position:n,isConnectable:t}),e==null?void 0:e.label);Ub.displayName="OutputNode";var Wb=T.memo(Ub);const Tp=()=>null;Tp.displayName="GroupNode";const u6=e=>({selectedNodes:e.getNodes().filter(t=>t.selected),selectedEdges:e.edges.filter(t=>t.selected).map(t=>({...t}))}),Aa=e=>e.id;function c6(e,t){return Oe(e.selectedNodes.map(Aa),t.selectedNodes.map(Aa))&&Oe(e.selectedEdges.map(Aa),t.selectedEdges.map(Aa))}const Yb=T.memo(({onSelectionChange:e})=>{const t=Ke(),{selectedNodes:n,selectedEdges:r}=De(u6,c6);return T.useEffect(()=>{const i={nodes:n,edges:r};e==null||e(i),t.getState().onSelectionChange.forEach(s=>s(i))},[n,r,e]),null});Yb.displayName="SelectionListener";const f6=e=>!!e.onSelectionChange;function d6({onSelectionChange:e}){const t=De(f6);return e||t?B.createElement(Yb,{onSelectionChange:e}):null}const h6=e=>({setNodes:e.setNodes,setEdges:e.setEdges,setDefaultNodesAndEdges:e.setDefaultNodesAndEdges,setMinZoom:e.setMinZoom,setMaxZoom:e.setMaxZoom,setTranslateExtent:e.setTranslateExtent,setNodeExtent:e.setNodeExtent,reset:e.reset});function di(e,t){T.useEffect(()=>{typeof e<"u"&&t(e)},[e])}function ae(e,t,n){T.useEffect(()=>{typeof t<"u"&&n({[e]:t})},[t])}const p6=({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,onConnect:i,onConnectStart:s,onConnectEnd:o,onClickConnectStart:a,onClickConnectEnd:l,nodesDraggable:u,nodesConnectable:c,nodesFocusable:f,edgesFocusable:d,edgesUpdatable:h,elevateNodesOnSelect:g,minZoom:y,maxZoom:w,nodeExtent:m,onNodesChange:x,onEdgesChange:v,elementsSelectable:k,connectionMode:N,snapGrid:S,snapToGrid:A,translateExtent:P,connectOnClick:D,defaultEdgeOptions:C,fitView:L,fitViewOptions:M,onNodesDelete:O,onEdgesDelete:_,onNodeDrag:R,onNodeDragStart:I,onNodeDragStop:V,onSelectionDrag:z,onSelectionDragStart:j,onSelectionDragStop:b,noPanClassName:F,nodeOrigin:H,rfId:E,autoPanOnConnect:q,autoPanOnNodeDrag:X,onError:G,connectionRadius:ne,isValidConnection:oe,nodeDragThreshold:de})=>{const{setNodes:le,setEdges:Ee,setDefaultNodesAndEdges:lt,setMinZoom:Nt,setMaxZoom:yt,setTranslateExtent:Ie,setNodeExtent:Tt,reset:ye}=De(h6,Oe),Z=Ke();return T.useEffect(()=>{const et=r==null?void 0:r.map(dn=>({...dn,...C}));return lt(n,et),()=>{ye()}},[]),ae("defaultEdgeOptions",C,Z.setState),ae("connectionMode",N,Z.setState),ae("onConnect",i,Z.setState),ae("onConnectStart",s,Z.setState),ae("onConnectEnd",o,Z.setState),ae("onClickConnectStart",a,Z.setState),ae("onClickConnectEnd",l,Z.setState),ae("nodesDraggable",u,Z.setState),ae("nodesConnectable",c,Z.setState),ae("nodesFocusable",f,Z.setState),ae("edgesFocusable",d,Z.setState),ae("edgesUpdatable",h,Z.setState),ae("elementsSelectable",k,Z.setState),ae("elevateNodesOnSelect",g,Z.setState),ae("snapToGrid",A,Z.setState),ae("snapGrid",S,Z.setState),ae("onNodesChange",x,Z.setState),ae("onEdgesChange",v,Z.setState),ae("connectOnClick",D,Z.setState),ae("fitViewOnInit",L,Z.setState),ae("fitViewOnInitOptions",M,Z.setState),ae("onNodesDelete",O,Z.setState),ae("onEdgesDelete",_,Z.setState),ae("onNodeDrag",R,Z.setState),ae("onNodeDragStart",I,Z.setState),ae("onNodeDragStop",V,Z.setState),ae("onSelectionDrag",z,Z.setState),ae("onSelectionDragStart",j,Z.setState),ae("onSelectionDragStop",b,Z.setState),ae("noPanClassName",F,Z.setState),ae("nodeOrigin",H,Z.setState),ae("rfId",E,Z.setState),ae("autoPanOnConnect",q,Z.setState),ae("autoPanOnNodeDrag",X,Z.setState),ae("onError",G,Z.setState),ae("connectionRadius",ne,Z.setState),ae("isValidConnection",oe,Z.setState),ae("nodeDragThreshold",de,Z.setState),di(e,le),di(t,Ee),di(y,Nt),di(w,yt),di(P,Ie),di(m,Tt),null},Ay={display:"none"},m6={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},qb="react-flow__node-desc",Kb="react-flow__edge-desc",g6="react-flow__aria-live",y6=e=>e.ariaLiveMessage;function x6({rfId:e}){const t=De(y6);return B.createElement("div",{id:`${g6}-${e}`,"aria-live":"assertive","aria-atomic":"true",style:m6},t)}function v6({rfId:e,disableKeyboardA11y:t}){return B.createElement(B.Fragment,null,B.createElement("div",{id:`${qb}-${e}`,style:Ay},"Press enter or space to select a node.",!t&&"You can then use the arrow keys to move the node around."," Press delete to remove it and escape to cancel."," "),B.createElement("div",{id:`${Kb}-${e}`,style:Ay},"Press enter or space to select an edge. You can then press delete to remove it or escape to cancel."),!t&&B.createElement(x6,{rfId:e}))}var Io=(e=null,t={actInsideInputWithModifier:!0})=>{const[n,r]=T.useState(!1),i=T.useRef(!1),s=T.useRef(new Set([])),[o,a]=T.useMemo(()=>{if(e!==null){const u=(Array.isArray(e)?e:[e]).filter(f=>typeof f=="string").map(f=>f.split("+")),c=u.reduce((f,d)=>f.concat(...d),[]);return[u,c]}return[[],[]]},[e]);return T.useEffect(()=>{const l=typeof document<"u"?document:null,u=(t==null?void 0:t.target)||l;if(e!==null){const c=h=>{if(i.current=h.ctrlKey||h.metaKey||h.shiftKey,(!i.current||i.current&&!t.actInsideInputWithModifier)&&_d(h))return!1;const y=jy(h.code,a);s.current.add(h[y]),Py(o,s.current,!1)&&(h.preventDefault(),r(!0))},f=h=>{if((!i.current||i.current&&!t.actInsideInputWithModifier)&&_d(h))return!1;const y=jy(h.code,a);Py(o,s.current,!0)?(r(!1),s.current.clear()):s.current.delete(h[y]),h.key==="Meta"&&s.current.clear(),i.current=!1},d=()=>{s.current.clear(),r(!1)};return u==null||u.addEventListener("keydown",c),u==null||u.addEventListener("keyup",f),window.addEventListener("blur",d),()=>{u==null||u.removeEventListener("keydown",c),u==null||u.removeEventListener("keyup",f),window.removeEventListener("blur",d)}}},[e,r]),n};function Py(e,t,n){return e.filter(r=>n||r.length===t.size).some(r=>r.every(i=>t.has(i)))}function jy(e,t){return t.includes(e)?"code":"key"}function Gb(e,t,n,r){var a,l;const i=e.parentNode||e.parentId;if(!i)return n;const s=t.get(i),o=Ui(s,r);return Gb(s,t,{x:(n.x??0)+o.x,y:(n.y??0)+o.y,z:(((a=s[Re])==null?void 0:a.z)??0)>(n.z??0)?((l=s[Re])==null?void 0:l.z)??0:n.z??0},r)}function Xb(e,t,n){e.forEach(r=>{var s;const i=r.parentNode||r.parentId;if(i&&!e.has(i))throw new Error(`Parent node ${i} not found`);if(i||n!=null&&n[r.id]){const{x:o,y:a,z:l}=Gb(r,e,{...r.position,z:((s=r[Re])==null?void 0:s.z)??0},t);r.positionAbsolute={x:o,y:a},r[Re].z=l,n!=null&&n[r.id]&&(r[Re].isParent=!0)}})}function Bc(e,t,n,r){const i=new Map,s={},o=r?1e3:0;return e.forEach(a=>{var h;const l=(qt(a.zIndex)?a.zIndex:0)+(a.selected?o:0),u=t.get(a.id),c={...a,positionAbsolute:{x:a.position.x,y:a.position.y}},f=a.parentNode||a.parentId;f&&(s[f]=!0);const d=(u==null?void 0:u.type)&&(u==null?void 0:u.type)!==a.type;Object.defineProperty(c,Re,{enumerable:!1,value:{handleBounds:d||(h=u==null?void 0:u[Re])==null?void 0:h.handleBounds,z:l}}),i.set(a.id,c)}),Xb(i,n,s),i}function Qb(e,t={}){const{getNodes:n,width:r,height:i,minZoom:s,maxZoom:o,d3Zoom:a,d3Selection:l,fitViewOnInitDone:u,fitViewOnInit:c,nodeOrigin:f}=e(),d=t.initial&&!u&&c;if(a&&l&&(d||!t.initial)){const g=n().filter(w=>{var x;const m=t.includeHiddenNodes?w.width&&w.height:!w.hidden;return(x=t.nodes)!=null&&x.length?m&&t.nodes.some(v=>v.id===w.id):m}),y=g.every(w=>w.width&&w.height);if(g.length>0&&y){const w=Ep(g,f),{x:m,y:x,zoom:v}=Rb(w,r,i,t.minZoom??s,t.maxZoom??o,t.padding??.1),k=yr.translate(m,x).scale(v);return typeof t.duration=="number"&&t.duration>0?a.transform(Fr(l,t.duration),k):a.transform(l,k),!0}}return!1}function w6(e,t){return e.forEach(n=>{const r=t.get(n.id);r&&t.set(r.id,{...r,[Re]:r[Re],selected:n.selected})}),new Map(t)}function k6(e,t){return t.map(n=>{const r=e.find(i=>i.id===n.id);return r&&(n.selected=r.selected),n})}function Pa({changedNodes:e,changedEdges:t,get:n,set:r}){const{nodeInternals:i,edges:s,onNodesChange:o,onEdgesChange:a,hasDefaultNodes:l,hasDefaultEdges:u}=n();e!=null&&e.length&&(l&&r({nodeInternals:w6(e,i)}),o==null||o(e)),t!=null&&t.length&&(u&&r({edges:k6(t,s)}),a==null||a(t))}const hi=()=>{},b6={zoomIn:hi,zoomOut:hi,zoomTo:hi,getZoom:()=>1,setViewport:hi,getViewport:()=>({x:0,y:0,zoom:1}),fitView:()=>!1,setCenter:hi,fitBounds:hi,project:e=>e,screenToFlowPosition:e=>e,flowToScreenPosition:e=>e,viewportInitialized:!1},S6=e=>({d3Zoom:e.d3Zoom,d3Selection:e.d3Selection}),_6=()=>{const e=Ke(),{d3Zoom:t,d3Selection:n}=De(S6,Oe);return T.useMemo(()=>n&&t?{zoomIn:i=>t.scaleBy(Fr(n,i==null?void 0:i.duration),1.2),zoomOut:i=>t.scaleBy(Fr(n,i==null?void 0:i.duration),1/1.2),zoomTo:(i,s)=>t.scaleTo(Fr(n,s==null?void 0:s.duration),i),getZoom:()=>e.getState().transform[2],setViewport:(i,s)=>{const[o,a,l]=e.getState().transform,u=yr.translate(i.x??o,i.y??a).scale(i.zoom??l);t.transform(Fr(n,s==null?void 0:s.duration),u)},getViewport:()=>{const[i,s,o]=e.getState().transform;return{x:i,y:s,zoom:o}},fitView:i=>Qb(e.getState,i),setCenter:(i,s,o)=>{const{width:a,height:l,maxZoom:u}=e.getState(),c=typeof(o==null?void 0:o.zoom)<"u"?o.zoom:u,f=a/2-i*c,d=l/2-s*c,h=yr.translate(f,d).scale(c);t.transform(Fr(n,o==null?void 0:o.duration),h)},fitBounds:(i,s)=>{const{width:o,height:a,minZoom:l,maxZoom:u}=e.getState(),{x:c,y:f,zoom:d}=Rb(i,o,a,l,u,(s==null?void 0:s.padding)??.1),h=yr.translate(c,f).scale(d);t.transform(Fr(n,s==null?void 0:s.duration),h)},project:i=>{const{transform:s,snapToGrid:o,snapGrid:a}=e.getState();return console.warn("[DEPRECATED] `project` is deprecated. Instead use `screenToFlowPosition`. There is no need to subtract the react flow bounds anymore! https://reactflow.dev/api-reference/types/react-flow-instance#screen-to-flow-position"),Nd(i,s,o,a)},screenToFlowPosition:i=>{const{transform:s,snapToGrid:o,snapGrid:a,domNode:l}=e.getState();if(!l)return i;const{x:u,y:c}=l.getBoundingClientRect(),f={x:i.x-u,y:i.y-c};return Nd(f,s,o,a)},flowToScreenPosition:i=>{const{transform:s,domNode:o}=e.getState();if(!o)return i;const{x:a,y:l}=o.getBoundingClientRect(),u=Db(i,s);return{x:u.x+a,y:u.y+l}},viewportInitialized:!0}:b6,[t,n])};function Au(){const e=_6(),t=Ke(),n=T.useCallback(()=>t.getState().getNodes().map(y=>({...y})),[]),r=T.useCallback(y=>t.getState().nodeInternals.get(y),[]),i=T.useCallback(()=>{const{edges:y=[]}=t.getState();return y.map(w=>({...w}))},[]),s=T.useCallback(y=>{const{edges:w=[]}=t.getState();return w.find(m=>m.id===y)},[]),o=T.useCallback(y=>{const{getNodes:w,setNodes:m,hasDefaultNodes:x,onNodesChange:v}=t.getState(),k=w(),N=typeof y=="function"?y(k):y;if(x)m(N);else if(v){const S=N.length===0?k.map(A=>({type:"remove",id:A.id})):N.map(A=>({item:A,type:"reset"}));v(S)}},[]),a=T.useCallback(y=>{const{edges:w=[],setEdges:m,hasDefaultEdges:x,onEdgesChange:v}=t.getState(),k=typeof y=="function"?y(w):y;if(x)m(k);else if(v){const N=k.length===0?w.map(S=>({type:"remove",id:S.id})):k.map(S=>({item:S,type:"reset"}));v(N)}},[]),l=T.useCallback(y=>{const w=Array.isArray(y)?y:[y],{getNodes:m,setNodes:x,hasDefaultNodes:v,onNodesChange:k}=t.getState();if(v){const S=[...m(),...w];x(S)}else if(k){const N=w.map(S=>({item:S,type:"add"}));k(N)}},[]),u=T.useCallback(y=>{const w=Array.isArray(y)?y:[y],{edges:m=[],setEdges:x,hasDefaultEdges:v,onEdgesChange:k}=t.getState();if(v)x([...m,...w]);else if(k){const N=w.map(S=>({item:S,type:"add"}));k(N)}},[]),c=T.useCallback(()=>{const{getNodes:y,edges:w=[],transform:m}=t.getState(),[x,v,k]=m;return{nodes:y().map(N=>({...N})),edges:w.map(N=>({...N})),viewport:{x,y:v,zoom:k}}},[]),f=T.useCallback(({nodes:y,edges:w})=>{const{nodeInternals:m,getNodes:x,edges:v,hasDefaultNodes:k,hasDefaultEdges:N,onNodesDelete:S,onEdgesDelete:A,onNodesChange:P,onEdgesChange:D}=t.getState(),C=(y||[]).map(R=>R.id),L=(w||[]).map(R=>R.id),M=x().reduce((R,I)=>{const V=I.parentNode||I.parentId,z=!C.includes(I.id)&&V&&R.find(b=>b.id===V);return(typeof I.deletable=="boolean"?I.deletable:!0)&&(C.includes(I.id)||z)&&R.push(I),R},[]),O=v.filter(R=>typeof R.deletable=="boolean"?R.deletable:!0),_=O.filter(R=>L.includes(R.id));if(M||_){const R=Lb(M,O),I=[..._,...R],V=I.reduce((z,j)=>(z.includes(j.id)||z.push(j.id),z),[]);if((N||k)&&(N&&t.setState({edges:v.filter(z=>!V.includes(z.id))}),k&&(M.forEach(z=>{m.delete(z.id)}),t.setState({nodeInternals:new Map(m)}))),V.length>0&&(A==null||A(I),D&&D(V.map(z=>({id:z,type:"remove"})))),M.length>0&&(S==null||S(M),P)){const z=M.map(j=>({id:j.id,type:"remove"}));P(z)}}},[]),d=T.useCallback(y=>{const w=Uz(y),m=w?null:t.getState().nodeInternals.get(y.id);return!w&&!m?[null,null,w]:[w?y:by(m),m,w]},[]),h=T.useCallback((y,w=!0,m)=>{const[x,v,k]=d(y);return x?(m||t.getState().getNodes()).filter(N=>{if(!k&&(N.id===v.id||!N.positionAbsolute))return!1;const S=by(N),A=Sd(S,x);return w&&A>0||A>=x.width*x.height}):[]},[]),g=T.useCallback((y,w,m=!0)=>{const[x]=d(y);if(!x)return!1;const v=Sd(x,w);return m&&v>0||v>=x.width*x.height},[]);return T.useMemo(()=>({...e,getNodes:n,getNode:r,getEdges:i,getEdge:s,setNodes:o,setEdges:a,addNodes:l,addEdges:u,toObject:c,deleteElements:f,getIntersectingNodes:h,isNodeIntersecting:g}),[e,n,r,i,s,o,a,l,u,c,f,h,g])}const C6={actInsideInputWithModifier:!1};var E6=({deleteKeyCode:e,multiSelectionKeyCode:t})=>{const n=Ke(),{deleteElements:r}=Au(),i=Io(e,C6),s=Io(t);T.useEffect(()=>{if(i){const{edges:o,getNodes:a}=n.getState(),l=a().filter(c=>c.selected),u=o.filter(c=>c.selected);r({nodes:l,edges:u}),n.setState({nodesSelectionActive:!1})}},[i]),T.useEffect(()=>{n.setState({multiSelectionActive:s})},[s])};function N6(e){const t=Ke();T.useEffect(()=>{let n;const r=()=>{var s,o;if(!e.current)return;const i=vp(e.current);(i.height===0||i.width===0)&&((o=(s=t.getState()).onError)==null||o.call(s,"004",Bn.error004())),t.setState({width:i.width||500,height:i.height||500})};return r(),window.addEventListener("resize",r),e.current&&(n=new ResizeObserver(()=>r()),n.observe(e.current)),()=>{window.removeEventListener("resize",r),n&&e.current&&n.unobserve(e.current)}},[])}const Ap={position:"absolute",width:"100%",height:"100%",top:0,left:0},T6=(e,t)=>e.x!==t.x||e.y!==t.y||e.zoom!==t.k,ja=e=>({x:e.x,y:e.y,zoom:e.k}),pi=(e,t)=>e.target.closest(`.${t}`),My=(e,t)=>t===2&&Array.isArray(e)&&e.includes(2),Dy=e=>{const t=e.ctrlKey&&Bl()?10:1;return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*t},A6=e=>({d3Zoom:e.d3Zoom,d3Selection:e.d3Selection,d3ZoomHandler:e.d3ZoomHandler,userSelectionActive:e.userSelectionActive}),P6=({onMove:e,onMoveStart:t,onMoveEnd:n,onPaneContextMenu:r,zoomOnScroll:i=!0,zoomOnPinch:s=!0,panOnScroll:o=!1,panOnScrollSpeed:a=.5,panOnScrollMode:l=Wr.Free,zoomOnDoubleClick:u=!0,elementsSelectable:c,panOnDrag:f=!0,defaultViewport:d,translateExtent:h,minZoom:g,maxZoom:y,zoomActivationKeyCode:w,preventScrolling:m=!0,children:x,noWheelClassName:v,noPanClassName:k})=>{const N=T.useRef(),S=Ke(),A=T.useRef(!1),P=T.useRef(!1),D=T.useRef(null),C=T.useRef({x:0,y:0,zoom:0}),{d3Zoom:L,d3Selection:M,d3ZoomHandler:O,userSelectionActive:_}=De(A6,Oe),R=Io(w),I=T.useRef(0),V=T.useRef(!1),z=T.useRef();return N6(D),T.useEffect(()=>{if(D.current){const j=D.current.getBoundingClientRect(),b=Rz().scaleExtent([g,y]).translateExtent(h),F=nn(D.current).call(b),H=yr.translate(d.x,d.y).scale(as(d.zoom,g,y)),E=[[0,0],[j.width,j.height]],q=b.constrain()(H,E,h);b.transform(F,q),b.wheelDelta(Dy),S.setState({d3Zoom:b,d3Selection:F,d3ZoomHandler:F.on("wheel.zoom"),transform:[q.x,q.y,q.k],domNode:D.current.closest(".react-flow")})}},[]),T.useEffect(()=>{M&&L&&(o&&!R&&!_?M.on("wheel.zoom",j=>{if(pi(j,v))return!1;j.preventDefault(),j.stopImmediatePropagation();const b=M.property("__zoom").k||1;if(j.ctrlKey&&s){const oe=yn(j),de=Dy(j),le=b*Math.pow(2,de);L.scaleTo(M,le,oe,j);return}const F=j.deltaMode===1?20:1;let H=l===Wr.Vertical?0:j.deltaX*F,E=l===Wr.Horizontal?0:j.deltaY*F;!Bl()&&j.shiftKey&&l!==Wr.Vertical&&(H=j.deltaY*F,E=0),L.translateBy(M,-(H/b)*a,-(E/b)*a,{internal:!0});const q=ja(M.property("__zoom")),{onViewportChangeStart:X,onViewportChange:G,onViewportChangeEnd:ne}=S.getState();clearTimeout(z.current),V.current||(V.current=!0,t==null||t(j,q),X==null||X(q)),V.current&&(e==null||e(j,q),G==null||G(q),z.current=setTimeout(()=>{n==null||n(j,q),ne==null||ne(q),V.current=!1},150))},{passive:!1}):typeof O<"u"&&M.on("wheel.zoom",function(j,b){if(!m&&j.type==="wheel"&&!j.ctrlKey||pi(j,v))return null;j.preventDefault(),O.call(this,j,b)},{passive:!1}))},[_,o,l,M,L,O,R,s,m,v,t,e,n]),T.useEffect(()=>{L&&L.on("start",j=>{var H,E;if(!j.sourceEvent||j.sourceEvent.internal)return null;I.current=(H=j.sourceEvent)==null?void 0:H.button;const{onViewportChangeStart:b}=S.getState(),F=ja(j.transform);A.current=!0,C.current=F,((E=j.sourceEvent)==null?void 0:E.type)==="mousedown"&&S.setState({paneDragging:!0}),b==null||b(F),t==null||t(j.sourceEvent,F)})},[L,t]),T.useEffect(()=>{L&&(_&&!A.current?L.on("zoom",null):_||L.on("zoom",j=>{var F;const{onViewportChange:b}=S.getState();if(S.setState({transform:[j.transform.x,j.transform.y,j.transform.k]}),P.current=!!(r&&My(f,I.current??0)),(e||b)&&!((F=j.sourceEvent)!=null&&F.internal)){const H=ja(j.transform);b==null||b(H),e==null||e(j.sourceEvent,H)}}))},[_,L,e,f,r]),T.useEffect(()=>{L&&L.on("end",j=>{if(!j.sourceEvent||j.sourceEvent.internal)return null;const{onViewportChangeEnd:b}=S.getState();if(A.current=!1,S.setState({paneDragging:!1}),r&&My(f,I.current??0)&&!P.current&&r(j.sourceEvent),P.current=!1,(n||b)&&T6(C.current,j.transform)){const F=ja(j.transform);C.current=F,clearTimeout(N.current),N.current=setTimeout(()=>{b==null||b(F),n==null||n(j.sourceEvent,F)},o?150:0)}})},[L,o,f,n,r]),T.useEffect(()=>{L&&L.filter(j=>{const b=R||i,F=s&&j.ctrlKey;if((f===!0||Array.isArray(f)&&f.includes(1))&&j.button===1&&j.type==="mousedown"&&(pi(j,"react-flow__node")||pi(j,"react-flow__edge")))return!0;if(!f&&!b&&!o&&!u&&!s||_||!u&&j.type==="dblclick"||pi(j,v)&&j.type==="wheel"||pi(j,k)&&(j.type!=="wheel"||o&&j.type==="wheel"&&!R)||!s&&j.ctrlKey&&j.type==="wheel"||!b&&!o&&!F&&j.type==="wheel"||!f&&(j.type==="mousedown"||j.type==="touchstart")||Array.isArray(f)&&!f.includes(j.button)&&j.type==="mousedown")return!1;const H=Array.isArray(f)&&f.includes(j.button)||!j.button||j.button<=1;return(!j.ctrlKey||j.type==="wheel")&&H})},[_,L,i,s,o,u,f,c,R]),B.createElement("div",{className:"react-flow__renderer",ref:D,style:Ap},x)},j6=e=>({userSelectionActive:e.userSelectionActive,userSelectionRect:e.userSelectionRect});function M6(){const{userSelectionActive:e,userSelectionRect:t}=De(j6,Oe);return e&&t?B.createElement("div",{className:"react-flow__selection react-flow__container",style:{width:t.width,height:t.height,transform:`translate(${t.x}px, ${t.y}px)`}}):null}function Iy(e,t){const n=t.parentNode||t.parentId,r=e.find(i=>i.id===n);if(r){const i=t.position.x+t.width-r.width,s=t.position.y+t.height-r.height;if(i>0||s>0||t.position.x<0||t.position.y<0){if(r.style={...r.style},r.style.width=r.style.width??r.width,r.style.height=r.style.height??r.height,i>0&&(r.style.width+=i),s>0&&(r.style.height+=s),t.position.x<0){const o=Math.abs(t.position.x);r.position.x=r.position.x-o,r.style.width+=o,t.position.x=0}if(t.position.y<0){const o=Math.abs(t.position.y);r.position.y=r.position.y-o,r.style.height+=o,t.position.y=0}r.width=r.style.width,r.height=r.style.height}}}function Zb(e,t){if(e.some(r=>r.type==="reset"))return e.filter(r=>r.type==="reset").map(r=>r.item);const n=e.filter(r=>r.type==="add").map(r=>r.item);return t.reduce((r,i)=>{const s=e.filter(a=>a.id===i.id);if(s.length===0)return r.push(i),r;const o={...i};for(const a of s)if(a)switch(a.type){case"select":{o.selected=a.selected;break}case"position":{typeof a.position<"u"&&(o.position=a.position),typeof a.positionAbsolute<"u"&&(o.positionAbsolute=a.positionAbsolute),typeof a.dragging<"u"&&(o.dragging=a.dragging),o.expandParent&&Iy(r,o);break}case"dimensions":{typeof a.dimensions<"u"&&(o.width=a.dimensions.width,o.height=a.dimensions.height),typeof a.updateStyle<"u"&&(o.style={...o.style||{},...a.dimensions}),typeof a.resizing=="boolean"&&(o.resizing=a.resizing),o.expandParent&&Iy(r,o);break}case"remove":return r}return r.push(o),r},n)}function Jb(e,t){return Zb(e,t)}function D6(e,t){return Zb(e,t)}const nr=(e,t)=>({id:e,type:"select",selected:t});function Di(e,t){return e.reduce((n,r)=>{const i=t.includes(r.id);return!r.selected&&i?(r.selected=!0,n.push(nr(r.id,!0))):r.selected&&!i&&(r.selected=!1,n.push(nr(r.id,!1))),n},[])}const Hc=(e,t)=>n=>{n.target===t.current&&(e==null||e(n))},I6=e=>({userSelectionActive:e.userSelectionActive,elementsSelectable:e.elementsSelectable,dragging:e.paneDragging}),e2=T.memo(({isSelecting:e,selectionMode:t=Mo.Full,panOnDrag:n,onSelectionStart:r,onSelectionEnd:i,onPaneClick:s,onPaneContextMenu:o,onPaneScroll:a,onPaneMouseEnter:l,onPaneMouseMove:u,onPaneMouseLeave:c,children:f})=>{const d=T.useRef(null),h=Ke(),g=T.useRef(0),y=T.useRef(0),w=T.useRef(),{userSelectionActive:m,elementsSelectable:x,dragging:v}=De(I6,Oe),k=()=>{h.setState({userSelectionActive:!1,userSelectionRect:null}),g.current=0,y.current=0},N=O=>{s==null||s(O),h.getState().resetSelectedElements(),h.setState({nodesSelectionActive:!1})},S=O=>{if(Array.isArray(n)&&(n!=null&&n.includes(2))){O.preventDefault();return}o==null||o(O)},A=a?O=>a(O):void 0,P=O=>{const{resetSelectedElements:_,domNode:R}=h.getState();if(w.current=R==null?void 0:R.getBoundingClientRect(),!x||!e||O.button!==0||O.target!==d.current||!w.current)return;const{x:I,y:V}=xr(O,w.current);_(),h.setState({userSelectionRect:{width:0,height:0,startX:I,startY:V,x:I,y:V}}),r==null||r(O)},D=O=>{const{userSelectionRect:_,nodeInternals:R,edges:I,transform:V,onNodesChange:z,onEdgesChange:j,nodeOrigin:b,getNodes:F}=h.getState();if(!e||!w.current||!_)return;h.setState({userSelectionActive:!0,nodesSelectionActive:!1});const H=xr(O,w.current),E=_.startX??0,q=_.startY??0,X={..._,x:H.xle.id),de=ne.map(le=>le.id);if(g.current!==de.length){g.current=de.length;const le=Di(G,de);le.length&&(z==null||z(le))}if(y.current!==oe.length){y.current=oe.length;const le=Di(I,oe);le.length&&(j==null||j(le))}h.setState({userSelectionRect:X})},C=O=>{if(O.button!==0)return;const{userSelectionRect:_}=h.getState();!m&&_&&O.target===d.current&&(N==null||N(O)),h.setState({nodesSelectionActive:g.current>0}),k(),i==null||i(O)},L=O=>{m&&(h.setState({nodesSelectionActive:g.current>0}),i==null||i(O)),k()},M=x&&(e||m);return B.createElement("div",{className:gt(["react-flow__pane",{dragging:v,selection:e}]),onClick:M?void 0:Hc(N,d),onContextMenu:Hc(S,d),onWheel:Hc(A,d),onMouseEnter:M?void 0:l,onMouseDown:M?P:void 0,onMouseMove:M?D:u,onMouseUp:M?C:void 0,onMouseLeave:M?L:c,ref:d,style:Ap},f,B.createElement(M6,null))});e2.displayName="Pane";function t2(e,t){const n=e.parentNode||e.parentId;if(!n)return!1;const r=t.get(n);return r?r.selected?!0:t2(r,t):!1}function Ly(e,t,n){let r=e;do{if(r!=null&&r.matches(t))return!0;if(r===n.current)return!1;r=r.parentElement}while(r);return!1}function L6(e,t,n,r){return Array.from(e.values()).filter(i=>(i.selected||i.id===r)&&(!i.parentNode||i.parentId||!t2(i,e))&&(i.draggable||t&&typeof i.draggable>"u")).map(i=>{var s,o;return{id:i.id,position:i.position||{x:0,y:0},positionAbsolute:i.positionAbsolute||{x:0,y:0},distance:{x:n.x-(((s=i.positionAbsolute)==null?void 0:s.x)??0),y:n.y-(((o=i.positionAbsolute)==null?void 0:o.y)??0)},delta:{x:0,y:0},extent:i.extent,parentNode:i.parentNode||i.parentId,parentId:i.parentNode||i.parentId,width:i.width,height:i.height,expandParent:i.expandParent}})}function R6(e,t){return!t||t==="parent"?t:[t[0],[t[1][0]-(e.width||0),t[1][1]-(e.height||0)]]}function n2(e,t,n,r,i=[0,0],s){const o=R6(e,e.extent||r);let a=o;const l=e.parentNode||e.parentId;if(e.extent==="parent"&&!e.expandParent)if(l&&e.width&&e.height){const f=n.get(l),{x:d,y:h}=Ui(f,i).positionAbsolute;a=f&&qt(d)&&qt(h)&&qt(f.width)&&qt(f.height)?[[d+e.width*i[0],h+e.height*i[1]],[d+f.width-e.width+e.width*i[0],h+f.height-e.height+e.height*i[1]]]:a}else s==null||s("005",Bn.error005()),a=o;else if(e.extent&&l&&e.extent!=="parent"){const f=n.get(l),{x:d,y:h}=Ui(f,i).positionAbsolute;a=[[e.extent[0][0]+d,e.extent[0][1]+h],[e.extent[1][0]+d,e.extent[1][1]+h]]}let u={x:0,y:0};if(l){const f=n.get(l);u=Ui(f,i).positionAbsolute}const c=a&&a!=="parent"?wp(t,a):t;return{position:{x:c.x-u.x,y:c.y-u.y},positionAbsolute:c}}function Uc({nodeId:e,dragItems:t,nodeInternals:n}){const r=t.map(i=>({...n.get(i.id),position:i.position,positionAbsolute:i.positionAbsolute}));return[e?r.find(i=>i.id===e):r[0],r]}const Ry=(e,t,n,r)=>{const i=t.querySelectorAll(e);if(!i||!i.length)return null;const s=Array.from(i),o=t.getBoundingClientRect(),a={x:o.width*r[0],y:o.height*r[1]};return s.map(l=>{const u=l.getBoundingClientRect();return{id:l.getAttribute("data-handleid"),position:l.getAttribute("data-handlepos"),x:(u.left-o.left-a.x)/n,y:(u.top-o.top-a.y)/n,...vp(l)}})};function Ps(e,t,n){return n===void 0?n:r=>{const i=t().nodeInternals.get(e);i&&n(r,{...i})}}function Ad({id:e,store:t,unselect:n=!1,nodeRef:r}){const{addSelectedNodes:i,unselectNodesAndEdges:s,multiSelectionActive:o,nodeInternals:a,onError:l}=t.getState(),u=a.get(e);if(!u){l==null||l("012",Bn.error012(e));return}t.setState({nodesSelectionActive:!1}),u.selected?(n||u.selected&&o)&&(s({nodes:[u],edges:[]}),requestAnimationFrame(()=>{var c;return(c=r==null?void 0:r.current)==null?void 0:c.blur()})):i([e])}function z6(){const e=Ke();return T.useCallback(({sourceEvent:n})=>{const{transform:r,snapGrid:i,snapToGrid:s}=e.getState(),o=n.touches?n.touches[0].clientX:n.clientX,a=n.touches?n.touches[0].clientY:n.clientY,l={x:(o-r[0])/r[2],y:(a-r[1])/r[2]};return{xSnapped:s?i[0]*Math.round(l.x/i[0]):l.x,ySnapped:s?i[1]*Math.round(l.y/i[1]):l.y,...l}},[])}function Wc(e){return(t,n,r)=>e==null?void 0:e(t,r)}function r2({nodeRef:e,disabled:t=!1,noDragClassName:n,handleSelector:r,nodeId:i,isSelectable:s,selectNodesOnDrag:o}){const a=Ke(),[l,u]=T.useState(!1),c=T.useRef([]),f=T.useRef({x:null,y:null}),d=T.useRef(0),h=T.useRef(null),g=T.useRef({x:0,y:0}),y=T.useRef(null),w=T.useRef(!1),m=T.useRef(!1),x=T.useRef(!1),v=z6();return T.useEffect(()=>{if(e!=null&&e.current){const k=nn(e.current),N=({x:P,y:D})=>{const{nodeInternals:C,onNodeDrag:L,onSelectionDrag:M,updateNodePositions:O,nodeExtent:_,snapGrid:R,snapToGrid:I,nodeOrigin:V,onError:z}=a.getState();f.current={x:P,y:D};let j=!1,b={x:0,y:0,x2:0,y2:0};if(c.current.length>1&&_){const H=Ep(c.current,V);b=kp(H)}if(c.current=c.current.map(H=>{const E={x:P-H.distance.x,y:D-H.distance.y};I&&(E.x=R[0]*Math.round(E.x/R[0]),E.y=R[1]*Math.round(E.y/R[1]));const q=[[_[0][0],_[0][1]],[_[1][0],_[1][1]]];c.current.length>1&&_&&!H.extent&&(q[0][0]=H.positionAbsolute.x-b.x+_[0][0],q[1][0]=H.positionAbsolute.x+(H.width??0)-b.x2+_[1][0],q[0][1]=H.positionAbsolute.y-b.y+_[0][1],q[1][1]=H.positionAbsolute.y+(H.height??0)-b.y2+_[1][1]);const X=n2(H,E,C,q,V,z);return j=j||H.position.x!==X.position.x||H.position.y!==X.position.y,H.position=X.position,H.positionAbsolute=X.positionAbsolute,H}),!j)return;O(c.current,!0,!0),u(!0);const F=i?L:Wc(M);if(F&&y.current){const[H,E]=Uc({nodeId:i,dragItems:c.current,nodeInternals:C});F(y.current,H,E)}},S=()=>{if(!h.current)return;const[P,D]=Cb(g.current,h.current);if(P!==0||D!==0){const{transform:C,panBy:L}=a.getState();f.current.x=(f.current.x??0)-P/C[2],f.current.y=(f.current.y??0)-D/C[2],L({x:P,y:D})&&N(f.current)}d.current=requestAnimationFrame(S)},A=P=>{var V;const{nodeInternals:D,multiSelectionActive:C,nodesDraggable:L,unselectNodesAndEdges:M,onNodeDragStart:O,onSelectionDragStart:_}=a.getState();m.current=!0;const R=i?O:Wc(_);(!o||!s)&&!C&&i&&((V=D.get(i))!=null&&V.selected||M()),i&&s&&o&&Ad({id:i,store:a,nodeRef:e});const I=v(P);if(f.current=I,c.current=L6(D,L,I,i),R&&c.current){const[z,j]=Uc({nodeId:i,dragItems:c.current,nodeInternals:D});R(P.sourceEvent,z,j)}};if(t)k.on(".drag",null);else{const P=YR().on("start",D=>{const{domNode:C,nodeDragThreshold:L}=a.getState();L===0&&A(D),x.current=!1;const M=v(D);f.current=M,h.current=(C==null?void 0:C.getBoundingClientRect())||null,g.current=xr(D.sourceEvent,h.current)}).on("drag",D=>{var O,_;const C=v(D),{autoPanOnNodeDrag:L,nodeDragThreshold:M}=a.getState();if(D.sourceEvent.type==="touchmove"&&D.sourceEvent.touches.length>1&&(x.current=!0),!x.current){if(!w.current&&m.current&&L&&(w.current=!0,S()),!m.current){const R=C.xSnapped-(((O=f==null?void 0:f.current)==null?void 0:O.x)??0),I=C.ySnapped-(((_=f==null?void 0:f.current)==null?void 0:_.y)??0);Math.sqrt(R*R+I*I)>M&&A(D)}(f.current.x!==C.xSnapped||f.current.y!==C.ySnapped)&&c.current&&m.current&&(y.current=D.sourceEvent,g.current=xr(D.sourceEvent,h.current),N(C))}}).on("end",D=>{if(!(!m.current||x.current)&&(u(!1),w.current=!1,m.current=!1,cancelAnimationFrame(d.current),c.current)){const{updateNodePositions:C,nodeInternals:L,onNodeDragStop:M,onSelectionDragStop:O}=a.getState(),_=i?M:Wc(O);if(C(c.current,!1,!1),_){const[R,I]=Uc({nodeId:i,dragItems:c.current,nodeInternals:L});_(D.sourceEvent,R,I)}}}).filter(D=>{const C=D.target;return!D.button&&(!n||!Ly(C,`.${n}`,e))&&(!r||Ly(C,r,e))});return k.call(P),()=>{k.on(".drag",null)}}}},[e,t,n,r,s,a,i,o,v]),l}function i2(){const e=Ke();return T.useCallback(n=>{const{nodeInternals:r,nodeExtent:i,updateNodePositions:s,getNodes:o,snapToGrid:a,snapGrid:l,onError:u,nodesDraggable:c}=e.getState(),f=o().filter(x=>x.selected&&(x.draggable||c&&typeof x.draggable>"u")),d=a?l[0]:5,h=a?l[1]:5,g=n.isShiftPressed?4:1,y=n.x*d*g,w=n.y*h*g,m=f.map(x=>{if(x.positionAbsolute){const v={x:x.positionAbsolute.x+y,y:x.positionAbsolute.y+w};a&&(v.x=l[0]*Math.round(v.x/l[0]),v.y=l[1]*Math.round(v.y/l[1]));const{positionAbsolute:k,position:N}=n2(x,v,r,i,void 0,u);x.position=N,x.positionAbsolute=k}return x});s(m,!0,!1)},[])}const Wi={ArrowUp:{x:0,y:-1},ArrowDown:{x:0,y:1},ArrowLeft:{x:-1,y:0},ArrowRight:{x:1,y:0}};var js=e=>{const t=({id:n,type:r,data:i,xPos:s,yPos:o,xPosOrigin:a,yPosOrigin:l,selected:u,onClick:c,onMouseEnter:f,onMouseMove:d,onMouseLeave:h,onContextMenu:g,onDoubleClick:y,style:w,className:m,isDraggable:x,isSelectable:v,isConnectable:k,isFocusable:N,selectNodesOnDrag:S,sourcePosition:A,targetPosition:P,hidden:D,resizeObserver:C,dragHandle:L,zIndex:M,isParent:O,noDragClassName:_,noPanClassName:R,initialized:I,disableKeyboardA11y:V,ariaLabel:z,rfId:j,hasHandleBounds:b})=>{const F=Ke(),H=T.useRef(null),E=T.useRef(null),q=T.useRef(A),X=T.useRef(P),G=T.useRef(r),ne=v||x||c||f||d||h,oe=i2(),de=Ps(n,F.getState,f),le=Ps(n,F.getState,d),Ee=Ps(n,F.getState,h),lt=Ps(n,F.getState,g),Nt=Ps(n,F.getState,y),yt=ye=>{const{nodeDragThreshold:Z}=F.getState();if(v&&(!S||!x||Z>0)&&Ad({id:n,store:F,nodeRef:H}),c){const et=F.getState().nodeInternals.get(n);et&&c(ye,{...et})}},Ie=ye=>{if(!_d(ye)&&!V)if(Nb.includes(ye.key)&&v){const Z=ye.key==="Escape";Ad({id:n,store:F,unselect:Z,nodeRef:H})}else x&&u&&Object.prototype.hasOwnProperty.call(Wi,ye.key)&&(F.setState({ariaLiveMessage:`Moved selected node ${ye.key.replace("Arrow","").toLowerCase()}. New position, x: ${~~s}, y: ${~~o}`}),oe({x:Wi[ye.key].x,y:Wi[ye.key].y,isShiftPressed:ye.shiftKey}))};T.useEffect(()=>()=>{E.current&&(C==null||C.unobserve(E.current),E.current=null)},[]),T.useEffect(()=>{if(H.current&&!D){const ye=H.current;(!I||!b||E.current!==ye)&&(E.current&&(C==null||C.unobserve(E.current)),C==null||C.observe(ye),E.current=ye)}},[D,I,b]),T.useEffect(()=>{const ye=G.current!==r,Z=q.current!==A,et=X.current!==P;H.current&&(ye||Z||et)&&(ye&&(G.current=r),Z&&(q.current=A),et&&(X.current=P),F.getState().updateNodeDimensions([{id:n,nodeElement:H.current,forceUpdate:!0}]))},[n,r,A,P]);const Tt=r2({nodeRef:H,disabled:D||!x,noDragClassName:_,handleSelector:L,nodeId:n,isSelectable:v,selectNodesOnDrag:S});return D?null:B.createElement("div",{className:gt(["react-flow__node",`react-flow__node-${r}`,{[R]:x},m,{selected:u,selectable:v,parent:O,dragging:Tt}]),ref:H,style:{zIndex:M,transform:`translate(${a}px,${l}px)`,pointerEvents:ne?"all":"none",visibility:I?"visible":"hidden",...w},"data-id":n,"data-testid":`rf__node-${n}`,onMouseEnter:de,onMouseMove:le,onMouseLeave:Ee,onContextMenu:lt,onClick:yt,onDoubleClick:Nt,onKeyDown:N?Ie:void 0,tabIndex:N?0:void 0,role:N?"button":void 0,"aria-describedby":V?void 0:`${qb}-${j}`,"aria-label":z},B.createElement(Qz,{value:n},B.createElement(e,{id:n,data:i,type:r,xPos:s,yPos:o,selected:u,isConnectable:k,sourcePosition:A,targetPosition:P,dragging:Tt,dragHandle:L,zIndex:M})))};return t.displayName="NodeWrapper",T.memo(t)};const F6=e=>{const t=e.getNodes().filter(n=>n.selected);return{...Ep(t,e.nodeOrigin),transformString:`translate(${e.transform[0]}px,${e.transform[1]}px) scale(${e.transform[2]})`,userSelectionActive:e.userSelectionActive}};function O6({onSelectionContextMenu:e,noPanClassName:t,disableKeyboardA11y:n}){const r=Ke(),{width:i,height:s,x:o,y:a,transformString:l,userSelectionActive:u}=De(F6,Oe),c=i2(),f=T.useRef(null);if(T.useEffect(()=>{var g;n||(g=f.current)==null||g.focus({preventScroll:!0})},[n]),r2({nodeRef:f}),u||!i||!s)return null;const d=e?g=>{const y=r.getState().getNodes().filter(w=>w.selected);e(g,y)}:void 0,h=g=>{Object.prototype.hasOwnProperty.call(Wi,g.key)&&c({x:Wi[g.key].x,y:Wi[g.key].y,isShiftPressed:g.shiftKey})};return B.createElement("div",{className:gt(["react-flow__nodesselection","react-flow__container",t]),style:{transform:l}},B.createElement("div",{ref:f,className:"react-flow__nodesselection-rect",onContextMenu:d,tabIndex:n?void 0:-1,onKeyDown:n?void 0:h,style:{width:i,height:s,top:a,left:o}}))}var V6=T.memo(O6);const $6=e=>e.nodesSelectionActive,s2=({children:e,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:i,onPaneContextMenu:s,onPaneScroll:o,deleteKeyCode:a,onMove:l,onMoveStart:u,onMoveEnd:c,selectionKeyCode:f,selectionOnDrag:d,selectionMode:h,onSelectionStart:g,onSelectionEnd:y,multiSelectionKeyCode:w,panActivationKeyCode:m,zoomActivationKeyCode:x,elementsSelectable:v,zoomOnScroll:k,zoomOnPinch:N,panOnScroll:S,panOnScrollSpeed:A,panOnScrollMode:P,zoomOnDoubleClick:D,panOnDrag:C,defaultViewport:L,translateExtent:M,minZoom:O,maxZoom:_,preventScrolling:R,onSelectionContextMenu:I,noWheelClassName:V,noPanClassName:z,disableKeyboardA11y:j})=>{const b=De($6),F=Io(f),H=Io(m),E=H||C,q=H||S,X=F||d&&E!==!0;return E6({deleteKeyCode:a,multiSelectionKeyCode:w}),B.createElement(P6,{onMove:l,onMoveStart:u,onMoveEnd:c,onPaneContextMenu:s,elementsSelectable:v,zoomOnScroll:k,zoomOnPinch:N,panOnScroll:q,panOnScrollSpeed:A,panOnScrollMode:P,zoomOnDoubleClick:D,panOnDrag:!F&&E,defaultViewport:L,translateExtent:M,minZoom:O,maxZoom:_,zoomActivationKeyCode:x,preventScrolling:R,noWheelClassName:V,noPanClassName:z},B.createElement(e2,{onSelectionStart:g,onSelectionEnd:y,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:i,onPaneContextMenu:s,onPaneScroll:o,panOnDrag:E,isSelecting:!!X,selectionMode:h},e,b&&B.createElement(V6,{onSelectionContextMenu:I,noPanClassName:z,disableKeyboardA11y:j})))};s2.displayName="FlowRenderer";var B6=T.memo(s2);function H6(e){return De(T.useCallback(n=>e?Ib(n.nodeInternals,{x:0,y:0,width:n.width,height:n.height},n.transform,!0):n.getNodes(),[e]))}function U6(e){const t={input:js(e.input||Hb),default:js(e.default||Td),output:js(e.output||Wb),group:js(e.group||Tp)},n={},r=Object.keys(e).filter(i=>!["input","default","output","group"].includes(i)).reduce((i,s)=>(i[s]=js(e[s]||Td),i),n);return{...t,...r}}const W6=({x:e,y:t,width:n,height:r,origin:i})=>!n||!r?{x:e,y:t}:i[0]<0||i[1]<0||i[0]>1||i[1]>1?{x:e,y:t}:{x:e-n*i[0],y:t-r*i[1]},Y6=e=>({nodesDraggable:e.nodesDraggable,nodesConnectable:e.nodesConnectable,nodesFocusable:e.nodesFocusable,elementsSelectable:e.elementsSelectable,updateNodeDimensions:e.updateNodeDimensions,onError:e.onError}),o2=e=>{const{nodesDraggable:t,nodesConnectable:n,nodesFocusable:r,elementsSelectable:i,updateNodeDimensions:s,onError:o}=De(Y6,Oe),a=H6(e.onlyRenderVisibleElements),l=T.useRef(),u=T.useMemo(()=>{if(typeof ResizeObserver>"u")return null;const c=new ResizeObserver(f=>{const d=f.map(h=>({id:h.target.getAttribute("data-id"),nodeElement:h.target,forceUpdate:!0}));s(d)});return l.current=c,c},[]);return T.useEffect(()=>()=>{var c;(c=l==null?void 0:l.current)==null||c.disconnect()},[]),B.createElement("div",{className:"react-flow__nodes",style:Ap},a.map(c=>{var N,S,A;let f=c.type||"default";e.nodeTypes[f]||(o==null||o("003",Bn.error003(f)),f="default");const d=e.nodeTypes[f]||e.nodeTypes.default,h=!!(c.draggable||t&&typeof c.draggable>"u"),g=!!(c.selectable||i&&typeof c.selectable>"u"),y=!!(c.connectable||n&&typeof c.connectable>"u"),w=!!(c.focusable||r&&typeof c.focusable>"u"),m=e.nodeExtent?wp(c.positionAbsolute,e.nodeExtent):c.positionAbsolute,x=(m==null?void 0:m.x)??0,v=(m==null?void 0:m.y)??0,k=W6({x,y:v,width:c.width??0,height:c.height??0,origin:e.nodeOrigin});return B.createElement(d,{key:c.id,id:c.id,className:c.className,style:c.style,type:f,data:c.data,sourcePosition:c.sourcePosition||Q.Bottom,targetPosition:c.targetPosition||Q.Top,hidden:c.hidden,xPos:x,yPos:v,xPosOrigin:k.x,yPosOrigin:k.y,selectNodesOnDrag:e.selectNodesOnDrag,onClick:e.onNodeClick,onMouseEnter:e.onNodeMouseEnter,onMouseMove:e.onNodeMouseMove,onMouseLeave:e.onNodeMouseLeave,onContextMenu:e.onNodeContextMenu,onDoubleClick:e.onNodeDoubleClick,selected:!!c.selected,isDraggable:h,isSelectable:g,isConnectable:y,isFocusable:w,resizeObserver:u,dragHandle:c.dragHandle,zIndex:((N=c[Re])==null?void 0:N.z)??0,isParent:!!((S=c[Re])!=null&&S.isParent),noDragClassName:e.noDragClassName,noPanClassName:e.noPanClassName,initialized:!!c.width&&!!c.height,rfId:e.rfId,disableKeyboardA11y:e.disableKeyboardA11y,ariaLabel:c.ariaLabel,hasHandleBounds:!!((A=c[Re])!=null&&A.handleBounds)})}))};o2.displayName="NodeRenderer";var q6=T.memo(o2);const K6=(e,t,n)=>n===Q.Left?e-t:n===Q.Right?e+t:e,G6=(e,t,n)=>n===Q.Top?e-t:n===Q.Bottom?e+t:e,zy="react-flow__edgeupdater",Fy=({position:e,centerX:t,centerY:n,radius:r=10,onMouseDown:i,onMouseEnter:s,onMouseOut:o,type:a})=>B.createElement("circle",{onMouseDown:i,onMouseEnter:s,onMouseOut:o,className:gt([zy,`${zy}-${a}`]),cx:K6(t,r,e),cy:G6(n,r,e),r,stroke:"transparent",fill:"transparent"}),X6=()=>!0;var mi=e=>{const t=({id:n,className:r,type:i,data:s,onClick:o,onEdgeDoubleClick:a,selected:l,animated:u,label:c,labelStyle:f,labelShowBg:d,labelBgStyle:h,labelBgPadding:g,labelBgBorderRadius:y,style:w,source:m,target:x,sourceX:v,sourceY:k,targetX:N,targetY:S,sourcePosition:A,targetPosition:P,elementsSelectable:D,hidden:C,sourceHandleId:L,targetHandleId:M,onContextMenu:O,onMouseEnter:_,onMouseMove:R,onMouseLeave:I,reconnectRadius:V,onReconnect:z,onReconnectStart:j,onReconnectEnd:b,markerEnd:F,markerStart:H,rfId:E,ariaLabel:q,isFocusable:X,isReconnectable:G,pathOptions:ne,interactionWidth:oe,disableKeyboardA11y:de})=>{const le=T.useRef(null),[Ee,lt]=T.useState(!1),[Nt,yt]=T.useState(!1),Ie=Ke(),Tt=T.useMemo(()=>`url('#${Ed(H,E)}')`,[H,E]),ye=T.useMemo(()=>`url('#${Ed(F,E)}')`,[F,E]);if(C)return null;const Z=xe=>{var Xe;const{edges:He,addSelectedEdges:Ge,unselectNodesAndEdges:At,multiSelectionActive:Pt}=Ie.getState(),Te=He.find(ut=>ut.id===n);Te&&(D&&(Ie.setState({nodesSelectionActive:!1}),Te.selected&&Pt?(At({nodes:[],edges:[Te]}),(Xe=le.current)==null||Xe.blur()):Ge([n])),o&&o(xe,Te))},et=As(n,Ie.getState,a),dn=As(n,Ie.getState,O),Tr=As(n,Ie.getState,_),Wn=As(n,Ie.getState,R),Yn=As(n,Ie.getState,I),Qt=(xe,He)=>{if(xe.button!==0)return;const{edges:Ge,isValidConnection:At}=Ie.getState(),Pt=He?x:m,Te=(He?M:L)||null,Xe=He?"target":"source",ut=At||X6,Pu=He,ps=Ge.find(Ar=>Ar.id===n);yt(!0),j==null||j(xe,ps,Xe);const ju=Ar=>{yt(!1),b==null||b(Ar,ps,Xe)};Ob({event:xe,handleId:Te,nodeId:Pt,onConnect:Ar=>z==null?void 0:z(ps,Ar),isTarget:Pu,getState:Ie.getState,setState:Ie.setState,isValidConnection:ut,edgeUpdaterType:Xe,onReconnectEnd:ju})},qn=xe=>Qt(xe,!0),$=xe=>Qt(xe,!1),Y=()=>lt(!0),ee=()=>lt(!1),ie=!D&&!o,he=xe=>{var He;if(!de&&Nb.includes(xe.key)&&D){const{unselectNodesAndEdges:Ge,addSelectedEdges:At,edges:Pt}=Ie.getState();xe.key==="Escape"?((He=le.current)==null||He.blur(),Ge({edges:[Pt.find(Xe=>Xe.id===n)]})):At([n])}};return B.createElement("g",{className:gt(["react-flow__edge",`react-flow__edge-${i}`,r,{selected:l,animated:u,inactive:ie,updating:Ee}]),onClick:Z,onDoubleClick:et,onContextMenu:dn,onMouseEnter:Tr,onMouseMove:Wn,onMouseLeave:Yn,onKeyDown:X?he:void 0,tabIndex:X?0:void 0,role:X?"button":"img","data-testid":`rf__edge-${n}`,"aria-label":q===null?void 0:q||`Edge from ${m} to ${x}`,"aria-describedby":X?`${Kb}-${E}`:void 0,ref:le},!Nt&&B.createElement(e,{id:n,source:m,target:x,selected:l,animated:u,label:c,labelStyle:f,labelShowBg:d,labelBgStyle:h,labelBgPadding:g,labelBgBorderRadius:y,data:s,style:w,sourceX:v,sourceY:k,targetX:N,targetY:S,sourcePosition:A,targetPosition:P,sourceHandleId:L,targetHandleId:M,markerStart:Tt,markerEnd:ye,pathOptions:ne,interactionWidth:oe}),G&&B.createElement(B.Fragment,null,(G==="source"||G===!0)&&B.createElement(Fy,{position:A,centerX:v,centerY:k,radius:V,onMouseDown:qn,onMouseEnter:Y,onMouseOut:ee,type:"source"}),(G==="target"||G===!0)&&B.createElement(Fy,{position:P,centerX:N,centerY:S,radius:V,onMouseDown:$,onMouseEnter:Y,onMouseOut:ee,type:"target"})))};return t.displayName="EdgeWrapper",T.memo(t)};function Q6(e){const t={default:mi(e.default||Hl),straight:mi(e.bezier||_p),step:mi(e.step||Sp),smoothstep:mi(e.step||Tu),simplebezier:mi(e.simplebezier||bp)},n={},r=Object.keys(e).filter(i=>!["default","bezier"].includes(i)).reduce((i,s)=>(i[s]=mi(e[s]||Hl),i),n);return{...t,...r}}function Oy(e,t,n=null){const r=((n==null?void 0:n.x)||0)+t.x,i=((n==null?void 0:n.y)||0)+t.y,s=(n==null?void 0:n.width)||t.width,o=(n==null?void 0:n.height)||t.height;switch(e){case Q.Top:return{x:r+s/2,y:i};case Q.Right:return{x:r+s,y:i+o/2};case Q.Bottom:return{x:r+s/2,y:i+o};case Q.Left:return{x:r,y:i+o/2}}}function Vy(e,t){return e?e.length===1||!t?e[0]:t&&e.find(n=>n.id===t)||null:null}const Z6=(e,t,n,r,i,s)=>{const o=Oy(n,e,t),a=Oy(s,r,i);return{sourceX:o.x,sourceY:o.y,targetX:a.x,targetY:a.y}};function J6({sourcePos:e,targetPos:t,sourceWidth:n,sourceHeight:r,targetWidth:i,targetHeight:s,width:o,height:a,transform:l}){const u={x:Math.min(e.x,t.x),y:Math.min(e.y,t.y),x2:Math.max(e.x+n,t.x+i),y2:Math.max(e.y+r,t.y+s)};u.x===u.x2&&(u.x2+=1),u.y===u.y2&&(u.y2+=1);const c=kp({x:(0-l[0])/l[2],y:(0-l[1])/l[2],width:o/l[2],height:a/l[2]}),f=Math.max(0,Math.min(c.x2,u.x2)-Math.max(c.x,u.x)),d=Math.max(0,Math.min(c.y2,u.y2)-Math.max(c.y,u.y));return Math.ceil(f*d)>0}function $y(e){var r,i,s,o,a;const t=((r=e==null?void 0:e[Re])==null?void 0:r.handleBounds)||null,n=t&&(e==null?void 0:e.width)&&(e==null?void 0:e.height)&&typeof((i=e==null?void 0:e.positionAbsolute)==null?void 0:i.x)<"u"&&typeof((s=e==null?void 0:e.positionAbsolute)==null?void 0:s.y)<"u";return[{x:((o=e==null?void 0:e.positionAbsolute)==null?void 0:o.x)||0,y:((a=e==null?void 0:e.positionAbsolute)==null?void 0:a.y)||0,width:(e==null?void 0:e.width)||0,height:(e==null?void 0:e.height)||0},t,!!n]}const eF=[{level:0,isMaxLevel:!0,edges:[]}];function tF(e,t,n=!1){let r=-1;const i=e.reduce((o,a)=>{var c,f;const l=qt(a.zIndex);let u=l?a.zIndex:0;if(n){const d=t.get(a.target),h=t.get(a.source),g=a.selected||(d==null?void 0:d.selected)||(h==null?void 0:h.selected),y=Math.max(((c=h==null?void 0:h[Re])==null?void 0:c.z)||0,((f=d==null?void 0:d[Re])==null?void 0:f.z)||0,1e3);u=(l?a.zIndex:0)+(g?y:0)}return o[u]?o[u].push(a):o[u]=[a],r=u>r?u:r,o},{}),s=Object.entries(i).map(([o,a])=>{const l=+o;return{edges:a,level:l,isMaxLevel:l===r}});return s.length===0?eF:s}function nF(e,t,n){const r=De(T.useCallback(i=>e?i.edges.filter(s=>{const o=t.get(s.source),a=t.get(s.target);return(o==null?void 0:o.width)&&(o==null?void 0:o.height)&&(a==null?void 0:a.width)&&(a==null?void 0:a.height)&&J6({sourcePos:o.positionAbsolute||{x:0,y:0},targetPos:a.positionAbsolute||{x:0,y:0},sourceWidth:o.width,sourceHeight:o.height,targetWidth:a.width,targetHeight:a.height,width:i.width,height:i.height,transform:i.transform})}):i.edges,[e,t]));return tF(r,t,n)}const rF=({color:e="none",strokeWidth:t=1})=>B.createElement("polyline",{style:{stroke:e,strokeWidth:t},strokeLinecap:"round",strokeLinejoin:"round",fill:"none",points:"-5,-4 0,0 -5,4"}),iF=({color:e="none",strokeWidth:t=1})=>B.createElement("polyline",{style:{stroke:e,fill:e,strokeWidth:t},strokeLinecap:"round",strokeLinejoin:"round",points:"-5,-4 0,0 -5,4 -5,-4"}),By={[Do.Arrow]:rF,[Do.ArrowClosed]:iF};function sF(e){const t=Ke();return T.useMemo(()=>{var i,s;return Object.prototype.hasOwnProperty.call(By,e)?By[e]:((s=(i=t.getState()).onError)==null||s.call(i,"009",Bn.error009(e)),null)},[e])}const oF=({id:e,type:t,color:n,width:r=12.5,height:i=12.5,markerUnits:s="strokeWidth",strokeWidth:o,orient:a="auto-start-reverse"})=>{const l=sF(t);return l?B.createElement("marker",{className:"react-flow__arrowhead",id:e,markerWidth:`${r}`,markerHeight:`${i}`,viewBox:"-10 -10 20 20",markerUnits:s,orient:a,refX:"0",refY:"0"},B.createElement(l,{color:n,strokeWidth:o})):null},aF=({defaultColor:e,rfId:t})=>n=>{const r=[];return n.edges.reduce((i,s)=>([s.markerStart,s.markerEnd].forEach(o=>{if(o&&typeof o=="object"){const a=Ed(o,t);r.includes(a)||(i.push({id:a,color:o.color||e,...o}),r.push(a))}}),i),[]).sort((i,s)=>i.id.localeCompare(s.id))},a2=({defaultColor:e,rfId:t})=>{const n=De(T.useCallback(aF({defaultColor:e,rfId:t}),[e,t]),(r,i)=>!(r.length!==i.length||r.some((s,o)=>s.id!==i[o].id)));return B.createElement("defs",null,n.map(r=>B.createElement(oF,{id:r.id,key:r.id,type:r.type,color:r.color,width:r.width,height:r.height,markerUnits:r.markerUnits,strokeWidth:r.strokeWidth,orient:r.orient})))};a2.displayName="MarkerDefinitions";var lF=T.memo(a2);const uF=e=>({nodesConnectable:e.nodesConnectable,edgesFocusable:e.edgesFocusable,edgesUpdatable:e.edgesUpdatable,elementsSelectable:e.elementsSelectable,width:e.width,height:e.height,connectionMode:e.connectionMode,nodeInternals:e.nodeInternals,onError:e.onError}),l2=({defaultMarkerColor:e,onlyRenderVisibleElements:t,elevateEdgesOnSelect:n,rfId:r,edgeTypes:i,noPanClassName:s,onEdgeContextMenu:o,onEdgeMouseEnter:a,onEdgeMouseMove:l,onEdgeMouseLeave:u,onEdgeClick:c,onEdgeDoubleClick:f,onReconnect:d,onReconnectStart:h,onReconnectEnd:g,reconnectRadius:y,children:w,disableKeyboardA11y:m})=>{const{edgesFocusable:x,edgesUpdatable:v,elementsSelectable:k,width:N,height:S,connectionMode:A,nodeInternals:P,onError:D}=De(uF,Oe),C=nF(t,P,n);return N?B.createElement(B.Fragment,null,C.map(({level:L,edges:M,isMaxLevel:O})=>B.createElement("svg",{key:L,style:{zIndex:L},width:N,height:S,className:"react-flow__edges react-flow__container"},O&&B.createElement(lF,{defaultColor:e,rfId:r}),B.createElement("g",null,M.map(_=>{const[R,I,V]=$y(P.get(_.source)),[z,j,b]=$y(P.get(_.target));if(!V||!b)return null;let F=_.type||"default";i[F]||(D==null||D("011",Bn.error011(F)),F="default");const H=i[F]||i.default,E=A===ii.Strict?j.target:(j.target??[]).concat(j.source??[]),q=Vy(I.source,_.sourceHandle),X=Vy(E,_.targetHandle),G=(q==null?void 0:q.position)||Q.Bottom,ne=(X==null?void 0:X.position)||Q.Top,oe=!!(_.focusable||x&&typeof _.focusable>"u"),de=_.reconnectable||_.updatable,le=typeof d<"u"&&(de||v&&typeof de>"u");if(!q||!X)return D==null||D("008",Bn.error008(q,_)),null;const{sourceX:Ee,sourceY:lt,targetX:Nt,targetY:yt}=Z6(R,q,G,z,X,ne);return B.createElement(H,{key:_.id,id:_.id,className:gt([_.className,s]),type:F,data:_.data,selected:!!_.selected,animated:!!_.animated,hidden:!!_.hidden,label:_.label,labelStyle:_.labelStyle,labelShowBg:_.labelShowBg,labelBgStyle:_.labelBgStyle,labelBgPadding:_.labelBgPadding,labelBgBorderRadius:_.labelBgBorderRadius,style:_.style,source:_.source,target:_.target,sourceHandleId:_.sourceHandle,targetHandleId:_.targetHandle,markerEnd:_.markerEnd,markerStart:_.markerStart,sourceX:Ee,sourceY:lt,targetX:Nt,targetY:yt,sourcePosition:G,targetPosition:ne,elementsSelectable:k,onContextMenu:o,onMouseEnter:a,onMouseMove:l,onMouseLeave:u,onClick:c,onEdgeDoubleClick:f,onReconnect:d,onReconnectStart:h,onReconnectEnd:g,reconnectRadius:y,rfId:r,ariaLabel:_.ariaLabel,isFocusable:oe,isReconnectable:le,pathOptions:"pathOptions"in _?_.pathOptions:void 0,interactionWidth:_.interactionWidth,disableKeyboardA11y:m})})))),w):null};l2.displayName="EdgeRenderer";var cF=T.memo(l2);const fF=e=>`translate(${e.transform[0]}px,${e.transform[1]}px) scale(${e.transform[2]})`;function dF({children:e}){const t=De(fF);return B.createElement("div",{className:"react-flow__viewport react-flow__container",style:{transform:t}},e)}function hF(e){const t=Au(),n=T.useRef(!1);T.useEffect(()=>{!n.current&&t.viewportInitialized&&e&&(setTimeout(()=>e(t),1),n.current=!0)},[e,t.viewportInitialized])}const pF={[Q.Left]:Q.Right,[Q.Right]:Q.Left,[Q.Top]:Q.Bottom,[Q.Bottom]:Q.Top},u2=({nodeId:e,handleType:t,style:n,type:r=sr.Bezier,CustomComponent:i,connectionStatus:s})=>{var S,A,P;const{fromNode:o,handleId:a,toX:l,toY:u,connectionMode:c}=De(T.useCallback(D=>({fromNode:D.nodeInternals.get(e),handleId:D.connectionHandleId,toX:(D.connectionPosition.x-D.transform[0])/D.transform[2],toY:(D.connectionPosition.y-D.transform[1])/D.transform[2],connectionMode:D.connectionMode}),[e]),Oe),f=(S=o==null?void 0:o[Re])==null?void 0:S.handleBounds;let d=f==null?void 0:f[t];if(c===ii.Loose&&(d=d||(f==null?void 0:f[t==="source"?"target":"source"])),!o||!d)return null;const h=a?d.find(D=>D.id===a):d[0],g=h?h.x+h.width/2:(o.width??0)/2,y=h?h.y+h.height/2:o.height??0,w=(((A=o.positionAbsolute)==null?void 0:A.x)??0)+g,m=(((P=o.positionAbsolute)==null?void 0:P.y)??0)+y,x=h==null?void 0:h.position,v=x?pF[x]:null;if(!x||!v)return null;if(i)return B.createElement(i,{connectionLineType:r,connectionLineStyle:n,fromNode:o,fromHandle:h,fromX:w,fromY:m,toX:l,toY:u,fromPosition:x,toPosition:v,connectionStatus:s});let k="";const N={sourceX:w,sourceY:m,sourcePosition:x,targetX:l,targetY:u,targetPosition:v};return r===sr.Bezier?[k]=Mb(N):r===sr.Step?[k]=Cd({...N,borderRadius:0}):r===sr.SmoothStep?[k]=Cd(N):r===sr.SimpleBezier?[k]=jb(N):k=`M${w},${m} ${l},${u}`,B.createElement("path",{d:k,fill:"none",className:"react-flow__connection-path",style:n})};u2.displayName="ConnectionLine";const mF=e=>({nodeId:e.connectionNodeId,handleType:e.connectionHandleType,nodesConnectable:e.nodesConnectable,connectionStatus:e.connectionStatus,width:e.width,height:e.height});function gF({containerStyle:e,style:t,type:n,component:r}){const{nodeId:i,handleType:s,nodesConnectable:o,width:a,height:l,connectionStatus:u}=De(mF,Oe);return!(i&&s&&a&&o)?null:B.createElement("svg",{style:e,width:a,height:l,className:"react-flow__edges react-flow__connectionline react-flow__container"},B.createElement("g",{className:gt(["react-flow__connection",u])},B.createElement(u2,{nodeId:i,handleType:s,style:t,type:n,CustomComponent:r,connectionStatus:u})))}function Hy(e,t){return T.useRef(null),Ke(),T.useMemo(()=>t(e),[e])}const c2=({nodeTypes:e,edgeTypes:t,onMove:n,onMoveStart:r,onMoveEnd:i,onInit:s,onNodeClick:o,onEdgeClick:a,onNodeDoubleClick:l,onEdgeDoubleClick:u,onNodeMouseEnter:c,onNodeMouseMove:f,onNodeMouseLeave:d,onNodeContextMenu:h,onSelectionContextMenu:g,onSelectionStart:y,onSelectionEnd:w,connectionLineType:m,connectionLineStyle:x,connectionLineComponent:v,connectionLineContainerStyle:k,selectionKeyCode:N,selectionOnDrag:S,selectionMode:A,multiSelectionKeyCode:P,panActivationKeyCode:D,zoomActivationKeyCode:C,deleteKeyCode:L,onlyRenderVisibleElements:M,elementsSelectable:O,selectNodesOnDrag:_,defaultViewport:R,translateExtent:I,minZoom:V,maxZoom:z,preventScrolling:j,defaultMarkerColor:b,zoomOnScroll:F,zoomOnPinch:H,panOnScroll:E,panOnScrollSpeed:q,panOnScrollMode:X,zoomOnDoubleClick:G,panOnDrag:ne,onPaneClick:oe,onPaneMouseEnter:de,onPaneMouseMove:le,onPaneMouseLeave:Ee,onPaneScroll:lt,onPaneContextMenu:Nt,onEdgeContextMenu:yt,onEdgeMouseEnter:Ie,onEdgeMouseMove:Tt,onEdgeMouseLeave:ye,onReconnect:Z,onReconnectStart:et,onReconnectEnd:dn,reconnectRadius:Tr,noDragClassName:Wn,noWheelClassName:Yn,noPanClassName:Qt,elevateEdgesOnSelect:qn,disableKeyboardA11y:$,nodeOrigin:Y,nodeExtent:ee,rfId:ie})=>{const he=Hy(e,U6),xe=Hy(t,Q6);return hF(s),B.createElement(B6,{onPaneClick:oe,onPaneMouseEnter:de,onPaneMouseMove:le,onPaneMouseLeave:Ee,onPaneContextMenu:Nt,onPaneScroll:lt,deleteKeyCode:L,selectionKeyCode:N,selectionOnDrag:S,selectionMode:A,onSelectionStart:y,onSelectionEnd:w,multiSelectionKeyCode:P,panActivationKeyCode:D,zoomActivationKeyCode:C,elementsSelectable:O,onMove:n,onMoveStart:r,onMoveEnd:i,zoomOnScroll:F,zoomOnPinch:H,zoomOnDoubleClick:G,panOnScroll:E,panOnScrollSpeed:q,panOnScrollMode:X,panOnDrag:ne,defaultViewport:R,translateExtent:I,minZoom:V,maxZoom:z,onSelectionContextMenu:g,preventScrolling:j,noDragClassName:Wn,noWheelClassName:Yn,noPanClassName:Qt,disableKeyboardA11y:$},B.createElement(dF,null,B.createElement(cF,{edgeTypes:xe,onEdgeClick:a,onEdgeDoubleClick:u,onlyRenderVisibleElements:M,onEdgeContextMenu:yt,onEdgeMouseEnter:Ie,onEdgeMouseMove:Tt,onEdgeMouseLeave:ye,onReconnect:Z,onReconnectStart:et,onReconnectEnd:dn,reconnectRadius:Tr,defaultMarkerColor:b,noPanClassName:Qt,elevateEdgesOnSelect:!!qn,disableKeyboardA11y:$,rfId:ie},B.createElement(gF,{style:x,type:m,component:v,containerStyle:k})),B.createElement("div",{className:"react-flow__edgelabel-renderer"}),B.createElement(q6,{nodeTypes:he,onNodeClick:o,onNodeDoubleClick:l,onNodeMouseEnter:c,onNodeMouseMove:f,onNodeMouseLeave:d,onNodeContextMenu:h,selectNodesOnDrag:_,onlyRenderVisibleElements:M,noPanClassName:Qt,noDragClassName:Wn,disableKeyboardA11y:$,nodeOrigin:Y,nodeExtent:ee,rfId:ie})))};c2.displayName="GraphView";var yF=T.memo(c2);const Pd=[[Number.NEGATIVE_INFINITY,Number.NEGATIVE_INFINITY],[Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY]],Xn={rfId:"1",width:0,height:0,transform:[0,0,1],nodeInternals:new Map,edges:[],onNodesChange:null,onEdgesChange:null,hasDefaultNodes:!1,hasDefaultEdges:!1,d3Zoom:null,d3Selection:null,d3ZoomHandler:void 0,minZoom:.5,maxZoom:2,translateExtent:Pd,nodeExtent:Pd,nodesSelectionActive:!1,userSelectionActive:!1,userSelectionRect:null,connectionNodeId:null,connectionHandleId:null,connectionHandleType:"source",connectionPosition:{x:0,y:0},connectionStatus:null,connectionMode:ii.Strict,domNode:null,paneDragging:!1,noPanClassName:"nopan",nodeOrigin:[0,0],nodeDragThreshold:0,snapGrid:[15,15],snapToGrid:!1,nodesDraggable:!0,nodesConnectable:!0,nodesFocusable:!0,edgesFocusable:!0,edgesUpdatable:!0,elementsSelectable:!0,elevateNodesOnSelect:!0,fitViewOnInit:!1,fitViewOnInitDone:!1,fitViewOnInitOptions:void 0,onSelectionChange:[],multiSelectionActive:!1,connectionStartHandle:null,connectionEndHandle:null,connectionClickStartHandle:null,connectOnClick:!0,ariaLiveMessage:"",autoPanOnConnect:!0,autoPanOnNodeDrag:!0,connectionRadius:20,onError:Wz,isValidConnection:void 0},xF=()=>iL((e,t)=>({...Xn,setNodes:n=>{const{nodeInternals:r,nodeOrigin:i,elevateNodesOnSelect:s}=t();e({nodeInternals:Bc(n,r,i,s)})},getNodes:()=>Array.from(t().nodeInternals.values()),setEdges:n=>{const{defaultEdgeOptions:r={}}=t();e({edges:n.map(i=>({...r,...i}))})},setDefaultNodesAndEdges:(n,r)=>{const i=typeof n<"u",s=typeof r<"u",o=i?Bc(n,new Map,t().nodeOrigin,t().elevateNodesOnSelect):new Map;e({nodeInternals:o,edges:s?r:[],hasDefaultNodes:i,hasDefaultEdges:s})},updateNodeDimensions:n=>{const{onNodesChange:r,nodeInternals:i,fitViewOnInit:s,fitViewOnInitDone:o,fitViewOnInitOptions:a,domNode:l,nodeOrigin:u}=t(),c=l==null?void 0:l.querySelector(".react-flow__viewport");if(!c)return;const f=window.getComputedStyle(c),{m22:d}=new window.DOMMatrixReadOnly(f.transform),h=n.reduce((y,w)=>{const m=i.get(w.id);if(m!=null&&m.hidden)i.set(m.id,{...m,[Re]:{...m[Re],handleBounds:void 0}});else if(m){const x=vp(w.nodeElement);!!(x.width&&x.height&&(m.width!==x.width||m.height!==x.height||w.forceUpdate))&&(i.set(m.id,{...m,[Re]:{...m[Re],handleBounds:{source:Ry(".source",w.nodeElement,d,u),target:Ry(".target",w.nodeElement,d,u)}},...x}),y.push({id:m.id,type:"dimensions",dimensions:x}))}return y},[]);Xb(i,u);const g=o||s&&!o&&Qb(t,{initial:!0,...a});e({nodeInternals:new Map(i),fitViewOnInitDone:g}),(h==null?void 0:h.length)>0&&(r==null||r(h))},updateNodePositions:(n,r=!0,i=!1)=>{const{triggerNodeChanges:s}=t(),o=n.map(a=>{const l={id:a.id,type:"position",dragging:i};return r&&(l.positionAbsolute=a.positionAbsolute,l.position=a.position),l});s(o)},triggerNodeChanges:n=>{const{onNodesChange:r,nodeInternals:i,hasDefaultNodes:s,nodeOrigin:o,getNodes:a,elevateNodesOnSelect:l}=t();if(n!=null&&n.length){if(s){const u=Jb(n,a()),c=Bc(u,i,o,l);e({nodeInternals:c})}r==null||r(n)}},addSelectedNodes:n=>{const{multiSelectionActive:r,edges:i,getNodes:s}=t();let o,a=null;r?o=n.map(l=>nr(l,!0)):(o=Di(s(),n),a=Di(i,[])),Pa({changedNodes:o,changedEdges:a,get:t,set:e})},addSelectedEdges:n=>{const{multiSelectionActive:r,edges:i,getNodes:s}=t();let o,a=null;r?o=n.map(l=>nr(l,!0)):(o=Di(i,n),a=Di(s(),[])),Pa({changedNodes:a,changedEdges:o,get:t,set:e})},unselectNodesAndEdges:({nodes:n,edges:r}={})=>{const{edges:i,getNodes:s}=t(),o=n||s(),a=r||i,l=o.map(c=>(c.selected=!1,nr(c.id,!1))),u=a.map(c=>nr(c.id,!1));Pa({changedNodes:l,changedEdges:u,get:t,set:e})},setMinZoom:n=>{const{d3Zoom:r,maxZoom:i}=t();r==null||r.scaleExtent([n,i]),e({minZoom:n})},setMaxZoom:n=>{const{d3Zoom:r,minZoom:i}=t();r==null||r.scaleExtent([i,n]),e({maxZoom:n})},setTranslateExtent:n=>{var r;(r=t().d3Zoom)==null||r.translateExtent(n),e({translateExtent:n})},resetSelectedElements:()=>{const{edges:n,getNodes:r}=t(),s=r().filter(a=>a.selected).map(a=>nr(a.id,!1)),o=n.filter(a=>a.selected).map(a=>nr(a.id,!1));Pa({changedNodes:s,changedEdges:o,get:t,set:e})},setNodeExtent:n=>{const{nodeInternals:r}=t();r.forEach(i=>{i.positionAbsolute=wp(i.position,n)}),e({nodeExtent:n,nodeInternals:new Map(r)})},panBy:n=>{const{transform:r,width:i,height:s,d3Zoom:o,d3Selection:a,translateExtent:l}=t();if(!o||!a||!n.x&&!n.y)return!1;const u=yr.translate(r[0]+n.x,r[1]+n.y).scale(r[2]),c=[[0,0],[i,s]],f=o==null?void 0:o.constrain()(u,c,l);return o.transform(a,f),r[0]!==f.x||r[1]!==f.y||r[2]!==f.k},cancelConnection:()=>e({connectionNodeId:Xn.connectionNodeId,connectionHandleId:Xn.connectionHandleId,connectionHandleType:Xn.connectionHandleType,connectionStatus:Xn.connectionStatus,connectionStartHandle:Xn.connectionStartHandle,connectionEndHandle:Xn.connectionEndHandle}),reset:()=>e({...Xn})}),Object.is),Pp=({children:e})=>{const t=T.useRef(null);return t.current||(t.current=xF()),B.createElement(zz,{value:t.current},e)};Pp.displayName="ReactFlowProvider";const f2=({children:e})=>T.useContext(Nu)?B.createElement(B.Fragment,null,e):B.createElement(Pp,null,e);f2.displayName="ReactFlowWrapper";const vF={input:Hb,default:Td,output:Wb,group:Tp},wF={default:Hl,straight:_p,step:Sp,smoothstep:Tu,simplebezier:bp},kF=[0,0],bF=[15,15],SF={x:0,y:0,zoom:1},_F={width:"100%",height:"100%",overflow:"hidden",position:"relative",zIndex:0},d2=T.forwardRef(({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,className:i,nodeTypes:s=vF,edgeTypes:o=wF,onNodeClick:a,onEdgeClick:l,onInit:u,onMove:c,onMoveStart:f,onMoveEnd:d,onConnect:h,onConnectStart:g,onConnectEnd:y,onClickConnectStart:w,onClickConnectEnd:m,onNodeMouseEnter:x,onNodeMouseMove:v,onNodeMouseLeave:k,onNodeContextMenu:N,onNodeDoubleClick:S,onNodeDragStart:A,onNodeDrag:P,onNodeDragStop:D,onNodesDelete:C,onEdgesDelete:L,onSelectionChange:M,onSelectionDragStart:O,onSelectionDrag:_,onSelectionDragStop:R,onSelectionContextMenu:I,onSelectionStart:V,onSelectionEnd:z,connectionMode:j=ii.Strict,connectionLineType:b=sr.Bezier,connectionLineStyle:F,connectionLineComponent:H,connectionLineContainerStyle:E,deleteKeyCode:q="Backspace",selectionKeyCode:X="Shift",selectionOnDrag:G=!1,selectionMode:ne=Mo.Full,panActivationKeyCode:oe="Space",multiSelectionKeyCode:de=Bl()?"Meta":"Control",zoomActivationKeyCode:le=Bl()?"Meta":"Control",snapToGrid:Ee=!1,snapGrid:lt=bF,onlyRenderVisibleElements:Nt=!1,selectNodesOnDrag:yt=!0,nodesDraggable:Ie,nodesConnectable:Tt,nodesFocusable:ye,nodeOrigin:Z=kF,edgesFocusable:et,edgesUpdatable:dn,elementsSelectable:Tr,defaultViewport:Wn=SF,minZoom:Yn=.5,maxZoom:Qt=2,translateExtent:qn=Pd,preventScrolling:$=!0,nodeExtent:Y,defaultMarkerColor:ee="#b1b1b7",zoomOnScroll:ie=!0,zoomOnPinch:he=!0,panOnScroll:xe=!1,panOnScrollSpeed:He=.5,panOnScrollMode:Ge=Wr.Free,zoomOnDoubleClick:At=!0,panOnDrag:Pt=!0,onPaneClick:Te,onPaneMouseEnter:Xe,onPaneMouseMove:ut,onPaneMouseLeave:Pu,onPaneScroll:ps,onPaneContextMenu:ju,children:Dp,onEdgeContextMenu:Ar,onEdgeDoubleClick:y2,onEdgeMouseEnter:x2,onEdgeMouseMove:v2,onEdgeMouseLeave:w2,onEdgeUpdate:k2,onEdgeUpdateStart:b2,onEdgeUpdateEnd:S2,onReconnect:_2,onReconnectStart:C2,onReconnectEnd:E2,reconnectRadius:N2=10,edgeUpdaterRadius:T2=10,onNodesChange:A2,onEdgesChange:P2,noDragClassName:j2="nodrag",noWheelClassName:M2="nowheel",noPanClassName:Ip="nopan",fitView:D2=!1,fitViewOptions:I2,connectOnClick:L2=!0,attributionPosition:R2,proOptions:z2,defaultEdgeOptions:F2,elevateNodesOnSelect:O2=!0,elevateEdgesOnSelect:V2=!1,disableKeyboardA11y:Lp=!1,autoPanOnConnect:$2=!0,autoPanOnNodeDrag:B2=!0,connectionRadius:H2=20,isValidConnection:U2,onError:W2,style:Y2,id:Rp,nodeDragThreshold:q2,...K2},G2)=>{const Mu=Rp||"1";return B.createElement("div",{...K2,style:{...Y2,..._F},ref:G2,className:gt(["react-flow",i]),"data-testid":"rf__wrapper",id:Rp},B.createElement(f2,null,B.createElement(yF,{onInit:u,onMove:c,onMoveStart:f,onMoveEnd:d,onNodeClick:a,onEdgeClick:l,onNodeMouseEnter:x,onNodeMouseMove:v,onNodeMouseLeave:k,onNodeContextMenu:N,onNodeDoubleClick:S,nodeTypes:s,edgeTypes:o,connectionLineType:b,connectionLineStyle:F,connectionLineComponent:H,connectionLineContainerStyle:E,selectionKeyCode:X,selectionOnDrag:G,selectionMode:ne,deleteKeyCode:q,multiSelectionKeyCode:de,panActivationKeyCode:oe,zoomActivationKeyCode:le,onlyRenderVisibleElements:Nt,selectNodesOnDrag:yt,defaultViewport:Wn,translateExtent:qn,minZoom:Yn,maxZoom:Qt,preventScrolling:$,zoomOnScroll:ie,zoomOnPinch:he,zoomOnDoubleClick:At,panOnScroll:xe,panOnScrollSpeed:He,panOnScrollMode:Ge,panOnDrag:Pt,onPaneClick:Te,onPaneMouseEnter:Xe,onPaneMouseMove:ut,onPaneMouseLeave:Pu,onPaneScroll:ps,onPaneContextMenu:ju,onSelectionContextMenu:I,onSelectionStart:V,onSelectionEnd:z,onEdgeContextMenu:Ar,onEdgeDoubleClick:y2,onEdgeMouseEnter:x2,onEdgeMouseMove:v2,onEdgeMouseLeave:w2,onReconnect:_2??k2,onReconnectStart:C2??b2,onReconnectEnd:E2??S2,reconnectRadius:N2??T2,defaultMarkerColor:ee,noDragClassName:j2,noWheelClassName:M2,noPanClassName:Ip,elevateEdgesOnSelect:V2,rfId:Mu,disableKeyboardA11y:Lp,nodeOrigin:Z,nodeExtent:Y}),B.createElement(p6,{nodes:e,edges:t,defaultNodes:n,defaultEdges:r,onConnect:h,onConnectStart:g,onConnectEnd:y,onClickConnectStart:w,onClickConnectEnd:m,nodesDraggable:Ie,nodesConnectable:Tt,nodesFocusable:ye,edgesFocusable:et,edgesUpdatable:dn,elementsSelectable:Tr,elevateNodesOnSelect:O2,minZoom:Yn,maxZoom:Qt,nodeExtent:Y,onNodesChange:A2,onEdgesChange:P2,snapToGrid:Ee,snapGrid:lt,connectionMode:j,translateExtent:qn,connectOnClick:L2,defaultEdgeOptions:F2,fitView:D2,fitViewOptions:I2,onNodesDelete:C,onEdgesDelete:L,onNodeDragStart:A,onNodeDrag:P,onNodeDragStop:D,onSelectionDrag:_,onSelectionDragStart:O,onSelectionDragStop:R,noPanClassName:Ip,nodeOrigin:Z,rfId:Mu,autoPanOnConnect:$2,autoPanOnNodeDrag:B2,onError:W2,connectionRadius:H2,isValidConnection:U2,nodeDragThreshold:q2}),B.createElement(d6,{onSelectionChange:M}),Dp,B.createElement(Oz,{proOptions:z2,position:R2}),B.createElement(v6,{rfId:Mu,disableKeyboardA11y:Lp})))});d2.displayName="ReactFlow";function h2(e){return t=>{const[n,r]=T.useState(t),i=T.useCallback(s=>r(o=>e(s,o)),[]);return[n,r,i]}}const CF=h2(Jb),EF=h2(D6);function NF(){return B.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32"},B.createElement("path",{d:"M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z"}))}function TF(){return B.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 5"},B.createElement("path",{d:"M0 0h32v4.2H0z"}))}function AF(){return B.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 30"},B.createElement("path",{d:"M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z"}))}function PF(){return B.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32"},B.createElement("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z"}))}function jF(){return B.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32"},B.createElement("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z"}))}const Os=({children:e,className:t,...n})=>B.createElement("button",{type:"button",className:gt(["react-flow__controls-button",t]),...n},e);Os.displayName="ControlButton";const MF=e=>({isInteractive:e.nodesDraggable||e.nodesConnectable||e.elementsSelectable,minZoomReached:e.transform[2]<=e.minZoom,maxZoomReached:e.transform[2]>=e.maxZoom}),p2=({style:e,showZoom:t=!0,showFitView:n=!0,showInteractive:r=!0,fitViewOptions:i,onZoomIn:s,onZoomOut:o,onFitView:a,onInteractiveChange:l,className:u,children:c,position:f="bottom-left"})=>{const d=Ke(),[h,g]=T.useState(!1),{isInteractive:y,minZoomReached:w,maxZoomReached:m}=De(MF,Oe),{zoomIn:x,zoomOut:v,fitView:k}=Au();if(T.useEffect(()=>{g(!0)},[]),!h)return null;const N=()=>{x(),s==null||s()},S=()=>{v(),o==null||o()},A=()=>{k(i),a==null||a()},P=()=>{d.setState({nodesDraggable:!y,nodesConnectable:!y,elementsSelectable:!y}),l==null||l(!y)};return B.createElement(_b,{className:gt(["react-flow__controls",u]),position:f,style:e,"data-testid":"rf__controls"},t&&B.createElement(B.Fragment,null,B.createElement(Os,{onClick:N,className:"react-flow__controls-zoomin",title:"zoom in","aria-label":"zoom in",disabled:m},B.createElement(NF,null)),B.createElement(Os,{onClick:S,className:"react-flow__controls-zoomout",title:"zoom out","aria-label":"zoom out",disabled:w},B.createElement(TF,null))),n&&B.createElement(Os,{className:"react-flow__controls-fitview",onClick:A,title:"fit view","aria-label":"fit view"},B.createElement(AF,null)),r&&B.createElement(Os,{className:"react-flow__controls-interactive",onClick:P,title:"toggle interactivity","aria-label":"toggle interactivity"},y?B.createElement(jF,null):B.createElement(PF,null)),c)};p2.displayName="Controls";var DF=T.memo(p2),un;(function(e){e.Lines="lines",e.Dots="dots",e.Cross="cross"})(un||(un={}));function IF({color:e,dimensions:t,lineWidth:n}){return B.createElement("path",{stroke:e,strokeWidth:n,d:`M${t[0]/2} 0 V${t[1]} M0 ${t[1]/2} H${t[0]}`})}function LF({color:e,radius:t}){return B.createElement("circle",{cx:t,cy:t,r:t,fill:e})}const RF={[un.Dots]:"#91919a",[un.Lines]:"#eee",[un.Cross]:"#e2e2e2"},zF={[un.Dots]:1,[un.Lines]:1,[un.Cross]:6},FF=e=>({transform:e.transform,patternId:`pattern-${e.rfId}`});function m2({id:e,variant:t=un.Dots,gap:n=20,size:r,lineWidth:i=1,offset:s=2,color:o,style:a,className:l}){const u=T.useRef(null),{transform:c,patternId:f}=De(FF,Oe),d=o||RF[t],h=r||zF[t],g=t===un.Dots,y=t===un.Cross,w=Array.isArray(n)?n:[n,n],m=[w[0]*c[2]||1,w[1]*c[2]||1],x=h*c[2],v=y?[x,x]:m,k=g?[x/s,x/s]:[v[0]/s,v[1]/s];return B.createElement("svg",{className:gt(["react-flow__background",l]),style:{...a,position:"absolute",width:"100%",height:"100%",top:0,left:0},ref:u,"data-testid":"rf__background"},B.createElement("pattern",{id:f+e,x:c[0]%m[0],y:c[1]%m[1],width:m[0],height:m[1],patternUnits:"userSpaceOnUse",patternTransform:`translate(-${k[0]},-${k[1]})`},g?B.createElement(LF,{color:d,radius:x/s}):B.createElement(IF,{dimensions:v,color:d,lineWidth:i})),B.createElement("rect",{x:"0",y:"0",width:"100%",height:"100%",fill:`url(#${f+e})`}))}m2.displayName="Background";var OF=T.memo(m2);const Uy={pending:{bg:"linear-gradient(135deg, rgba(30,41,59,0.9) 0%, rgba(51,65,85,0.85) 100%)",border:"rgba(148, 163, 184, 0.4)",text:"#cbd5e1",shadow:"rgba(148, 163, 184, 0.5)",glow:"0 0 20px rgba(148, 163, 184, 0.3), 0 0 30px rgba(148, 163, 184, 0.15)"},running:{bg:"linear-gradient(135deg, rgba(6,182,212,0.2) 0%, rgba(14,165,233,0.15) 50%, rgba(12,74,110,0.85) 100%)",border:"rgba(56, 189, 248, 0.6)",text:"#bae6fd",shadow:"rgba(56, 189, 248, 0.7)",glow:"0 0 25px rgba(56, 189, 248, 0.4), 0 0 35px rgba(6, 182, 212, 0.25), inset 0 0 20px rgba(56, 189, 248, 0.08)"},completed:{bg:"linear-gradient(135deg, rgba(16,185,129,0.2) 0%, rgba(74,222,128,0.12) 50%, rgba(20,83,45,0.85) 100%)",border:"rgba(74, 222, 128, 0.6)",text:"#bbf7d0",shadow:"rgba(74, 222, 128, 0.7)",glow:"0 0 25px rgba(74, 222, 128, 0.4), 0 0 35px rgba(16, 185, 129, 0.25), inset 0 0 20px rgba(74, 222, 128, 0.08)"},failed:{bg:"linear-gradient(135deg, rgba(239,68,68,0.2) 0%, rgba(248,113,113,0.12) 50%, rgba(127,29,29,0.85) 100%)",border:"rgba(248, 113, 113, 0.6)",text:"#fecaca",shadow:"rgba(248, 113, 113, 0.7)",glow:"0 0 25px rgba(248, 113, 113, 0.4), 0 0 35px rgba(239, 68, 68, 0.25), inset 0 0 20px rgba(248, 113, 113, 0.08)"},skipped:{bg:"linear-gradient(135deg, rgba(250,204,21,0.2) 0%, rgba(253,224,71,0.12) 50%, rgba(113,63,18,0.85) 100%)",border:"rgba(250, 204, 21, 0.6)",text:"#fef3c7",shadow:"rgba(250, 204, 21, 0.7)",glow:"0 0 25px rgba(250, 204, 21, 0.4), 0 0 35px rgba(250, 204, 21, 0.25), inset 0 0 20px rgba(250, 204, 21, 0.08)"}},VF=e=>{if(!e)return p.jsx(Gs,{className:"h-4 w-4"});const t=e.toLowerCase();return t==="running"||t==="in_progress"?p.jsx(Vo,{className:"h-4 w-4 animate-spin"}):t==="completed"||t==="success"||t==="finish"?p.jsx(Kr,{className:"h-4 w-4"}):t==="failed"||t==="error"?p.jsx($o,{className:"h-4 w-4"}):t==="pending"||t==="waiting"?p.jsx(au,{className:"h-4 w-4 animate-pulse"}):t==="skipped"?p.jsx(Gs,{className:"h-4 w-4"}):p.jsx(Gs,{className:"h-4 w-4"})},$F={star:({data:e})=>{const t=Uy[e.status??"pending"]??Uy.pending,n=VF(e.status);return p.jsxs("div",{className:"relative w-[280px]",children:[p.jsx(ls,{type:"target",position:Q.Left,style:{opacity:0}}),p.jsx(ls,{type:"source",position:Q.Right,style:{opacity:0}}),p.jsxs("div",{className:"rounded-2xl border-2 px-5 py-4 text-left shadow-2xl backdrop-blur-sm transition-all duration-300 hover:scale-105",style:{background:t.bg,borderColor:t.border,boxShadow:`${t.glow}, 0 8px 32px rgba(0,0,0,0.4), inset 0 1px 2px rgba(255,255,255,0.1)`},children:[p.jsx("div",{className:"absolute -top-2 -right-2 flex items-center justify-center rounded-full border-2 p-1.5 shadow-lg transition-all duration-300",style:{background:t.bg,borderColor:t.border,color:t.text,boxShadow:`0 0 15px ${t.shadow}, 0 0 8px ${t.border}`},children:n}),p.jsx("div",{className:"absolute top-0 left-0 right-0 h-[1px] opacity-50",style:{background:`linear-gradient(90deg, transparent 0%, ${t.border} 50%, transparent 100%)`}}),p.jsx("div",{className:"text-xl font-semibold uppercase tracking-wider mb-2 drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]",style:{color:t.text,opacity:.85},children:e.taskId}),p.jsx("div",{className:"text-2xl font-bold leading-snug drop-shadow-[0_2px_8px_rgba(0,0,0,0.6)]",style:{color:t.text},children:e.label}),p.jsx("div",{className:"absolute bottom-0 left-0 right-0 h-[1px] opacity-30",style:{background:`linear-gradient(90deg, transparent 0%, ${t.border} 50%, transparent 100%)`}})]})]})}},BF=(e,t)=>{const n=new Set(e.map(y=>y.id)),r=new Map,i=new Map,s=new Map,o=new Map;e.forEach(y=>{r.set(y.id,0),i.set(y.id,0),s.set(y.id,[]),o.set(y.id,[])}),t.forEach(y=>{var w,m;!n.has(y.source)||!n.has(y.target)||(r.set(y.target,(r.get(y.target)??0)+1),i.set(y.source,(i.get(y.source)??0)+1),(w=s.get(y.source))==null||w.push(y.target),(m=o.get(y.target))==null||m.push(y.source))});const a=[],l=new Map;r.forEach((y,w)=>{y===0&&(a.push(w),l.set(w,0))});const u=new Map(r);for(;a.length>0;){const y=a.shift(),w=l.get(y)??0;(s.get(y)??[]).forEach(m=>{const x=Math.max(l.get(m)??0,w+1);l.set(m,x);const v=(u.get(m)??0)-1;u.set(m,v),v===0&&a.push(m)})}e.forEach(y=>{l.has(y.id)||l.set(y.id,0)});const c=new Map;e.forEach(y=>{const w=l.get(y.id)??0;c.has(w)||c.set(w,[]),c.get(w).push(y)});const f=500,d=200,h=-100,g=new Map;return Array.from(c.entries()).sort(([y],[w])=>y-w).forEach(([y,w])=>{const m=w.sort((k,N)=>{const S=o.get(k.id)??[],A=o.get(N.id)??[];if(S.length>0&&A.length>0){const P=S.reduce((C,L)=>{const M=g.get(L);return C+((M==null?void 0:M.y)??0)},0)/S.length,D=A.reduce((C,L)=>{const M=g.get(L);return C+((M==null?void 0:M.y)??0)},0)/A.length;return P-D}return S.length>0?S.reduce((D,C)=>{const L=g.get(C);return D+((L==null?void 0:L.y)??0)},0)/S.length:A.length>0?-(A.reduce((D,C)=>{const L=g.get(C);return D+((L==null?void 0:L.y)??0)},0)/A.length):k.label.localeCompare(N.label)}),x=m.length,v=d+Math.min(x*15,150);if(y===0){const k=(x-1)*v,N=k>0?-(k/2):0;m.forEach((S,A)=>{g.set(S.id,{x:h+y*f,y:N+A*v})})}else{const k=new Map;m.forEach(N=>{const S=o.get(N.id)??[],A=S.length>0?S.reduce((D,C)=>{const L=g.get(C);return D+((L==null?void 0:L.y)??0)},0)/S.length:0,P=Math.round(A/10)*10;k.has(P)||k.set(P,[]),k.get(P).push(N)}),k.forEach((N,S)=>{const A=N.length;if(A===1)g.set(N[0].id,{x:h+y*f,y:S});else{const P=(A-1)*v,D=S-P/2;N.forEach((C,L)=>{g.set(C.id,{x:h+y*f,y:D+L*v})})}})}}),g},Wy=(e,t)=>{const n=BF(e,t);return e.map(r=>{const i=n.get(r.id)??{x:0,y:0};return{id:r.id,type:"star",data:{label:r.label,status:r.status,taskId:r.id},position:i,draggable:!1,connectable:!1,sourcePosition:Q.Right,targetPosition:Q.Left}})},Yy=e=>e.map(t=>{const n=t.isSatisfied===!1?{color:"rgba(248, 113, 113, 0.7)",glowColor:"rgba(239, 68, 68, 0.5)",markerColor:"rgba(248, 113, 113, 0.9)"}:t.isSatisfied===!0?{color:"rgba(74, 222, 128, 0.7)",glowColor:"rgba(16, 185, 129, 0.5)",markerColor:"rgba(74, 222, 128, 0.9)"}:{color:"rgba(56, 189, 248, 0.7)",glowColor:"rgba(6, 182, 212, 0.5)",markerColor:"rgba(56, 189, 248, 0.9)"};return{id:t.id,source:t.source,target:t.target,type:"default",animated:t.isSatisfied===!1,style:{stroke:n.color,strokeWidth:2.5,filter:`drop-shadow(0 0 2px ${n.glowColor})`},markerEnd:{type:Do.Arrow,color:n.markerColor,width:20,height:20,strokeWidth:2}}}),HF=({nodes:e,edges:t,onSelectNode:n})=>{const[r,i,s]=CF(Wy(e,t)),[o,a,l]=EF(Yy(t)),{setViewport:u}=Au(),c=T.useRef(!1);return T.useEffect(()=>{i(Wy(e,t)),a(Yy(t))},[t,e,a,i]),T.useEffect(()=>{r.length>0&&!c.current&&setTimeout(()=>{const f=Math.min(...r.map(D=>D.position.x)),d=Math.max(...r.map(D=>D.position.x)),h=Math.min(...r.map(D=>D.position.y)),g=Math.max(...r.map(D=>D.position.y)),y=d-f+280,w=g-h+180,m=document.querySelector(".react-flow"),x=(m==null?void 0:m.clientWidth)||800,v=(m==null?void 0:m.clientHeight)||600,k=x*.95/y,N=v*.9/w,S=Math.max(Math.min(k,N,1.5),.45),A=-f*S+30,P=(v-w*S)/2-h*S;u({x:A,y:P,zoom:S}),c.current=!0},150)},[r,u]),p.jsxs(d2,{nodes:r,edges:o,nodeTypes:$F,onNodesChange:s,onEdgesChange:l,fitView:!1,defaultViewport:{x:-50,y:0,zoom:.6},minZoom:.1,maxZoom:2,onNodeClick:(f,d)=>n==null?void 0:n(d.id),panOnScroll:!0,zoomOnScroll:!0,nodesDraggable:!1,nodesConnectable:!1,edgesFocusable:!1,elementsSelectable:!0,proOptions:{hideAttribution:!0},className:"rounded-2xl border border-white/5 bg-black/40",style:{height:"100%",minHeight:260},defaultEdgeOptions:{type:"default",animated:!1,style:{strokeWidth:2.5}},children:[p.jsx(DF,{showInteractive:!1,position:"bottom-left"}),p.jsx(OF,{gap:28,size:1.8,color:"rgba(100, 116, 139, 0.2)"})]})},UF=e=>p.jsx(Pp,{children:p.jsx(HF,{...e})}),WF=({constellation:e,onBack:t})=>{var S;const n=((S=e.metadata)==null?void 0:S.statistics)||{},r=n.task_status_counts||{},i=n.total_tasks||e.statistics.total,s=n.total_dependencies||0,o=r.completed||0,a=r.failed||0,l=r.running||0,u=r.pending||0,c=r.ready||0,f=o+a,d=f>0?o/f*100:0,h=n.execution_duration,g=h!=null?`${h.toFixed(2)}s`:"N/A",y=n.critical_path_length,w=n.total_work,m=n.parallelism_ratio,x=A=>{if(!A)return"N/A";try{const P=new Date(A);return new Intl.DateTimeFormat("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit"}).format(P)}catch{return"N/A"}},v=x(n.created_at),k=x(n.execution_start_time),N=x(n.execution_end_time);return p.jsxs("div",{className:"flex h-full flex-col gap-4 overflow-y-auto p-1",children:[p.jsxs("div",{className:"flex items-center gap-3",children:[p.jsxs("button",{onClick:t,className:"flex items-center gap-2 rounded-full border border-white/10 bg-black/30 px-3 py-2 text-xs text-slate-200 transition hover:border-white/30 hover:bg-black/40",children:[p.jsx(lv,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Back to DAG"]}),p.jsx("div",{className:"text-sm font-semibold text-white",children:"Execution Summary"})]}),p.jsx("div",{className:"rounded-2xl border border-emerald-400/30 bg-gradient-to-br from-emerald-500/10 to-cyan-500/10 p-4",children:p.jsxs("div",{className:"flex items-center justify-between",children:[p.jsxs("div",{children:[p.jsx("div",{className:"text-xs uppercase tracking-[0.2em] text-slate-400",children:"Success Rate"}),p.jsx("div",{className:"mt-1 text-3xl font-bold text-emerald-300",children:f>0?`${d.toFixed(1)}%`:"N/A"}),p.jsxs("div",{className:"mt-1 text-xs text-slate-400",children:[o," of ",f," completed tasks"]})]}),p.jsx(qC,{className:"h-10 w-10 text-emerald-400/40","aria-hidden":!0})]})}),p.jsxs("div",{className:"grid grid-cols-4 gap-2 text-center",children:[p.jsxs("div",{className:"rounded-xl border border-white/10 bg-white/5 px-2 py-2",children:[p.jsx("div",{className:"text-[9px] uppercase tracking-[0.2em] text-slate-400",children:"Total"}),p.jsx("div",{className:"mt-0.5 text-lg font-bold text-white",children:i})]}),p.jsxs("div",{className:"rounded-xl border border-white/10 bg-white/5 px-2 py-2",children:[p.jsx("div",{className:"text-[9px] uppercase tracking-[0.2em] text-slate-400",children:"Pending"}),p.jsx("div",{className:"mt-0.5 text-lg font-bold text-slate-300",children:u})]}),p.jsxs("div",{className:"rounded-xl border border-white/10 bg-white/5 px-2 py-2",children:[p.jsx("div",{className:"text-[9px] uppercase tracking-[0.2em] text-slate-400",children:"Running"}),p.jsx("div",{className:"mt-0.5 text-lg font-bold text-cyan-300",children:l})]}),p.jsxs("div",{className:"rounded-xl border border-white/10 bg-white/5 px-2 py-2",children:[p.jsx("div",{className:"text-[9px] uppercase tracking-[0.2em] text-slate-400",children:"Done"}),p.jsx("div",{className:"mt-0.5 text-lg font-bold text-emerald-300",children:o})]})]}),p.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[p.jsxs("div",{className:"flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-slate-400",children:[p.jsx(Kr,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Completed"]}),p.jsx("div",{className:"mt-2 text-2xl font-bold text-emerald-300",children:o}),p.jsxs("div",{className:"mt-1 text-xs text-slate-500",children:[i>0?`${(o/i*100).toFixed(0)}%`:"0%"," of total"]})]}),p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[p.jsxs("div",{className:"flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-slate-400",children:[p.jsx($o,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Failed"]}),p.jsx("div",{className:"mt-2 text-2xl font-bold text-rose-300",children:a}),p.jsxs("div",{className:"mt-1 text-xs text-slate-500",children:[i>0?`${(a/i*100).toFixed(0)}%`:"0%"," of total"]})]}),p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[p.jsxs("div",{className:"flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-slate-400",children:[p.jsx(au,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Running"]}),p.jsx("div",{className:"mt-2 text-2xl font-bold text-cyan-300",children:l}),p.jsx("div",{className:"mt-1 text-xs text-slate-500",children:"Active execution"})]}),p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[p.jsxs("div",{className:"flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-slate-400",children:[p.jsx(IC,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Pending"]}),p.jsx("div",{className:"mt-2 text-2xl font-bold text-slate-300",children:u}),p.jsx("div",{className:"mt-1 text-xs text-slate-500",children:"Awaiting execution"})]})]}),(s>0||c>0)&&p.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c>0&&p.jsxs("div",{className:"rounded-2xl border border-yellow-400/30 bg-yellow-500/10 p-4",children:[p.jsx("div",{className:"text-xs uppercase tracking-[0.2em] text-slate-400",children:"Ready"}),p.jsx("div",{className:"mt-2 text-2xl font-bold text-yellow-300",children:c}),p.jsx("div",{className:"mt-1 text-xs text-slate-500",children:"Can be executed"})]}),s>0&&p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[p.jsx("div",{className:"text-xs uppercase tracking-[0.2em] text-slate-400",children:"Dependencies"}),p.jsx("div",{className:"mt-2 text-2xl font-bold text-slate-300",children:s}),p.jsx("div",{className:"mt-1 text-xs text-slate-500",children:"Total links"})]})]}),m!=null&&p.jsxs("div",{className:"rounded-2xl border border-purple-400/30 bg-gradient-to-br from-purple-500/10 to-blue-500/10 p-4",children:[p.jsx("div",{className:"text-xs font-semibold uppercase tracking-[0.2em] text-slate-400 mb-3",children:"Parallelism Analysis"}),p.jsxs("div",{className:"grid grid-cols-3 gap-4 text-center",children:[p.jsxs("div",{children:[p.jsx("div",{className:"text-xs text-slate-400",children:"Critical Path"}),p.jsx("div",{className:"mt-1 text-xl font-bold text-purple-300",children:y!=null?Number(y).toFixed(2):"N/A"})]}),p.jsxs("div",{children:[p.jsx("div",{className:"text-xs text-slate-400",children:"Total Work"}),p.jsx("div",{className:"mt-1 text-xl font-bold text-blue-300",children:w!=null?Number(w).toFixed(2):"N/A"})]}),p.jsxs("div",{children:[p.jsx("div",{className:"text-xs text-slate-400",children:"Ratio"}),p.jsx("div",{className:"mt-1 text-xl font-bold text-cyan-300",children:m?`${m.toFixed(2)}x`:"N/A"})]})]})]}),p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[p.jsx("div",{className:"text-xs font-semibold uppercase tracking-[0.2em] text-slate-400 mb-3",children:"Execution Timeline"}),p.jsxs("div",{className:"space-y-2 text-xs",children:[p.jsxs("div",{className:"flex justify-between",children:[p.jsx("span",{className:"text-slate-400",children:"Created:"}),p.jsx("span",{className:"font-mono text-slate-200",children:v})]}),p.jsxs("div",{className:"flex justify-between",children:[p.jsx("span",{className:"text-slate-400",children:"Started:"}),p.jsx("span",{className:"font-mono text-slate-200",children:k})]}),e.status==="completed"&&p.jsxs("div",{className:"flex justify-between",children:[p.jsx("span",{className:"text-slate-400",children:"Ended:"}),p.jsx("span",{className:"font-mono text-slate-200",children:N})]}),p.jsxs("div",{className:"flex justify-between border-t border-white/10 pt-2 mt-2",children:[p.jsx("span",{className:"text-slate-400 font-semibold",children:"Duration:"}),p.jsx("span",{className:"font-mono text-emerald-300 font-semibold",children:g})]})]})]}),e.metadata&&Object.keys(e.metadata).length>0&&p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4",children:[p.jsx("div",{className:"text-xs font-semibold uppercase tracking-[0.2em] text-slate-400 mb-3",children:"Additional Information"}),p.jsxs("div",{className:"space-y-2 text-xs",children:[e.description&&p.jsxs("div",{children:[p.jsx("span",{className:"text-slate-400",children:"Description:"}),p.jsx("div",{className:"mt-1 text-slate-200",children:e.description})]}),e.metadata.display_name&&p.jsxs("div",{className:"flex justify-between",children:[p.jsx("span",{className:"text-slate-400",children:"Name:"}),p.jsx("span",{className:"text-slate-200",children:e.metadata.display_name})]})]})]})]})},YF={pending:"text-slate-300",running:"text-cyan-300",completed:"text-emerald-300",failed:"text-rose-300"},qF=({constellation:e,onSelectTask:t,variant:n="standalone"})=>{const[r,i]=T.useState(!1);if(!e)return p.jsxs("div",{className:me("flex h-full flex-col items-center justify-center gap-3 rounded-3xl p-8 text-center text-sm text-slate-300",n==="standalone"?"glass-card":"border border-white/5 bg-black/30"),children:[p.jsx(BC,{className:"h-6 w-6","aria-hidden":!0}),p.jsx("div",{children:"No active constellation yet."}),p.jsx("div",{className:"text-xs text-slate-500",children:"Launch a request to generate a TaskConstellation."})]});const s=YF[e.status]||"text-slate-300",o=me("flex h-full flex-col gap-4 rounded-3xl p-5",n==="standalone"?"glass-card":"border border-white/5 bg-black/30",n==="embedded"&&"max-h-[420px]"),a=me("flex-1 overflow-hidden rounded-3xl border border-white/5 bg-black/30",n==="embedded"?"h-[260px]":"h-[320px]"),l=e.status==="completed"||e.status==="failed";return p.jsxs("div",{className:o,children:[p.jsxs("div",{className:"flex items-center justify-between gap-4",children:[p.jsxs("div",{className:"flex items-center gap-2 text-xs text-slate-400",children:[p.jsx(YC,{className:"h-3 w-3","aria-hidden":!0}),p.jsxs("span",{children:[e.taskIds.length," tasks"]}),p.jsx("span",{className:"mx-1",children:"•"}),p.jsx("span",{className:s,children:e.status})]}),l&&p.jsxs("button",{onClick:()=>i(!r),className:me("flex items-center gap-2 rounded-full border border-white/10 px-3 py-1.5 text-xs transition",r?"bg-emerald-500/20 border-emerald-400/40 text-emerald-300":"bg-black/30 text-slate-300 hover:border-white/30 hover:bg-black/40"),title:"View execution summary",children:[p.jsx(NC,{className:"h-3.5 w-3.5","aria-hidden":!0}),"Stats"]})]}),p.jsx("div",{className:a,children:r?p.jsx(WF,{constellation:e,onBack:()=>i(!1)}):p.jsx(UF,{nodes:e.dag.nodes,edges:e.dag.edges,onSelectNode:t})})]})},KF=e=>{const t=e.toLowerCase();return t==="running"||t==="in_progress"?p.jsx(Vo,{className:"h-3.5 w-3.5 animate-spin text-cyan-300","aria-hidden":!0}):t==="completed"||t==="success"||t==="finish"?p.jsx(PC,{className:"h-3.5 w-3.5 text-emerald-300","aria-hidden":!0}):t==="failed"||t==="error"?p.jsx($o,{className:"h-3.5 w-3.5 text-rose-400","aria-hidden":!0}):t==="pending"||t==="waiting"?p.jsx(au,{className:"h-3.5 w-3.5 animate-pulse text-slate-300","aria-hidden":!0}):t==="skipped"?p.jsx(Gs,{className:"h-3.5 w-3.5 text-amber-300","aria-hidden":!0}):p.jsx(Gs,{className:"h-3.5 w-3.5 text-slate-300","aria-hidden":!0})},GF=["all","pending","running","completed","failed"],XF={all:"All",pending:"Pending",running:"Running",completed:"Completed",failed:"Failed"},QF=({tasks:e,activeTaskId:t,onSelectTask:n})=>{const[r,i]=T.useState("all"),s=T.useMemo(()=>{const o={running:0,pending:1,failed:2,completed:3,skipped:4};return e.filter(a=>r==="all"||a.status===r).sort((a,l)=>{const u=(o[a.status]??99)-(o[l.status]??99);return u!==0?u:(a.name||a.id).localeCompare(l.name||l.id)})},[r,e]);return p.jsxs("div",{className:"flex h-full flex-col gap-3 text-xs text-slate-200",children:[p.jsx("div",{className:"flex items-center justify-between",children:p.jsx("div",{className:"flex items-center gap-1 rounded-full border border-white/10 bg-black/30 px-2 py-1",children:GF.map(o=>p.jsx("button",{type:"button",onClick:()=>i(o),className:me("rounded-full px-2 py-1 text-[10px] uppercase tracking-[0.18em]",r===o?"bg-gradient-to-r from-galaxy-blue/40 to-galaxy-purple/40 text-white":"text-slate-400"),children:XF[o]},o))})}),p.jsx("div",{className:"flex-1 space-y-2 overflow-y-auto",children:s.length===0?p.jsx("div",{className:"flex flex-col items-center gap-2 rounded-2xl border border-dashed border-white/10 bg-white/5 p-6 text-center text-xs text-slate-400",children:"No tasks match this filter yet."}):s.map(o=>{const a=KF(o.status);return p.jsxs("button",{type:"button",onClick:()=>n(o.id),className:me("w-full rounded-2xl border px-3 py-3 text-left transition",t===o.id?"border-galaxy-blue/60 bg-galaxy-blue/15 shadow-glow":"border-white/10 bg-white/5 hover:border-white/25 hover:bg-white/10"),children:[p.jsxs("div",{className:"flex items-center justify-between gap-3 text-xs text-slate-200",children:[p.jsxs("div",{className:"flex items-center gap-2",children:[a,p.jsx("span",{className:"font-medium text-white",children:o.name||o.id})]}),p.jsx("div",{className:"text-[10px] uppercase tracking-[0.18em] text-slate-400",children:o.status})]}),p.jsx("div",{className:"mt-1 text-[11px] text-slate-400",children:o.deviceId?`device: ${o.deviceId}`:"No device assigned"})]},o.id)})})]})},Ma=e=>{if(!e)return"∅";try{return JSON.stringify(e,null,2)}catch{return String(e)}},ZF=e=>e.length?p.jsx("ul",{className:"space-y-2 text-xs",children:e.map(t=>p.jsxs("li",{className:me("rounded-xl border px-3 py-2",t.level==="error"?"border-rose-400/40 bg-rose-500/10 text-rose-100":"border-white/10 bg-white/5 text-slate-200"),children:[p.jsxs("div",{className:"flex items-center justify-between text-[10px] uppercase tracking-[0.18em] text-slate-400",children:[p.jsx("span",{children:t.level}),p.jsx("span",{children:new Date(t.timestamp).toLocaleTimeString()})]}),p.jsx("div",{className:"mt-1 text-xs",children:t.message})]},t.id))}):p.jsx("div",{className:"text-xs text-slate-400",children:"No logs streamed yet."}),JF=({task:e,onBack:t})=>{const n=Ce(o=>o.setActiveTask);if(!e)return p.jsx("div",{className:"flex h-full flex-col items-center justify-center gap-3 text-center text-sm text-slate-300",children:"Select a task to inspect details."});const r=()=>{sn().send({type:"task_retry",taskId:e.id,constellationId:e.constellationId,timestamp:Date.now()})},i=()=>{sn().send({type:"task_skip",taskId:e.id,constellationId:e.constellationId,timestamp:Date.now()})},s=()=>{t?t():n(null)};return p.jsxs("div",{className:"flex h-full flex-col gap-4 text-xs text-slate-200",children:[p.jsxs("div",{className:"flex items-center justify-between",children:[p.jsxs("div",{className:"flex items-center gap-3",children:[p.jsxs("button",{type:"button",onClick:s,className:"inline-flex items-center gap-1 rounded-full border border-white/10 bg-gradient-to-r from-white/8 to-white/5 px-3 py-2 text-xs text-slate-100 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.08)] transition-all hover:border-white/25 hover:shadow-[0_4px_12px_rgba(0,0,0,0.25),0_0_15px_rgba(15,123,255,0.15)]",title:"Back to task list",children:[p.jsx(lv,{className:"h-3 w-3","aria-hidden":!0}),"Back"]}),p.jsxs("div",{children:[p.jsx("div",{className:"text-xs uppercase tracking-[0.25em] text-slate-400",children:"Task Detail"}),p.jsx("div",{className:"text-lg font-semibold text-white drop-shadow-[0_0_8px_rgba(255,255,255,0.3)]",children:e.name||e.id}),p.jsxs("div",{className:"text-[11px] text-slate-400",children:["Status: ",e.status]})]})]}),p.jsxs("div",{className:"flex items-center gap-2",children:[p.jsxs("button",{type:"button",onClick:r,className:"inline-flex items-center gap-2 rounded-full border border-white/10 bg-gradient-to-r from-white/8 to-white/5 px-3 py-2 text-xs text-slate-100 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.08)] transition-all hover:border-emerald-400/40 hover:shadow-[0_4px_12px_rgba(0,0,0,0.25),0_0_15px_rgba(16,185,129,0.2)]",children:[p.jsx(VC,{className:"h-3 w-3","aria-hidden":!0}),"Retry"]}),p.jsxs("button",{type:"button",onClick:i,className:"inline-flex items-center gap-2 rounded-full border border-white/10 bg-gradient-to-r from-white/8 to-white/5 px-3 py-2 text-xs text-slate-100 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.08)] transition-all hover:border-amber-400/40 hover:shadow-[0_4px_12px_rgba(0,0,0,0.25),0_0_15px_rgba(245,158,11,0.2)]",children:[p.jsx(HC,{className:"h-3 w-3","aria-hidden":!0}),"Skip"]})]})]}),p.jsxs("div",{className:"grid gap-3 text-[11px] text-slate-300",children:[p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-gradient-to-br from-white/8 to-white/4 px-3 py-2 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.06)]",children:[p.jsxs("div",{children:["Device: ",e.deviceId||"not assigned"]}),p.jsxs("div",{children:["Started: ",e.startedAt?new Date(e.startedAt).toLocaleTimeString():"—"]}),p.jsxs("div",{children:["Completed: ",e.completedAt?new Date(e.completedAt).toLocaleTimeString():"—"]})]}),p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-gradient-to-br from-white/8 to-white/4 px-3 py-2 shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.06)]",children:[p.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-[0.18em] text-slate-400",children:"Dependencies"}),p.jsx("div",{children:e.dependencies.length?e.dependencies.join(", "):"None"})]}),e.error&&p.jsxs("div",{className:"rounded-2xl border border-rose-400/40 bg-gradient-to-r from-rose-500/15 to-rose-600/10 px-3 py-2 text-rose-100 shadow-[0_0_20px_rgba(244,63,94,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)]",children:["Error: ",e.error]})]}),p.jsxs("div",{className:"grid grid-cols-1 gap-3",children:[p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-gradient-to-br from-black/50 to-black/30 shadow-[0_4px_16px_rgba(0,0,0,0.3),inset_0_1px_1px_rgba(255,255,255,0.05)]",children:[p.jsxs("div",{className:"flex items-center justify-between border-b border-white/10 bg-white/5 px-3 py-2 text-[10px] uppercase tracking-[0.18em] text-slate-400",children:["Input",p.jsxs("button",{type:"button",className:"inline-flex items-center gap-1 text-[10px] text-slate-400 transition hover:text-slate-200",onClick:()=>{navigator!=null&&navigator.clipboard&&navigator.clipboard.writeText(Ma(e.input))},children:[p.jsx(Km,{className:"h-3 w-3","aria-hidden":!0})," Copy"]})]}),p.jsx("pre",{className:"max-h-60 overflow-auto px-3 py-3 text-[11px] text-slate-200",children:Ma(e.input)})]}),p.jsxs("div",{className:"rounded-2xl border border-white/10 bg-gradient-to-br from-black/50 to-black/30 shadow-[0_4px_16px_rgba(0,0,0,0.3),inset_0_1px_1px_rgba(255,255,255,0.05)]",children:[p.jsxs("div",{className:"flex items-center justify-between border-b border-white/10 bg-white/5 px-3 py-2 text-[10px] uppercase tracking-[0.18em] text-slate-400",children:["Output",p.jsxs("button",{type:"button",className:"inline-flex items-center gap-1 text-[10px] text-slate-400 transition hover:text-slate-200",onClick:()=>{navigator!=null&&navigator.clipboard&&navigator.clipboard.writeText(Ma(e.output||e.result))},children:[p.jsx(Km,{className:"h-3 w-3","aria-hidden":!0})," Copy"]})]}),p.jsx("pre",{className:"max-h-60 overflow-auto px-3 py-3 text-[11px] text-slate-200",children:Ma(e.output||e.result)})]})]}),p.jsxs("div",{className:"flex-1 overflow-y-auto rounded-2xl border border-white/10 bg-gradient-to-br from-black/50 to-black/30 px-3 py-3 shadow-[0_4px_16px_rgba(0,0,0,0.3),inset_0_1px_1px_rgba(255,255,255,0.05)]",children:[p.jsx("div",{className:"mb-2 text-[10px] uppercase tracking-[0.18em] text-slate-400",children:"Logs"}),ZF(e.logs)]})]})},qy={pending:"bg-slate-500/20 text-slate-300 border-slate-400/30",running:"bg-cyan-500/20 text-cyan-300 border-cyan-400/40",executing:"bg-cyan-500/20 text-cyan-300 border-cyan-400/40",completed:"bg-emerald-500/20 text-emerald-300 border-emerald-400/40",failed:"bg-rose-500/20 text-rose-300 border-rose-400/40"},Ky=()=>{const{constellations:e,tasks:t,ui:n,setActiveConstellation:r,setActiveTask:i}=Ce(f=>({constellations:f.constellations,tasks:f.tasks,ui:f.ui,setActiveConstellation:f.setActiveConstellation,setActiveTask:f.setActiveTask}),Oe),s=T.useMemo(()=>Object.values(e).sort((f,d)=>(d.updatedAt??0)-(f.updatedAt??0)),[e]),o=T.useMemo(()=>{const f=Object.values(e).sort((h,g)=>(h.createdAt??0)-(g.createdAt??0)),d={};return f.forEach((h,g)=>{d[h.id]=g+1}),d},[e]);T.useEffect(()=>{!n.activeConstellationId&&s.length>0&&r(s[0].id)},[s,r,n.activeConstellationId]);const a=n.activeConstellationId?e[n.activeConstellationId]:s[0],l=T.useMemo(()=>a?a.taskIds.map(f=>t[f]).filter(f=>!!f):[],[a,t]),u=n.activeTaskId?t[n.activeTaskId]:void 0,c=f=>{const d=f.target.value;r(d||null)};return p.jsxs("div",{className:"flex h-full w-full flex-col gap-3",children:[p.jsxs("div",{className:"flex flex-1 min-h-0 flex-col gap-3 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-4 overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(147,51,234,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:[p.jsxs("div",{className:"flex items-center justify-between flex-shrink-0",children:[p.jsxs("div",{className:"flex items-center gap-3",children:[p.jsx(FC,{className:"h-5 w-5 text-purple-400 drop-shadow-[0_0_8px_rgba(147,51,234,0.5)]","aria-hidden":!0}),p.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:"Constellation Overview"}),a&&p.jsx("span",{className:me("rounded-full border px-3 py-1.5 text-xs font-semibold uppercase tracking-wider shadow-[0_2px_8px_rgba(0,0,0,0.2),inset_0_1px_1px_rgba(255,255,255,0.1)]",qy[a.status]||qy.pending),children:a.status})]}),p.jsxs("select",{value:(a==null?void 0:a.id)||"",onChange:c,className:"rounded-full border border-white/5 bg-gradient-to-r from-black/30 to-black/20 px-3 py-1.5 text-xs text-slate-200 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus:border-white/15 focus:outline-none focus:ring-1 focus:ring-white/10",children:[s.map(f=>p.jsxs("option",{value:f.id,children:["Request ",o[f.id]||"?"]},f.id)),s.length===0&&p.jsx("option",{value:"",children:"No constellations"})]})]}),p.jsx("div",{className:"flex-1 min-h-0 overflow-hidden",children:p.jsx(qF,{constellation:a,onSelectTask:f=>i(f),variant:"embedded"})})]}),p.jsx("div",{className:"flex flex-1 min-h-0 flex-col gap-3 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-4 overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(6,182,212,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:u?p.jsx(JF,{task:u,onBack:()=>i(null)}):p.jsxs(p.Fragment,{children:[p.jsx("div",{className:"flex items-center justify-between flex-shrink-0",children:p.jsxs("div",{className:"flex items-center gap-2",children:[p.jsx(UC,{className:"h-5 w-5 text-cyan-400 drop-shadow-[0_0_8px_rgba(6,182,212,0.5)]","aria-hidden":!0}),p.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:"TaskStar List"})]})}),p.jsx("div",{className:"flex-1 min-h-0 overflow-hidden",children:p.jsx(QF,{tasks:l,activeTaskId:n.activeTaskId,onSelectTask:f=>i(f)})})]})})]})},eO=e=>{const t=["white","blue","yellow","orange","red"],n=[.35,.3,.2,.1,.05];return Array.from({length:e},(r,i)=>{const s=Math.random();let o=0,a="white";for(let l=0;lArray.from({length:e},(t,n)=>({id:`shooting-${n}`,top:Math.random()*60+10,left:Math.random()*80,width:Math.random()*100+120,opacity:Math.random()*.3+.3})),nO=()=>{const e=T.useMemo(()=>eO(40),[]),t=T.useMemo(()=>tO(3),[]);return p.jsxs("div",{className:"absolute inset-0 overflow-hidden pointer-events-none",children:[e.map(n=>p.jsx("span",{className:"star-static","data-color":n.color,style:{left:`${n.left}%`,top:`${n.top}%`,width:`${n.size}rem`,height:`${n.size}rem`,opacity:n.opacity},"aria-hidden":!0},n.id)),t.map(n=>p.jsx("span",{className:"shooting-star-static",style:{top:`${n.top}%`,left:`${n.left}%`,width:`${n.width}px`,opacity:n.opacity},"aria-hidden":!0},n.id))]})},Gy={connecting:{label:"Connecting",color:"text-cyan-300"},connected:{label:"Connected",color:"text-emerald-300"},reconnecting:{label:"Reconnecting",color:"text-amber-300"},disconnected:{label:"Disconnected",color:"text-rose-300"},idle:{label:"Idle",color:"text-slate-400"}},rO=()=>{const{session:e,connectionStatus:t,ui:n,toggleLeftDrawer:r,toggleRightDrawer:i}=Ce(o=>({session:o.session,connectionStatus:o.connectionStatus,ui:o.ui,toggleLeftDrawer:o.toggleLeftDrawer,toggleRightDrawer:o.toggleRightDrawer}),Oe);T.useEffect(()=>{const o=document.documentElement,a=document.body;e.highContrast?(o.classList.add("high-contrast"),a.classList.add("high-contrast")):(o.classList.remove("high-contrast"),a.classList.remove("high-contrast"))},[e.highContrast]);const s=Gy[t]??Gy.idle;return p.jsxs("div",{className:"relative min-h-screen w-full text-white galaxy-bg",children:[p.jsx("div",{className:"pointer-events-none absolute inset-0",children:p.jsx(nO,{})}),p.jsx("header",{className:"relative z-20 border-b border-white/5 bg-transparent",children:p.jsxs("div",{className:"mx-auto flex max-w-[2560px] items-center justify-between gap-4 px-4 sm:px-6 lg:px-8 py-3",children:[p.jsxs("div",{className:"flex items-center gap-2 lg:hidden",children:[p.jsx("button",{onClick:()=>r(),className:"rounded-lg border border-white/10 bg-white/5 p-2 text-slate-300 transition hover:bg-white/10 hover:text-white","aria-label":"Toggle left sidebar",children:p.jsx(OC,{className:"h-5 w-5"})}),p.jsx("button",{onClick:()=>i(),className:"rounded-lg border border-white/10 bg-white/5 p-2 text-slate-300 transition hover:bg-white/10 hover:text-white","aria-label":"Toggle right sidebar",children:p.jsx(RC,{className:"h-5 w-5"})})]}),p.jsxs("div",{className:"flex items-center gap-2",children:[p.jsx("div",{className:"relative",children:p.jsx("img",{src:"/logo3.png",alt:"UFO3 logo",className:"relative h-12 w-12 sm:h-16 sm:w-16 lg:h-20 lg:w-20 drop-shadow-[0_0_20px_rgba(6,182,212,0.3)]"})}),p.jsx("div",{className:"hidden sm:block",children:p.jsxs("h1",{className:"font-heading text-xl sm:text-2xl lg:text-3xl font-bold tracking-tighter drop-shadow-[0_2px_12px_rgba(0,0,0,0.5)]",children:[p.jsx("span",{className:"text-transparent bg-clip-text bg-gradient-to-r from-cyan-300 via-white to-purple-300",children:"UFO"}),p.jsx("sup",{className:"text-sm sm:text-base lg:text-lg font-semibold text-transparent bg-clip-text bg-gradient-to-r from-cyan-300 via-white to-purple-300 ml-0.5",children:"3"}),p.jsx("span",{className:"ml-2 lg:ml-3 text-base sm:text-lg lg:text-xl font-normal tracking-wide text-transparent bg-clip-text bg-gradient-to-r from-cyan-200 via-purple-200 to-cyan-200 hidden md:inline",children:"Weaving the Digital Agent Galaxy"})]})})]}),p.jsxs("div",{className:"flex items-center gap-3 sm:gap-4 rounded-full border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] to-[rgba(8,15,28,0.85)] px-3 sm:px-5 py-2 sm:py-2.5 shadow-[0_4px_16px_rgba(0,0,0,0.3),0_1px_4px_rgba(15,123,255,0.1),inset_0_1px_1px_rgba(255,255,255,0.06)] ring-1 ring-inset ring-white/5",children:[p.jsx("span",{className:`h-2 w-2 sm:h-2.5 sm:w-2.5 rounded-full shadow-neon ${t==="connected"?"bg-emerald-400 animate-pulse":t==="reconnecting"?"bg-amber-400 animate-pulse":"bg-rose-400"}`}),p.jsxs("div",{className:"flex flex-col leading-tight",children:[p.jsx("span",{className:`text-[10px] sm:text-xs font-medium uppercase tracking-[0.2em] ${s.color}`,children:s.label}),p.jsx("span",{className:"text-[9px] sm:text-[11px] text-slate-400/80",children:e.displayName})]})]})]})}),p.jsxs("main",{className:"relative z-10 mx-auto flex h-[calc(100vh-94px)] max-w-[2560px] gap-4 px-4 sm:px-6 lg:px-8 pb-6 pt-1",children:[n.showLeftDrawer&&p.jsxs("div",{className:"fixed inset-0 z-50 lg:hidden",children:[p.jsx("div",{className:"absolute inset-0 bg-black/60 backdrop-blur-sm",onClick:()=>r(!1)}),p.jsxs("div",{className:"absolute left-0 top-0 h-full w-80 max-w-[85vw] bg-[#0a0e1a] shadow-2xl animate-slide-in-left",children:[p.jsxs("div",{className:"flex items-center justify-between border-b border-white/10 p-4",children:[p.jsx("h2",{className:"text-lg font-semibold text-white",children:"Devices"}),p.jsx("button",{onClick:()=>r(!1),className:"rounded-lg p-1.5 text-slate-400 transition hover:bg-white/5 hover:text-white",children:p.jsx(Vi,{className:"h-5 w-5"})})]}),p.jsx("div",{className:"h-[calc(100%-64px)] overflow-y-auto",children:p.jsx(ty,{})})]})]}),n.showRightDrawer&&p.jsxs("div",{className:"fixed inset-0 z-50 lg:hidden",children:[p.jsx("div",{className:"absolute inset-0 bg-black/60 backdrop-blur-sm",onClick:()=>i(!1)}),p.jsxs("div",{className:"absolute right-0 top-0 h-full w-96 max-w-[90vw] bg-[#0a0e1a] shadow-2xl animate-slide-in-right",children:[p.jsxs("div",{className:"flex items-center justify-between border-b border-white/10 p-4",children:[p.jsx("h2",{className:"text-lg font-semibold text-white",children:"Constellation"}),p.jsx("button",{onClick:()=>i(!1),className:"rounded-lg p-1.5 text-slate-400 transition hover:bg-white/5 hover:text-white",children:p.jsx(Vi,{className:"h-5 w-5"})})]}),p.jsx("div",{className:"h-[calc(100%-64px)] overflow-y-auto",children:p.jsx(Ky,{})})]})]}),p.jsx("div",{className:"hidden xl:flex xl:w-72 2xl:w-80",children:p.jsx(ty,{})}),p.jsx("div",{className:"flex min-w-0 flex-1 flex-col",children:p.jsx(HI,{})}),p.jsx("div",{className:"hidden lg:flex lg:w-[520px] xl:w-[560px] 2xl:w-[640px]",children:p.jsx(Ky,{})})]}),p.jsx(eL,{})]})},jp=sn();jp.onStatusChange(e=>{const t=Ce.getState();switch(e){case"connected":t.setConnectionStatus("connected");break;case"connecting":t.setConnectionStatus("connecting");break;case"reconnecting":t.setConnectionStatus("reconnecting");break;case"disconnected":t.setConnectionStatus("disconnected");break}});const ro=e=>e!=null&&e.timestamp?Math.round(e.timestamp*1e3):Date.now(),Yc=e=>{if(!e)return;const t=Date.parse(e);return Number.isNaN(t)?void 0:t},Yi=e=>{try{const t={...e};if(t.thought&&typeof t.thought=="string"&&t.thought.length>100){let r=100;const i=[". ",`. +`,"! ",`! +`,"? ",`? +`];for(const s of i){const o=t.thought.lastIndexOf(s,100);if(o>100*.7){r=o+s.length;break}}t.thought=t.thought.substring(0,r).trim()+`... [Truncated: ${t.thought.length} chars total]`}return JSON.stringify(t,null,2)}catch(t){return console.error("Failed to stringify payload",t),String(e)}},iO=e=>e.map(t=>`- ${typeof t=="string"?t:Yi(t)}`).join(` +`),sO=e=>{if(!e)return"Agent responded.";if(typeof e=="string"){if(e.length>100){let r=100;const i=[". ",`. +`,"! ",`! +`,"? ",`? +`];for(const o of i){const a=e.lastIndexOf(o,100);if(a>100*.7){r=a+o.length;break}}return`${e.substring(0,r).trim()}... + +_[Truncated: ${e.length} chars total]_`}return e}const t=[];if(e.thought){const n=String(e.thought),r=100;if(n.length>r){let i=r;const s=[". ",`. +`,"! ",`! +`,"? ",`? +`];for(const a of s){const l=n.lastIndexOf(a,r);if(l>r*.7){i=l+a.length;break}}const o=n.substring(0,i).trim();t.push(`**💭 Thought** +${o}... + +_[Truncated: ${n.length} chars total]_`)}else t.push(`**💭 Thought** +${n}`)}if(e.plan){const n=Array.isArray(e.plan)?iO(e.plan):e.plan;t.push(`**📋 Plan** +${n}`)}return e.actions_summary&&t.push(`**⚡ Actions Summary** +${e.actions_summary}`),e.response&&t.push(`${e.response}`),e.final_response&&t.push(`${e.final_response}`),t.length===0&&e.message&&t.push(String(e.message)),t.length===0&&t.push(Yi(e)),t.join(` + +`)},oO=e=>e?Array.isArray(e.actions)?e.actions.map((n,r)=>{const i=n.description||n.name||`Action ${r+1}`,s=n.target_device_id?` _(device: ${n.target_device_id})_`:"";return`**${i}**${s} +${Yi(n.parameters??n)}`}).join(` + +`):e.action_type||e.name?`**${e.action_type||e.name}** +${Yi(e)}`:Yi(e):"Action executed.",g2=e=>{var n;const t=e.data||{};return t.constellation||t.updated_constellation||t.new_constellation||((n=e.output_data)==null?void 0:n.constellation)||null},Mp=e=>{var l,u;const t=g2(e);if(!t)return;const n=Ce.getState(),r=t.constellation_id||e.constellation_id||n.ensureSession(),i=t.dependencies||{},s=[];t.tasks&&Object.entries(t.tasks).forEach(([c,f])=>{const d=f,h=d.task_id||c;s.push({id:h,constellationId:r,name:d.name||h,description:d.description,status:d.status,deviceId:d.target_device_id||d.device_id,input:d.input,output:d.output,result:d.result,error:d.error,startedAt:Yc(d.started_at),completedAt:Yc(d.completed_at),logs:Array.isArray(d.logs)?d.logs.map((g,y)=>({id:`${h}-log-${y}`,timestamp:Date.now(),level:g.level||"info",message:g.message||Yi(g),payload:g.payload})):[]})}),n.bulkUpsertTasks(r,s,i);const o=s.map(c=>({id:c.id,label:c.name||c.id,status:c.status,deviceId:c.deviceId})),a=Object.entries(i).flatMap(([c,f])=>Array.isArray(f)?f.map(d=>({id:`${d}->${c}`,source:d,target:c})):[]);n.upsertConstellation({id:r,name:t.name||r,status:t.state||e.constellation_state||"running",description:t.description,metadata:{...t.metadata||{},statistics:t.statistics,execution_start_time:(l=t.metadata)==null?void 0:l.execution_start_time,execution_end_time:(u=t.metadata)==null?void 0:u.execution_end_time},createdAt:Yc(t.created_at),taskIds:s.map(c=>c.id),dag:{nodes:o,edges:a}})},jd=e=>{Ce.getState().pushNotification({id:ts(),timestamp:Date.now(),read:!1,...e})},aO=e=>{var s,o,a;const t=Ce.getState();if(t.ui.isTaskStopped){console.log("⚠️ Ignoring agent response - task was stopped by user");return}const n=t.ensureSession(((s=e.data)==null?void 0:s.session_id)||null),r=sO(e.output_data);t.addMessage({id:ts(),sessionId:n,role:"assistant",kind:"response",author:e.agent_name||"Galaxy Agent",content:r,payload:e.output_data,timestamp:ro(e),agentName:e.agent_name}),Mp(e);const i=(a=(o=e.output_data)==null?void 0:o.status)==null?void 0:a.toLowerCase();(i==="finish"||i==="fail")&&t.setTaskRunning(!1)},lO=e=>{var i,s;const t=Ce.getState();if(t.ui.isTaskStopped){console.log("⚠️ Ignoring agent action - task was stopped by user");return}const n=t.ensureSession(((i=e.data)==null?void 0:i.session_id)||null),r=oO(e.output_data);t.addMessage({id:ts(),sessionId:n,role:"assistant",kind:"action",author:e.agent_name||"Galaxy Agent",content:r,payload:e.output_data,timestamp:ro(e),agentName:e.agent_name,actionType:(s=e.output_data)==null?void 0:s.action_type})},uO=e=>{var i,s,o,a,l,u,c,f,d,h;const t=Ce.getState(),n=e.constellation_id||((i=e.data)==null?void 0:i.constellation_id)||((s=g2(e))==null?void 0:s.constellation_id);if(!e.task_id||!n)return;const r={status:e.status,result:e.result??((o=e.data)==null?void 0:o.result),error:e.error??((a=e.data)==null?void 0:a.error)??null,deviceId:((l=e.data)==null?void 0:l.device_id)??((u=e.data)==null?void 0:u.deviceId)};if(e.event_type==="task_completed"&&(r.completedAt=ro(e)),e.event_type==="task_started"&&(r.startedAt=ro(e)),t.updateTask(e.task_id,r),(c=e.data)!=null&&c.log_entry){const g=e.data.log_entry;t.appendTaskLog(e.task_id,g)}else(f=e.data)!=null&&f.message&&t.appendTaskLog(e.task_id,{id:`${e.task_id}-${e.event_type}-${Date.now()}`,timestamp:ro(e),level:e.event_type==="task_failed"?"error":"info",message:e.data.message,payload:e.data});e.event_type==="task_failed"&&jd({severity:"error",title:`Task ${e.task_id} failed`,description:((d=e.error)==null?void 0:d.toString())||"A task reported a failure.",source:n}),(e.event_type==="task_completed"||e.event_type==="task_failed")&&((h=e.data)!=null&&h.constellation)&&(Mp(e),console.log(`🔄 Updated constellation from ${e.event_type} event`))},cO=e=>{if(Mp(e),e.event_type==="constellation_started"){const t=Ce.getState(),n=e.constellation_id;n&&(Object.keys(t.constellations).forEach(r=>{r.startsWith("temp-")&&(t.removeConstellation(r),console.log(`🗑️ Removed temporary constellation: ${r}`))}),t.setActiveConstellation(n),console.log(`🌟 Auto-switched to new constellation: ${n}`))}e.event_type==="constellation_completed"&&jd({severity:"success",title:"Constellation completed",description:`Constellation ${e.constellation_id||""} finished execution successfully.`,source:e.constellation_id}),e.event_type==="constellation_failed"&&jd({severity:"error",title:"Constellation failed",description:`Constellation ${e.constellation_id||""} reported a failure.`,source:e.constellation_id})},fO=e=>{var a,l,u,c,f;console.log("📱 Device event received:",{event_type:e.event_type,device_id:e.device_id,device_status:e.device_status,device_info_status:(a=e.device_info)==null?void 0:a.status,full_event:e});const t=Ce.getState(),n=e.all_devices||((l=e.data)==null?void 0:l.all_devices);n&&e.event_type==="device_snapshot"&&t.setDevicesFromSnapshot(n);const r=e.device_info||((u=e.data)==null?void 0:u.device_info)||{},i=e.device_id||r.device_id||((c=e.data)==null?void 0:c.device_id)||null;if(!i)return;const{statusChanged:s,previousStatus:o}=t.upsertDevice({id:i,name:r.device_id||i,status:e.device_status||r.status,os:r.os,serverUrl:r.server_url,capabilities:r.capabilities,metadata:r.metadata,lastHeartbeat:r.last_heartbeat,connectionAttempts:r.connection_attempts,maxRetries:r.max_retries,currentTaskId:r.current_task_id,tags:(f=r.metadata)==null?void 0:f.tags,metrics:r.metrics});console.log("📱 Device upserted:",{deviceId:i,statusChanged:s,previousStatus:o,newStatus:e.device_status||r.status}),window.setTimeout(()=>{Ce.getState().clearDeviceHighlight(i)},4e3)},dO=e=>{var n;const t=e.type||e.event_type;if(t==="reset_acknowledged"){console.log("✅ Session reset acknowledged:",e),Ce.getState().pushNotification({id:`reset-${Date.now()}`,title:"Session Reset",description:e.message||"Session has been reset successfully",severity:"success",timestamp:Date.now(),read:!1});return}if(t==="next_session_acknowledged"){console.log("✅ Next session acknowledged:",e),Ce.getState().pushNotification({id:`next-session-${Date.now()}`,title:"New Session",description:e.message||"New session created successfully",severity:"success",timestamp:Date.now(),read:!1});return}if(t==="stop_acknowledged"){console.log("✅ Task stop acknowledged:",e),Ce.getState().pushNotification({id:`stop-task-${Date.now()}`,title:"Task Stopped",description:e.message||"Task stopped and new session created",severity:"info",timestamp:Date.now(),read:!1});return}if((n=e.event_type)!=null&&n.startsWith("device_")){fO(e);return}switch(e.event_type){case"agent_response":aO(e);break;case"agent_action":lO(e);break;case"constellation_started":case"constellation_modified":case"constellation_completed":case"constellation_failed":cO(e);break;case"task_started":case"task_completed":case"task_failed":uO(e);break}};jp.connect().catch(e=>{console.error("❌ Failed to connect to Galaxy WebSocket server:",e),Ce.getState().setConnectionStatus("disconnected")});jp.onEvent(e=>{Ce.getState().addEventToLog(e),dO(e)});qc.createRoot(document.getElementById("root")).render(p.jsx(B.StrictMode,{children:p.jsx(rO,{})})); diff --git a/galaxy/webui/frontend/dist/index.html b/galaxy/webui/frontend/dist/index.html index a96301be4..f5faf1617 100644 --- a/galaxy/webui/frontend/dist/index.html +++ b/galaxy/webui/frontend/dist/index.html @@ -8,8 +8,8 @@ 🌌 Galaxy WebUI - Weaving the Digital Agent Galaxy - - + +
    diff --git a/galaxy/webui/frontend/src/components/devices/AddDeviceModal.tsx b/galaxy/webui/frontend/src/components/devices/AddDeviceModal.tsx new file mode 100644 index 000000000..2d8aa90c3 --- /dev/null +++ b/galaxy/webui/frontend/src/components/devices/AddDeviceModal.tsx @@ -0,0 +1,475 @@ +import React, { useState, useCallback, useMemo } from 'react'; +import { X, Plus, Loader2 } from 'lucide-react'; +import clsx from 'clsx'; + +interface AddDeviceModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (device: DeviceFormData) => Promise; + existingDeviceIds: string[]; +} + +export interface DeviceFormData { + device_id: string; + server_url: string; + os: string; + capabilities: string[]; + metadata?: Record; + auto_connect?: boolean; + max_retries?: number; +} + +const AddDeviceModal: React.FC = ({ + isOpen, + onClose, + onSubmit, + existingDeviceIds, +}) => { + const [formData, setFormData] = useState({ + device_id: '', + server_url: '', + os: '', + capabilities: [], + metadata: {}, + auto_connect: true, + max_retries: 5, + }); + + const [capabilityInput, setCapabilityInput] = useState(''); + const [metadataKey, setMetadataKey] = useState(''); + const [metadataValue, setMetadataValue] = useState(''); + const [errors, setErrors] = useState>({}); + const [isSubmitting, setIsSubmitting] = useState(false); + const [showCustomOS, setShowCustomOS] = useState(false); + const [customOS, setCustomOS] = useState(''); + + const validateForm = (): boolean => { + const newErrors: Record = {}; + + // Device ID validation + if (!formData.device_id.trim()) { + newErrors.device_id = 'Device ID is required'; + } else if (existingDeviceIds.includes(formData.device_id.trim())) { + newErrors.device_id = 'Device ID already exists'; + } + + // Server URL validation + if (!formData.server_url.trim()) { + newErrors.server_url = 'Server URL is required'; + } else if (!formData.server_url.match(/^wss?:\/\/.+/)) { + newErrors.server_url = 'Invalid WebSocket URL (must start with ws:// or wss://)'; + } + + // OS validation + if (!formData.os.trim()) { + newErrors.os = 'OS is required'; + } + + // Capabilities validation + if (formData.capabilities.length === 0) { + newErrors.capabilities = 'At least one capability is required'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!validateForm()) { + return; + } + + setIsSubmitting(true); + try { + await onSubmit(formData); + handleClose(); + } catch (error) { + setErrors({ submit: error instanceof Error ? error.message : 'Failed to add device' }); + } finally { + setIsSubmitting(false); + } + }; + + const handleClose = useCallback(() => { + setFormData({ + device_id: '', + server_url: '', + os: '', + capabilities: [], + metadata: {}, + auto_connect: true, + max_retries: 5, + }); + setCapabilityInput(''); + setMetadataKey(''); + setMetadataValue(''); + setErrors({}); + setIsSubmitting(false); + setShowCustomOS(false); + setCustomOS(''); + onClose(); + }, [onClose]); + + const addCapability = useCallback(() => { + if (capabilityInput.trim() && !formData.capabilities.includes(capabilityInput.trim())) { + setFormData((prev) => ({ + ...prev, + capabilities: [...prev.capabilities, capabilityInput.trim()], + })); + setCapabilityInput(''); + setErrors((prev) => ({ ...prev, capabilities: '' })); + } + }, [capabilityInput, formData.capabilities]); + + const removeCapability = useCallback((capability: string) => { + setFormData((prev) => ({ + ...prev, + capabilities: prev.capabilities.filter((c) => c !== capability), + })); + }, []); + + const addMetadata = useCallback(() => { + if (metadataKey.trim() && metadataValue.trim()) { + setFormData((prev) => ({ + ...prev, + metadata: { + ...prev.metadata, + [metadataKey.trim()]: metadataValue.trim(), + }, + })); + setMetadataKey(''); + setMetadataValue(''); + } + }, [metadataKey, metadataValue]); + + const removeMetadata = useCallback((key: string) => { + setFormData((prev) => { + const newMetadata = { ...prev.metadata }; + delete newMetadata[key]; + return { + ...prev, + metadata: newMetadata, + }; + }); + }, []); + + // Memoize metadata entries to avoid re-renders + const metadataEntries = useMemo(() => + Object.entries(formData.metadata || {}), + [formData.metadata] + ); + + if (!isOpen) return null; + + return ( +
    + {/* Backdrop - Solid color, no blur */} +
    + + {/* Modal - Lightweight design */} +
    + {/* Header */} +
    +

    + Add New Device +

    + +
    + +
    + {/* Device ID */} +
    + + setFormData({ ...formData, device_id: e.target.value })} + placeholder="e.g., windows_agent_01" + className={clsx( + 'w-full rounded-lg border bg-slate-800/80 px-4 py-3 text-sm text-white placeholder-slate-500 transition-colors focus:outline-none', + errors.device_id + ? 'border-rose-500/50 focus:border-rose-400 focus:ring-2 focus:ring-rose-400/30' + : 'border-slate-700 focus:border-cyan-400 focus:ring-2 focus:ring-cyan-400/30' + )} + /> + {errors.device_id && ( +

    {errors.device_id}

    + )} +
    + + {/* Server URL */} +
    + + setFormData({ ...formData, server_url: e.target.value })} + placeholder="ws://localhost:5001/ws" + className={clsx( + 'w-full rounded-lg border bg-slate-800/80 px-4 py-3 text-sm text-white placeholder-slate-500 transition-colors focus:outline-none', + errors.server_url + ? 'border-rose-500/50 focus:border-rose-400 focus:ring-2 focus:ring-rose-400/30' + : 'border-slate-700 focus:border-cyan-400 focus:ring-2 focus:ring-cyan-400/30' + )} + /> + {errors.server_url && ( +

    {errors.server_url}

    + )} +
    + + {/* OS */} +
    + + + + {/* Custom OS Input */} + {showCustomOS && ( + { + setCustomOS(e.target.value); + setFormData({ ...formData, os: e.target.value }); + }} + placeholder="Enter custom OS name" + className="mt-2 w-full rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-3 text-sm text-white placeholder-slate-500 transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30" + autoFocus + /> + )} + + {errors.os &&

    {errors.os}

    } +
    + + {/* Capabilities */} +
    + +
    + setCapabilityInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + addCapability(); + } + }} + placeholder="e.g., web_browsing" + className="flex-1 rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-3 text-sm text-white placeholder-slate-500 transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30" + /> + +
    + {errors.capabilities && ( +

    {errors.capabilities}

    + )} + {formData.capabilities.length > 0 && ( +
    + {formData.capabilities.map((capability) => ( + + {capability} + + + ))} +
    + )} +
    + + {/* Metadata (Optional) */} +
    + +
    + setMetadataKey(e.target.value)} + placeholder="Key" + className="flex-1 rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-2.5 text-sm text-white placeholder-slate-500 transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30" + /> + setMetadataValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + addMetadata(); + } + }} + placeholder="Value" + className="flex-1 rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-2.5 text-sm text-white placeholder-slate-500 transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30" + /> + +
    + {metadataEntries.length > 0 && ( +
    + {metadataEntries.map(([key, value]) => ( +
    + + {key}: {String(value)} + + +
    + ))} +
    + )} +
    + + {/* Advanced Options */} +
    +
    + + +
    +
    + + + setFormData({ ...formData, max_retries: parseInt(e.target.value) || 5 }) + } + className="w-full rounded-lg border border-slate-700 bg-slate-800/80 px-4 py-2.5 text-sm text-white transition-colors focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/30" + /> +
    +
    + + {/* Submit Error */} + {errors.submit && ( +
    + {errors.submit} +
    + )} + + {/* Actions */} +
    + + +
    +
    +
    +
    + ); +}; + +export default AddDeviceModal; diff --git a/galaxy/webui/frontend/src/components/devices/DevicePanel.tsx b/galaxy/webui/frontend/src/components/devices/DevicePanel.tsx index 8c00a0782..9c9db5c01 100644 --- a/galaxy/webui/frontend/src/components/devices/DevicePanel.tsx +++ b/galaxy/webui/frontend/src/components/devices/DevicePanel.tsx @@ -1,8 +1,10 @@ import React, { useMemo, useState } from 'react'; import { shallow } from 'zustand/shallow'; -import { Cpu, WifiOff, Search, Clock, Bot } from 'lucide-react'; +import { Cpu, WifiOff, Search, Clock, Bot, Plus } from 'lucide-react'; import clsx from 'clsx'; import { Device, DeviceStatus, useGalaxyStore } from '../../store/galaxyStore'; +import AddDeviceModal, { DeviceFormData } from './AddDeviceModal'; +import { getApiUrl } from '../../config/api'; const statusMeta: Record = { connected: { label: 'Connected', dot: 'bg-emerald-400', text: 'text-emerald-300' }, @@ -97,6 +99,7 @@ const DevicePanel: React.FC = () => { ); const [query, setQuery] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); const deviceList = useMemo(() => { const list = Object.values(devices); @@ -115,6 +118,31 @@ const DevicePanel: React.FC = () => { const total = deviceList.length; const online = deviceList.filter((device) => device.status === 'connected' || device.status === 'idle' || device.status === 'busy').length; + const handleAddDevice = async (deviceData: DeviceFormData) => { + // Send device data to backend API + try { + const response = await fetch(getApiUrl('api/devices'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(deviceData), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.message || 'Failed to add device'); + } + + // Success - modal will close automatically + } catch (error) { + // Rethrow to let modal handle the error display + throw error; + } + }; + + const existingDeviceIds = Object.keys(devices); + return (
    @@ -125,6 +153,14 @@ const DevicePanel: React.FC = () => { {online}/{total} online
    +
    @@ -148,6 +184,13 @@ const DevicePanel: React.FC = () => { deviceList.map((device) => ) )}
    + + setIsModalOpen(false)} + onSubmit={handleAddDevice} + existingDeviceIds={existingDeviceIds} + /> ); }; diff --git a/galaxy/webui/frontend/src/config/api.ts b/galaxy/webui/frontend/src/config/api.ts new file mode 100644 index 000000000..3f9068525 --- /dev/null +++ b/galaxy/webui/frontend/src/config/api.ts @@ -0,0 +1,28 @@ +// API configuration +// Auto-detect API base URL based on environment + +function getApiBaseUrl(): string { + // In production, API is served from the same origin + if (import.meta.env.PROD) { + return ''; + } + + // In development, check for environment variable first + const envBackendUrl = import.meta.env.VITE_BACKEND_URL; + if (envBackendUrl) { + return envBackendUrl; + } + + // Default to localhost:8000 in development + // This can be overridden by setting VITE_BACKEND_URL environment variable + return 'http://localhost:8000'; +} + +export const API_BASE_URL = getApiBaseUrl(); + +// Helper function to construct full API URLs +export function getApiUrl(path: string): string { + // Remove leading slash if present to avoid double slashes + const cleanPath = path.startsWith('/') ? path.slice(1) : path; + return `${API_BASE_URL}/${cleanPath}`; +} diff --git a/galaxy/webui/frontend/vite.config.ts b/galaxy/webui/frontend/vite.config.ts index 5572c118b..c72ca71da 100644 --- a/galaxy/webui/frontend/vite.config.ts +++ b/galaxy/webui/frontend/vite.config.ts @@ -6,15 +6,5 @@ export default defineConfig({ plugins: [react()], server: { port: 3000, - proxy: { - '/api': { - target: 'http://localhost:8000', - changeOrigin: true, - }, - '/ws': { - target: 'ws://localhost:8000', - ws: true, - }, - }, }, }) diff --git a/galaxy/webui/handlers/__init__.py b/galaxy/webui/handlers/__init__.py new file mode 100644 index 000000000..10197d671 --- /dev/null +++ b/galaxy/webui/handlers/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Handlers for Galaxy Web UI. + +This package contains handlers for processing various types of messages +and requests, particularly WebSocket message handlers. +""" + +from galaxy.webui.handlers.websocket_handlers import WebSocketMessageHandler + +__all__ = [ + "WebSocketMessageHandler", +] diff --git a/galaxy/webui/handlers/websocket_handlers.py b/galaxy/webui/handlers/websocket_handlers.py new file mode 100644 index 000000000..5de292f84 --- /dev/null +++ b/galaxy/webui/handlers/websocket_handlers.py @@ -0,0 +1,317 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +WebSocket message handlers for Galaxy Web UI. + +This module contains handlers for processing different types of WebSocket +messages from clients, implementing the business logic for each message type. +""" + +import asyncio +import logging +import time +from typing import Any, Dict + +from fastapi import WebSocket + +from galaxy.webui.dependencies import AppState +from galaxy.webui.models.enums import WebSocketMessageType, RequestStatus +from galaxy.webui.services import DeviceService, GalaxyService + + +class WebSocketMessageHandler: + """ + Handler for processing WebSocket messages. + + This class encapsulates the logic for handling different types of + WebSocket messages, separating concerns and improving testability. + """ + + def __init__(self, app_state: AppState) -> None: + """ + Initialize the WebSocket message handler. + + :param app_state: Application state containing Galaxy client references + """ + self.app_state = app_state + self.galaxy_service = GalaxyService(app_state) + self.device_service = DeviceService(app_state) + self.logger: logging.Logger = logging.getLogger(__name__) + + async def handle_message(self, websocket: WebSocket, data: dict) -> None: + """ + Route incoming WebSocket messages to appropriate handlers. + + :param websocket: The WebSocket connection + :param data: The message data from client + """ + message_type: str = data.get("type", "") + self.logger.info(f"Received message - Type: {message_type}, Full data: {data}") + + # Route to specific handler based on message type + if message_type == WebSocketMessageType.PING: + await self._handle_ping(websocket, data) + elif message_type == WebSocketMessageType.REQUEST: + await self._handle_request(websocket, data) + elif message_type == WebSocketMessageType.RESET: + await self._handle_reset(websocket, data) + elif message_type == WebSocketMessageType.NEXT_SESSION: + await self._handle_next_session(websocket, data) + elif message_type == WebSocketMessageType.STOP_TASK: + await self._handle_stop_task(websocket, data) + else: + await self._handle_unknown(websocket, message_type) + + async def _handle_ping(self, websocket: WebSocket, data: dict) -> None: + """ + Handle ping message by responding with pong. + + Provides health check functionality for clients to verify + the server is responsive. + + :param websocket: The WebSocket connection + :param data: The ping message data + """ + await websocket.send_json( + { + "type": WebSocketMessageType.PONG, + "timestamp": asyncio.get_event_loop().time(), + } + ) + self.logger.debug("Responded to ping with pong") + + async def _handle_request(self, websocket: WebSocket, data: dict) -> None: + """ + Handle user request to process a natural language command. + + Sends immediate acknowledgment and processes the request in the background, + sending completion or failure messages when done. + + :param websocket: The WebSocket connection + :param data: The request message data containing 'text' field + """ + request_text: str = data.get("text", "") + self.logger.info(f"Received request: {request_text}") + + if not self.galaxy_service.is_client_available(): + await websocket.send_json( + { + "type": WebSocketMessageType.ERROR, + "message": "Galaxy client not initialized", + } + ) + return + + # Send immediate acknowledgment to client + await websocket.send_json( + { + "type": WebSocketMessageType.REQUEST_RECEIVED, + "request": request_text, + "status": RequestStatus.PROCESSING, + } + ) + + # Process request in background task + async def process_in_background() -> None: + try: + result = await self.galaxy_service.process_request(request_text) + await websocket.send_json( + { + "type": WebSocketMessageType.REQUEST_COMPLETED, + "request": request_text, + "status": RequestStatus.COMPLETED, + "result": str(result), + } + ) + except Exception as e: + self.logger.error(f"❌ Error processing request: {e}", exc_info=True) + await websocket.send_json( + { + "type": WebSocketMessageType.REQUEST_FAILED, + "request": request_text, + "status": RequestStatus.FAILED, + "error": str(e), + } + ) + + # Start background task + asyncio.create_task(process_in_background()) + + async def _handle_reset(self, websocket: WebSocket, data: dict) -> None: + """ + Handle session reset request. + + Resets the current Galaxy session and clears state. + + :param websocket: The WebSocket connection + :param data: The reset message data + """ + self.logger.info("Received reset request") + + if not self.galaxy_service.is_client_available(): + await websocket.send_json( + { + "type": WebSocketMessageType.RESET_ACKNOWLEDGED, + "status": RequestStatus.WARNING, + "message": "No active client to reset", + } + ) + return + + try: + result = await self.galaxy_service.reset_session() + await websocket.send_json( + { + "type": WebSocketMessageType.RESET_ACKNOWLEDGED, + "status": result.get("status", RequestStatus.SUCCESS), + "message": result.get("message", "Session reset"), + "timestamp": result.get("timestamp"), + } + ) + except Exception as e: + self.logger.error(f"Failed to reset session: {e}", exc_info=True) + await websocket.send_json( + { + "type": WebSocketMessageType.ERROR, + "message": f"Failed to reset session: {str(e)}", + } + ) + + async def _handle_next_session(self, websocket: WebSocket, data: dict) -> None: + """ + Handle next session creation request. + + Creates a new Galaxy session while potentially maintaining some context. + + :param websocket: The WebSocket connection + :param data: The next session message data + """ + self.logger.info("Received next_session request") + + if not self.galaxy_service.is_client_available(): + await websocket.send_json( + { + "type": WebSocketMessageType.ERROR, + "message": "Galaxy client not initialized", + } + ) + return + + try: + result = await self.galaxy_service.create_next_session() + await websocket.send_json( + { + "type": WebSocketMessageType.NEXT_SESSION_ACKNOWLEDGED, + "status": result.get("status", RequestStatus.SUCCESS), + "message": result.get("message", "Next session created"), + "session_name": result.get("session_name"), + "task_name": result.get("task_name"), + "timestamp": result.get("timestamp"), + } + ) + except Exception as e: + self.logger.error(f"Failed to create next session: {e}", exc_info=True) + await websocket.send_json( + { + "type": WebSocketMessageType.ERROR, + "message": f"Failed to create next session: {str(e)}", + } + ) + + async def _handle_stop_task(self, websocket: WebSocket, data: dict) -> None: + """ + Handle task stop/cancel request. + + Shuts down the Galaxy client to clean up device tasks, then reinitializes + the client and creates a new session. + + :param websocket: The WebSocket connection + :param data: The stop task message data + """ + self.logger.info("Received stop_task request") + + if not self.galaxy_service.is_client_available(): + self.logger.warning("No active galaxy client to stop") + await websocket.send_json( + { + "type": WebSocketMessageType.STOP_ACKNOWLEDGED, + "status": RequestStatus.WARNING, + "message": "No active task to stop", + "timestamp": time.time(), + } + ) + return + + try: + new_session_result = await self.galaxy_service.stop_task_and_restart() + await websocket.send_json( + { + "type": WebSocketMessageType.STOP_ACKNOWLEDGED, + "status": RequestStatus.SUCCESS, + "message": "Task stopped and client restarted", + "session_name": new_session_result.get("session_name"), + "timestamp": time.time(), + } + ) + except Exception as e: + self.logger.error( + f"Failed to stop task and restart client: {e}", exc_info=True + ) + await websocket.send_json( + { + "type": WebSocketMessageType.ERROR, + "message": f"Failed to stop task: {str(e)}", + } + ) + + async def _handle_unknown(self, websocket: WebSocket, message_type: str) -> None: + """ + Handle unknown message types. + + Logs a warning and sends an error response to the client. + + :param websocket: The WebSocket connection + :param message_type: The unknown message type + """ + self.logger.warning(f"Unknown message type: {message_type}") + await websocket.send_json( + { + "type": WebSocketMessageType.ERROR, + "message": f"Unknown message type: {message_type}", + } + ) + + async def send_welcome_message(self, websocket: WebSocket) -> None: + """ + Send welcome message to newly connected client. + + Sends a welcome message and initial device snapshot to help + the UI render current state immediately. + + :param websocket: The WebSocket connection + """ + # Send welcome message + await websocket.send_json( + { + "type": WebSocketMessageType.WELCOME, + "message": "Connected to Galaxy Web UI", + "timestamp": asyncio.get_event_loop().time(), + } + ) + + # Send initial device snapshot + device_snapshot = self.device_service.build_device_snapshot() + if device_snapshot: + await websocket.send_json( + { + "event_type": "device_snapshot", + "source_id": "webui.server", + "timestamp": time.time(), + "data": { + "event_name": "device_snapshot", + "device_count": len(device_snapshot), + }, + "all_devices": device_snapshot, + } + ) diff --git a/galaxy/webui/models/__init__.py b/galaxy/webui/models/__init__.py new file mode 100644 index 000000000..d94eee719 --- /dev/null +++ b/galaxy/webui/models/__init__.py @@ -0,0 +1,63 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Data models for Galaxy Web UI. + +This package contains Pydantic models and enums used throughout the Web UI. +""" + +from galaxy.webui.models.enums import ( + WebSocketMessageType, + RequestStatus, +) +from galaxy.webui.models.requests import ( + DeviceAddRequest, + WebSocketMessage, + RequestMessage, + ResetMessage, + NextSessionMessage, + StopTaskMessage, + PingMessage, +) +from galaxy.webui.models.responses import ( + StandardResponse, + HealthResponse, + DeviceAddResponse, + WelcomeMessage, + RequestReceivedMessage, + RequestCompletedMessage, + RequestFailedMessage, + ResetAcknowledgedMessage, + NextSessionAcknowledgedMessage, + StopAcknowledgedMessage, + PongMessage, + ErrorMessage, +) + +__all__ = [ + # Enums + "WebSocketMessageType", + "RequestStatus", + # Requests + "DeviceAddRequest", + "WebSocketMessage", + "RequestMessage", + "ResetMessage", + "NextSessionMessage", + "StopTaskMessage", + "PingMessage", + # Responses + "StandardResponse", + "HealthResponse", + "DeviceAddResponse", + "WelcomeMessage", + "RequestReceivedMessage", + "RequestCompletedMessage", + "RequestFailedMessage", + "ResetAcknowledgedMessage", + "NextSessionAcknowledgedMessage", + "StopAcknowledgedMessage", + "PongMessage", + "ErrorMessage", +] diff --git a/galaxy/webui/models/enums.py b/galaxy/webui/models/enums.py new file mode 100644 index 000000000..4875d09ed --- /dev/null +++ b/galaxy/webui/models/enums.py @@ -0,0 +1,51 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Enumerations for Galaxy Web UI. + +This module defines all enum types used in the Web UI for type safety +and standardization of string constants. +""" + +from enum import Enum + + +class WebSocketMessageType(str, Enum): + """ + Types of WebSocket messages exchanged between client and server. + + These message types define the protocol for communication over WebSocket. + """ + + # Client -> Server messages + PING = "ping" + REQUEST = "request" + RESET = "reset" + NEXT_SESSION = "next_session" + STOP_TASK = "stop_task" + + # Server -> Client messages + PONG = "pong" + WELCOME = "welcome" + REQUEST_RECEIVED = "request_received" + REQUEST_COMPLETED = "request_completed" + REQUEST_FAILED = "request_failed" + RESET_ACKNOWLEDGED = "reset_acknowledged" + NEXT_SESSION_ACKNOWLEDGED = "next_session_acknowledged" + STOP_ACKNOWLEDGED = "stop_acknowledged" + ERROR = "error" + + +class RequestStatus(str, Enum): + """ + Status of a user request being processed. + + These statuses track the lifecycle of a request from submission to completion. + """ + + PROCESSING = "processing" + COMPLETED = "completed" + FAILED = "failed" + SUCCESS = "success" + WARNING = "warning" diff --git a/galaxy/webui/models/requests.py b/galaxy/webui/models/requests.py new file mode 100644 index 000000000..ee0e23085 --- /dev/null +++ b/galaxy/webui/models/requests.py @@ -0,0 +1,108 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Request models for Galaxy Web UI. + +This module defines Pydantic models for all incoming requests, +both HTTP API requests and WebSocket messages. +""" + +from typing import Any, Dict, List, Literal, Optional + +from pydantic import BaseModel, Field + +from galaxy.webui.models.enums import WebSocketMessageType + + +class DeviceAddRequest(BaseModel): + """ + Request model for adding a new device to the Galaxy configuration. + + This model is used for the POST /api/devices endpoint to validate + and structure device registration data. + """ + + device_id: str = Field(..., description="Unique identifier for the device") + server_url: str = Field(..., description="URL of the device's server endpoint") + os: str = Field( + ..., description="Operating system of the device (e.g., 'Windows', 'Linux')" + ) + capabilities: List[str] = Field( + ..., description="List of capabilities the device supports" + ) + metadata: Optional[Dict[str, Any]] = Field( + None, description="Additional metadata about the device" + ) + auto_connect: Optional[bool] = Field( + True, description="Whether to automatically connect to the device" + ) + max_retries: Optional[int] = Field( + 5, description="Maximum number of connection retry attempts" + ) + + +class WebSocketMessage(BaseModel): + """ + Base model for WebSocket messages. + + All WebSocket messages must include a type field to identify + the message purpose and optional data payload. + """ + + type: WebSocketMessageType = Field(..., description="Type of the WebSocket message") + data: Optional[Dict[str, Any]] = Field( + None, description="Optional data payload for the message" + ) + + +class PingMessage(BaseModel): + """ + Ping message for health check. + + Client sends this to check if the server is responsive. + """ + + type: Literal[WebSocketMessageType.PING] = WebSocketMessageType.PING + + +class RequestMessage(BaseModel): + """ + Request message to process a user request. + + Client sends this to initiate processing of a natural language request. + """ + + type: Literal[WebSocketMessageType.REQUEST] = WebSocketMessageType.REQUEST + text: str = Field(..., description="The natural language request text to process") + + +class ResetMessage(BaseModel): + """ + Reset message to reset the current session. + + Client sends this to reset the Galaxy session and clear state. + """ + + type: Literal[WebSocketMessageType.RESET] = WebSocketMessageType.RESET + + +class NextSessionMessage(BaseModel): + """ + Next session message to create a new session. + + Client sends this to create a new Galaxy session while maintaining + some context from the previous session. + """ + + type: Literal[WebSocketMessageType.NEXT_SESSION] = WebSocketMessageType.NEXT_SESSION + + +class StopTaskMessage(BaseModel): + """ + Stop task message to cancel current task execution. + + Client sends this to stop the currently executing task and clean up resources. + """ + + type: Literal[WebSocketMessageType.STOP_TASK] = WebSocketMessageType.STOP_TASK diff --git a/galaxy/webui/models/responses.py b/galaxy/webui/models/responses.py new file mode 100644 index 000000000..da3d9b9b1 --- /dev/null +++ b/galaxy/webui/models/responses.py @@ -0,0 +1,197 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Response models for Galaxy Web UI. + +This module defines Pydantic models for all outgoing responses, +both HTTP API responses and WebSocket messages. +""" + +from typing import Any, Dict, List, Literal, Optional + +from pydantic import BaseModel, Field + +from galaxy.webui.models.enums import WebSocketMessageType, RequestStatus + + +class StandardResponse(BaseModel): + """ + Standard response format for API endpoints. + + Provides a consistent structure for API responses with status, + message, timestamp, and optional data payload. + """ + + status: str = Field( + ..., description="Status of the response (e.g., 'success', 'error')" + ) + message: str = Field( + ..., description="Human-readable message describing the response" + ) + timestamp: float = Field( + ..., description="Unix timestamp when the response was generated" + ) + data: Optional[Dict[str, Any]] = Field(None, description="Optional data payload") + + +class HealthResponse(BaseModel): + """ + Response model for health check endpoint. + + Returns information about the server's current health status. + """ + + status: str = Field(..., description="Health status of the server") + connections: int = Field(..., description="Number of active WebSocket connections") + events_sent: int = Field(..., description="Total number of events sent to clients") + + +class DeviceAddResponse(BaseModel): + """ + Response model for device addition endpoint. + + Returns confirmation of device addition with device details. + """ + + status: str = Field(..., description="Status of the device addition operation") + message: str = Field(..., description="Human-readable message about the operation") + device: Dict[str, Any] = Field(..., description="Details of the added device") + + +class WelcomeMessage(BaseModel): + """ + Welcome message sent when WebSocket connection is established. + + Confirms successful connection and provides initial timestamp. + """ + + type: Literal[WebSocketMessageType.WELCOME] = WebSocketMessageType.WELCOME + message: str = Field(..., description="Welcome message text") + timestamp: float = Field( + ..., description="Server timestamp when connection was established" + ) + + +class PongMessage(BaseModel): + """ + Pong response to ping message. + + Confirms server is responsive to client health checks. + """ + + type: Literal[WebSocketMessageType.PONG] = WebSocketMessageType.PONG + timestamp: float = Field(..., description="Server timestamp of the pong response") + + +class RequestReceivedMessage(BaseModel): + """ + Acknowledgment that a request has been received and is being processed. + + Sent immediately after receiving a user request to provide feedback. + """ + + type: Literal[WebSocketMessageType.REQUEST_RECEIVED] = ( + WebSocketMessageType.REQUEST_RECEIVED + ) + request: str = Field(..., description="The request text that was received") + status: Literal[RequestStatus.PROCESSING] = RequestStatus.PROCESSING + + +class RequestCompletedMessage(BaseModel): + """ + Message indicating request processing has completed successfully. + + Contains the result of the processed request. + """ + + type: Literal[WebSocketMessageType.REQUEST_COMPLETED] = ( + WebSocketMessageType.REQUEST_COMPLETED + ) + request: str = Field(..., description="The request text that was processed") + status: Literal[RequestStatus.COMPLETED] = RequestStatus.COMPLETED + result: str = Field(..., description="The result of the request processing") + + +class RequestFailedMessage(BaseModel): + """ + Message indicating request processing has failed. + + Contains error information about why the request failed. + """ + + type: Literal[WebSocketMessageType.REQUEST_FAILED] = ( + WebSocketMessageType.REQUEST_FAILED + ) + request: str = Field(..., description="The request text that failed") + status: Literal[RequestStatus.FAILED] = RequestStatus.FAILED + error: str = Field(..., description="Error message explaining the failure") + + +class ResetAcknowledgedMessage(BaseModel): + """ + Acknowledgment that session reset has been completed. + + Confirms the session state has been cleared. + """ + + type: Literal[WebSocketMessageType.RESET_ACKNOWLEDGED] = ( + WebSocketMessageType.RESET_ACKNOWLEDGED + ) + status: str = Field(..., description="Status of the reset operation") + message: str = Field(..., description="Message describing the reset result") + timestamp: Optional[float] = Field( + None, description="Timestamp of the reset operation" + ) + + +class NextSessionAcknowledgedMessage(BaseModel): + """ + Acknowledgment that a new session has been created. + + Contains information about the newly created session. + """ + + type: Literal[WebSocketMessageType.NEXT_SESSION_ACKNOWLEDGED] = ( + WebSocketMessageType.NEXT_SESSION_ACKNOWLEDGED + ) + status: str = Field(..., description="Status of the session creation") + message: str = Field(..., description="Message describing the session creation") + session_name: Optional[str] = Field( + None, description="Name of the newly created session" + ) + task_name: Optional[str] = Field( + None, description="Name of the task for the new session" + ) + timestamp: Optional[float] = Field( + None, description="Timestamp of session creation" + ) + + +class StopAcknowledgedMessage(BaseModel): + """ + Acknowledgment that task has been stopped. + + Confirms the task execution has been terminated and resources cleaned up. + """ + + type: Literal[WebSocketMessageType.STOP_ACKNOWLEDGED] = ( + WebSocketMessageType.STOP_ACKNOWLEDGED + ) + status: str = Field(..., description="Status of the stop operation") + message: str = Field(..., description="Message describing the stop result") + session_name: Optional[str] = Field( + None, description="Name of the session after restart" + ) + timestamp: float = Field(..., description="Timestamp of the stop operation") + + +class ErrorMessage(BaseModel): + """ + Error message for communicating failures to the client. + + Provides detailed error information for debugging and user feedback. + """ + + type: Literal[WebSocketMessageType.ERROR] = WebSocketMessageType.ERROR + message: str = Field(..., description="Error message describing what went wrong") diff --git a/galaxy/webui/routers/__init__.py b/galaxy/webui/routers/__init__.py new file mode 100644 index 000000000..b393162e5 --- /dev/null +++ b/galaxy/webui/routers/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Routers for Galaxy Web UI. + +This package contains FastAPI routers that define API endpoints +and WebSocket endpoints for the Web UI. +""" + +from galaxy.webui.routers.health import router as health_router +from galaxy.webui.routers.devices import router as devices_router +from galaxy.webui.routers.websocket import router as websocket_router + +__all__ = [ + "health_router", + "devices_router", + "websocket_router", +] diff --git a/galaxy/webui/routers/devices.py b/galaxy/webui/routers/devices.py new file mode 100644 index 000000000..965ad1e04 --- /dev/null +++ b/galaxy/webui/routers/devices.py @@ -0,0 +1,104 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Device management router for Galaxy Web UI. + +This module defines API endpoints for managing devices in the Galaxy framework. +""" + +import logging +from typing import Dict, Any + +from fastapi import APIRouter, HTTPException + +from galaxy.webui.dependencies import get_app_state +from galaxy.webui.models.requests import DeviceAddRequest +from galaxy.webui.models.responses import DeviceAddResponse +from galaxy.webui.services import ConfigService, DeviceService + +router = APIRouter(prefix="/api", tags=["devices"]) +logger = logging.getLogger(__name__) + + +@router.post("/devices", response_model=DeviceAddResponse) +async def add_device(device: DeviceAddRequest) -> Dict[str, Any]: + """ + Add a new device to the Galaxy configuration. + + This endpoint: + 1. Validates the device configuration data + 2. Checks for device ID conflicts + 3. Saves the device to devices.yaml configuration file + 4. Registers the device with the device manager + 5. Optionally initiates connection to the device + + :param device: Device configuration data validated against DeviceAddRequest model + :return: Success response with device details + :raises HTTPException: 404 if devices.yaml not found + :raises HTTPException: 409 if device ID already exists + :raises HTTPException: 500 if device addition fails + """ + logger.info(f"Received request to add device: {device.device_id}") + + # Initialize services + config_service = ConfigService() + app_state = get_app_state() + device_service = DeviceService(app_state) + + try: + # Check if devices.yaml exists + try: + config_service.load_devices_config() + except FileNotFoundError: + raise HTTPException(status_code=404, detail="devices.yaml not found") + + # Check for device_id conflict + if config_service.device_id_exists(device.device_id): + raise HTTPException( + status_code=409, + detail=f"Device ID '{device.device_id}' already exists", + ) + + # Add device to configuration file + new_device = config_service.add_device_to_config( + device_id=device.device_id, + server_url=device.server_url, + os=device.os, + capabilities=device.capabilities, + metadata=device.metadata, + auto_connect=( + device.auto_connect if device.auto_connect is not None else True + ), + max_retries=device.max_retries if device.max_retries is not None else 5, + ) + + # Attempt to register and connect the device via device manager + await device_service.register_and_connect_device( + device_id=device.device_id, + server_url=device.server_url, + os=device.os, + capabilities=device.capabilities, + metadata=device.metadata, + max_retries=device.max_retries if device.max_retries is not None else 5, + auto_connect=( + device.auto_connect if device.auto_connect is not None else True + ), + ) + + return { + "status": "success", + "message": f"Device '{device.device_id}' added successfully", + "device": new_device, + } + + except HTTPException: + # Re-raise HTTP exceptions as-is + raise + except ValueError as e: + # Handle validation errors from services + raise HTTPException(status_code=409, detail=str(e)) + except Exception as e: + # Handle unexpected errors + logger.error(f"❌ Error adding device: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to add device: {str(e)}") diff --git a/galaxy/webui/routers/health.py b/galaxy/webui/routers/health.py new file mode 100644 index 000000000..15ec19175 --- /dev/null +++ b/galaxy/webui/routers/health.py @@ -0,0 +1,45 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Health check router for Galaxy Web UI. + +This module defines the health check endpoint that returns server status. +""" + +from typing import Dict, Any + +from fastapi import APIRouter + +from galaxy.webui.dependencies import get_app_state +from galaxy.webui.models.responses import HealthResponse + +router = APIRouter(tags=["health"]) + + +@router.get("/health", response_model=HealthResponse) +async def health_check() -> Dict[str, Any]: + """ + Health check endpoint. + + Returns the current status of the server including: + - Overall health status + - Number of active WebSocket connections + - Total number of events sent to clients + + This endpoint can be used for monitoring and load balancer health checks. + + :return: Dictionary containing health status information + """ + app_state = get_app_state() + websocket_observer = app_state.websocket_observer + + return { + "status": "healthy", + "connections": ( + websocket_observer.connection_count if websocket_observer else 0 + ), + "events_sent": ( + websocket_observer.total_events_sent if websocket_observer else 0 + ), + } diff --git a/galaxy/webui/routers/websocket.py b/galaxy/webui/routers/websocket.py new file mode 100644 index 000000000..11376b2dd --- /dev/null +++ b/galaxy/webui/routers/websocket.py @@ -0,0 +1,79 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +WebSocket router for Galaxy Web UI. + +This module defines the WebSocket endpoint for real-time event streaming +and bidirectional communication with clients. +""" + +import logging + +from fastapi import APIRouter, WebSocket, WebSocketDisconnect + +from galaxy.webui.dependencies import get_app_state +from galaxy.webui.handlers import WebSocketMessageHandler + +router = APIRouter(tags=["websocket"]) +logger = logging.getLogger(__name__) + + +@router.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket) -> None: + """ + WebSocket endpoint for real-time event streaming. + + This endpoint establishes a persistent connection with clients to: + - Send welcome messages and initial state (device snapshots) + - Receive and process client messages (requests, commands) + - Broadcast Galaxy events to all connected clients in real-time + + The connection lifecycle: + 1. Accept the WebSocket connection + 2. Register with the WebSocket observer for event broadcasting + 3. Send welcome message and initial device snapshot + 4. Process incoming messages until disconnection + 5. Cleanup and remove from observer on disconnect + + :param websocket: The WebSocket connection from the client + """ + await websocket.accept() + logger.info(f"WebSocket connection established from {websocket.client}") + + # Get application state and message handler + app_state = get_app_state() + message_handler = WebSocketMessageHandler(app_state) + + # Add connection to observer for event broadcasting + websocket_observer = app_state.websocket_observer + if websocket_observer: + websocket_observer.add_connection(websocket) + + try: + # Send welcome message and initial device snapshot + await message_handler.send_welcome_message(websocket) + + # Keep connection alive and handle incoming messages + while True: + try: + # Wait for and receive message from client + data: dict = await websocket.receive_json() + + # Process the message through handler + await message_handler.handle_message(websocket, data) + + except WebSocketDisconnect: + logger.info("WebSocket client disconnected normally") + break + except Exception as e: + logger.error(f"Error receiving/processing WebSocket message: {e}") + # Continue listening for messages unless it's a connection error + # The outer try-finally will handle cleanup + break + + finally: + # Remove connection from observer on disconnect + if websocket_observer: + websocket_observer.remove_connection(websocket) + logger.info("WebSocket connection closed") diff --git a/galaxy/webui/server.py b/galaxy/webui/server.py index 2ce42d657..edfb0f341 100644 --- a/galaxy/webui/server.py +++ b/galaxy/webui/server.py @@ -6,81 +6,32 @@ FastAPI-based server that provides WebSocket communication for the Galaxy Web UI. Integrates with the Galaxy event system to provide real-time updates. + +This is the refactored version with improved architecture: +- Pydantic models and enums in separate modules +- Business logic separated into services +- Routers for endpoint organization +- Dependency injection for state management """ -import asyncio import logging -import time from contextlib import asynccontextmanager -from datetime import datetime -from typing import Any, Dict, Optional +from pathlib import Path +from typing import TYPE_CHECKING -from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, HTMLResponse from fastapi.staticfiles import StaticFiles -from pathlib import Path from galaxy.core.events import get_event_bus +from galaxy.webui.dependencies import get_app_state +from galaxy.webui.routers import health_router, devices_router, websocket_router from galaxy.webui.websocket_observer import WebSocketObserver - -# Global WebSocket observer instance -_websocket_observer: Optional[WebSocketObserver] = None -# Global Galaxy session and client instances -_galaxy_session: Optional[Any] = None -_galaxy_client: Optional[Any] = None -# Counter for unique task names in Web UI mode -_request_counter: int = 0 - - -def _build_device_snapshot() -> Optional[Dict[str, Dict[str, Any]]]: - """ - Construct a serializable snapshot of all known devices. - - Retrieves device information from the Galaxy client's device manager - and formats it for transmission to the frontend. - - :return: Dictionary mapping device IDs to device information, or None if unavailable - """ - if not _galaxy_client: - return None - - # Get constellation client from Galaxy client - constellation_client = getattr(_galaxy_client, "_client", None) - if not constellation_client: - return None - - # Get device manager from constellation client - device_manager = getattr(constellation_client, "device_manager", None) - if not device_manager: - return None - - try: - snapshot: Dict[str, Dict[str, Any]] = {} - for device_id, device in device_manager.get_all_devices().items(): - snapshot[device_id] = { - "device_id": device.device_id, - "status": getattr(device.status, "value", str(device.status)), - "os": device.os, - "server_url": device.server_url, - "capabilities": ( - list(device.capabilities) if device.capabilities else [] - ), - "metadata": dict(device.metadata) if device.metadata else {}, - "last_heartbeat": ( - device.last_heartbeat.isoformat() if device.last_heartbeat else None - ), - "connection_attempts": device.connection_attempts, - "max_retries": device.max_retries, - "current_task_id": device.current_task_id, - } - return snapshot if snapshot else None - except Exception as exc: # pragma: no cover - defensive logging - logging.getLogger(__name__).warning( - "Failed to build device snapshot: %s", exc, exc_info=True - ) - return None +if TYPE_CHECKING: + from galaxy.galaxy_client import GalaxyClient + from galaxy.session.galaxy_session import GalaxySession @asynccontextmanager @@ -94,20 +45,23 @@ async def lifespan(app: FastAPI): :param app: The FastAPI application instance """ - global _websocket_observer - # Startup phase logger: logging.Logger = logging.getLogger(__name__) logger.info("🚀 Starting Galaxy Web UI Server") print("🚀 Starting Galaxy Web UI Server") + # Get application state + app_state = get_app_state() + # Create and register WebSocket observer with event bus - _websocket_observer = WebSocketObserver() + websocket_observer = WebSocketObserver() + app_state.websocket_observer = websocket_observer + event_bus = get_event_bus() - event_bus.subscribe(_websocket_observer) + event_bus.subscribe(websocket_observer) logger.info( - f"✅ WebSocket observer registered with event bus (observer: {_websocket_observer})" + f"✅ WebSocket observer registered with event bus (observer: {websocket_observer})" ) print(f"✅ WebSocket observer registered with event bus") print(f"📊 Event bus has {len(event_bus._observers)} observers") @@ -117,10 +71,10 @@ async def lifespan(app: FastAPI): # Shutdown phase logger.info("👋 Shutting down Galaxy Web UI Server") print("👋 Shutting down Galaxy Web UI Server") - event_bus.unsubscribe(_websocket_observer) + event_bus.unsubscribe(websocket_observer) -# Create FastAPI app +# Create FastAPI app with lifespan management app = FastAPI( title="Galaxy Web UI", description="Modern web interface for Galaxy Framework", @@ -128,7 +82,7 @@ async def lifespan(app: FastAPI): lifespan=lifespan, ) -# Add CORS middleware +# Add CORS middleware to allow cross-origin requests app.add_middleware( CORSMiddleware, allow_origins=["*"], # In production, specify exact origins @@ -137,6 +91,11 @@ async def lifespan(app: FastAPI): allow_headers=["*"], ) +# Include routers for different endpoint groups +app.include_router(health_router) +app.include_router(devices_router) +app.include_router(websocket_router) + # Mount frontend static files if built frontend_dist = Path(__file__).parent / "frontend" / "dist" if frontend_dist.exists(): @@ -164,7 +123,7 @@ async def root() -> HTMLResponse: Root endpoint that serves the web UI. Attempts to serve the built React application if available, - otherwise returns a placeholder HTML page. + otherwise returns a placeholder HTML page from templates. :return: HTMLResponse containing the web UI or placeholder """ @@ -182,430 +141,36 @@ async def root() -> HTMLResponse: }, ) - # Fallback to placeholder HTML - return HTMLResponse(content=get_index_html(), status_code=200) - - -@app.get("/health") -async def health() -> Dict[str, Any]: - """ - Health check endpoint. - - Returns the current status of the server including connection count - and total events sent. - - :return: Dictionary containing health status information - """ - return { - "status": "healthy", - "connections": ( - _websocket_observer.connection_count if _websocket_observer else 0 - ), - "events_sent": ( - _websocket_observer.total_events_sent if _websocket_observer else 0 - ), - } - - -@app.websocket("/ws") -async def websocket_endpoint(websocket: WebSocket) -> None: - """ - WebSocket endpoint for real-time event streaming. - - Clients connect to this endpoint to receive real-time Galaxy events. - The endpoint handles: - - Sending welcome messages and initial device snapshots - - Processing incoming client messages - - Broadcasting events to all connected clients - - :param websocket: The WebSocket connection from the client - """ - await websocket.accept() - logger: logging.Logger = logging.getLogger(__name__) - logger.info(f"WebSocket connection established from {websocket.client}") - - # Add connection to observer for event broadcasting - if _websocket_observer: - _websocket_observer.add_connection(websocket) - - try: - # Send welcome message to client - await websocket.send_json( - { - "type": "welcome", - "message": "Connected to Galaxy Web UI", - "timestamp": asyncio.get_event_loop().time(), - } - ) - - # Send an initial device snapshot so the UI can render current state immediately - device_snapshot: Optional[Dict[str, Dict[str, Any]]] = _build_device_snapshot() - if device_snapshot: - await websocket.send_json( - { - "event_type": "device_snapshot", - "source_id": "webui.server", - "timestamp": time.time(), - "data": { - "event_name": "device_snapshot", - "device_count": len(device_snapshot), - }, - "all_devices": device_snapshot, - } - ) - - # Keep connection alive and handle incoming messages - while True: - try: - data: dict = await websocket.receive_json() - await handle_client_message(websocket, data) - except WebSocketDisconnect: - logger.info("WebSocket client disconnected normally") - break - except Exception as e: - logger.error(f"Error receiving WebSocket message: {e}") - break - - finally: - # Remove connection from observer - if _websocket_observer: - _websocket_observer.remove_connection(websocket) - logger.info("WebSocket connection closed") - - -async def handle_client_message(websocket: WebSocket, data: dict) -> None: - """ - Handle messages from WebSocket clients. - - Processes different message types including: - - ping: Health check ping-pong - - request: Process user request to start a Galaxy session - - reset: Reset the current session - - next_session: Create a new session - - stop_task: Stop current task execution - - :param websocket: The WebSocket connection - :param data: The message data from client - """ - global _request_counter - - logger: logging.Logger = logging.getLogger(__name__) - message_type: Optional[str] = data.get("type") - - logger.info(f"Received message - Type: {message_type}, Full data: {data}") - - if message_type == "ping": - # Respond to ping with pong - await websocket.send_json( - {"type": "pong", "timestamp": asyncio.get_event_loop().time()} - ) - - elif message_type == "request": - # Handle user request to start a Galaxy session - request_text: str = data.get("text", "") - logger.info(f"Received request: {request_text}") - - if _galaxy_client: - # Send immediate acknowledgment to client - await websocket.send_json( - { - "type": "request_received", - "request": request_text, - "status": "processing", - } - ) - - # Process request in background task - async def process_in_background() -> None: - global _request_counter - try: - # Increment counter and update task_name for this request with timestamp - _request_counter += 1 - timestamp: str = datetime.now().strftime("%Y%m%d_%H%M%S") - _galaxy_client.task_name = f"request_{timestamp}_{_request_counter}" - - logger.info( - f"🚀 Starting to process request {_request_counter}: {request_text}" - ) - result = await _galaxy_client.process_request(request_text) - logger.info(f"✅ Request processing completed") - await websocket.send_json( - { - "type": "request_completed", - "request": request_text, - "status": "completed", - "result": str(result), - } - ) - except Exception as e: - logger.error(f"❌ Error processing request: {e}", exc_info=True) - await websocket.send_json( - { - "type": "request_failed", - "request": request_text, - "status": "failed", - "error": str(e), - } - ) - - # Start background task - asyncio.create_task(process_in_background()) - else: - await websocket.send_json( - { - "type": "error", - "message": "Galaxy client not initialized", - } - ) - - elif message_type == "reset": - # Handle session reset - logger.info("Received reset request") - - if _galaxy_client: - try: - result = await _galaxy_client.reset_session() - - # Reset request counter on session reset - _request_counter = 0 - - await websocket.send_json( - { - "type": "reset_acknowledged", - "status": result.get("status", "success"), - "message": result.get("message", "Session reset"), - "timestamp": result.get("timestamp"), - } - ) - logger.info(f"✅ Session reset completed: {result.get('message')}") - except Exception as e: - logger.error(f"Failed to reset session: {e}", exc_info=True) - await websocket.send_json( - { - "type": "error", - "message": f"Failed to reset session: {str(e)}", - } - ) - else: - await websocket.send_json( - { - "type": "reset_acknowledged", - "status": "warning", - "message": "No active client to reset", - } - ) + # Fallback to placeholder HTML from templates + template_path: Path = Path(__file__).parent / "templates" / "index.html" + if template_path.exists(): + with open(template_path, "r", encoding="utf-8") as f: + return HTMLResponse(content=f.read(), status_code=200) - elif message_type == "next_session": - # Handle next session creation - logger.info("Received next_session request") - - if _galaxy_client: - try: - result = await _galaxy_client.create_next_session() - await websocket.send_json( - { - "type": "next_session_acknowledged", - "status": result.get("status", "success"), - "message": result.get("message", "Next session created"), - "session_name": result.get("session_name"), - "task_name": result.get("task_name"), - "timestamp": result.get("timestamp"), - } - ) - logger.info(f"✅ Next session created: {result.get('session_name')}") - except Exception as e: - logger.error(f"Failed to create next session: {e}", exc_info=True) - await websocket.send_json( - { - "type": "error", - "message": f"Failed to create next session: {str(e)}", - } - ) - else: - await websocket.send_json( - { - "type": "error", - "message": "Galaxy client not initialized", - } - ) - - elif message_type == "stop_task": - # Handle task stop/cancel - shutdown client to clean up device tasks, then restart - logger.info("Received stop_task request") - - if _galaxy_client: - try: - # Shutdown the galaxy client to properly clean up device agent tasks - logger.info( - "🛑 Shutting down Galaxy client to clean up device tasks..." - ) - await _galaxy_client.shutdown() - logger.info("✅ Galaxy client shutdown completed") - - # Reinitialize the client to restore device connections - logger.info("🔄 Reinitializing Galaxy client...") - await _galaxy_client.initialize() - logger.info("✅ Galaxy client reinitialized") - - # Reset request counter on stop - _request_counter = 0 - - # Create a new session - new_session_result = await _galaxy_client.create_next_session() - logger.info(f"✅ New session created: {new_session_result}") - - await websocket.send_json( - { - "type": "stop_acknowledged", - "status": "success", - "message": "Task stopped and client restarted", - "session_name": new_session_result.get("session_name"), - "timestamp": time.time(), - } - ) - except Exception as e: - logger.error( - f"Failed to stop task and restart client: {e}", exc_info=True - ) - await websocket.send_json( - { - "type": "error", - "message": f"Failed to stop task: {str(e)}", - } - ) - else: - logger.warning("No active galaxy client to stop") - await websocket.send_json( - { - "type": "stop_acknowledged", - "status": "warning", - "message": "No active task to stop", - "timestamp": time.time(), - } - ) - - else: - logger.warning(f"Unknown message type: {message_type}") - await websocket.send_json( - { - "type": "error", - "message": f"Unknown message type: {message_type}", - } - ) - - -def get_index_html() -> str: - """ - Get the HTML content for the main UI page. - - Returns a placeholder HTML page with Galaxy branding. - In production, this serves as a fallback when the React build is not available. - - :return: HTML string for the placeholder page - """ - return """ - - - - - - Galaxy Web UI - - - -
    -

    🌌 Galaxy Web UI

    -

    Weaving the Digital Agent Galaxy

    -
    - - Server is running -
    -

    - Frontend React application will be served here.
    - WebSocket endpoint: ws://localhost:8000/ws -

    -

    - Health Check -

    -
    - - - """ + # Ultimate fallback if template file doesn't exist + return HTMLResponse( + content="

    Galaxy Web UI

    Server is running

    ", status_code=200 + ) -def set_galaxy_session(session: Any) -> None: +def set_galaxy_session(session: "GalaxySession") -> None: """ Set the Galaxy session for the web UI. :param session: The GalaxySession instance """ - global _galaxy_session - _galaxy_session = session + app_state = get_app_state() + app_state.galaxy_session = session -def set_galaxy_client(client: Any) -> None: +def set_galaxy_client(client: "GalaxyClient") -> None: """ Set the Galaxy client for the web UI. :param client: The GalaxyClient instance """ - global _galaxy_client - _galaxy_client = client + app_state = get_app_state() + app_state.galaxy_client = client def start_server(host: str = "0.0.0.0", port: int = 8000) -> None: diff --git a/galaxy/webui/services/__init__.py b/galaxy/webui/services/__init__.py new file mode 100644 index 000000000..2c72c9491 --- /dev/null +++ b/galaxy/webui/services/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Services for Galaxy Web UI. + +This package contains business logic services that encapsulate +operations and interact with the Galaxy framework. +""" + +from galaxy.webui.services.device_service import DeviceService +from galaxy.webui.services.galaxy_service import GalaxyService +from galaxy.webui.services.config_service import ConfigService + +__all__ = [ + "DeviceService", + "GalaxyService", + "ConfigService", +] diff --git a/galaxy/webui/services/config_service.py b/galaxy/webui/services/config_service.py new file mode 100644 index 000000000..545273ef4 --- /dev/null +++ b/galaxy/webui/services/config_service.py @@ -0,0 +1,169 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Configuration service for Galaxy Web UI. + +This service handles reading and writing configuration files, +particularly the devices.yaml file. +""" + +import logging +from pathlib import Path +from typing import Any, Dict, List, Optional + +import yaml + + +class ConfigService: + """ + Service for managing configuration files. + + Provides methods to read and write YAML configuration files, + with specific support for the devices.yaml file. + """ + + def __init__(self, config_dir: Path = Path("config/galaxy")) -> None: + """ + Initialize the configuration service. + + :param config_dir: Directory containing configuration files + """ + self.config_dir = config_dir + self.devices_config_path = config_dir / "devices.yaml" + self.logger: logging.Logger = logging.getLogger(__name__) + + def load_devices_config(self) -> Dict[str, Any]: + """ + Load the devices configuration from devices.yaml. + + :return: Dictionary containing the devices configuration + :raises FileNotFoundError: If devices.yaml does not exist + :raises yaml.YAMLError: If YAML parsing fails + """ + if not self.devices_config_path.exists(): + raise FileNotFoundError( + f"Configuration file not found: {self.devices_config_path}" + ) + + try: + with open(self.devices_config_path, "r", encoding="utf-8") as f: + config_data = yaml.safe_load(f) or {} + + self.logger.debug(f"Loaded devices config from {self.devices_config_path}") + return config_data + + except yaml.YAMLError as e: + self.logger.error(f"Failed to parse YAML config: {e}") + raise + + def save_devices_config(self, config_data: Dict[str, Any]) -> None: + """ + Save the devices configuration to devices.yaml. + + :param config_data: Dictionary containing the devices configuration + :raises IOError: If file writing fails + """ + try: + # Ensure the directory exists + self.config_dir.mkdir(parents=True, exist_ok=True) + + with open(self.devices_config_path, "w", encoding="utf-8") as f: + yaml.dump( + config_data, + f, + default_flow_style=False, + sort_keys=False, + allow_unicode=True, + ) + + self.logger.debug(f"Saved devices config to {self.devices_config_path}") + + except IOError as e: + self.logger.error(f"Failed to write config file: {e}") + raise + + def get_all_device_ids(self) -> List[str]: + """ + Get a list of all device IDs in the configuration. + + :return: List of device IDs + """ + try: + config_data = self.load_devices_config() + devices = config_data.get("devices", []) + return [ + d.get("device_id") + for d in devices + if isinstance(d, dict) and "device_id" in d + ] + except Exception as e: + self.logger.error(f"Failed to get device IDs: {e}") + return [] + + def device_id_exists(self, device_id: str) -> bool: + """ + Check if a device ID already exists in the configuration. + + :param device_id: Device ID to check + :return: True if device ID exists, False otherwise + """ + existing_ids = self.get_all_device_ids() + return device_id in existing_ids + + def add_device_to_config( + self, + device_id: str, + server_url: str, + os: str, + capabilities: List[str], + metadata: Optional[Dict[str, Any]], + auto_connect: bool, + max_retries: int, + ) -> Dict[str, Any]: + """ + Add a new device to the configuration. + + :param device_id: Unique identifier for the device + :param server_url: URL of the device's server endpoint + :param os: Operating system of the device + :param capabilities: List of capabilities the device supports + :param metadata: Additional metadata about the device + :param auto_connect: Whether to automatically connect to the device + :param max_retries: Maximum number of connection retry attempts + :return: The device entry that was added + :raises ValueError: If device ID already exists + """ + # Load existing configuration + config_data = self.load_devices_config() + + # Ensure devices list exists + if "devices" not in config_data: + config_data["devices"] = [] + + # Check for device_id conflict + if self.device_id_exists(device_id): + raise ValueError(f"Device ID '{device_id}' already exists") + + # Create new device entry + new_device = { + "device_id": device_id, + "server_url": server_url, + "os": os, + "capabilities": capabilities, + "auto_connect": auto_connect, + "max_retries": max_retries, + } + + # Add metadata if provided + if metadata: + new_device["metadata"] = metadata + + # Append new device to configuration + config_data["devices"].append(new_device) + + # Save updated configuration + self.save_devices_config(config_data) + + self.logger.info(f"✅ Device '{device_id}' added to configuration") + return new_device diff --git a/galaxy/webui/services/device_service.py b/galaxy/webui/services/device_service.py new file mode 100644 index 000000000..690f2e20c --- /dev/null +++ b/galaxy/webui/services/device_service.py @@ -0,0 +1,159 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Device management service for Galaxy Web UI. + +This service handles device-related operations including registration, +configuration management, and device snapshot creation. +""" + +import logging +from typing import Any, Dict, Optional + +from galaxy.webui.dependencies import AppState + + +class DeviceService: + """ + Service for managing devices in the Galaxy framework. + + Provides methods to interact with the device manager, create device snapshots, + and manage device lifecycle operations. + """ + + def __init__(self, app_state: AppState) -> None: + """ + Initialize the device service. + + :param app_state: Application state containing Galaxy client references + """ + self.app_state = app_state + self.logger: logging.Logger = logging.getLogger(__name__) + + def build_device_snapshot(self) -> Optional[Dict[str, Dict[str, Any]]]: + """ + Construct a serializable snapshot of all known devices. + + Retrieves device information from the Galaxy client's device manager + and formats it for transmission to the frontend. + + :return: Dictionary mapping device IDs to device information, or None if unavailable + """ + galaxy_client = self.app_state.galaxy_client + if not galaxy_client: + self.logger.warning("Galaxy client not available for device snapshot") + return None + + # Get constellation client from Galaxy client + constellation_client = getattr(galaxy_client, "_client", None) + if not constellation_client: + self.logger.warning("Constellation client not available") + return None + + # Get device manager from constellation client + device_manager = getattr(constellation_client, "device_manager", None) + if not device_manager: + self.logger.warning("Device manager not available") + return None + + try: + snapshot: Dict[str, Dict[str, Any]] = {} + for device_id, device in device_manager.get_all_devices().items(): + snapshot[device_id] = { + "device_id": device.device_id, + "status": getattr(device.status, "value", str(device.status)), + "os": device.os, + "server_url": device.server_url, + "capabilities": ( + list(device.capabilities) if device.capabilities else [] + ), + "metadata": dict(device.metadata) if device.metadata else {}, + "last_heartbeat": ( + device.last_heartbeat.isoformat() + if device.last_heartbeat + else None + ), + "connection_attempts": device.connection_attempts, + "max_retries": device.max_retries, + "current_task_id": device.current_task_id, + } + + self.logger.debug(f"Built device snapshot with {len(snapshot)} devices") + return snapshot if snapshot else None + + except Exception as exc: + self.logger.warning( + f"Failed to build device snapshot: {exc}", exc_info=True + ) + return None + + def get_device_manager(self) -> Optional[Any]: + """ + Get the device manager from the Galaxy client. + + :return: Device manager instance or None if not available + """ + galaxy_client = self.app_state.galaxy_client + if not galaxy_client: + return None + + constellation_client = getattr(galaxy_client, "_client", None) + if not constellation_client: + return None + + return getattr(constellation_client, "device_manager", None) + + async def register_and_connect_device( + self, + device_id: str, + server_url: str, + os: str, + capabilities: list, + metadata: Optional[Dict[str, Any]], + max_retries: int, + auto_connect: bool, + ) -> bool: + """ + Register a device with the device manager and optionally connect to it. + + :param device_id: Unique identifier for the device + :param server_url: URL of the device's server endpoint + :param os: Operating system of the device + :param capabilities: List of capabilities the device supports + :param metadata: Additional metadata about the device + :param max_retries: Maximum number of connection retry attempts + :param auto_connect: Whether to automatically connect to the device + :return: True if registration and connection succeeded, False otherwise + """ + device_manager = self.get_device_manager() + if not device_manager: + self.logger.warning("Device manager not available for device registration") + return False + + try: + # Register the device with device manager + device_manager.device_registry.register_device( + device_id=device_id, + server_url=server_url, + os=os, + capabilities=capabilities, + metadata=metadata or {}, + max_retries=max_retries, + ) + self.logger.info(f"✅ Device '{device_id}' registered with device manager") + + # If auto_connect is enabled, try to connect + if auto_connect: + import asyncio + + asyncio.create_task(device_manager.connect_device(device_id)) + self.logger.info(f"🔄 Initiated connection for device '{device_id}'") + + return True + + except Exception as e: + self.logger.warning( + f"⚠️ Failed to register/connect device with manager: {e}" + ) + return False diff --git a/galaxy/webui/services/galaxy_service.py b/galaxy/webui/services/galaxy_service.py new file mode 100644 index 000000000..8fe583fa0 --- /dev/null +++ b/galaxy/webui/services/galaxy_service.py @@ -0,0 +1,167 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Galaxy service for Galaxy Web UI. + +This service handles interactions with the Galaxy client, +including request processing, session management, and task control. +""" + +import logging +from datetime import datetime +from typing import Any, Dict, Optional + +from galaxy.webui.dependencies import AppState + + +class GalaxyService: + """ + Service for interacting with the Galaxy client. + + Provides methods to process requests, manage sessions, and control + task execution in the Galaxy framework. + """ + + def __init__(self, app_state: AppState) -> None: + """ + Initialize the Galaxy service. + + :param app_state: Application state containing Galaxy client references + """ + self.app_state = app_state + self.logger: logging.Logger = logging.getLogger(__name__) + + def is_client_available(self) -> bool: + """ + Check if the Galaxy client is available. + + :return: True if client is initialized, False otherwise + """ + return self.app_state.galaxy_client is not None + + async def process_request(self, request_text: str) -> Any: + """ + Process a user request through the Galaxy client. + + Updates the task name with a timestamp and counter, then processes + the request through the Galaxy framework. + + :param request_text: The natural language request to process + :return: Result from the Galaxy client + :raises ValueError: If Galaxy client is not initialized + """ + galaxy_client = self.app_state.galaxy_client + if not galaxy_client: + raise ValueError("Galaxy client not initialized") + + # Increment counter and update task_name for this request with timestamp + counter = self.app_state.increment_request_counter() + timestamp: str = datetime.now().strftime("%Y%m%d_%H%M%S") + task_name = f"request_{timestamp}_{counter}" + galaxy_client.task_name = task_name + + self.logger.info(f"🚀 Processing request #{counter}: {request_text}") + + try: + result = await galaxy_client.process_request(request_text) + self.logger.info(f"✅ Request processing completed for #{counter}") + return result + except Exception as e: + self.logger.error( + f"❌ Error processing request #{counter}: {e}", exc_info=True + ) + raise + + async def reset_session(self) -> Dict[str, Any]: + """ + Reset the current Galaxy session. + + Clears the session state and resets the request counter. + + :return: Dictionary with status, message, and timestamp + :raises ValueError: If Galaxy client is not initialized + """ + galaxy_client = self.app_state.galaxy_client + if not galaxy_client: + raise ValueError("Galaxy client not initialized") + + self.logger.info("Resetting Galaxy session...") + + try: + result = await galaxy_client.reset_session() + + # Reset request counter on session reset + self.app_state.reset_request_counter() + + self.logger.info(f"✅ Session reset completed: {result.get('message')}") + return result + except Exception as e: + self.logger.error(f"Failed to reset session: {e}", exc_info=True) + raise + + async def create_next_session(self) -> Dict[str, Any]: + """ + Create a new Galaxy session. + + Creates a new session while potentially maintaining some context + from the previous session. + + :return: Dictionary with status, message, session_name, task_name, and timestamp + :raises ValueError: If Galaxy client is not initialized + """ + galaxy_client = self.app_state.galaxy_client + if not galaxy_client: + raise ValueError("Galaxy client not initialized") + + self.logger.info("Creating next Galaxy session...") + + try: + result = await galaxy_client.create_next_session() + self.logger.info(f"✅ Next session created: {result.get('session_name')}") + return result + except Exception as e: + self.logger.error(f"Failed to create next session: {e}", exc_info=True) + raise + + async def stop_task_and_restart(self) -> Dict[str, Any]: + """ + Stop the current task and restart the Galaxy client. + + Shuts down the Galaxy client to properly clean up device agent tasks, + then reinitializes the client and creates a new session. + + :return: Dictionary with status, message, session_name, and timestamp + :raises ValueError: If Galaxy client is not initialized + """ + galaxy_client = self.app_state.galaxy_client + if not galaxy_client: + raise ValueError("Galaxy client not initialized") + + try: + # Shutdown the galaxy client to properly clean up device agent tasks + self.logger.info( + "🛑 Shutting down Galaxy client to clean up device tasks..." + ) + await galaxy_client.shutdown() + self.logger.info("✅ Galaxy client shutdown completed") + + # Reinitialize the client to restore device connections + self.logger.info("🔄 Reinitializing Galaxy client...") + await galaxy_client.initialize() + self.logger.info("✅ Galaxy client reinitialized") + + # Reset request counter on stop + self.app_state.reset_request_counter() + + # Create a new session + new_session_result = await galaxy_client.create_next_session() + self.logger.info(f"✅ New session created: {new_session_result}") + + return new_session_result + + except Exception as e: + self.logger.error( + f"Failed to stop task and restart client: {e}", exc_info=True + ) + raise diff --git a/galaxy/webui/templates/index.html b/galaxy/webui/templates/index.html new file mode 100644 index 000000000..afe2a882d --- /dev/null +++ b/galaxy/webui/templates/index.html @@ -0,0 +1,82 @@ + + + + + + Galaxy Web UI + + + +
    +

    🌌 Galaxy Web UI

    +

    Weaving the Digital Agent Galaxy

    +
    + + Server is running +
    +

    + Frontend React application will be served here.
    + WebSocket endpoint: ws://localhost:8000/ws +

    +

    + Health Check +

    +
    + + diff --git a/galaxy/webui/websocket_observer.py b/galaxy/webui/websocket_observer.py index 7fc97ffcd..b9ed78702 100644 --- a/galaxy/webui/websocket_observer.py +++ b/galaxy/webui/websocket_observer.py @@ -5,292 +5,310 @@ WebSocket Observer for Galaxy Web UI. This observer subscribes to all Galaxy events and pushes them to connected WebSocket clients. +Provides efficient event serialization and broadcasting capabilities. """ -import json import logging from dataclasses import asdict, is_dataclass -from typing import Any, Dict, List, Optional, Set +from datetime import datetime +from typing import Any, Callable, Dict, List, Optional, Set, Type from fastapi import WebSocket -from galaxy.core.events import Event, IEventObserver +from galaxy.core.events import ( + AgentEvent, + ConstellationEvent, + DeviceEvent, + Event, + IEventObserver, + TaskEvent, +) -class WebSocketObserver(IEventObserver): +class EventSerializer: """ - Observer that forwards all Galaxy events to WebSocket clients. + Handles serialization of various data types to JSON-compatible format. - This observer maintains a set of active WebSocket connections and - broadcasts events to all connected clients in real-time. + This class provides a centralized, extensible way to serialize complex + Python objects into JSON-serializable dictionaries for WebSocket transmission. """ def __init__(self) -> None: - """Initialize the WebSocket observer.""" + """Initialize the event serializer with cached imports and type handlers.""" self.logger: logging.Logger = logging.getLogger(__name__) - self._connections: Set[WebSocket] = set() - self._event_count: int = 0 - async def on_event(self, event: Event) -> None: + # Cache imports to avoid repeated import attempts + self._cached_types: Dict[str, Optional[Type]] = {} + self._initialize_type_cache() + + # Register serialization handlers for specific types + self._type_handlers: Dict[Type, Callable[[Any], Any]] = {} + self._register_handlers() + + def _initialize_type_cache(self) -> None: """ - Handle an event by broadcasting to all WebSocket clients. + Initialize cache for commonly used types. - :param event: The event to broadcast + Pre-loads types that will be frequently serialized to avoid + repeated import attempts and try-except blocks. """ + # Try to import TaskStarLine try: - self._event_count += 1 - - # Convert event to JSON-serializable format - event_data: Dict[str, Any] = self._event_to_dict(event) - - self.logger.debug( - f"Broadcasting event #{self._event_count}: {event.event_type.value} to {len(self._connections)} clients" - ) - - # Broadcast to all connected clients - disconnected: Set[WebSocket] = set() - for connection in self._connections: - try: - await connection.send_json(event_data) - self.logger.debug(f"Successfully sent event to client") - except Exception as e: - self.logger.warning( - f"Failed to send event to client: {e}, marking for removal" - ) - disconnected.add(connection) + from galaxy.constellation.task_star_line import TaskStarLine - # Remove disconnected clients - self._connections -= disconnected + self._cached_types["TaskStarLine"] = TaskStarLine + except ImportError: + self._cached_types["TaskStarLine"] = None + self.logger.debug("TaskStarLine not available for serialization") - except Exception as e: - self.logger.error(f"Error broadcasting event: {e}") + # Try to import TaskConstellation + try: + from galaxy.constellation import TaskConstellation - def add_connection(self, websocket: WebSocket) -> None: - """ - Add a WebSocket connection to receive events. + self._cached_types["TaskConstellation"] = TaskConstellation + except ImportError: + self._cached_types["TaskConstellation"] = None + self.logger.debug("TaskConstellation not available for serialization") - :param websocket: The WebSocket connection to add + def _register_handlers(self) -> None: """ - self._connections.add(websocket) - self.logger.info( - f"WebSocket client connected. Total connections: {len(self._connections)}" - ) + Register type-specific serialization handlers. - def remove_connection(self, websocket: WebSocket) -> None: + Maps Python types to their corresponding serialization functions + for efficient lookup during serialization. """ - Remove a WebSocket connection. + task_star_line_type = self._cached_types.get("TaskStarLine") + if task_star_line_type: + self._type_handlers[task_star_line_type] = self._serialize_task_star_line - :param websocket: The WebSocket connection to remove - """ - self._connections.discard(websocket) - self.logger.info( - f"WebSocket client disconnected. Total connections: {len(self._connections)}" - ) + task_constellation_type = self._cached_types.get("TaskConstellation") + if task_constellation_type: + self._type_handlers[task_constellation_type] = self._serialize_constellation - def _event_to_dict(self, event: Event) -> Dict[str, Any]: + def serialize_event(self, event: Event) -> Dict[str, Any]: """ Convert an Event object to a JSON-serializable dictionary. :param event: The event to convert :return: Dictionary representation of the event """ - # Import here to avoid circular imports - from galaxy.core.events import ( - AgentEvent, - ConstellationEvent, - DeviceEvent, - TaskEvent, - ) - + # Build base dictionary with common fields base_dict = { "event_type": event.event_type.value, "source_id": event.source_id, "timestamp": event.timestamp, - "data": self._serialize_value(event.data), # Serialize data field + "data": self.serialize_value(event.data), } - # Add type-specific fields + # Add type-specific fields using polymorphism if isinstance(event, TaskEvent): - base_dict.update( - { - "task_id": event.task_id, - "status": event.status, - "result": self._serialize_value(event.result), - "error": str(event.error) if event.error else None, - } - ) + base_dict.update(self._serialize_task_event_fields(event)) elif isinstance(event, ConstellationEvent): - base_dict.update( - { - "constellation_id": event.constellation_id, - "constellation_state": event.constellation_state, - "new_ready_tasks": event.new_ready_tasks or [], - } - ) + base_dict.update(self._serialize_constellation_event_fields(event)) elif isinstance(event, AgentEvent): - base_dict.update( - { - "agent_name": event.agent_name, - "agent_type": event.agent_type, - "output_type": event.output_type, - "output_data": self._serialize_value( - event.output_data - ), # Serialize output_data - } - ) + base_dict.update(self._serialize_agent_event_fields(event)) elif isinstance(event, DeviceEvent): - base_dict.update( - { - "device_id": event.device_id, - "device_status": event.device_status, - "device_info": self._serialize_value(event.device_info), - "all_devices": self._serialize_value(event.all_devices), - } - ) + base_dict.update(self._serialize_device_event_fields(event)) return base_dict - def _serialize_value(self, value: Any) -> Any: + def _serialize_task_event_fields(self, event: TaskEvent) -> Dict[str, Any]: + """ + Extract task-specific fields from a TaskEvent. + + :param event: The task event to serialize + :return: Dictionary of task-specific fields + """ + return { + "task_id": event.task_id, + "status": event.status, + "result": self.serialize_value(event.result), + "error": str(event.error) if event.error else None, + } + + def _serialize_constellation_event_fields( + self, event: ConstellationEvent + ) -> Dict[str, Any]: + """ + Extract constellation-specific fields from a ConstellationEvent. + + :param event: The constellation event to serialize + :return: Dictionary of constellation-specific fields + """ + return { + "constellation_id": event.constellation_id, + "constellation_state": event.constellation_state, + "new_ready_tasks": event.new_ready_tasks or [], + } + + def _serialize_agent_event_fields(self, event: AgentEvent) -> Dict[str, Any]: + """ + Extract agent-specific fields from an AgentEvent. + + :param event: The agent event to serialize + :return: Dictionary of agent-specific fields + """ + return { + "agent_name": event.agent_name, + "agent_type": event.agent_type, + "output_type": event.output_type, + "output_data": self.serialize_value(event.output_data), + } + + def _serialize_device_event_fields(self, event: DeviceEvent) -> Dict[str, Any]: + """ + Extract device-specific fields from a DeviceEvent. + + :param event: The device event to serialize + :return: Dictionary of device-specific fields + """ + return { + "device_id": event.device_id, + "device_status": event.device_status, + "device_info": self.serialize_value(event.device_info), + "all_devices": self.serialize_value(event.all_devices), + } + + def serialize_value(self, value: Any) -> Any: """ Serialize a value to JSON-compatible format. Handles various data types including primitives, collections, dataclasses, - TaskStarLine objects, and TaskConstellation objects. + and custom Galaxy objects using a chain of serialization strategies. :param value: The value to serialize :return: JSON-serializable value """ + # Handle None early if value is None: return None - # Handle common primitive types + # Handle primitive types if isinstance(value, (str, int, float, bool)): return value - # Handle dictionary - recursively serialize values + # Handle datetime objects + if isinstance(value, datetime): + return value.isoformat() + + # Handle collections - recursively serialize if isinstance(value, dict): - return {k: self._serialize_value(v) for k, v in value.items()} + return {k: self.serialize_value(v) for k, v in value.items()} - # Handle list/tuple - recursively serialize items if isinstance(value, (list, tuple)): - return [self._serialize_value(item) for item in value] - - # Handle TaskStarLine objects - try: - from galaxy.constellation.task_star_line import TaskStarLine + return [self.serialize_value(item) for item in value] - if isinstance(value, TaskStarLine): - # Use the built-in to_dict method - return value.to_dict() - except ImportError: - pass - - # Handle TaskConstellation objects - try: - from galaxy.constellation import TaskConstellation - - if isinstance(value, TaskConstellation): - # Serialize constellation to dictionary with all relevant fields - constellation_dict: Dict[str, Any] = { - "constellation_id": value.constellation_id, - "name": value.name, - "state": ( - value.state.value - if hasattr(value.state, "value") - else str(value.state) - ), - "tasks": { - task_id: { - "task_id": task.task_id, - "name": task.name, - "description": task.description, - "target_device_id": task.target_device_id, - "status": ( - task.status.value - if hasattr(task.status, "value") - else str(task.status) - ), - "result": self._serialize_value(task.result), - "error": str(task.error) if task.error else None, - "input": ( - self._serialize_value(task.input) - if hasattr(task, "input") - else None - ), - "output": ( - self._serialize_value(task.output) - if hasattr(task, "output") - else None - ), - "started_at": ( - task.execution_start_time.isoformat() - if hasattr(task, "execution_start_time") - and task.execution_start_time - else None - ), - "completed_at": ( - task.execution_end_time.isoformat() - if hasattr(task, "execution_end_time") - and task.execution_end_time - else None - ), - } - for task_id, task in value.tasks.items() - }, - "dependencies": ( - self._serialize_dependencies(value.dependencies) - if hasattr(value, "dependencies") - else {} - ), - "metadata": ( - self._serialize_value(value.metadata) - if hasattr(value, "metadata") - else {} - ), - "created_at": ( - value.created_at.isoformat() - if hasattr(value, "created_at") and value.created_at - else None - ), - } - - # Add statistics if available - if hasattr(value, "get_statistics"): - try: - constellation_dict["statistics"] = value.get_statistics() - except Exception as e: - self.logger.warning( - f"Failed to get constellation statistics: {e}" - ) - - return constellation_dict - except ImportError: - pass + # Check registered type handlers first + value_type = type(value) + if value_type in self._type_handlers: + return self._type_handlers[value_type](value) - # Handle dataclasses - if is_dataclass(value): + # Try dataclass serialization + if is_dataclass(value) and not isinstance(value, type): try: - return self._serialize_value(asdict(value)) - except Exception: - pass + return self.serialize_value(asdict(value)) + except (TypeError, ValueError) as e: + self.logger.debug(f"Failed to serialize dataclass: {e}") - # Try to convert to dict if object has model_dump (Pydantic models) + # Try Pydantic model serialization if hasattr(value, "model_dump"): try: - return self._serialize_value(value.model_dump()) - except Exception: - pass + return self.serialize_value(value.model_dump()) + except Exception as e: + self.logger.debug(f"Failed to serialize Pydantic model: {e}") - # Try to convert to dict if object has to_dict method - if hasattr(value, "to_dict"): + # Try generic to_dict method + if hasattr(value, "to_dict") and callable(value.to_dict): try: - return self._serialize_value(value.to_dict()) - except Exception: - pass + return self.serialize_value(value.to_dict()) + except Exception as e: + self.logger.debug(f"Failed to serialize using to_dict: {e}") # Fallback to string representation return str(value) + def _serialize_task_star_line(self, value: Any) -> Dict[str, Any]: + """ + Serialize a TaskStarLine object. + + :param value: TaskStarLine instance + :return: Serialized dictionary + """ + try: + return value.to_dict() + except Exception as e: + self.logger.warning(f"Failed to serialize TaskStarLine: {e}") + return str(value) + + def _serialize_constellation(self, value: Any) -> Dict[str, Any]: + """ + Serialize a TaskConstellation object. + + :param value: TaskConstellation instance + :return: Serialized dictionary with constellation details + """ + try: + constellation_dict = { + "constellation_id": value.constellation_id, + "name": value.name, + "state": self._extract_enum_value(value.state), + "tasks": self._serialize_constellation_tasks(value.tasks), + "dependencies": self._serialize_dependencies( + getattr(value, "dependencies", {}) + ), + "metadata": self.serialize_value(getattr(value, "metadata", {})), + "created_at": self._serialize_datetime( + getattr(value, "created_at", None) + ), + } + + # Add statistics if available + if hasattr(value, "get_statistics") and callable(value.get_statistics): + try: + constellation_dict["statistics"] = value.get_statistics() + except Exception as e: + self.logger.warning(f"Failed to get constellation statistics: {e}") + + return constellation_dict + except Exception as e: + self.logger.warning(f"Failed to serialize TaskConstellation: {e}") + return str(value) + + def _serialize_constellation_tasks( + self, tasks: Dict[str, Any] + ) -> Dict[str, Dict[str, Any]]: + """ + Serialize all tasks in a constellation. + + :param tasks: Dictionary of task ID to task object + :return: Dictionary of serialized tasks + """ + serialized_tasks = {} + for task_id, task in tasks.items(): + try: + serialized_tasks[task_id] = { + "task_id": task.task_id, + "name": task.name, + "description": task.description, + "target_device_id": task.target_device_id, + "status": self._extract_enum_value(task.status), + "result": self.serialize_value(task.result), + "error": str(task.error) if task.error else None, + "input": self.serialize_value(getattr(task, "input", None)), + "output": self.serialize_value(getattr(task, "output", None)), + "started_at": self._serialize_datetime( + getattr(task, "execution_start_time", None) + ), + "completed_at": self._serialize_datetime( + getattr(task, "execution_end_time", None) + ), + } + except Exception as e: + self.logger.warning(f"Failed to serialize task {task_id}: {e}") + serialized_tasks[task_id] = {"task_id": task_id, "error": str(e)} + + return serialized_tasks + def _serialize_dependencies( self, dependencies: Dict[str, Any] ) -> Dict[str, List[str]]: @@ -303,18 +321,114 @@ def _serialize_dependencies( :param dependencies: Dictionary of TaskStarLine objects keyed by line_id :return: Dictionary mapping child task IDs to lists of parent task IDs """ - result = {} + result: Dict[str, List[str]] = {} for dep in dependencies.values(): - # Each TaskStarLine has from_task_id (parent) and to_task_id (child) - child_id = dep.to_task_id - parent_id = dep.from_task_id + try: + # Each TaskStarLine has from_task_id (parent) and to_task_id (child) + child_id = dep.to_task_id + parent_id = dep.from_task_id - if child_id not in result: - result[child_id] = [] - result[child_id].append(parent_id) + if child_id not in result: + result[child_id] = [] + result[child_id].append(parent_id) + except AttributeError as e: + self.logger.debug(f"Failed to extract dependency IDs: {e}") + continue return result + @staticmethod + def _extract_enum_value(value: Any) -> Any: + """ + Extract the value from an enum, or return as string. + + :param value: Potential enum value + :return: Enum value or string representation + """ + return value.value if hasattr(value, "value") else str(value) + + @staticmethod + def _serialize_datetime(dt: Optional[datetime]) -> Optional[str]: + """ + Serialize a datetime object to ISO format string. + + :param dt: Datetime object or None + :return: ISO format string or None + """ + return dt.isoformat() if dt is not None else None + + +class WebSocketObserver(IEventObserver): + """ + Observer that forwards all Galaxy events to WebSocket clients. + + This observer maintains a set of active WebSocket connections and + broadcasts events to all connected clients in real-time. + """ + + def __init__(self) -> None: + """Initialize the WebSocket observer.""" + self.logger: logging.Logger = logging.getLogger(__name__) + self._connections: Set[WebSocket] = set() + self._event_count: int = 0 + self._serializer: EventSerializer = EventSerializer() + + async def on_event(self, event: Event) -> None: + """ + Handle an event by broadcasting to all WebSocket clients. + + :param event: The event to broadcast + """ + try: + self._event_count += 1 + + # Convert event to JSON-serializable format using the serializer + event_data: Dict[str, Any] = self._serializer.serialize_event(event) + + self.logger.debug( + f"Broadcasting event #{self._event_count}: {event.event_type.value} to {len(self._connections)} clients" + ) + + # Broadcast to all connected clients + disconnected: Set[WebSocket] = set() + for connection in self._connections: + try: + await connection.send_json(event_data) + self.logger.debug(f"Successfully sent event to client") + except Exception as e: + self.logger.warning( + f"Failed to send event to client: {e}, marking for removal" + ) + disconnected.add(connection) + + # Remove disconnected clients + self._connections -= disconnected + + except Exception as e: + self.logger.error(f"Error broadcasting event: {e}") + + def add_connection(self, websocket: WebSocket) -> None: + """ + Add a WebSocket connection to receive events. + + :param websocket: The WebSocket connection to add + """ + self._connections.add(websocket) + self.logger.info( + f"WebSocket client connected. Total connections: {len(self._connections)}" + ) + + def remove_connection(self, websocket: WebSocket) -> None: + """ + Remove a WebSocket connection. + + :param websocket: The WebSocket connection to remove + """ + self._connections.discard(websocket) + self.logger.info( + f"WebSocket client disconnected. Total connections: {len(self._connections)}" + ) + @property def connection_count(self) -> int: """Get the number of active connections."""